Skip to content

Vue#1007

Open
kesselb wants to merge 2 commits into
masterfrom
hello-vue
Open

Vue#1007
kesselb wants to merge 2 commits into
masterfrom
hello-vue

Conversation

@kesselb
Copy link
Copy Markdown
Collaborator

@kesselb kesselb commented Jun 3, 2026

No description provided.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.php output with a Vue mount point and loads new bundled assets from AdminSettings.
  • 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.

Comment thread src/components/PhpSection.vue
Comment thread src/components/DiskSection.vue
Comment thread src/components/DiskSection.vue
Comment thread src/components/DiskSection.vue
@kesselb kesselb requested a review from ChristophWurst June 3, 2026 19:15
@kesselb kesselb marked this pull request as ready for review June 3, 2026 19:15
@kesselb kesselb requested a review from benjaminfrueh as a code owner June 3, 2026 19:15
kesselb added 2 commits June 4, 2026 22:23
Assisted-by: Claude:claude-opus-4-8
Assisted-by: Copilot:github-copilot/gpt-5.4

Signed-off-by: Daniel Kesselberg <mail@danielkesselberg.de>
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Daniel Kesselberg <mail@danielkesselberg.de>
@kesselb
Copy link
Copy Markdown
Collaborator Author

kesselb commented Jun 4, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 4, 2026

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 4, 2026

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 4, 2026

Review Change Stack

Walkthrough

This 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 (StaticData, LiveData, UptimeFormatter) that endpoints return as JSON. Controllers are refactored to use these services. The frontend introduces 14 new Vue SFC components rendering system information, thermal zones, CPU/memory charts, disk usage, network details, PHP configuration, active users, shares, and external monitoring endpoints. Two composables (useLiveData, useStaticData) handle data fetching and polling. Build infrastructure includes GitHub CI workflows, npm scripts, Vite configuration, and ESLint rules. Old JavaScript files and custom charting libraries are removed in favor of Chart.js and Vue components. Tests and tooling are updated accordingly.

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (1 warning, 2 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 36.36% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Vue' is vague and generic, using a single non-descriptive term that does not clearly convey the scope or purpose of the substantial changeset. Enhance the title to specifically describe the main change, such as 'Migrate admin settings to Vue' or 'Refactor serverinfo UI to Vue architecture'.
Description check ❓ Inconclusive No pull request description was provided by the author, making it impossible to assess whether the description relates to the changeset. Add a detailed description explaining the purpose, scope, and rationale for this large refactor to Vue.
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 23099c4 and 18c8df7.

⛔ Files ignored due to path filters (3)
  • js/Chart.min.js is excluded by !**/*.min.js
  • js/serverinfo-main.mjs.map is excluded by !**/*.map
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (53)
  • .gitattributes
  • .github/workflows/npm-audit-fix.yml
  • .github/workflows/npm-build.yml
  • .gitignore
  • LICENSES/Apache-2.0.txt
  • LICENSES/GPL-3.0-or-later.txt
  • LICENSES/ISC.txt
  • LICENSES/MPL-2.0.txt
  • REUSE.toml
  • appinfo/routes.php
  • css/main-BClh5c04.chunk.css
  • css/serverinfo-main.css
  • eslint.config.mjs
  • js/script.js
  • js/serverinfo-main.mjs
  • js/serverinfo-main.mjs.license
  • js/serverinfo-main.mjs.map.license
  • js/smoothie.js
  • lib/Controller/ApiController.php
  • lib/Controller/PageController.php
  • lib/JobQueueInfo.php
  • lib/LiveData.php
  • lib/OperatingSystems/Linux.php
  • lib/PhpStatistics.php
  • lib/Settings/AdminSettings.php
  • lib/SlowestJobs.php
  • lib/StaticData.php
  • lib/SystemStatistics.php
  • lib/UptimeFormatter.php
  • package.json
  • src/components/ActiveUsersSection.vue
  • src/components/CpuChartSection.vue
  • src/components/DatabaseSection.vue
  • src/components/DiskSection.vue
  • src/components/MemoryChartSection.vue
  • src/components/MonitoringSection.vue
  • src/components/NetworkSection.vue
  • src/components/PhpSection.vue
  • src/components/SectionSkeleton.vue
  • src/components/SharesSection.vue
  • src/components/SystemSection.vue
  • src/components/ThermalSection.vue
  • src/composables/useLiveData.ts
  • src/composables/useStaticData.ts
  • src/main.css
  • src/main.ts
  • src/utils.ts
  • src/views/SettingsAdmin.vue
  • templates/settings-admin.php
  • tests/lib/ApiControllerTest.php
  • tests/lib/UptimeFormatterTest.php
  • tests/psalm-baseline.xml
  • vite.config.mjs
💤 Files with no reviewable changes (2)
  • js/smoothie.js
  • js/script.js

Comment on lines +42 to +59
- name: Read package.json node and npm engines version
uses: skjnldsv/read-package-engines-version-actions@06d6baf7d8f41934ab630e97d9e6c0bc9c9ac5e4 # v3
id: versions
with:
fallbackNode: '^24'
fallbackNpm: '^11.3'

- name: Set up node ${{ steps.versions.outputs.nodeVersion }}
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: ${{ steps.versions.outputs.nodeVersion }}

- name: Set up npm ${{ steps.versions.outputs.npmVersion }}
run: npm i -g 'npm@${{ steps.versions.outputs.npmVersion }}'

- name: Fix npm audit
id: npm-audit
uses: nextcloud-libraries/npm-audit-action@1b1728b2b4a7a78d69de65608efcf4db0e3e42d0 # v0.2.0
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Gate post-checkout steps on successful checkout.

continue-on-error on checkout is neutralized because Read package.json..., Node/npm setup, and Fix npm audit still run when checkout fails. That causes avoidable job failures in matrix branches that don’t exist.

Suggested fix
       - name: Read package.json node and npm engines version
+        if: steps.checkout.outcome == 'success'
         uses: skjnldsv/read-package-engines-version-actions@06d6baf7d8f41934ab630e97d9e6c0bc9c9ac5e4 # v3
         id: versions
         with:
           fallbackNode: '^24'
           fallbackNpm: '^11.3'

       - name: Set up node ${{ steps.versions.outputs.nodeVersion }}
+        if: steps.checkout.outcome == 'success'
         uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
         with:
           node-version: ${{ steps.versions.outputs.nodeVersion }}

       - name: Set up npm ${{ steps.versions.outputs.npmVersion }}
+        if: steps.checkout.outcome == 'success'
         run: npm i -g 'npm@${{ steps.versions.outputs.npmVersion }}'

       - name: Fix npm audit
+        if: steps.checkout.outcome == 'success'
         id: npm-audit
         uses: nextcloud-libraries/npm-audit-action@1b1728b2b4a7a78d69de65608efcf4db0e3e42d0 # v0.2.0
🤖 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 @.github/workflows/npm-audit-fix.yml around lines 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.

@@ -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)}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 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 css

Repository: 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.

Comment on lines +260 to +265
$zones = glob($driver . '/temp*_label') ?: [];
foreach ($zones as $zone) {
try {
$type = $name . ' ' . $this->readContent($zone);
$temp = (float)((int)$this->readContent(str_replace('_label', '_input', $zone)) / 1000);
$data[] = new ThermalZone(md5($zone), $type, $temp);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle hwmon sensors that do not expose temp*_label.

The current enumeration only scans temp*_label, so valid temp*_input sensors without labels are dropped entirely. That undercuts the new hwmon support on common setups.

Proposed fix
-		$zones = glob($driver . '/temp*_label') ?: [];
-		foreach ($zones as $zone) {
+		$inputs = glob($driver . '/temp*_input') ?: [];
+		foreach ($inputs as $input) {
 			try {
-				$type = $name . ' ' . $this->readContent($zone);
-				$temp = (float)((int)$this->readContent(str_replace('_label', '_input', $zone)) / 1000);
-				$data[] = new ThermalZone(md5($zone), $type, $temp);
+				$labelPath = str_replace('_input', '_label', $input);
+				$label = is_readable($labelPath) ? $this->readContent($labelPath) : basename($input, '_input');
+				$type = trim($name . ' ' . $label);
+				$temp = (float)((int)$this->readContent($input) / 1000);
+				$data[] = new ThermalZone(md5($input), $type, $temp);
 			} catch (RuntimeException) {
 				// unable to read sensor
 			}
 		}
🤖 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 `@lib/OperatingSystems/Linux.php` around lines 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.

Comment on lines +74 to +76
min: 0,
max: 100,
ticks: { color: passiveColor(), callback: (v: number | string) => v + ' %' },
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Y-axis clamps valid overload values.

Line 75 hard-caps at 100, but Line 110 can produce values above 100. This clips real overload data and understates CPU pressure.

Suggested fix
+const yMax = computed(() => {
+	const points = chartData.value.datasets[0].data.filter((v): v is number => typeof v === 'number')
+	const peak = points.length > 0 ? Math.max(...points) : 0
+	return Math.max(100, Math.ceil(peak / 10) * 10)
+})
+
 const chartOptions = {
 	animation: false,
 	responsive: true,
 	maintainAspectRatio: false,
 	scales: {
 		x: { display: false },
 		y: {
 			min: 0,
-			max: 100,
+			max: yMax.value,
 			ticks: { color: passiveColor(), callback: (v: number | string) => v + ' %' },
 			grid: { display: false },
 		},
 	},

Also applies to: 109-114

🤖 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 `@src/components/CpuChartSection.vue` around lines 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).

Comment on lines +93 to +101
if (props.cpuload === false || props.cpunum <= 0) {
return t('serverinfo', 'CPU info not available')
}
const pct = props.cpuload.map((l) => ((l / props.cpunum) * 100).toFixed(1))
const load = props.cpuload.map((l) => l.toFixed(2))
return t('serverinfo', 'Load average: {percentage} % ({load}) last minute', {
percentage: pct[0],
load: load[0],
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Guard empty cpuload before footer formatting.

Lines 96–101 index [0] without checking length, so an empty array yields undefined text.

Suggested fix
 const footerText = computed(() => {
-	if (props.cpuload === false || props.cpunum <= 0) {
+	if (props.cpuload === false || props.cpunum <= 0 || props.cpuload.length === 0) {
 		return t('serverinfo', 'CPU info not available')
 	}
-	const pct = props.cpuload.map((l) => ((l / props.cpunum) * 100).toFixed(1))
-	const load = props.cpuload.map((l) => l.toFixed(2))
+	const currentLoad = props.cpuload[0]
+	const pct = ((currentLoad / props.cpunum) * 100).toFixed(1)
+	const load = currentLoad.toFixed(2)
 	return t('serverinfo', 'Load average: {percentage} % ({load}) last minute', {
-		percentage: pct[0],
-		load: load[0],
+		percentage: pct,
+		load,
 	})
 })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (props.cpuload === false || props.cpunum <= 0) {
return t('serverinfo', 'CPU info not available')
}
const pct = props.cpuload.map((l) => ((l / props.cpunum) * 100).toFixed(1))
const load = props.cpuload.map((l) => l.toFixed(2))
return t('serverinfo', 'Load average: {percentage} % ({load}) last minute', {
percentage: pct[0],
load: load[0],
})
if (props.cpuload === false || props.cpunum <= 0 || props.cpuload.length === 0) {
return t('serverinfo', 'CPU info not available')
}
const currentLoad = props.cpuload[0]
const pct = ((currentLoad / props.cpunum) * 100).toFixed(1)
const load = currentLoad.toFixed(2)
return t('serverinfo', 'Load average: {percentage} % ({load}) last minute', {
percentage: pct,
load,
})
🤖 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 `@src/components/CpuChartSection.vue` around lines 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.

<div class="interface-wrapper">
<h3>{{ iface.name }}</h3>
{{ t('serverinfo', 'Status:') }}
<span class="info">{{ iface.up ? 'up' : 'down' }}</span><br>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Localize interface status values.

Line 36 renders up/down as hardcoded English text, so this string won’t be translated while the rest of the section is localized.

💡 Suggested fix
-								<span class="info">{{ iface.up ? 'up' : 'down' }}</span><br>
+								<span class="info">{{ iface.up ? t('serverinfo', 'Up') : t('serverinfo', 'Down') }}</span><br>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<span class="info">{{ iface.up ? 'up' : 'down' }}</span><br>
<span class="info">{{ iface.up ? t('serverinfo', 'Up') : t('serverinfo', 'Down') }}</span><br>
🤖 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 `@src/components/NetworkSection.vue` at 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.

Comment on lines +31 to +41
async function poll() {
try {
const response = await axios.get(generateUrl('/apps/serverinfo/update'))
data.value = response.data
tick.value++
} catch {
// Keep previous values on error
} finally {
if (!stopped) {
timeoutId = setTimeout(poll, 2000)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether a global timeout is already configured for `@nextcloud/axios` usage.
rg -nP '\baxios\.defaults\.timeout\b|timeout\s*:' 
rg -nP "from '`@nextcloud/axios`'"

Repository: nextcloud/serverinfo

Length of output: 50378


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== useLiveData.ts (relevant lines) =="
nl -ba src/composables/useLiveData.ts | sed -n '1,140p'

echo
echo "== timeout usage in useLiveData.ts =="
rg -n "timeout" src/composables/useLiveData.ts || true

echo
echo "== axios timeout config in src/ =="
rg -n "axios\.defaults\.timeout|defaults\.timeout|axios\.create|from '`@nextcloud/axios`'" src -S || true
rg -n "timeout\s*:" src -S || true

echo
echo "== `@nextcloud/axios` default timeout config (node_modules) =="
if [ -d node_modules/@nextcloud/axios ]; then
  rg -n "defaults\.timeout|timeout\s*:" node_modules/@nextcloud/axios -S --max-count 50 || true
else
  echo "node_modules/@nextcloud/axios not present in sandbox."
fi

Repository: nextcloud/serverinfo

Length of output: 163


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the file in case path differs
FILE="$(fd -a "useLiveData\.ts" src || true)"
echo "Found: $FILE"

echo
echo "== useLiveData.ts line count =="
wc -l $FILE

echo
echo "== useLiveData.ts lines 1-160 =="
cat -n $FILE | sed -n '1,160p'

echo
echo "== timeout-related strings in useLiveData.ts =="
rg -n "timeout|AbortController|signal|withTimeout" $FILE || true

echo
echo "== axios import/usage patterns in src/composables =="
rg -n "from '`@nextcloud/axios`'|axios\.get|axios\.create|axios\.defaults\.timeout|timeout\s*:" src/composables -S || true

echo
echo "== axios wrapper config across src/ (timeout + axios create/defaults) =="
rg -n "from '`@nextcloud/axios`'|axios\.create|axios\.defaults\.timeout|defaults\.timeout|timeout\s*:" src -S || true

Repository: nextcloud/serverinfo

Length of output: 2724


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== list possible axios sources =="
ls -la | head
ls -la node_modules 2>/dev/null | head || true

echo
echo "== search `@nextcloud/axios` in repo =="
fd -a "`@nextcloud/axios`" . --type f 2>/dev/null | head -n 50 || true

echo
echo "== check for bundled axios wrapper in dist/build (if present) =="
fd -a "serverinfo-main\.mjs" . || true
fd -a "client\.js$|`@nextcloud/axios`" js src node_modules . 2>/dev/null | head -n 50 || true

echo
echo "== search for defaults.timeout / timeout in bundled files =="
rg -n "defaults\.timeout|timeout\s*:" js src node_modules/@nextcloud/axios 2>/dev/null --max-count 50 || true

echo
echo "== search for create(...) usage in `@nextcloud/axios` wrapper (if present) =="
rg -n "axios\.create|create\(" node_modules/@nextcloud/axios js src 2>/dev/null --max-count 50 || true

Repository: nextcloud/serverinfo

Length of output: 50378


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Find whether any axios.get generics are used in src =="
rg -n "axios\.get<" src --max-count 50 || true

echo
echo "== Inspect `@nextcloud/axios` dist for default timeout config (small output) =="
for f in node_modules/@nextcloud/axios/dist/client.js node_modules/@nextcloud/axios/dist/index.js node_modules/@nextcloud/axios/dist/*.js; do
  [ -f "$f" ] || continue
  echo "--- $f ---"
  rg -n "timeout\s*:\s*[0-9]+|defaults\.timeout|timeoutMessage" "$f" -S --max-count 30 || true
done

echo
echo "== Inspect TypeScript types for axios.get signature (if available) =="
for f in node_modules/@nextcloud/axios/dist/index.d.ts node_modules/@nextcloud/axios/dist/*.d.ts node_modules/@nextcloud/axios/*.d.ts; do
  [ -f "$f" ] || continue
  echo "--- $f ---"
  rg -n "get<|get\(" "$f" -S --max-count 30 || true
done

Repository: nextcloud/serverinfo

Length of output: 767


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Search `@nextcloud/axios` for defaults.timeout / timeout assignment =="
rg -n "defaults\.timeout|axios\.defaults\.timeout|timeoutMessage|timeout\s*:" node_modules/@nextcloud/axios -S --max-count 50 || true

echo
echo "== Search axios core defaults for timeout =="
rg -n "timeout\s*:" node_modules/axios -S --max-count 50 || true

echo
echo "== Search axios for defaults.timeout explicitly =="
rg -n "defaults\.timeout" node_modules/axios -S --max-count 50 || true

Repository: nextcloud/serverinfo

Length of output: 50378


Add an explicit HTTP request timeout to prevent the polling loop from stalling indefinitely

In src/composables/useLiveData.ts, poll() awaits axios.get(...) (line 33) and only schedules the next setTimeout after that promise resolves. With no timeout passed, Axios’s default is timeout: 0 (no timeout), so a hung request can block future polls forever.

Suggested fix
 export function useLiveData() {
+	const POLL_INTERVAL_MS = 2000
+	const REQUEST_TIMEOUT_MS = 10000
 	const data = ref<LiveData | null>(null)
 	const tick = ref(0)
@@
 	async function poll() {
 		try {
-			const response = await axios.get(generateUrl('/apps/serverinfo/update'))
+			const response = await axios.get<LiveData>(generateUrl('/apps/serverinfo/update'), {
+				timeout: REQUEST_TIMEOUT_MS,
+			})
 			data.value = response.data
 			tick.value++
 		} catch {
 			// Keep previous values on error
 		} finally {
 			if (!stopped) {
-				timeoutId = setTimeout(poll, 2000)
+				timeoutId = setTimeout(poll, POLL_INTERVAL_MS)
 			}
 		}
 	}
🤖 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 `@src/composables/useLiveData.ts` around lines 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.

Comment thread src/utils.ts
Comment on lines +46 to +55
export function formatBytes(bytes: number): string {
const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']
let value = Math.abs(bytes)
let i = 0
while (value >= 1024 && i < units.length - 1) {
value /= 1024
i++
}
return value.toFixed(1) + ' ' + units[i]
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Preserve sign in formatBytes output.

Negative inputs are converted to absolute values and returned as positive strings, which is incorrect.

Suggested fix
 export function formatBytes(bytes: number): string {
 	const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']
+	const sign = bytes < 0 ? '-' : ''
 	let value = Math.abs(bytes)
 	let i = 0
 	while (value >= 1024 && i < units.length - 1) {
 		value /= 1024
 		i++
 	}
-	return value.toFixed(1) + ' ' + units[i]
+	return sign + value.toFixed(1) + ' ' + units[i]
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export function formatBytes(bytes: number): string {
const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']
let value = Math.abs(bytes)
let i = 0
while (value >= 1024 && i < units.length - 1) {
value /= 1024
i++
}
return value.toFixed(1) + ' ' + units[i]
}
export function formatBytes(bytes: number): string {
const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']
const sign = bytes < 0 ? '-' : ''
let value = Math.abs(bytes)
let i = 0
while (value >= 1024 && i < units.length - 1) {
value /= 1024
i++
}
return sign + value.toFixed(1) + ' ' + units[i]
}
🤖 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 `@src/utils.ts` around lines 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.

Comment on lines +139 to +140
const { data: staticData } = useStaticData()
const { data: liveData, tick } = useLiveData()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Handle static-data error/loading state instead of infinite skeleton fallback.

At Lines 139-140, the view ignores loading/error from useStaticData(). When the initial request fails, staticData stays null and the page never exits skeleton mode, which blocks admins from understanding failure state.

💡 Suggested fix
+import { t } from '`@nextcloud/l10n`'
 ...
-const { data: staticData } = useStaticData()
+const { data: staticData, loading: staticLoading, error: staticError } = useStaticData()
 const { data: liveData, tick } = useLiveData()
-		<template v-if="staticData">
+		<template v-if="staticData">
 			...
 		</template>
+		<template v-else-if="staticError">
+			<div class="section">
+				{{ t('serverinfo', 'Failed to load server information.') }}
+			</div>
+		</template>
 
-		<template v-else>
+		<template v-else>
 			...
 		</template>
🤖 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 `@src/views/SettingsAdmin.vue` around lines 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants