Commit Graph

14 Commits (aa7a7b1db817f49a352e5e21b2fc5b1e73c23494)

Author SHA1 Message Date
boban aa7a7b1db8 fix(ux): editor click bug (@js→index), self-hosted fonts, split-brand auth, R14
- Fix Alpine "Invalid or unexpected token" on file/service/key actions: @js()
  does NOT compile inside a Blade component (x-btn) attribute, so a literal
  @js(...) reached the DOM and Alpine choked on it. Switched all 10 wire:click
  arguments to loop-index lookups (files: open/go/download/edit/confirmDelete;
  services: confirm; keys: confirmKeyRemoval) — the component resolves the value
  server-side by index. {{ $loop->index }} compiles in component attributes; @js
  does not.
- Self-host fonts (R14): Chakra Petch / Space Grotesk / JetBrains Mono as local
  .woff2 in resources/fonts/, declared via @font-face in app.css, Vite-bundled
  with relative urls so they resolve in dev AND the prod build. No Google Fonts
  / CDN link or @import. New rule R14 added to rules.md + CLAUDE.md.
- Auth redesign (split-brand, per reference): new auth layout with a brand panel
  (faux terminal + glow mesh, hidden below lg) + redesigned login,
  two-factor-challenge, two-factor-setup, password-change forms.
- x-btn: add size="lg" (h-11, >=44px touch target) for the full-width auth CTA;
  one shared button component, no bespoke styles.
- Remove unused Laravel welcome.blade.php (was full of raw hex — R3 cleanup).

Verified (R12): /login 200 / 0 console errors, fonts load (Chakra+Grotesk+JB),
brand panel visible @1440; file-editor REAL click -> modal opens, textarea
renders, 0 Alpine errors; editor load/save roundtrip (/etc/hostname='debian');
all 7 routes 200 / 0 console errors. Full rules audit R1-R14 clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 01:16:31 +02:00
boban f418ab35ab feat(ux): real metrics+context, working file manager, settings redesign, EN routes
Full feedback round — all browser-verified (R12: 7 routes 200, zero console errors)
and functionally tested live against the target host.

- Metrics are real AND honest: poller now stores cpu/mem/DISK/LOAD history (all
  four sparklines real, not synthetic); KPIs show absolute context (Memory
  "1,9 / 7,8 GB", Disk "13 / 80 GB", Load "4 Kerne"); tiles are status-coloured
  and Load is rated against core count (so "yellow" actually means load≈0.7-1.0×
  cores), not a fixed warning tint.
- File manager is functional: Hochladen (Livewire upload -> SFTP put), Download
  (SFTP get -> streamed), and view/edit (new FileEditor modal: SFTP get, binary/
  size guards, SFTP put on save + audit). File names + Bearbeiten open the editor.
- Settings redesigned (/frontend-design): account identity header + section nav
  (Profil / Sicherheit) instead of flat stacked panels.
- Routes are English (R13): /einstellungen -> /settings; rule added to rules.md +
  CLAUDE.md. Dummy data removed: the topbar "Flotte online / Uptime 42d" + dead
  bell button are gone, replaced by a real "<online>/<total> online" pill.

New: app/Livewire/Modals/FileEditor + view; FleetService get/read/write/upload +
Sftp putFromFile/size.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 00:32:05 +02:00
boban 0a7fc4780c feat(ux): detail-page redesign, settings page, key generation, scrollable services
Round of UX work from user feedback (browser-verified per R12 — all routes 200,
zero console errors).

- Server-Details redesign (/frontend-design): hero header band (status-tinted
  server glyph + name + meta strip + actions), a full-width row of dense vital
  cards (ring + label + absolute used/total) instead of 3 donuts floating in a
  height-stretched panel, hardening checklist with status-colored accent borders,
  and a clear section hierarchy (vitals → specs+security → volumes+net → keys).
- New user Settings page (/einstellungen): profile (name/email), password change
  (current-password gated), 2FA status + enable link / disable (confirm + audit).
  Sidebar "Konto" nav group + clickable user block.
- SSH key generation: "Neues Paar generieren" in the add-key modal makes an
  ed25519 keypair (phpseclib) — public key installed on save, private shown once.
- Services list is height-capped + scrollable so the Journal below is reachable.
- <x-btn> now also renders <a> (href) for link-buttons.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-13 00:07:56 +02:00
boban 5479896bf3 fix(modal)+ux: panel above backdrop, one <x-btn> style, SSH credential form
The modal panel rendered *behind* its own backdrop (it "appeared then vanished"):
Tailwind v4 no longer turns `transform` into a stacking context, so the static
panel sat under the fixed/blurred backdrop. Fixed with `relative z-10` on the
panel (interactively verified by force-opening the modal).

- New <x-btn> component: one compact, consistent style (h-8 text-xs) with
  variants (primary/accent/secondary/danger/ghost/ghost-danger). Replaced every
  ad-hoc action button — modals, service start/stop/restart, file row actions +
  upload, server-details (Zugang/Dateien/key add+remove). No more oversized,
  inconsistent buttons.
- New EditCredential form modal + "Zugang" button on Server-Details: deposit or
  update a server's SSH login (e.g. root) — this is where the privileged
  credential for systemctl/journal gets stored (encrypted vault). Changing it
  re-pulls the snapshot with the new login.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 23:32:28 +02:00
boban 94972c78bd perf+ux: lazy-load pages (skeletons), fix modal, normalize buttons + spinners
Route switching was slow because each page did its SSH reads inside mount(),
blocking the response. Now mount() only sets cheap shell state and the SSH read
runs in load() via wire:init — the page shell + skeletons render instantly and
content streams in.

- Lazy-load Dashboard (services), Services, Files, Server-Details: SSH moved from
  mount() to load(); skeleton placeholders (new <x-skeleton>) while !$ready.
- Modal container: z-10 -> z-50 (was rendering *under* the z-40 sidebar) and a
  real max-w-lg (the dynamic modalWidth classes were never generated by Tailwind,
  so modals spanned full width). This also fixes the "flash + disappear".
- Modal/action buttons: normalize size (min-h-11 uppercase font-display -> h-9
  text-sm font-medium) and add a wire:loading spinner + disabled-while-running on
  the confirm/save actions (no double-submit, clear feedback).
- Server-Details gauges keep wire:poll (live) but no longer block initial render.

Verified: every page renders ready=false + skeleton with no SSH, then load()
populates real data; modal buttons carry the spinner.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 23:17:02 +02:00
boban b7fe66406c fix(live): poll-driven metrics (chart + gauges), bigger donuts, drop fake fleet
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>
2026-06-12 22:38:51 +02:00
boban 218806727c feat(r5): wire-elements/modal confirmations for destructive actions
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>
2026-06-12 15:05:50 +02:00
boban 688e9e7866 feat: reference-depth dashboard (sparkline KPIs + live dual chart + table)
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>
2026-06-12 14:44:11 +02:00
boban b3e5d3a024 feat: auth + 2FA (login wall + TOTP + forced onboarding)
- Custom Livewire auth (full-page, R1/R2): Login (rate-limited), TwoFactorChallenge,
  TwoFactorSetup (QR via pragmarx/google2fa-qrcode), PasswordChange.
- Guard: panel routes behind auth + EnsureSecurityOnboarded middleware — forces
  password rotation, then 2FA enrolment, before the panel unlocks.
- User: two_factor_secret (encrypted), two_factor_confirmed_at, must_change_password.
- Centered auth layout, POST logout, dynamic sidebar user + 2FA status.
- Seeded admin with a one-time password + must_change_password.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 13:57:02 +02:00
boban db0a1de92f feat: multi-server switcher + fleet context
- 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>
2026-06-12 13:45:40 +02:00
boban cd5f7c107e style: align UI to the BASTION reference (atmosphere + depth)
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>
2026-06-12 13:39:57 +02:00
boban 47eae61371 fix: apply v1 UI review (a11y, contrast, live-chart robustness)
- 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>
2026-06-12 06:16:52 +02:00
boban 6302ef5e7a feat: v1 dashboard UI — app shell + component kit + mock fleet
- Responsive app shell: sidebar (brand, server switcher, nav, user) as an
  off-canvas drawer on mobile/tablet, fixed on desktop; sticky topbar with
  hamburger. x-cloak + overflow-x-clip guards.
- Blade component kit (tokens only; no inline styles except progress width):
  x-icon (Lucide inline SVG), x-status-dot/-pill, x-badge, x-nav-item,
  x-sidebar, x-topbar, x-panel, x-kpi, x-ring (SVG gauge), x-server-item.
- Dashboard (full-page Livewire) with mock fleet held in the component: KPI
  grid (1->2->4), live-load sparkline + resource rings, server list, systemd
  table, audit log. German, no emoji, status via color/dots/pills.
- Verified rendered at 1366px (desktop) via headless screenshot.
- docs/install-update-design.md: install/TLS/self-update architecture (analysis).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 01:10:23 +02:00
boban 53f7309c0b feat: scaffold — Dockerized Laravel 13 + Livewire 3 + Tailwind 4 + Reverb
Working dev stack on :80 (Freigabe step 5: app boots, page renders, Vite HMR runs):
- Multi-stage Dockerfile (php8.3-fpm + nginx + supervisor + node). Dev runs
  php-fpm + nginx + vite in ONE app container; prod target bakes vendor + assets.
- docker-compose.yml (dev, bind-mount) + docker-compose.prod.yml (GHCR image).
  Env-driven ports/UID (APP_PORT, VITE_PORT, REVERB_HOST_PORT, DB_PORT,
  HOST_UID/HOST_GID) — nothing hardcoded. MariaDB only on 127.0.0.1:3306.
- Laravel 13, Livewire 3.8 (class-based, no Volt), wire-elements/modal 2,
  phpseclib 3, laravel/reverb 1. composer platform pinned to php 8.3.
- Tailwind v4 @theme tokens ("Tactical Terminal") in app.css; Vite remote HMR
  (host 0.0.0.0, hmr.host 10.10.90.136).
- Dashboard as a full-page Livewire route (/) in layouts/app; §5 folder skeleton.
- Docs: rules.md R1-R11 (incl. R11 UUID-in-URLs) + CLAUDE.md updated for
  Laravel 13, clusev/ root, port 80, vite-in-app, env-driven config.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-12 00:31:50 +02:00