Repository navigation
Merged
Conversation
Adds apexcharts/vue3-apexcharts deps and two shared utils that consolidate what's currently duplicated across chart files: apexChartTheme.ts merges chartTheme.ts (getChartPalette) with useDashboard.ts's more robust CSS-var resolution + reactive MutationObserver theming; chartTimeAxis.ts consolidates the clampTimestamp/getMinPointTimestamp/getMaxPointTimestamp helpers duplicated in DiskHistoryChart, HostMetricsPanel, NetworkFlowsHistoryChart and useDashboard. Phase 0 of the Chart.js -> ApexCharts migration; no visual change yet. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
First chart migrated off Chart.js/vue-chartjs, establishing the
reference pattern for the rest of the migration: lazy-loaded
<apexchart> via defineAsyncComponent(() => import('vue3-apexcharts')),
shared apexChartTheme.ts palette (this chart previously read its own
--tblr-yellow directly, now uses palette.disk) and chartTimeAxis.ts
for the min/max/clamp helpers. Custom HTML tooltip reproduces the
original % + Go breakdown. Adds a vendor-apexcharts manual chunk in
vite.config.js so the new dependency stays lazy-loaded and out of the
main bundle, verified via npm run build.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
CPU + memory line charts, following the DiskHistoryChart pattern: lazy-loaded <apexchart>, shared apexChartTheme.ts/chartTimeAxis.ts (this component already used the Chart.js chartTheme.ts palette, now on the ApexCharts equivalent), custom HTML tooltips (memory's shows used/total bytes like before). Removes the per-file clampTimestamp/getMin/MaxHistoryTimestamp duplicates in favor of the shared chartTimeAxis helpers. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Migrates the 6 RRD chart instances (CPU/RAM/IO Wait/Réseau/Température/
RPM) to ApexCharts. RRDChartCard.vue's chartData/options props were
previously `as any` — now typed as `RRDChartSeries` (a
NonNullable<ApexOptions['series']> alias, since ApexAxisChartSeries
itself isn't exported by the apexcharts package) and `ApexOptions`.
useProxmoxNode.ts now builds {x,y} timestamp series instead of
Chart.js's {labels, datasets} shape, switching all 6 RRD charts to
xaxis type datetime with per-timeframe label formatting owned by
ProxmoxNodeChartsPanel.vue (reactive to the timeframe prop, replacing
the old pre-formatted label strings). CPU/RAM series colors now come
from the shared apexChartTheme palette (matching HostMetricsPanel);
IO Wait/temperature/fan/network colors stay on their existing
Tabler CSS var reads, unchanged in appearance.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Migrates the guest detail page's 2-series (CPU%/RAM%) history chart, and moves its chart-options construction out of the view into useProxmoxGuest.ts (was inline in ProxmoxGuestView.vue, contrary to frontend/CLAUDE.md's per-view composable convention). Colors now come from the shared apexChartTheme palette (cpu/ram, matching HostMetricsPanel/RRDChartCard) instead of the view's own hardcoded tooltip/grid colors. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
RX/TX 2-series area chart, following the established pattern. Colors now come from apexChartTheme's networkRx/networkTx palette entries (added in Phase 0 specifically to preserve this chart's CVD-safe azure/teal pair) instead of the file's own hardcoded --tblr-azure/ --tblr-teal reads. Also drops the local clampTimestamp/getMinPoint/ getMaxPointTimestamp duplicates in favor of chartTimeAxis.ts — the last of the 4 duplicate sites flagged in the migration plan. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…exCharts
The one category-scale chart in the migration (xaxis type category
with pre-formatted bucket labels, instead of datetime) — isolated to
its own phase since it's the only file that doesn't fit the
timestamp/{x,y} pattern used everywhere else. useUptimeProbeDetail.ts's
chartData now returns { categories, series } instead of Chart.js's
{ labels, datasets }. Uses the shared apexChartTheme palette for grid/
tick/tooltip styling; the latency series color stays the same literal
green as before (not promoted to the shared palette — this metric has
no other consumer to share a semantic color with).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Migrates the dashboard's CPU/RAM summary trend chart and extracts its previously dashboard-only reactive theming system (CSS-var resolution + MutationObserver for live theme-flip updates) into the shared useReactiveApexChartPalette() from apexChartTheme.ts (Phase 0) — this was the more robust of the two theming implementations the plan set out to consolidate, now available to any chart that needs live theme reactivity, not hardcoded to the dashboard alone. Also drops the local clampTimestamp/getSummaryMin/MaxTimestamp duplicates in favor of chartTimeAxis.ts. The IntersectionObserver lazy-mount (rootMargin 200px) is untouched — only the component being gated changed from Chart.js's <Line> to <apexchart>. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…clarative ApexCharts
Replaces the imperative Chart.js pattern (new Chart()/chart.destroy()
on every data change, manual legend onHover/onLeave cursor styling)
with reactive <apexchart :options :series> — vue3-apexcharts diffs
and updates in place, so the destroy/recreate lifecycle and the
deep watch(timeseries)/watch(statusDistribution) triggering it are
gone entirely. Legend hover cursor is now plain CSS
(:deep(.apexcharts-legend-series) { cursor: pointer }) instead of
canvas-context JS handlers, since the ApexCharts legend is real DOM.
Both charts now use the shared apexChartTheme palette for
legend/tick/grid/tooltip text (the originals had no theme
integration at all); the traffic-specific series colors (human/bot,
2xx-5xx) stay as their existing literal hex values.
These were the last two Chart.js/vue-chartjs consumers in the app —
`npm run build` no longer even emits a vendor-chart chunk.
Test updates:
- TrafficOverviewPanel.spec.ts: removed the chart.js mock entirely
(dead code — TrafficRequestsChart/TrafficStatusChart are already
stubbed at the component level in this test, so their own
<script setup> never runs and nothing chart-library-specific needs
mocking).
- TrafficOverviewPanel.browser.test.ts: canvas assertions ported to
ApexCharts' SVG output (svg.apexcharts-svg — narrower than
'.apexcharts-canvas svg', which also matches nested legend-marker
<svg> icons with clientWidth 0, would have made the sizing check
flaky). Also fixed a pre-existing fixture bug this surfaced: the
mock timeseries used ts/total/bots fields that never matched
useTraffic.ts's actual timestamp/human/bot shape — Chart.js
silently tolerated the resulting undefined labels, ApexCharts
throws on them. Verified locally with npx playwright install +
npm run test:browser (not part of CI).
- ProxmoxNodeView.spec.ts: updated a stale comment mentioning
Chart.js by name (functionally unchanged, stub was already
library-agnostic).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…tion Final phase of the Chart.js -> ApexCharts migration: all 10 chart files were already ported (Phases 1-5); this drops the now-unused dependencies and dead code. - Remove chart.js, vue-chartjs (dependencies) and @types/chart.js (devDependencies) from package.json; npm install to sync the lockfile (also drops chart.js's own @kurkle/color dependency). - Delete utils/chartTheme.ts, fully superseded by apexChartTheme.ts since Phase 0. - Remove the now-dead chart.js/vue-chartjs branch from vite.config.js's manualChunks (no reachable import matches it anymore). - Update frontend/CLAUDE.md's browser-test note (Chart.js -> ApexCharts; the D3 reference, covering NetworkGraph.vue/the map, is unrelated to this migration and untouched). Verified via `grep -r "chart.js\|vue-chartjs" frontend/src` returning nothing before removing the deps, plus a clean typecheck/lint/test/ build afterwards — `npm run build` no longer emits a vendor-chart chunk at all, confirming no reachable import remained. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
feat(frontend): migrate Chart.js/vue-chartjs to ApexCharts
…r classes
Replaces the hand-rolled .notification-dropdown positioning/background/
border/shadow/z-index with Tabler's .dropdown/.dropdown-menu/
.dropdown-menu-end classes, which already encode all of that via the
app's design tokens. Only top/right/width stay as explicit overrides,
since Bootstrap's JS (which sets [data-bs-popper] for auto-positioning)
is never loaded in this app. z-index now comes from the app's own
.dropdown-menu { z-index: var(--z-index-popover) } rule in style.css
instead of a bespoke 2100 that predated that token system.
Replaces the hand-rolled .ss-toast-container/.ss-toast (background/
border/border-radius/box-shadow/width/font-size) with Tabler's
.toast-container/.toast/.toast-body classes, which already encode all
of that via design tokens — including z-index, via the app's existing
.toast { z-index: var(--z-index-toast) } rule in style.css that this
component wasn't using yet. Only the per-type accent (a colored left
border + matching icon color, which Tabler's .toast has no built-in
variant for) stays custom.
refactor(frontend): migrate NotificationBell dropdown to native Tabler classes
refactor(frontend): migrate ToastContainer to native Tabler .toast
Replaces the hand-built <dl class="row"> label/value grid (Sujet/ Émetteur/Numéro de série/SAN-DNS) in SslDetailSection.vue with Tabler's .datagrid/.datagrid-item/.datagrid-title/.datagrid-content — the component's actual intended use case (dense key/value metadata), per docs.tabler.io/ui/components/. First real adoption of .datagrid in the codebase.
refactor(frontend): adopt Tabler .datagrid for SSL certificate details
…ty bars Both the probe detail's "Disponibilité" bar (UptimeDetailSection.vue) and the monitoring list's inline availability column (MonitoringOverviewPanel.vue) hand-rolled Tabler's .tracking component (flex row of gapped blocks) via `d-flex align-items-end gap-1`. Swapped for the real .tracking class. Individual blocks stay flex-fill + bg-success/bg-danger/bg-secondary-lt rather than adding .tracking-block: in the compiled Tabler CSS, .tracking-block's own `background` rule comes after the bg-* utilities, so it would win at equal specificity and paint every block gray regardless of status. Documented inline where this matters.
refactor(frontend): use Tabler .tracking layout for uptime availability bars
…design system Rebuilds alert_email_template.html against the design tokens from Tabler's official email template pack (tabler.io/emails) instead of ad-hoc CSS: table-based layout throughout (the old template used CSS grid for the details row, which most email clients don't support), Inter font stack, and the exact same brand hex values the app already uses (frontend/CLAUDE.md's semantic color table — verified against Tabler's compiled email CSS, not eyeballed): danger #d63939, warning #f59f00, success #2fb344, primary #066fd1. Also: - Severity-aware accent color/badge (crit=red/"Critique", warn=yellow/"Avertissement") via a new AlertEmailData.Severity field, computed with the existing alerts.DetermineSeverity — the previous template was always red regardless of actual severity. - French copy throughout (subject, body, footer), matching the rest of the app; the previous template was English in an otherwise all-French product. - Bonus prefers-color-scheme dark-mode support (Tabler's own pack ships light/dark as two separate static files with no adaptive single-file version; this app is closer to a single automated transactional template, so a light base + dark media-query override made more sense here). - No external image/font-CDN dependencies (no logo asset exists for this self-hosted tool) — text wordmark only. Verified by rendering both severities in both color schemes (Playwright screenshot against the rendered HTML directly, file://) before/after — see PR description.
refactor(server): align alert email template with Tabler's own email design system
…ern) Splits the single overloaded navbar into two stacked rows, matching Tabler's own dashboard demo layout (and the two-row pattern used by comparable self-hosted tools like Nginx Proxy Manager, referenced directly by the user for this change): - Row 1 (always visible, never collapses): brand, hosts-down badge, search/command-palette trigger, notification bell, user menu. - Row 2 (collapses on mobile behind the hamburger, unchanged content): the 6 intent-section nav-dropdowns (config/navigation.ts). Everything used to share one navbar-expand-lg bar, which is what the project's own git history already flags as a recurring pain point (commit history includes a prior "fix-navbar-overflow-secondary-hover" branch) — cramming brand + 6 section dropdowns + search + notifications + user menu into one row left little room at anything under ~1600px, and the search box/user name were visibly cramped in demo-mode screenshots taken earlier in this session. Two real bugs caught during visual QA (not just style tweaks): - Tabler's own .nav-item class sets flex-direction: column. Row 1's icon cluster was wrapped in a single .nav-item div, which silently stacked the search/bell/user-menu icons vertically instead of the intended horizontal row — invisible in a quick glance, obvious once screenshotted. Fixed by adding .flex-row to that wrapper (the section nav-items in row 2 don't hit this, since dropdown-menu is taken out of flow and each has only one in-flow child). - Row 1 and row 2 are sibling <header> elements at the same z-index (1030) in DOM order — row 1's absolutely-positioned dropdowns (notification bell, user menu) visually extend past row 1's own bottom edge into row 2's screen area, and row 2, being a later sibling stacking context at equal z-index, painted over that overlap. The user-menu dropdown's "Compte"/"Rôle" lines were genuinely in the DOM with correct color/position (confirmed via elementFromPoint()) but visually hidden. Fixed by dropping row 2's z-index to 1020, below row 1's dropdowns. Tabler ships no .navbar-light in this dark-only app (body[data-bs- theme=dark] forces dark navbar colors on every .navbar regardless of that class), so row 2 gets a small custom .navbar-secondary rule using --tblr-bg-surface-secondary (Tabler's own "one step darker" surface token) to read as a visually distinct row. Verified visually at 1440/820/500/375px, both rows' dropdowns open (section dropdown, notification bell, user menu), and the mobile collapsed-menu state.
Row 1 (hamburger, brand, search/bell/user icons) was wrapping to two lines below ~500px — "ServerSupervisor" plus the icon cluster didn't fit on one line, so the icons dropped to their own row instead of sitting next to the burger/brand as intended. - Brand collapses to just the IconServer mark below sm (576px); the wordmark text only shows once there's room for it. - .container-xl gets flex-nowrap so the row can't wrap once it fits — it was relying on flex-wrap's default "wrap" purely by omission. - The icon cluster's order-md-last (only reordered from md/768px up) is now order-last, so it's pinned to the right edge at every width, including the small ones where this actually matters. Verified at 320/375/414px (single line, all icons visible) and re-checked 820/1440px + the mobile open state for regressions.
refactor(frontend): split navbar into two rows (Tabler dashboard pattern)
govulncheck flagged 7 (server) / 5 (agent) reachable stdlib vulnerabilities fixed in Go 1.25.12/1.25.13 (crypto/tls, net/http, net/url, encoding/asn1, html/template) plus an unreachable OOB-read in klauspost/compress. Both go.mod min versions were pinned at 1.25.11. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
npm audit --audit-level=moderate (blocking in security.yml) failed on nanoid <3.3.18's zero-size generator infinite loop, pulled in transitively via postcss-html. npm audit fix resolves it in place. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…#316) SonarCloud's Quality Gate on PR #268 (dev -> main) fails partly on new-code coverage. Adds/extends happy-dom unit and component specs for the composables, utils, api client modules and non-chart-rendering Vue components flagged with the most uncovered new lines: useProxmoxNode, apexChartTheme, App.vue, useDashboard, useProxmoxGuest, chartTimeAxis, UptimeDetailSection/SslDetailSection, useProxmoxConsole, main.ts, HostTasksTab, ProxmoxGuestView, useGlobalScheduledTasks, ToastContainer, api/proxmox, api/tasks. Co-authored-by: root <root@CT-50-claude.local> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…age into SonarCloud (#317) * ci(frontend): re-enable real-browser tests in CI and wire their coverage into SonarCloud The upstream Vite 8 (rolldown)/Vitest 4 browser-mode dep-optimizer bug (rolldown/rolldown#9502, vitejs/vite#22583) is fixed as of rolldown 1.1.2; the currently pinned vite (8.2.2, rolldown ~1.2.5) already ships the fix. Verified locally: npm run test:browser passes reliably, including a pre-existing test that mounts a real Vue SFC via @vue/test-utils and asserts on actual ApexCharts/D3 SVG rendering — exactly what both previously-documented failure modes (dual Vue instances, dangling rolldown chunk-split init call) would have broken. - ci-frontend.yml: add a browser-tests job (Playwright/Chromium install + npm run test:browser), replacing the "intentionally not run" comment. - sonar.yml: frontend-coverage job also runs npm run test:browser:coverage, producing a second lcov report (coverage-browser/) alongside the happy-dom one; both are uploaded/downloaded via the same artifact and path-rewritten for SonarCloud. - sonar-project.properties: sonar.javascript.lcov.reportPaths lists both report paths (SonarCloud merges per-file hit counts across them); *.browser.test.ts added to sonar.test.inclusions for the same reason Go *_test.go files are listed there (own added lines otherwise count as uncovered new code). - vitest.browser.config.ts: coverage config (v8 provider, lcov reporter) mirroring vitest.config.ts's happy-dom setup; stale "known issue" comment removed. - CLAUDE.md / frontend/CLAUDE.md / .github/workflows/README.md: updated to describe the current state (both test suites run in CI and feed coverage). Confirmed effect: the one existing browser test already covers TrafficRequestsChart.vue (18/20 lines) and TrafficStatusChart.vue (9/9 lines) once its coverage is measured — both previously 0%-covered new code. Writing tests for the remaining uncovered chart components (HostMetricsPanel, ProxmoxNodeChartsPanel, DiskHistoryChart, NetworkFlowsHistoryChart) is a follow-up; this change only makes the pipeline capable of running and measuring them. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * ci(frontend): exclude coverage output from ESLint frontend/coverage/ and frontend/coverage-browser/ (both generated by Vitest coverage reporters) contain an lcov-report/block-navigation.js whose eslint-disable directive ESLint otherwise flags as unused. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: root <root@CT-50-claude.local> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…cheduled-task/task-run paths (#318) Adds unit and integration tests for the SonarCloud new-code-coverage shortfall on the v11 release PR (#268): the interactive LXC console feature (proxmoxclient.TermSession framing, ws.ProxmoxConsole handler, proxmox.Service.OpenGuestConsole), the proxmox_connections console-credential columns, the poller's vzdump task-merge/backup-run path, the scheduled-task RunCustomTask endpoint (service, handler, dispatch), the CSP style-src relaxation, and the router wiring for the new console route. Co-authored-by: root <root@CT-50-claude.local> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…ongo-driver API (#319) CVE-2026-81521 flags go.mongodb.org/mongo-driver/v2's Client.BulkWrite (unescaped database name in a MongoDB bulk-write namespace). This server only talks to Postgres/TimescaleDB — mongo-driver is an indirect dependency pulled in solely for its bson sub-package, which gin/binding links in for optional BSON request binding (`go mod why -m go.mongodb.org/mongo-driver/v2`: gin -> gin/binding -> mongo-driver/v2/bson). The vulnerable mongo.Client type is a different sub-package, never imported, never linked into the binary. Currently failing on dev's own tip (unrelated to any in-flight PR), so every PR's nancy (server) check is red until this lands. Co-authored-by: root <root@CT-50-claude.local> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
1 task done
) * test(frontend): cover HostCustomTasksTab (loading/empty/list/run/errors) This component had zero test coverage since it was added — every branch (loading skeleton, empty state, active-gated lazy load, run success with toast/track/emitted events, canRunApt=false hiding the run button, run failure, load failure) is exercised, following the existing HostTasksTab.spec.ts mocking pattern. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(frontend): avoid Array.prototype.at() unsupported by the project's ts lib target vue-tsc flagged TS2550 on wrapper.emitted(...)?.at(-1) — the tsconfig's target lib predates ES2022's Array.at(). Index by length instead. --------- Co-authored-by: root <root@CT-50-claude.local> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…le (#322) A coverage percentage is a symptom, not a goal — while closing a SonarCloud new-code coverage gap, some added tests were coverage-driven rather than behavior-driven. Documents the correction so future coverage work stays anchored to real assertions over line counts. Co-authored-by: root <root@CT-50-claude.local>
…and healthchecks (#320) * feat(server): production readiness bootstrap, persistent jwt secret, and healthchecks - Auto-generate secure random admin password on first run with clear log banner and must_change_password=true - Persist auto-generated JWT_SECRET in settings table for seamless container upgrades - Standardize Dockerfile base images (Node 22, Go 1.25, Alpine 3.21) and add native HEALTHCHECK - Add Cache-Control headers to router (immutable for hashed assets, no-cache for SPA entrypoints) - Fix RFC3339 query timezone parameter parsing in timerange handlers - Add non-Linux stub and test skips for cross-platform agent build integrity - Update documentation and .env.example * fix(server): address PR review findings on security, caching, logging and edge cases - Restore strict rejection of default DB_PASSWORD in production - Re-apply must_change_password enforcement on existing installations configured with default admin credentials - Register HEAD routes for static assets and ensure 404s under /assets never receive immutable caching - Pin exact Go builder image golang:1.25.13-alpine to match go.mod - Migrate main.go bootstrap logging to structured slog.*Context - Clean up unused CountUsers method and protect GenerateRandomPassword against length bounds panic * fix(server): extract admin bootstrap into a testable function, cover the must_change_password safety net The existing-installation branch of the admin bootstrap logic (added in a prior commit on this PR to re-apply must_change_password when ADMIN_PASSWORD is still the literal default "admin") had no test — it lived inline in main(), which isn't unit-testable. Extracts it to bootstrapAdminAccount(ctx, db, cfg) in its own file and covers all four paths: first-run with a generated password (forces the change flag), first-run with a configured password (doesn't), an existing install still on the default password (re-asserts the flag on restart — the actual regression this safety net exists for), and an existing install on a non-default password (leaves the flag alone). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * test(server): cover bootstrapAdminAccount's admin-check error path Deterministic via a canceled context rather than a broken-connection mock — verifies the wrapped "failed to check existing admin user" error actually surfaces to the caller instead of being swallowed. * test(server): cover the persisted-JWT-secret path in OverrideFromDB OverrideFromDB's new jwt_secret branch (the persistent-JWT-secret feature this PR adds) had no test: neither "a secret saved at a prior boot is picked back up" nor "an env-provided secret takes precedence over a persisted one" was covered. Added alongside the existing fakeSettingsLoader-based tests in the same file. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: root <root@CT-50-claude.local> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
* fix(server): serialize TermSession's PVE websocket writes
ws/proxmox_console.go calls Write/Resize from its browser-to-PVE
forwarding goroutine while a separate keepalive-ticker goroutine calls
Ping concurrently — gorilla/websocket allows at most one concurrent
writer per connection, so a keystroke/resize landing at the same
moment as the 60s ping could corrupt the termproxy wire framing or
panic. The browser-facing socket already had this protection
(safeWriteMessage/writeGuard); TermSession's PVE-facing socket had
none.
Added a regression test that reproduces the panic without the fix
(verified: reverting writeMu locking crashes the test with "panic:
concurrent write to websocket connection" under -race).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(server): don't silently no-op the must_change_password safety net on a renamed ADMIN_USER
HasAdminUser only proves *some* admin account exists (role='admin'),
not that cfg.AdminUser resolves to one. If ADMIN_USER is changed after
first boot, the existing-installation branch would call
SetUserMustChangePassword against a username matching no row —
a silent UPDATE affecting 0 rows, with no signal that the intended
safety net never actually applied to the real (differently-named)
admin account. Now verifies the account exists first and logs an
explicit warning when it doesn't, rather than continuing quietly.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(server): bound the termproxy login handshake read with a deadline
OpenLXCConsole's login()/createTermproxy() HTTP calls are already
bounded by the client's own Timeout (20s), and the WS dial already has
dialer.HandshakeTimeout (10s) — but the write/read pair immediately
after the dial (send "<user>:<ticket>", wait for the literal "OK")
had no bound at all. A PVE that accepts the WS upgrade but never
completes the handshake (wedged termproxy) would hang the calling
goroutine indefinitely. The deadline is cleared before returning the
session, so the ongoing interactive session (kept alive by
TermSession's own Ping ticker, not a read deadline) is unaffected.
handshakeTimeout is a var rather than a const so the new regression
test can shrink it instead of waiting out the real 10s.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(server): populate PVEUsername/ConsoleConfigured in GetEnabledProxmoxConnections
Unlike ListProxmoxConnections/GetProxmoxConnectionByID, this
poller-facing query didn't select pve_username/console_configured, so
any caller reading those fields off the returned (embedded)
models.ProxmoxConnection got silently zero-valued/false data instead
of a compile error. Not currently exploited (console credentials are
fetched separately via GetProxmoxConsoleCredentials), but a latent
trap for a future caller. Extended the existing CRUD test to assert
on both fields so this can't regress silently again.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(frontend): clear stale chartOptions alongside series in useProxmoxGuest
Both the empty-response and fetch-failure paths in loadGuestSummary
reset series.value to null but left chartOptions.value (with the
previous load's time window) stale. Currently masked by the
template's v-else-if="series && chartOptions" gate, but that's an
accident of both being checked together, not something the composable
itself guarantees.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(frontend): remove dead light-theme CSS in AlertRuleStepSource's metric cards
.metric-card/.metric-card.selected each had a base rule plus a
[data-bs-theme='dark'] override with different colors — this app is
dark-only, so the dark-scoped selector always won on specificity and
the base rule's --tblr-bg-surface/--tblr-border-color values were
never actually rendered. Same pattern already cleaned up in
AlertRuleModal's step chips; this component was left out of that pass.
Merged into a single un-scoped rule using the values that were already
winning — no rendered output change.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* perf(server): fetch a node's plain and vzdump-filtered task lists concurrently
The two GetNodeTasks calls (general window + vzdump-filtered window,
merged by UPID) are independent PVE API calls with no data dependency,
but ran back to back — doubling this step's wall-clock cost per node,
every poll cycle. Now fetched concurrently via a small WaitGroup fan-out
(safego.Recover-guarded, matching the rest of the codebase's goroutine
conventions), bounding the cost by whichever call is slower instead of
their sum.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* refactor(frontend): deduplicate ApexChartInstance type and the async chart loader
Both were copy-pasted identically across the ApexCharts migration:
- ApexChartInstance (the one vue3-apexcharts instance method every
ref-based imperative update actually calls) was restated verbatim in
5 files.
- const ApexChart = defineAsyncComponent(() => import('vue3-apexcharts')...)
was restated identically in 9 files.
Both now live once in apexChartTheme.ts, alongside the palette helpers
that already serve the same "one shared place for ApexCharts glue"
role. A future vue3-apexcharts signature/loading change is now a
one-file edit instead of finding every copy.
Moving the async component out of each SFC's own <script setup> block
lost Vue's compiler-inferred component name (only locally-declared
script-setup components get one, not imported ones), which broke
vue-test-utils' global.stubs: { ApexChart } name matching in one spec
(UptimeDetailSection's ref().updateOptions() test) — fixed by setting
.name explicitly on the shared export. Verified: full unit suite
(436 tests), typecheck, lint, build, and the real-browser ApexCharts
rendering test all pass.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* docs(server): document ValidateStrict's load-bearing ordering with JWT/admin generation
ValidateStrict deliberately accepts an empty JWTSecret/AdminPassword —
main.go/bootstrapAdminAccount generate and persist a random value for
each right after this check runs. That's correct by design, but the
invariant lives entirely in call-site ordering across two files with
nothing enforcing it — documented so a future refactor doesn't
silently reorder it into a real gap.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
---------
Co-authored-by: root <root@CT-50-claude.local>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
…chart rendering) (#324) * test(frontend): cover the emit-based field setters the S8951 fix introduced AlertRuleStepConditions/Source, SettingsThreatDetectionCard, SettingsProxmoxCard and WebhookTrackerFields all had characterization specs (which fields render for which mode) but none actually wrote to a field and asserted the emitted update:form event — the exact behavior the prop-mutation-to-emit fix added. Every new test asserts both that the correct key changed (a copy-paste error assigning the wrong key to a fieldModel() call would show up as one field silently controlling another's) and that the original prop object stayed untouched, i.e. the regression these components were originally flagged for can't come back unnoticed. Also fixes a real, unrelated gap in SettingsProxmoxCard.spec.ts: the existing connection-test spec's `input[autocomplete="new-password"]` selector matched the token-secret field first, so form.pve_password — part of the interactive-console feature — was never exercised by any test despite looking covered by proxy. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * test(frontend): add real-browser rendering tests for the remaining 0%-covered ApexCharts components DiskHistoryChart, HostMetricsPanel, NetworkFlowsHistoryChart and ProxmoxNodeChartsPanel had zero test coverage since the Chart.js → ApexCharts migration — nothing could exercise real SVG rendering before the Vite/Vitest browser-mode fix. Each new test mounts the component into a real attached DOM node and polls for actual apexcharts-svg elements + drawn series paths, following the existing TrafficOverviewPanel.browser.test.ts pattern: - DiskHistoryChart: renders on load and after switching time range. - HostMetricsPanel: both CPU and memory charts render independently. - NetworkFlowsHistoryChart: RX/TX render as two distinct series. - ProxmoxNodeChartsPanel: one SVG per populated RRD series (CPU/RAM/ network), empty-text fallback confirmed for the untouched ones (iowait/temp/fan) — this component takes series as plain props, no API mocking needed. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: root <root@CT-50-claude.local> Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
|
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Summary
Fusionne
devdansmain— 80 commits couvrant sept chantiers distincts, du plus ancien au plus récent.1. Migration ApexCharts (Chart.js → vue-chartjs supprimés) + stabilisation post-déploiement (#257, #269, #272-276, #283)
vendor-chartsupprimé.formatter/tooltip.custompar le cloneJSON.parse(JSON.stringify(...))de vue3-apexcharts sur mise à jour réactive (corrigé en figeant l'identité deoptionsaprès le premier rendu), thème tooltip clair par défaut, fond de graphique non transparent, CSS mort dans le stepper d'alertes.2. Alignement Tabler (#262-267)
NotificationBell/ToastContainermigrés vers les composants natifs Tabler (dropdown/toast), adoption de.datagrid(détails certificat SSL) et.tracking(barres de disponibilité uptime), template e-mail d'alerte reconstruit à partir du pack Tabler Emails officiel, navbar refaite en deux rangées sur le modèle du dashboard Tabler officiel.3. Console interactive Proxmox LXC (#280, #288, #323)
Nouvelle fonctionnalité : shell interactif (xterm.js) sur un conteneur LXC via le protocole termproxy PVE non documenté, implémenté contre le source
pve-xtermjsofficiel plutôt que deviné. Identifiants console dédiés par connexion, poste admin-only avec audit log.#288corrige le graphique invité, la console/nav mobile et les tâches de sauvegarde manquantes.#323corrige une race condition confirmée sur l'écriture WebSocket concurrente (goroutine clavier vs. ticker de keepalive) et un handshake termproxy sans timeout (PVE bloqué = goroutine qui ne se termine jamais).4. Tâches personnalisées / planifiées (#285, #321)
Exécution ad-hoc d'une tâche personnalisée,
actionoptionnelle pour le modulecustom, rafraîchissement live du statut ; couverture de tests dédiée pour l'onglet.5. Bootstrap production readiness (#320, #323)
Génération et persistance automatique du secret JWT et du mot de passe admin initial quand ils ne sont pas configurés (fini le mot de passe par défaut
adminobligatoire), garde-fou qui réappliquemust_change_passwordsur une installation existante toujours sur le mot de passe par défaut, endpoints de healthcheck.#323corrige un contournement silencieux du garde-fou quandADMIN_USERest renommé après le premier démarrage, et une alerte CodeQL réapparue (déjà tranchée « won't fix » par ailleurs) suite à l'extraction de cette logique dans une fonction testable.6. Remédiation qualité SonarCloud (#305, #315)
Audit complet des signaux SonarCloud (voir
AUDIT-SONARQUBE-2026.md) : correctifs réels (vérification hostname/chaîne SSL manquante, origine non vérifiée sur les messages du service worker), faux positifs documentés (NOSONARjustifiés), première puis deuxième passe d'accessibilité (labels de formulaire).#315corrige les 59 occurrences restantes du pattern "mutation directe d'une propform" dans les composants d'alertes/webhooks/paramètres, remplacées par unemitd'événement.7. Durcissement sécurité & CI (#293, #295, #297, #304, #306, #307, #309, #310, #314, #319, #323)
Scan de secrets (TruffleHog) et Trivy passés en bloquant, analyse statique SonarCloud sur les 3 modules, Dependabot ciblant
devavec des libellés valides et Sonar/Nancy désactivés sur ses PRs, CodeQL scindé dans son propre workflow et pointé sur la bonne version de Go.#314/#319corrigent deux faux positifs de sécurité rendant le signal Trivy/Nancy incohérent (CVE OpenSSL réelle + package Go non atteignable, dépendance MongoDB jamais utilisée).#323ajoute un mutex manquant sur la console Proxmox et un timeout de handshake (voir section 3).8. Couverture de tests pour la jauge de release (#316, #317, #318, #321)
Le bug Vite 8 (rolldown)/Vitest 4 bloquant les tests navigateur en CI s'est révélé déjà corrigé en amont (jamais retesté après les montées de version) — réactivés et branchés sur la couverture SonarCloud (#317). Complété par des tests ciblés côté frontend (#316, #321) et serveur (#318) sur les zones nouvellement ajoutées (console Proxmox, tâches planifiées/personnalisées).
9. Dépendances, CVE et documentation (#254, #279, #289, #291, #292, #294, #296, #301, #302, #303, #311, #312, #313, #322, + montées Go/nancy ad hoc)
Montées de version pour fermer des CVE (toolchain Go,
klauspost/compress,nanoid,moby/go-archive,x/net), cross-compilation de l'image Docker serveur au lieu de l'émulation arm64, LICENSE (AGPLv3) et SECURITY.md, migration dedocs/vers le wiki GitHub auto-déployé, documentation du flux de branchedev→main, règle de style documentaire (ton factuel, pas de citation de numéro de PR comme justification) et règle de qualité des tests (une couverture est un symptôme, pas un objectif).Test plan
dev(voir chaque PR liée pour le détail).git diff main...dev --stat: 192 fichiers, +13868/-2879.-race+ Vitest), tests navigateur réels (ApexCharts/D3), CodeQL, Trivy, Nancy, govulncheck, npm audit, TruffleHog.-race), bootstrap admin/JWT (4 scénarios testés : premier démarrage généré/configuré, installation existante mot de passe par défaut/renommé).