Make the panel actually operate the server, not just display it — for everything
that works in the SSH user's own space (no sudo required).
- FleetService: addAuthorizedKey (append, dedup, perms-safe via base64 transport),
removeAuthorizedKey (by SHA256 fingerprint, preserves every other key — never
rewrites blindly, no lockout), sshKeys (reload), deleteFile (SFTP unlink).
- AddSshKey form modal: paste a public key -> appended over SSH + AuditEvent +
list reload. Wired to the "Schlüssel hinzufügen" button on Server-Details.
- Server-Details: key removal now performs the real SSH removal then reloads;
new "Dateien" button opens the file manager scoped to this server.
- Files: delete performs the real SFTP unlink then reloads the directory.
Verified live: add+remove of a throwaway key leaves the existing key intact;
real file delete confirmed gone. systemctl start/stop/restart still need a
privileged credential (the demo account has no passwordless sudo).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Addresses "chart/gauges don't update" and "design": the visible live layer no
longer depends on the Reverb WS path (which was unreliable through the dev NAT).
- Poller is now the single SSH metrics source: FleetService::applyMetrics writes
the latest reading + a rolling cpu/mem history into the cache (and the DB).
- Dashboard reads latest + history from cache (no SSH on web render); the big
chart and KPI sparklines render real history; wire:poll.10s refreshes them.
- Server-Details gauges read the poller-updated row via wire:poll.10s; the donut
rings get a size="lg" variant (h-24) so they read clearly + animate.
- FleetSeeder no longer seeds a fake fleet (those servers had no credentials and
showed no data). It seeds one real server from CLUSEV_DEMO_SSH_* env vars, or
an empty fleet otherwise. Existing fake servers removed from the demo DB.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Resolve all 12 confirmed findings from the SSH-integration review.
Security:
- TOFU host-key pinning (new VerifiesHostKey trait + servers.ssh_host_key column):
SshClient/Sftp record the server host key on first connect and refuse the
connection on any later mismatch — phpseclib does not pin host keys itself.
- PollMetrics keeps one long-lived SSH connection per server and reuses it across
ticks (a single login, not one per interval) — avoids fail2ban/auth-log churn;
dead connections are dropped and re-established next tick.
- @js() escaping for every server-controlled value interpolated into wire:click
(file names, paths, service names, SSH-key comments/fingerprints) — prevents
JS-string breakage / injection from untrusted remote data.
Correctness:
- parseKeys regex makes the key comment optional (keys without a comment were
silently dropped).
- parseVolumes pops the three trailing single-token df fields and rejoins the
rest, so mount points containing spaces parse correctly.
- Sftp gains a disconnect() method to match SshClient (explicit cleanup).
Rules:
- Services "Start" -> "Starten" (R9, German).
- Files breadcrumb buttons get min-w-11 + padding (R7, >=44px touch target).
Verified live: host key stored + mismatch refused + correct key reconnects;
poller reuse polls ok; all pages still render real data.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>