Skip to content

chore(release): merge dev into main — v11 (security hardening, production readiness, Tabler/ApexCharts) - #268

Merged
Rem7474 merged 81 commits into
mainfrom
dev
Aug 30, 2026
Merged

Rem7474 merged 81 commits into
mainfrom
dev

Conversation

@Rem7474

@Rem7474 Rem7474 commented Aug 16, 2026 •

Copy link
Copy Markdown
Owner

Summary

Fusionne dev dans main — 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)

2. Alignement Tabler (#262-267)

NotificationBell/ToastContainer migré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-xtermjs officiel plutôt que deviné. Identifiants console dédiés par connexion, poste admin-only avec audit log. #288 corrige le graphique invité, la console/nav mobile et les tâches de sauvegarde manquantes. #323 corrige 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, action optionnelle pour le module custom, 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 admin obligatoire), garde-fou qui réapplique must_change_password sur une installation existante toujours sur le mot de passe par défaut, endpoints de healthcheck. #323 corrige un contournement silencieux du garde-fou quand ADMIN_USER est 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 (NOSONAR justifiés), première puis deuxième passe d'accessibilité (labels de formulaire). #315 corrige les 59 occurrences restantes du pattern "mutation directe d'une prop form" dans les composants d'alertes/webhooks/paramètres, remplacées par un emit d'é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 dev avec 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/#319 corrigent 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). #323 ajoute 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 de docs/ vers le wiki GitHub auto-déployé, documentation du flux de branche dev→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

  • Chaque PR individuelle testée et validée avant merge dans dev (voir chaque PR liée pour le détail).
  • git diff main...dev --stat : 192 fichiers, +13868/-2879.
  • CI GitHub verte sur cette PR : build, typecheck/lint (frontend + Go), tests unitaires et d'intégration (Go -race + Vitest), tests navigateur réels (ApexCharts/D3), CodeQL, Trivy, Nancy, govulncheck, npm audit, TruffleHog.
  • SonarCloud Quality Gate : Reliability Rating résolu (0 bug ouvert sur le code nouveau) ; coverage sur code nouveau ~78-79% (seuil 80% non atteint sur des branches défensives non testables sans hack fragile — accepté en connaissance de cause plutôt que d'ajouter des tests artificiels).
  • Fonctionnalités à plus fort risque vérifiées manuellement : console Proxmox interactive (race condition reproduite puis corrigée avec test de régression sous -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é).

root and others added 24 commits August 16, 2026 16:40
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)
github-actions Bot and others added 3 commits August 16, 2026 18:15
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>
)

* 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>
@Rem7474 Rem7474 changed the title chore(release): merge dev into main — Tabler design system alignment chore(release): merge dev into main — v11 (security hardening, production readiness, Tabler/ApexCharts) Aug 30, 2026
…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>
@sonarqubecloud

Copy link
Copy Markdown

This branch was successfully deployed

1 active deployment
Action — 0bd2a3ff Deployed Aug 30, 2026 by Rem7474 via nancy (agent) #240
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.

1 participant