Repository navigation
Conversation
1c9cabe to
285ba33
Compare
There was a problem hiding this comment.
Pull request overview
This PR migrates the ServerInfo admin settings page from a PHP-rendered template + legacy JS (Smoothie) to a Vite-built Vue 3 frontend, backed by new JSON endpoints that split static (one-time) and live (polled) data.
Changes:
- Replaces the large
templates/settings-admin.phpoutput with a Vue mount point and loads new bundled assets fromAdminSettings. - Adds
/data(static snapshot) and updates/update(live snapshot) endpoints, with new PHP service classes (StaticData,LiveData,UptimeFormatter). - Introduces a new Vue UI composed of sections/components and Chart.js-based charts, plus Vite/ESLint/node tooling and REUSE license asset handling.
Reviewed changes
Copilot reviewed 47 out of 53 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
.gitignore |
Ignores node_modules/ for the new Node/Vite toolchain. |
REUSE.toml |
Updates REUSE annotations to cover generated CSS assets. |
appinfo/routes.php |
Adds /data route and keeps /update for the Vue app’s API calls. |
css/main-CrF6ckF0.chunk.css |
Adds generated CSS bundle output. |
css/serverinfo-main.css |
Adds CSS entrypoint that imports the generated chunk. |
eslint.config.mjs |
Adds ESLint configuration for the new JS/Vue code. |
js/script.js |
Removes legacy jQuery/Smoothie-based frontend code. |
js/serverinfo-main.mjs.license |
Adds generated license metadata for the JS bundle. |
js/serverinfo-main.mjs.map.license |
Adds generated license metadata for the source map bundle. |
js/smoothie.js |
Removes bundled Smoothie charting library source. |
lib/Controller/ApiController.php |
Refactors uptime formatting into UptimeFormatter. |
lib/Controller/PageController.php |
Adds data() endpoint and updates update() to return new live payload. |
lib/JobQueueInfo.php |
Adds Psalm API marker metadata. |
lib/LiveData.php |
Introduces a live-data aggregator for the polling endpoint. |
lib/OperatingSystems/Linux.php |
Enhances thermal zone discovery by reading hwmon sensors in addition to thermal zones. |
lib/PhpStatistics.php |
Ensures stable indexed array output for PHP extensions list. |
lib/Settings/AdminSettings.php |
Switches admin UI to load built JS/CSS bundles and returns an empty template. |
lib/SlowestJobs.php |
Adds Psalm API marker metadata. |
lib/StaticData.php |
Introduces a static-data aggregator for the one-time snapshot endpoint. |
lib/SystemStatistics.php |
Makes getFreeSpace() public for reuse by StaticData. |
lib/UptimeFormatter.php |
Extracts uptime formatting into a reusable service. |
LICENSES/Apache-2.0.txt |
Adds third-party license text for bundle compliance. |
LICENSES/GPL-3.0-or-later.txt |
Adds third-party license text for bundle compliance. |
LICENSES/ISC.txt |
Adds third-party license text for bundle compliance. |
LICENSES/MPL-2.0.txt |
Adds third-party license text for bundle compliance. |
package.json |
Adds Vite/Vue/Chart.js dependencies, scripts, and engine constraints. |
src/components/ActiveUsersSection.vue |
New Vue section for active-user statistics. |
src/components/CpuChartSection.vue |
New Vue CPU chart implementation (Chart.js + vue-chartjs). |
src/components/DatabaseSection.vue |
New Vue section for database stats. |
src/components/DiskSection.vue |
New Vue section for disk stats with Chart.js doughnut charts. |
src/components/MemoryChartSection.vue |
New Vue memory/swap chart implementation (Chart.js + vue-chartjs). |
src/components/MonitoringSection.vue |
New Vue section to build the external monitoring URL interactively. |
src/components/NetworkSection.vue |
New Vue section for network/interface information. |
src/components/PhpSection.vue |
New Vue section for PHP/FPM statistics. |
src/components/SectionSkeleton.vue |
New skeleton loading section shown before data loads. |
src/components/SharesSection.vue |
New Vue section for share statistics. |
src/components/SystemSection.vue |
New Vue section for hostname/OS/CPU/memory/time/uptime. |
src/components/ThermalSection.vue |
New Vue section for thermal sensor data. |
src/composables/useLiveData.ts |
Adds client polling logic for /update. |
src/composables/useStaticData.ts |
Adds one-time fetch logic for /data. |
src/main.css |
Adjusts styles for new icon rendering and bundled CSS behavior. |
src/main.ts |
Adds Vue entrypoint to mount the new admin settings UI. |
src/utils.ts |
Adds formatting and theming utilities for the Vue UI. |
src/views/SettingsAdmin.vue |
Implements the new composed admin page layout and data wiring. |
templates/settings-admin.php |
Replaces legacy PHP-rendered content with the Vue mount container. |
tests/lib/ApiControllerTest.php |
Updates controller test to use UptimeFormatter dependency. |
tests/lib/UptimeFormatterTest.php |
Adds unit tests for the new uptime formatter. |
tests/psalm-baseline.xml |
Updates Psalm baseline for new/removed classes and new Psalm version. |
vite.config.mjs |
Adds Vite config via @nextcloud/vite-config, including license extraction settings. |
Files not reviewed (1)
- css/main-CrF6ckF0.chunk.css: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
26eae52 to
18c8df7
Compare
|
@coderabbitai review |
✅ Action performedReview finished.
|
✅ Action performedReview finished.
|
WalkthroughThis PR transforms the serverinfo Nextcloud app from server-side templated pages to a modern Vue 3 single-page application. The backend extracts server data collection into dedicated PHP classes ( 🚥 Pre-merge checks | ✅ 2 | ❌ 3❌ Failed checks (1 warning, 2 inconclusive)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 9
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/npm-audit-fix.yml:
- Around line 42-59: The post-checkout steps run even when checkout failed; add
a guard so those steps only run on a successful checkout. Add an if condition
checking the checkout step outcome (e.g. if: ${{ steps.checkout.outcome ==
'success' }}) to the "Read package.json node and npm engines version" step (id:
versions), the "Set up node ..." step (actions/setup-node), the "Set up npm ..."
run step, and the "Fix npm audit" step (id: npm-audit) so they are skipped when
the checkout step failed.
In `@css/main-BClh5c04.chunk.css`:
- Line 1: Replace the deprecated property word-break: break-word in the source
CSS for the .infobox rule (the `@media`(width<=1280px){.infobox{...}} block) with
a modern equivalent such as overflow-wrap: anywhere (and remove the word-break
line), then rebuild the frontend assets so the generated chunk
(css/main-*.chunk.css) is regenerated without the deprecated keyword; ensure the
change is made in the source file (where .infobox is defined) not only in the
generated artifact so it persists after future builds.
In `@lib/OperatingSystems/Linux.php`:
- Around line 260-265: The enumeration only looks for temp*_label files so
temp*_input sensors without labels are ignored; update the scan to also include
temp*_input entries and create ThermalZone instances for them using a label
fallback: first glob temp*_label as before, then glob temp*_input and for each
input that wasn't already handled try to read a matching temp*_label
(str_replace('_input','_label',$inputPath)); if label exists use it, otherwise
derive a sensible type string (e.g. $name plus basename of the input file or the
temp index) and still compute $temp from readContent($inputPath)/1000 and create
new ThermalZone(md5($inputPath), $type, $temp); use readContent and ThermalZone
in the same way and maintain a set of processed input paths to avoid duplicates.
In `@src/components/CpuChartSection.vue`:
- Around line 74-76: The y-axis is hard-capped with max: 100 which clips real
overload values produced elsewhere in this component; update the y-axis options
(the object containing min, max, ticks and passiveColor()) to remove the fixed
max or replace it with a computed maximum based on the data (e.g., const
computedMax = Math.max(100, ...seriesValues) + margin) so charts auto-scale to
values >100, keep min: 0 and the ticks callback unchanged, and apply the same
change to the other y-axis config block (the similar min/max/ticks object around
the other chart setup).
- Around line 93-101: The code indexes pct[0] and load[0] without ensuring
props.cpuload has elements, causing undefined output for an empty array; update
the guard in CpuChartSection.vue to check that props.cpuload is an array with
length > 0 (e.g., Array.isArray(props.cpuload) && props.cpuload.length > 0)
before mapping/indexing, and if it's empty return the same 'CPU info not
available' (or a safe fallback) so pct and load mappings and the pct[0]/load[0]
accesses are never executed on an empty array.
In `@src/components/NetworkSection.vue`:
- Line 36: In NetworkSection.vue replace the hardcoded 'up'/'down' text with
localized strings by using the app's i18n translation method (e.g. $t or i18n.t)
instead of plain literals; update the template expression that renders iface.up
(the <span class="info">{{ iface.up ? 'up' : 'down' }}</span>) to something like
{{ iface.up ? $t('network.status.up') : $t('network.status.down') }} and ensure
corresponding keys (network.status.up, network.status.down) are present in your
locale files.
In `@src/composables/useLiveData.ts`:
- Around line 31-41: The poll() function can hang because
axios.get(generateUrl('/apps/serverinfo/update')) has no timeout; modify that
call to pass an explicit timeout (e.g.
axios.get(generateUrl('/apps/serverinfo/update'), { timeout: 5000 })) so the
request will reject on slow/hung connections and the catch/finally path (which
schedules timeoutId and increments tick.value/data.value handling) continues;
keep existing catch behavior and ensure timeoutId/stopped logic around
setTimeout(poll, 2000) remains unchanged.
In `@src/utils.ts`:
- Around line 46-55: The formatBytes function currently uses Math.abs(bytes) so
negative values lose their sign; update formatBytes to preserve the sign by
recording whether bytes is negative (e.g., const sign = bytes < 0 ? '-' : ''),
use Math.abs(bytes) for magnitude calculation with the existing units/loop
logic, and then prepend the sign to the returned string (e.g., return sign +
value.toFixed(1) + ' ' + units[i]) so negative inputs produce negative-formatted
outputs; keep the function name formatBytes and the units array unchanged.
In `@src/views/SettingsAdmin.vue`:
- Around line 139-140: The view currently only reads staticData from
useStaticData() and leaves the skeleton active when the initial fetch fails;
update the component to also read and react to the loading and error properties
returned by useStaticData() (e.g., const { data: staticData, loading:
staticLoading, error: staticError } = useStaticData()). Use staticLoading to
show the skeleton only while true, and render a clear error state/UI when
staticError is set (or show a retry button that re-invokes the fetch), ensuring
the skeleton is not permanently shown when staticData is null due to an error.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 69a56bcb-269d-4431-95fb-6838e0135a51
⛔ Files ignored due to path filters (3)
js/Chart.min.jsis excluded by!**/*.min.jsjs/serverinfo-main.mjs.mapis excluded by!**/*.mappackage-lock.jsonis excluded by!**/package-lock.json
📒 Files selected for processing (53)
.gitattributes.github/workflows/npm-audit-fix.yml.github/workflows/npm-build.yml.gitignoreLICENSES/Apache-2.0.txtLICENSES/GPL-3.0-or-later.txtLICENSES/ISC.txtLICENSES/MPL-2.0.txtREUSE.tomlappinfo/routes.phpcss/main-BClh5c04.chunk.csscss/serverinfo-main.csseslint.config.mjsjs/script.jsjs/serverinfo-main.mjsjs/serverinfo-main.mjs.licensejs/serverinfo-main.mjs.map.licensejs/smoothie.jslib/Controller/ApiController.phplib/Controller/PageController.phplib/JobQueueInfo.phplib/LiveData.phplib/OperatingSystems/Linux.phplib/PhpStatistics.phplib/Settings/AdminSettings.phplib/SlowestJobs.phplib/StaticData.phplib/SystemStatistics.phplib/UptimeFormatter.phppackage.jsonsrc/components/ActiveUsersSection.vuesrc/components/CpuChartSection.vuesrc/components/DatabaseSection.vuesrc/components/DiskSection.vuesrc/components/MemoryChartSection.vuesrc/components/MonitoringSection.vuesrc/components/NetworkSection.vuesrc/components/PhpSection.vuesrc/components/SectionSkeleton.vuesrc/components/SharesSection.vuesrc/components/SystemSection.vuesrc/components/ThermalSection.vuesrc/composables/useLiveData.tssrc/composables/useStaticData.tssrc/main.csssrc/main.tssrc/utils.tssrc/views/SettingsAdmin.vuetemplates/settings-admin.phptests/lib/ApiControllerTest.phptests/lib/UptimeFormatterTest.phptests/psalm-baseline.xmlvite.config.mjs
💤 Files with no reviewable changes (2)
- js/smoothie.js
- js/script.js
| @@ -0,0 +1 @@ | |||
| .chart-wrapper[data-v-f3151c1d],.chart-wrapper[data-v-409c36e2]{height:200px;width:100%}.section-skeleton .skeleton-bar[data-v-9a2b13f0]{background:var(--color-background-dark, #ededed);border-radius:var(--border-radius, 3px);animation:skeleton-pulse-9a2b13f0 1.5s ease-in-out infinite}.section-skeleton .skeleton-title[data-v-9a2b13f0]{height:24px;width:40%;margin-bottom:20px}.section-skeleton .skeleton-chart[data-v-9a2b13f0]{height:200px;margin-bottom:16px}@keyframes skeleton-pulse-9a2b13f0{50%{opacity:.5}}.server-info-wrapper{max-width:100vw}.monitoring-wrapper{display:-webkit-flex;display:flex;max-width:50%;min-width:50%}.monitoring-wrapper>input{flex:1 0 auto}.monitoring-wrapper+.settings-hint{margin-top:0}.rambox,.cpubox{height:10px;width:10px;background-color:var(--color-primary-element)}.swapbox{height:10px;width:10px;background-color:var(--color-background-darker)}.info{font-weight:700}.smallinfo{color:var(--color-text-maxcontrast)}.infobox{display:flex;flex-wrap:nowrap;align-items:center;padding:16px;margin:16px 0;background-color:var(--color-main-background);border:2px solid var(--color-border);border-radius:var(--border-radius-large)}.diskchart-container{width:100px;margin-right:25px;border:1px var(--color-border)}.info-color-label--available:before,.info-color-label--used:before{content:"";display:inline-block;width:10px;height:10px;border-radius:10px;margin-right:5px}.info-color-label--available:before{background-color:var(--color-border-maxcontrast)}.info-color-label--used:before{background-color:var(--color-primary-element)}@media(width<=1280px){.infobox{display:block;word-break:break-word}.text-center-mobile{text-align:center}.diskchart-container{margin:0 auto}.monitoring-wrapper{min-width:100%;max-width:100%}}.diskinfo-container{flex:1 0 auto}.infobox h3{font-weight:700;padding:5px 0 10px;margin:0}.infoicon{display:inline-flex;vertical-align:middle;margin-right:8px}.chart-container{position:relative;margin:10px}.clipboardButton{display:inline-block;position:relative;top:2px;margin-left:5px}.row{display:-webkit-flex;display:flex;flex:0 1 auto;flex-direction:row;flex-wrap:wrap;margin-left:-15px;margin-right:-15px}.col{flex-grow:1;flex-basis:0;max-width:100%;padding-left:15px;padding-right:15px}.col-1{flex-basis:8.333%;max-width:8.333%;min-width:8.333%}.col-2{flex-basis:16.666%;max-width:16.666%;min-width:16.666%}.col-3{flex-basis:25%;max-width:25%;min-width:25%}.col-4{flex-basis:33.333%;max-width:33.333%;min-width:33.333%}.col-5{flex-basis:41.666%;max-width:41.666%;min-width:41.666%}.col-6{flex-basis:50%;max-width:50%;min-width:50%}.col-7{flex-basis:58.333%;max-width:58.333%;min-width:58.333%}.col-8{flex-basis:66.666%;max-width:66.666%;min-width:66.666%}.col-9{flex-basis:75%;max-width:75%;min-width:75%}.col-10{flex-basis:83.333%;max-width:83.333%;min-width:83.333%}.col-11{flex-basis:91.666%;max-width:91.666%;min-width:91.666%}.col-12{flex-basis:100%;max-width:100%;min-width:100%}@media only screen and (max-width:1200px){.col-xl-6{flex-basis:50%;max-width:50%;min-width:50%}}@media only screen and (max-width:1024px){.col-l-6{flex-basis:50%;max-width:50%;min-width:50%}.col-l-12{flex-basis:100%;max-width:100%;min-width:100%}}@media only screen and (max-width:768px){.col-m-6{flex-basis:50%;max-width:50%;min-width:50%}.col-m-12{flex-basis:100%;max-width:100%;min-width:100%}}.monitoring-url-params{margin-top:3px;margin-bottom:3px}.monitoring-url-param{display:flex;align-items:start;margin-bottom:3px}.monitoring-url-param input{margin-top:4px;min-height:initial;height:initial}.active-users-wrapper{flex:1;display:flex;gap:0 1rem;flex-wrap:wrap;align-items:center;justify-content:center}.active-users-box{padding:.25rem 1rem;flex:1;min-width:150px;border-left:2px solid var(--color-border)}@media(width<=1280px){.active-users-box{padding:1rem .5rem;border:none;text-align:center}}.active-users-box:first-child{border:none}.active-users-box .info{font-size:2rem}.smoothie-chart-tooltip{display:flex;flex-wrap:nowrap;align-items:center;padding:16px;margin:0 .25rem;background-color:var(--color-main-background);border:2px solid var(--color-border);border-radius:var(--border-radius-large)}.server-info-table table{width:100%}.server-info-table td{padding:6px}.server-info__tag-wrapper{display:flex;flex-wrap:wrap}.server-info__php-extension-tag{display:inline-block;margin:2px;padding:1px 9px;border-radius:16px;border:1px solid var(--color-main-text)} | |||
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify deprecated usage is fully removed from source and generated CSS.
rg -nP 'word-break\s*:\s*break-word' src cssRepository: nextcloud/serverinfo
Length of output: 4557
Fix remaining deprecated word-break: break-word and regenerate chunk
word-break: break-word is still present in src/main.css (line 80) and in the generated css/main-BClh5c04.chunk.css (inside @media(width<=1280px){.infobox{...}}), so the stylelint-flagged keyword hasn’t been removed from the artifact.
Source-level fix (apply in src/main.css, then rebuild assets)
`@media` (width <= 1280px) {
.infobox {
display: block;
- word-break: break-word;
+ overflow-wrap: anywhere;
}🧰 Tools
🪛 Stylelint (17.12.0)
[error] 1-1: Deprecated keyword "break-word" for property "word-break" (declaration-property-value-keyword-no-deprecated)
(declaration-property-value-keyword-no-deprecated)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@css/main-BClh5c04.chunk.css` at line 1, Replace the deprecated property
word-break: break-word in the source CSS for the .infobox rule (the
`@media`(width<=1280px){.infobox{...}} block) with a modern equivalent such as
overflow-wrap: anywhere (and remove the word-break line), then rebuild the
frontend assets so the generated chunk (css/main-*.chunk.css) is regenerated
without the deprecated keyword; ensure the change is made in the source file
(where .infobox is defined) not only in the generated artifact so it persists
after future builds.
ChristophWurst
left a comment
There was a problem hiding this comment.
A .nextcloudignore should be used to exclude source and package manager files from shipping, right?
|
Was the second commit placed here intentionally? |
It's not necessary, because the build script covers all cases we have here. I'm adding still, because then it's explicit what should be in the release and what not.
Yes, otherwise I won't see the sensors on my device ;) It also fixes #551. |
7a0fb3c to
5ecf4c2
Compare
Assisted-by: Claude:claude-opus-4-8 Signed-off-by: Daniel Kesselberg <mail@danielkesselberg.de>
No description provided.