Replace the page mock data with real reads from the target host via the
phpseclib SSH layer. New FleetService parses raw command output into the exact
shapes the Livewire pages already consume; all parsing forces LC_ALL=C and reads
/proc to stay locale-independent.
- app/Services/FleetService.php: metrics (cpu via two /proc/stat samples, mem via
/proc/meminfo, disk via df), full snapshot (identity/specs incl. virt+disk_gb,
volumes, interfaces from ip+/proc/net/dev, sshd/fail2ban/ufw hardening, authorized
keys), systemd units + journal, and an ls-based directory listing. One compound
command per read; connect/parse failures bubble up.
- clusev:poll-metrics command replaces the mock emitter in the dev supervisor:
polls every credentialed server, persists cpu/mem/disk/status, broadcasts
MetricsTicked(server) — unreachable boxes flagged offline, loop never dies.
- Pages wired with graceful failure (offline state, never a 500):
Dashboard (live cached metrics + notable units), Services (real units+journal),
Files (real listing + dir navigation via open/go/up), Server-Details (live
snapshot persisted onto the row + offline banner).
- WithFleetContext prefers a credentialed, non-offline server as the default.
- dualChart filters ticks by server name so the chart tracks the active host.
Verified live against a real Debian 13 box: metrics/services(91)/journal(25)/
files(navigable)/snapshot(5 ifaces, real hardening, real key) all parse correctly.
Credentials are stored encrypted in the vault — never in source.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
One-command, idempotent production install per docs/install-update-design.md
(the "JETZT" phase). Onboarding (forced password change + 2FA) already ships
via EnsureSecurityOnboarded; the in-dashboard self-updater stays deferred to v1.x.
- install.sh: preflight -> idempotent secrets (set_kv never regenerates) ->
derive SITE_ADDRESS/APP_URL/REVERB_* from the single APP_DOMAIN knob -> build ->
up -> wait DB -> migrate + cache -> clusev:install. One-time admin password is
printed only on the terminal, never stored. Bare-IP and private-IP warnings.
- app/Console/Commands/Install.php: idempotent first admin (Str::password(20),
must_change_password); hard no-op once a user exists (INSTALL_LOCK idiom).
- docker/caddy/Caddyfile: one template, both modes; Reverb wss over /app/* and
/apps/* on the same address; admin-API-free, security headers. Validated for
bare-IP (:80) and domain (https) modes.
- docker-compose.prod.yml: Caddy is the only host-published service (80/443 +
HTTP/3); app/reverb/queue/mariadb lose host ports (internal net only);
CLUSEV_IMAGE indirection (locally-built tag, GHCR digest later); Redis requires
a password; caddy-data/caddy-config volumes persist ACME certs.
- .env.example: APP_DOMAIN/APP_SCHEME/ACME_EMAIL/SITE_ADDRESS/CLUSEV_IMAGE/
UPDATE_HMAC_KEY documented.
- README: real Clusev install/deploy guide (replaces Laravel boilerplate).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a generic ConfirmAction modal (LivewireUI\Modal\ModalComponent). On confirm
it persists exactly one AuditEvent and re-dispatches a page event so the origin
applies its own state change — domain-agnostic and reused everywhere (R5).
Wired the destructive actions, each writing an audit row:
- Services: start/stop/restart -> confirm + audit; service state reflects result
- Files: delete -> confirm + audit; entry removed
- Server-Details: revoke SSH key (new per-row trash button) -> confirm + audit
(carries server_id)
Supporting changes:
- Publish + restyle the modal container for the dark theme (void backdrop +
surface panel + shadow-pop instead of the package's gray/white defaults)
- Toaster island in the app layout that catches the `notify` browser event
- Add alert/power/rotate/trash icons to x-icon
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Manage clusev:mock-metrics as a supervisor program in the dev image so exactly
one emitter runs and survives container recreates (no stray exec -d processes).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Align the dashboard to the BASTION reference (clusev-template) — the active
server's detail view:
- x-metric: KPI tiles with sparklines + trend (CPU/Memory/Disk/Load).
- live dual-series chart (CPU+MEM, grid + Y/X axes + legend) as a dualChart
Alpine island; MetricsTicked + clusev:mock-metrics now broadcast CPU+MEM.
Static SSR paths remain as the no-JS fallback.
- systemd services as a table.
Tokens/currentColor only — no inline styles; Y-axis via utilities.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
getQRCodeInline() returns a base64 data URI (data:image/svg+xml;base64,…), not raw
SVG — so {!! !!} dumped it as text. Render it via <img src> with a light quiet-zone.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- WithFleetContext concern: resolves the active server (session, default first
online) + the fleet.
- ServerSwitcher Livewire component in the persistent sidebar: dropdown of the
fleet; selecting persists active_server_id in the session and re-navigates so
panels reflect the choice.
- Dashboard reacts: resource rings + panel subtitles follow the active server.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
From clusev-template/reference: void background with a fine dot-grid + orange top
vignette, panel elevation (shadow-panel token), tonal scrollbars, a left accent
bar + accent icon on the active nav item, status-dot + brandmark glows, and a
motion-safe ping. @theme/tokens only — no inline styles.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Focus-visible token ring + prefers-reduced-motion guard (app.css).
- Lift ink-3/ink-4 to WCAG-AA text contrast.
- Touch targets >=44px (topbar, sidebar, dashboard buttons).
- Off-canvas drawer removed from tab order when closed (breakpoint-aware inert)
+ sidebar aria-label.
- Live chart: NaN guard on incoming cpu; Echo.leave('metrics') + unbind on
destroy (no subscription leak under wire:navigate).
- KPI progress bar follows the tone (status triad), not always accent.
- Resource rings bound to real server data with threshold-based tone.
- docs/v1-ui-review.md: full adversarial review.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- MetricsTicked broadcast event (channel 'metrics', as 'tick') + a
clusev:mock-metrics command (dev placeholder for the SSH MetricsPoller),
broadcasting a rolling CPU value every 2s.
- Echo + pusher-js client in app.js with an env-driven Reverb connection
(VITE_REVERB_*). metricsChart Alpine island seeds from server data, appends
each tick and redraws the sparkline; the indicator reflects the real WS state.
- Published config/reverb.php + config/broadcasting.php.
- Backend verified: the queue processes MetricsTicked every 2s with no errors.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Generate the two governing docs from handoff.md:
- rules.md: STRICT RULES R1-R10, each with rationale + correct/forbidden example
- CLAUDE.md: product, stack, folder map, commands, conventions, before-you-code checklist
Add a $HOME-rooted .gitignore that blocks all secrets and home-dir dotfiles
(explicit-add-only workflow). Track handoff.md + kickoff-prompt.md as context.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>