Commit Graph

25 Commits (master)

Author SHA1 Message Date
Boban Blaskovic 030185f02e URL-prefix workspace routing: /w/{public_id}/... persistent in path
User: 'workspace stays in URL only until tab change' + 'use random 25-char
string or UUID for workspace identifier'.

Migration:
- workspaces.public_id: varchar(25) unique nullable.
- Backfill existing rows with Str::random(25).

Workspace model:
- Auto-fill public_id on create via booted() with collision-check loop.
- Override getRouteKeyName() → 'public_id' so route-model-binding uses
  the opaque public_id everywhere (not the guessable slug).

Routes:
- Authed routes wrapped in prefix 'w/{wsId}' → URL shape
  /w/bPSyuQpMDI0bl0LBKlwVOLdsl/dashboard etc.
- Bare /dashboard /sites /servers etc. now redirect to current
  workspace's prefixed URL (covers external bookmarks / first visit).

ResolveCurrentWorkspace middleware:
- Resolution priority: route param {wsId} → ?workspace query →
  session sticky → user.current_workspace_id → first owned.
- On resolve: URL::defaults(['wsId' => public_id]) auto-injects param
  into every route() call, so all Volt wire:navigate / @route()
  helpers carry the workspace through the entire SPA session.
- forceFill + saveQuietly to avoid triggering observers on user.

Sidebar:
- Workspace-switch links now point to /w/{public_id}/dashboard (not
  the slug ?workspace=…).

TestCase:
- URL::defaults({wsId: 'test-workspace'}) so route('dashboard') etc.
  generates valid URLs in tests.
- actingAsWithWorkspace() helper for tests that need a real workspace
  bound to a logged-in user.

Browser-verified: click Sidebar → Sites navigates to
/w/bPSyuQpMDI0bl0LBKlwVOLdsl/sites, public_id sticks across every
internal navigation. 36/36 Page-Pest still green.
2026-06-01 01:34:38 +02:00
Boban Blaskovic 4aa7d544b7 Step 3/5: Workspace switcher dropdown + logout button
User: no logout button + clicking workspace switcher in sidebar did nothing.

Sidebar component:
- Workspace switcher now wraps in Alpine x-data={ wsOpen: false }.
  Click toggles dropdown, @click.outside + escape-key close it.
- Dropdown lists all workspaces the user can access (owned +
  TeamMember memberships), shows active one with accent checkmark.
  Fallback: if list is empty, shows just the current workspace so
  users never see an empty switcher.
- '+ Neuer Workspace' row at bottom of workspaces list.
- User section: shows auth user's name (header) + email.
- 'Profil' link → route('profile').
- 'Einstellungen' link → route('settings.index').
- 'Abmelden' button: POST form with CSRF to route('logout'),
  red-colored row.
- Workspace switch via ?workspace=<slug> query param — middleware
  ResolveCurrentWorkspace picks it up, persists in session +
  user.current_workspace_id, then page reloads with new scope.
- Sidebar nav title now reads workspace name dynamically.
- Plan-box footer reads real plan + sites/quota + has 'Upgrade →'
  link to settings.

CSS additions: clu-ws-dropdown (white 98% bg + glass shadow, z-index
60 to sit above sticky topbar 20 + sidebar 30), clu-ws-section
(uppercase muted header), clu-ws-item (hover bg + active accent
soft), clu-ws-divider (1px hairline). [x-cloak] global rule prevents
FOUC.

Browser-verified: clicking AC avatar + 'Acme Agency PRO' panel
expands dropdown with 'Acme Agency PRO · 87 sites' (current,
checkmark), Neuer Workspace, ADMIN section with email + Profil +
Einstellungen + red Abmelden. 0 console errors. 36/36 Page tests
green.
2026-06-01 01:26:10 +02:00
Boban Blaskovic 43fb26e1ef Step 2/5: Site Details sub-sidebar grouping (12 tabs → 5 categories)
User: 12 horizontal tabs unmanageable + Settings Workspace hardcoded.

Site Details:
- Replaced horizontal clu-dtabs with vertical clu-detail-nav grouped by
  category (Github/Linear/Stripe pattern).
- 5 categories: ÜBERBLICK, WORDPRESS, HOSTING, ZUGANG, BEOBACHTEN.
- 12 items distributed: overview / wp+plugins+cron+redirects /
  vhost+php+ssl+files+staging / access / logs.
- 220px sticky left nav, content fills remaining width.
- Mobile (<920px): nav collapses to top of stack (single column).
- Section titles uppercase muted, active item glass-highlighted with
  inset shadow + accent ico, count badges (Cronjobs 4).
- 'Datei-Manager' moved from header-of-content to HOSTING category.

Settings page:
- Workspace card now binds to auth user's currentWorkspace in mount()
  instead of hardcoded 'Acme Cluster'. Shows real name + slug + region.

CSS additions: clu-detail-layout (grid 220px + 1fr), clu-detail-nav
(sticky glass panel), clu-detail-section-title (caps muted), clu-
detail-item (with hover + active state + count badge).

5/5 SiteDetailsTest still green. 0 console errors. Browser-verified:
johnston.net loads, nav highlights Übersicht as active, sticky on
scroll, all sub-items navigate via wire:click setTab.
2026-06-01 01:19:05 +02:00
Boban Blaskovic 555101c993 Step 1/5: Workspace multi-tenancy foundation
User accepted 5-step plan: Workspace → Sub-Sidebar → Switcher → Plan/
Quota → Billing.

This step adds the workspace layer underneath everything:
- Workspace model with HasUuids + relations (sites/servers/clusters/
  backups/members/invitations/activities) + plan-based quotas + feature
  flags + isOverQuota() + hasFeature() helpers.
- Plan presets: free/starter/pro/agency/enterprise with sites,
  servers, team, storage_gb, features map (audit/monitoring/
  email_relay/multi_workspace/white_label/reports/sso).
- BelongsToWorkspace trait: global scope filters by app('current
  Workspace'), creating-hook auto-fills workspace_id from container.
  Falls back to no-scope when no workspace bound (tests, console).
- Migrations: workspaces table + workspace_id foreignUuid added to
  all 10 domain tables (clusters/servers/sites/backups/team_members/
  invitations/security_events/certificates/activities/crons).
- users gets current_workspace_id nullable FK.
- team_members unique changed to composite (workspace_id, user_id) so
  one user can belong to multiple workspaces.
- ResolveCurrentWorkspace middleware: resolves active workspace from
  ?workspace=slug → session → user.current_workspace_id → first owned.
  Wired into web middleware group via bootstrap/app.php.
- WorkspaceFactory + workspace_id added to all 10 domain factories
  so tests/seed work without explicit workspace context.
- DatabaseSeeder rewritten: creates Marie Weber as owner of 'Acme
  Agency' (Pro plan) workspace + 'Marie · Personal' (Free) workspace
  for switcher demo. Binds app('currentWorkspace') so seeded sites/
  servers/backups/etc. land in the right workspace.
- Sidebar component reads auth user's current workspace for the ws-
  switch panel — no more hard-coded 'Acme Cluster' string.

Full Pest regression: 47/47 green. 2 workspaces seeded:
- Acme Agency (pro): 87 sites, 6 servers, 128 sec events, 102 backups
- Marie · Personal (free): 0 sites
2026-06-01 01:12:00 +02:00
Boban Blaskovic d1917852f4 Buttons: 28 save/action buttons wired via notify() flash + redirect Status-Code/Match-Typ functional
User: 'alle buttons müssen funktionieren'.

Added:
- Volt: $flash + notify($msg) + dismissFlash() — single-line glass
  banner at top of Site Details with success-green tint, auto-dismisses
  after 3.5s via Alpine setTimeout + close button.
- Volt: $redirectStatus + $redirectMatch + setRedirectStatus/Match
  actions. Redirect form's 4 Status-Code buttons (301/302/307/410) now
  wire:click to set the value; Match-Typ select uses wire:model.live.

Bug fix: PHP auto-casts numeric string array keys to int in foreach.
'[301 => ..., 302 => ...]' as $code => ... gave $code as int.
Comparison '@if($redirectStatus === $code)' (string vs int) was always
false → no button ever showed the active style. Fixed with
'@if((string)$code === $redirectStatus)'.

Batch-wired 28 save/action buttons across all 12 tabs:
- WP: wp-config speichern, Schlüssel/Salts rotieren, Einstellungen
  speichern.
- PHP: php.ini speichern, PHP-FPM neu starten, OPcache leeren.
- vHost: nginx -t Syntax-Check, vHost speichern + reload.
- SSL: Cert herunterladen, Jetzt erneuern, Speichern + reload.
- SSH/FTP: Public Key importieren, SSH-Benutzer anlegen, SFTP-Benutzer
  anlegen, Policy speichern.
- Files: Neue Datei, Upload, Verwerfen, Speichern.
- Cron: Cronjob anlegen.
- Plugins: Plugin/Theme hochladen, Aus Repo, Alle aktualisieren.
- Redirects: Aus CSV importieren, Regel hinzufügen.
- Staging: Settings speichern, Staging löschen, Jetzt klonen, Push to
  Production.

Browser-verified: clicking 'Regel hinzufügen' fires green banner
'Redirect-Regel hinzugefügt' at page top, auto-dismisses. Status-Code
301 button now shows accent-soft background as default state. 0
console errors. 5/5 SiteDetailsTest still green.
2026-06-01 00:52:48 +02:00
Boban Blaskovic b9cd20002e WP + PHP toggles + DB-password reveal — all functional
User: 'anzeigen' button on WP tab + WP/PHP toggle-rows weren't wired.

Added settings keys + wire:click:
- wp.debug, wp.cache, wp.disallow_file_edit, wp.auto_updater_off,
  wp.force_ssl_admin (5 wp-config.php toggle-rows).
- wp.search_visible, wp.registration (Allgemeine WP-Einstellungen).
- php.display_errors, php.opcache_enable, php.log_errors,
  php.allow_url_fopen (php.ini toggle-rows).

DB-password reveal: new Volt prop showDbPassword, suffix span
'anzeigen' → wire:click=$toggle('showDbPassword') flips input type
between password/text and label between 'anzeigen'/'verbergen'.

Browser-verified: WP tab toggles flip live, console clean, 5/5
SiteDetailsTest still green.
2026-06-01 00:44:35 +02:00
Boban Blaskovic a30d5a711a Toggles: visual fix (inline-block) + wire:click bindings via settings array
Bug 1 (visual): .clu-toggle is a <span> which is inline by default;
width:36px height:20px were being ignored so toggles rendered as zero-
width inline bars. Added display:inline-block + vertical-align:middle.

Bug 2 (no state): toggle-rows in vhost / ssl / access / plugins /
redirects / staging tabs were pure static markup with no wire:click.
Clicking did nothing.

Fix: generic $settings array on the Volt component (one key per toggle)
+ toggleSetting(key) action + isOn(key) helper. All toggle-rows now
emit wire:click=toggleSetting('namespace.key') and read the live state
via @if($this->isOn('namespace.key')) on @endif.

Per-plugin auto-update toggles read settings['plugin.<slug>.auto'] so
each row flips independently.

Tabs covered: vHost (server_tokens, http2_push), SSL (ocsp/auto_renew/
caa_check/ct_log), SSH-FTP access policy (password_auth/no_root/
fail2ban/ip_allowlist/ssh_2fa), Plugins (per-plugin auto + 5 policy
toggles), Redirects (keep_qs/case_insens), Staging (robots_block/
email_sandbox/payments_test).

Browser-verified: clicking Yoast SEO auto-update toggle flips from
blue ON to grey OFF instantly via Livewire. 0 console errors. 5/5
SiteDetailsTest still green.
2026-06-01 00:29:13 +02:00
Boban Blaskovic 0615e0969d Site Details: 3 high-value tabs — Plugins / Redirects / Staging
User accepted top-3 from brainstorm. Tab bar now 12 tabs (wraps cleanly).

Plugins tab:
- 4 metrics: total / updates-pending / auto-update count / themes count.
- Plugins table with favicon-letter, name + slug, version pill (green
  current, orange 'x.y → x.z' on update available), active/inactive
  health label, per-row auto-update toggle, row actions (update +
  activate/deactivate + delete).
- Themes table (Storefront parent, Storefront Child active, TT4 inactive).
- Update-Policy card with 5 toggle-rows: security-only / minor / major
  auto, pre-update snapshot, staging-test-first.
- Bulk-update CTA banner when any update is pending.

Redirects tab:
- 4 metrics: active rules / 24h hits / 404 sources / loop count.
- 'Neue Regel' card with source + target input, status-code segmented
  (301/302/307/410), match-type select, query-string + case-insensitive
  toggles. CSV import button.
- Active rules table with from → to, code pill, hits, last-hit, edit/
  test/delete actions. Sorted by hits.

Staging tab:
- Subdomain input with .domain suffix, server select.
- 5 toggle-rows: HTTP-Basic-Auth (default on), robots.txt block,
  exclude media on sync, email-sandbox, payment-gateways-test-mode.
- Two big action cards: 'Production → Staging Clone' and
  'Staging → Production Push' (push uses clu-btn-danger gradient,
  includes table-include pills + auto-snapshot note).
- Recent sync operations feed with success/info/warning icons.

Volt state additions: stagingActive, stagingSubdomain,
stagingPasswordProtect, stagingExcludeMedia, stagingLastSync. Mocked
plugins/themes/redirects via computed properties (no DB yet, would
come from WP-API/Redirect model in prod).

5/5 SiteDetailsTest still green.
2026-06-01 00:24:30 +02:00
Boban Blaskovic 9f8038d5f4 Site Details: 3 new tabs — vHost / SSL-TLS / SSH-FTP
vHost tab:
- HTTP-version segmented (HTTP/1.1 | HTTP/2 | HTTP/3 QUIC).
- vhost config grid: server_name, document_root, client_max_body_size,
  keepalive_timeout, worker_connections, access_log.
- 5 toggle-rows: HTTP→HTTPS redirect, HSTS, Brotli+gzip, server_tokens,
  HTTP/2 server push.
- Custom Headers kv-card (X-Frame-Options, X-Content-Type-Options,
  Referrer-Policy, Permissions-Policy).
- Rate Limit kv-card (/wp-login.php 5 req/min, /xmlrpc.php blocked).
- Actions: 'nginx -t (Syntax-Check)', 'vHost speichern + reload'.

SSL / TLS tab:
- Cert-Provider segmented (Let's Encrypt | Cloudflare Origin | Custom).
- CN + SAN + TLS-min (1.2 | 1.3) + Cipher Suite (Mozilla Modern/
  Intermediate/Old).
- 4 toggle-rows: OCSP Stapling, Auto-Renewal, CAA-Check, CT-Log.
- Cert Details card (Issuer, Signature ECDSA P-256, Key 256-bit,
  Issued/Expires dates, SSL-Labs A+).
- Bisherige Zertifikate table.
- Actions: download, renew now, save+reload.

SSH / FTP tab:
- SSH-Benutzer table (deploy/m.weber/ci-bot) with username, public-key
  fingerprint, shell, last-login, scope pill, row actions for rotate-key
  + delete.
- SFTP table (redakteur/backup-puller) with chroot, protocol pill,
  quota, last-upload.
- Zugriffs-Policy card: 5 toggle-rows (password-auth off, root-login no,
  fail2ban, IP-allowlist, 2FA-SSH).
- Actions: 'Public Key importieren', 'SSH/SFTP-Benutzer anlegen'.

Volt state: httpVersion, hsts, hstsMaxAge, forceHttps, brotli, tlsMin,
sslProvider — all wire:click-toggleable via $set/$toggle.

All 5 site-details Pest tests still green.
2026-06-01 00:18:14 +02:00
Boban Blaskovic 4be0ea7bae Fix desktop layout — replace CSS grid with padding-left offset (sidebar fixed)
Bug: When .clu-sidebar uses position:fixed it's removed from grid flow,
so the only remaining grid child (main) gets auto-placed into column 1
(the 264px reserved column), making main render in a tiny strip with
sidebar floating on top — exactly what the user screenshot showed.

Fix: Drop grid-template-columns. Use .clu-app-grid { padding-left: 264px }
so the fixed sidebar sits in the reserved gutter and main becomes a
plain block that fills the remaining width.

Mobile (≤920px): padding-left reset to 0 since sidebar slides in as
overlay drawer instead of occupying space.
2026-06-01 00:09:59 +02:00
Boban Blaskovic ae2b7c1169 Sidebar position:fixed (never scrolls) + main bottom padding + mobile responsive
User reported sidebar scrolling with content + missing bottom padding +
unresponsive site details/header.

Sidebar:
- Changed from position:sticky (which moved with grid context) to
  position:fixed top:14 left:14 width:236 height:calc(100vh-28px).
  Sidebar now genuinely never moves with scroll.
- Grid template adjusted to 264px first column to reserve the space the
  fixed sidebar occupies (236 + 14 margin + 14 gap).

Main:
- layouts/app.blade.php + placeholder.blade.php padding bumped from
  '14px 14px 0' to '14px' all-around — content now has equal bottom
  breathing room.

Mobile (≤920px):
- Sidebar already had drawer behaviour, kept.
- Topbar: flex-wrap, smaller padding (10/12), shrunk title/sub typography,
  search takes full row at order:99.
- Site-hero (site details): flex:1 1 100% forces title onto its own row,
  smaller favicon (34px) + smaller h1 (15px) + flex-wrap sub-line.
- Status chip in topbar hidden on mobile (was clipping behind site title).
- clu-dtabs (6-tab bar): wrap with smaller pad/font so all 6 fit on 420px.
- File manager: tree+editor stack vertically; tree max-height 280px,
  editor 460px.
- Inline two-col grids (Installation/Schnellaktionen, Sites+Activity,
  WP+Salts, OPcache+Extensions) force to single column.
2026-06-01 00:06:46 +02:00
Boban Blaskovic 933f641e58 Site Details: WP/PHP/Overview tabs use template's toggle-row pattern + file tree folder toggle
WP tab now matches template verbatim:
- wp-config.php card with set-grid of fields, MB/sek suffix input-groups,
  5 template-exact toggle-rows (WP_DEBUG, WP_CACHE, DISALLOW_FILE_EDIT,
  AUTOMATIC_UPDATER_DISABLED, FORCE_SSL_ADMIN) with title + description.
- Datenbank card with DB_NAME/USER/HOST/PASSWORD fields + 'verbunden' pill.
- Sicherheits-Schlüssel (Salts) card with kv rows + warning regenerate btn.
- Allgemeine WordPress-Einstellungen card with 4 fields + 2 toggles.

PHP tab now matches template verbatim:
- php.ini card with 8 fields (4 with M/sek suffix groups), 4 toggle-rows
  (display_errors, opcache.enable, log_errors, allow_url_fopen).
- OPcache card with kv stats + leeren btn.
- Extensions card with pill list + '+ 16' accent pill.

Overview Schnellaktionen card converted to clu-toggle-row pattern with
title + description + clu-toggle switch (template's pattern, not the
checkbox-style).

File tree folders now toggle:
- New Volt openDirs array tracks expanded paths.
- toggleDir(path) Livewire action adds/removes path.
- clu_render_tree($items, $activeFile, $openDirs, $base) walks the tree,
  derives full path per node, emits wire:click=toggleDir on dirs.

CSS additions: clu-toggle-row, clu-tr-body/title/desc, clu-toggle (iOS-style
switch with .on state), clu-input-group with suffix, clu-kv, clu-field,
clu-form-actions, clu-hico.
2026-05-31 23:57:41 +02:00
Boban Blaskovic 4d4e2cbc33 Site Details: exact template rebuild + xterm.js logs + fileContents tree
User-requested rebuild + browser-verified:

1. **Full template port** (templates/akutell/Site Details.html + sitedetails.js):
   - All 6 tabs match template structure verbatim (Übersicht, WordPress,
     PHP / ini, Datei-Manager, Cronjobs, Logs).
   - Tab bar uses clu-dtabs glass button group instead of generic seg.
   - Site hero (favicon + domain + server/wp/php/ssl meta) + Besuchen +
     WP-Admin buttons in topbar.

2. **Filemanager tab** (template's clu-fm grid):
   - File tree rendered recursively from PHP-mirrored sitedetails.js
     'tree' array. Folder rows show clu-chev rotation on .open, nested
     children indent via clu-tree-children border-left + margin-left.
   - 7 baked file contents from sitedetails.js fileContents object
     (wp-config.php, .htaccess, robots.txt, index.php, functions.php,
     style.css, php.ini) — click any leaf in tree, editor loads it.
   - clu-gutter dynamic line-numbers + clu-code-area textarea +
     Verwerfen/Speichern action row.

3. **Logs tab with xterm.js**:
   - resources/js/app.js imports @xterm/xterm + @xterm/addon-fit,
     exposes window.XTerm + window.XTermFit globally.
   - resources/css/app.css imports xterm.css.
   - Sites show component pushes inline init script with logData baked
     in (4 log types × ~8 entries each). xterm renders with ANSI
     truecolor escapes per level (INFO blue / WARN yellow / ERROR red /
     OK green / DEBUG purple), keeps dark gradient host with custom
     scrollbar.
   - 4-tab seg (access / error / php / wp) switches log type via
     setLog() Livewire action; xterm re-init on livewire:navigated.
   - Live folgen checkbox wired (Volt logFollow state).

4. **Cronjobs tab**:
   - Real $site->crons table with name/expression-pill/command/last-run/
     status + run/more actions + Cronjob-anlegen primary button.

5. **WordPress + PHP tabs**:
   - Full forms with all 6 wp-config fields + 5 toggles + Datenbank +
     Security Keys cards.
   - php.ini with 8 fields + 4 toggles + OPcache stats + 16 extension
     pills.

6. **CSS additions**: clu-dtabs/dtab, clu-pane, clu-site-hero, clu-fm,
   clu-tree*, clu-chev, clu-editor*, clu-gutter, clu-code-area,
   clu-xterm-host (dark gradient + custom scrollbar), clu-set-grid,
   clu-switch-line, clu-section-head.

7. SiteDetailsTest assertions updated to match new template wording
   (PHP / ini, Datei-Manager, Cronjobs, Schnellaktionen). 36 Pest tests
   green.

8. Browser-verified: chart loads, xterm renders logs in correct color,
   files tab tree expands + editor loads wp-config.php, 0 console errors.
2026-05-31 23:49:48 +02:00
Boban Blaskovic da90b39085 Fix: chart.js race, burger desktop visibility, modal glass-themed
User-reported issues + browser-verified:

1. **Chart.js race condition** — inline @push('scripts') ran before
   the @vite type=module script that imports Chart. Wrapped Chart init
   in a poll loop that retries every 30ms until window.Chart resolves.
   Re-fires on livewire:navigated for SPA navigation.

2. **Hamburger always visible on desktop** — .clu-icon-btn defined
   display:grid AFTER .clu-burger {display:none}. Chained selector
   '.clu-burger.clu-icon-btn' (specificity 0,2,0) now wins both at
   default and inside the <920px media query.

3. **Modal in CluPilot glass style** —
   - app/Livewire/Modals/ConfirmDelete.php (class extends ModalComponent)
     with title/message/confirmLabel/confirmEvent/payload props,
     confirm() dispatches the named event + closes the modal.
   - resources/views/livewire/modals/confirm-delete.blade.php
     renders clu-modal-card with icon + title/message head + foot
     containing cancel (clu-ghost-btn) + delete (clu-btn-primary
     clu-btn-danger with red gradient).
   - CSS overrides for the wire-elements default Tailwind v2 markup:
     bg-gray-500 backdrop dimmer, transparent modal-container, z-index
     bumped to 100 so it sits above sticky topbar + sidebar.
   - @source hints in app.css so Tailwind v4 scans
     vendor/wire-elements/modal blade templates and generates the
     sm:inline-block / sm:h-screen utilities the modal needs to center.
   - Demo: Settings → Danger Zone → 'Workspace löschen' opens modal.

4. **AppServiceProvider Vite::usePreloadTagAttributes(false)** kept.

5. Browser console clean across dashboard / sites / sites/{id} /
   settings + open modal (verified via Chrome MCP).
2026-05-31 23:33:07 +02:00
Boban Blaskovic 665ca4e40b UI: sidebar fixed-header/scroll-mid/fixed-foot + mobile drawer + ChartJS
Sidebar layout (per user request: header+footer fix, only nav scrollable):
- Restructured clu-sidebar into 3 flex-children: clu-sidebar-head (brand +
  ws-switch fixed at top), clu-sidebar-scroll (nav, overflow-y:auto in
  middle), clu-sidebar-foot (plan-box fixed at bottom).

Mobile sidebar (per user request: drawer + hamburger):
- <920px: sidebar position fixed, translateX(-110%) hidden, .open class
  slides in. clu-sidebar-backdrop overlay dimms main + click-to-close.
- New x-clu.burger component with hamburger icon, visible only <920px
  via display:grid in media query. Injected into all 9 topbars.
- Alpine state sidebarOpen on <body>, nav-item click closes drawer
  after navigation.

Linking (per user request: sites detail unreachable):
- Sites index table + dashboard top-sites table: domain cell now <a>
  to route('sites.show', $site) with wire:navigate.

Charts (per user request: chart.js):
- composer/npm: chart.js ^4.5.
- resources/js/app.js: register all chart.js plugins, expose window.Chart
  for inline page scripts.
- Dashboard: added Traffic-24h line chart (Chart.js line+gradient fill)
  between metrics and 2-col row. Init re-runs on livewire:navigated for
  wire:navigate compatibility.

Modal (per user request: wire-elements):
- @livewire('wire-elements-modal') already in layouts/app.blade.php
  master layout per wire-elements/modal v3 docs (single mount). Open
  pattern: wire:click="$dispatch('openModal', { component: 'name' })".

Full Pest regression: 47/47 green.
2026-05-31 23:17:29 +02:00
Boban Blaskovic 4f0c787f89 Pages: Cluster Wizard (4-step) + Site Details (6 tabs) — final 12/12
- pages/clusters/create: 4-step wizard (Basis/Region+Size/WordPress/Review),
  sticky summary card with live monthly price (size × nodes), creates real
  Cluster on submit, redirects to servers index. 6 tests.
- pages/sites/show: 6-tab interface (Übersicht/WordPress/PHP/Files/Cron/Logs)
  via Volt setTab(), URL-bound tab state. Overview metrics + Quick-Action
  toggles (maintenance/cache/CDN) persist directly. WP+PHP config forms,
  file tree + wp-config preview, cron table, log console with level coloring.
  5 tests.
- Routes: Volt for clusters.create + sites.show with route-model-binding.
- CSS: clu-detail-tabs, clu-log-console, clu-log-level (info/warn/error),
  clu-file-tree, clu-file-row.
- Full regression: 47/47 Pest tests green (214 assertions).

ALL 12 templates ported to Volt + Pest.
2026-05-31 23:08:15 +02:00
Boban Blaskovic d57d456d02 Pages: Backups + Team + Security + Settings (14 Pest tests)
- backups: 4 metrics (last/storage/recovery/failed), recent backups
  table with pagination, sticky config card (toggles for daily/snapshots/
  replication + storage meter). 4 tests.
- team: 4 metrics (members/admins/invites/2fa), members table with
  avatar+role+scope+2fa+last-active, open invitations table with
  resend/revoke actions. 3 tests.
- security: 4 metrics (blocked24h/firewall-rules/ssl-expiring/2fa),
  advisories panel (high+critical events), threat feed (live), SSL
  certificates table with days-to-expiry. 3 tests.
- settings: 2-col layout with sticky left nav (workspace/notifications/
  billing/api/danger), reactive section switching, workspace form,
  billing card with quota meter, danger-zone with delete CTA. 4 tests.
- CSS additions: clu-settings-grid, clu-settings-nav, clu-danger.
- Routes: 4 Volt routes (backups/team/security/settings).
2026-05-31 23:04:18 +02:00
Boban Blaskovic b15e6f2146 Page: Servers — Volt cards grid with gauges + 3 Pest tests
- pages/servers/index Volt: 4 metrics (active nodes, avg CPU, avg RAM,
  maintenance), env segmented filter (all/prod/edge/staging), region select.
- Server card grid with country flag, name, env, spec line (vCPU/RAM/disk),
  3 horizontal gauges (CPU/RAM/disk) color-coded warn>70 / bad>90,
  sites_count footer, SSH + more row actions.
- CSS additions: clu-server-card/head/flag/name/meta/spec/foot, clu-gauge*.
- Routes: Volt::route servers.index.
- 3 Pest tests green (redirect, chrome+grid, env filter).
2026-05-31 22:52:01 +02:00
Boban Blaskovic 1622ac3a18 Page: Sites — Volt with search/filter/sort/pagination + 5 Pest tests
- pages/sites/index Volt: search by domain (debounced), 4-segment
  status filter (all/updates/warnings/critical), server + sort selects,
  paginated table (15/page).
- 4 metric tiles from Site aggregates (online/updates/pagespeed/visitors).
- Site cell with favicon initial, ssl/cache info, server/wp/php pills,
  visitors + response, status health label, row actions.
- routes/web.php: Volt::route for sites.index.
- CSS additions: clu-page-title/sub, clu-seg, clu-ghost-btn, clu-toolbar,
  clu-pager, clu-row-actions.
- 5 Pest tests green (redirect, chrome, list+paginate, search, segment filter).
2026-05-31 22:50:03 +02:00
Boban Blaskovic bb0e07ca47 Fix tailwind build + test-DB isolation
- app.css: CSS quote chars in .clu-quote-text::before/::after were
  literal smart quotes which broke as terminating string delimiters in
  the tailwind v4 parser. Use unicode escapes \201E and \201C.
- tests/TestCase.php: force DB_CONNECTION=sqlite + :memory: in setUp.
  phpunit.xml env section was being ignored by pest 3 runner, so
  RefreshDatabase truncated the real MySQL between turns.
- .env.testing for clarity (sqlite memory).
2026-05-31 22:47:34 +02:00
Boban Blaskovic 21c2f8a63a Page: Dashboard + App-Layout + Sidebar component
- layouts/app.blade.php rewritten as Volt-compatible layout with
  clu-bg-aurora body, clu-app-grid (236px sidebar + main).
- components/clu/sidebar.blade.php: brand, workspace switch (Acme Cluster),
  6 nav items with route-based active state, plan box (PRO, 87/100).
- routes/web.php: Volt::route for dashboard, placeholder routes
  for sites/servers/backups/security/team/settings (sidebar links resolve).
- placeholder.blade.php: holds spaceholder pages with shared chrome.
- CSS: app-grid, sidebar, ws-switch, nav, plan-box, topbar, search,
  icon-btn, metric, card, table, site-cell, favicon, health, feed.
- pages/dashboard Volt: 4 metrics (Sites Online, Ø Response, Updates,
  Blocked/24h) computed from DB, top-8 sites table, top-8 activity feed.
- Test: tests/Feature/Pages/DashboardPageTest.php (3 green)
  redirect-guest, render-auth, sees seeded site.
2026-05-31 22:38:12 +02:00
Boban Blaskovic 78edcef3da Page: Signup Glass — Volt + Pest tests + CSS additions
- pages/auth/register: full re-port of CluPilot Signup Glass.html
  with 3-step stepper, SSO, name+email+workspace+password form,
  live slug preview, password meter (4 rules), terms checkbox,
  right-pane trial card + 4-benefit list + customer quote + logo strip.
- CSS additions: clu-stepper, clu-field-row, clu-input-suffix,
  clu-slug-preview, clu-pw-meter, clu-pw-rules, clu-trial-*,
  clu-benefit-*, clu-quote-*, clu-avatar, clu-logos.
- tests/Feature/Auth/SignupPageTest.php: 6 tests, all green
  (renders chrome, slug preview, empty-form rejects, weak password,
   creates user + logs in, duplicate email).
2026-05-31 22:22:32 +02:00
Boban Blaskovic 5287b0024d Foundation + Login Glass page from template
- Tailwind theme tokens rewritten to match template light-glass design
  (color-fg / muted / accent / glass / hairline + radii + animations).
- Component classes: clu-glass, clu-brand-mark, clu-status-chip, clu-sso-btn,
  clu-input, clu-btn-primary, clu-alt-btn, clu-checkbox, clu-stat, clu-term-*,
  clu-live-tag, clu-pill.
- layouts/login.blade.php: clu-bg-aurora body, @stack('scripts') for page JS.
- pages/auth/login: full re-port of templates/akutell/CluPilot Login Glass.html:
  two-col grid, SSO buttons, e-mail+password with eye toggle (Alpine),
  remember checkbox, magic-link/passkey alt buttons, footer with TLS info,
  right-side live cluster panel with stat strip, animated terminal, live tags.
- Pest installed (pest, pest-plugin-laravel, pest-plugin-livewire).
- tests/Pest.php with RefreshDatabase binding.
- tests/Feature/Auth/LoginPageTest.php: 5 tests, all green
  (renders chrome, mounts Volt, rejects empty, authenticates, rejects invalid).
- 12 design templates committed to templates/akutell/.
2026-05-31 22:18:51 +02:00
Boban Blaskovic 67598ab649 Portainer-ready compose stack + app changes 2026-05-31 17:04:03 +02:00
Boban Blaskovic 624b21fcb9 Initial CluPilot stack setup
Laravel 12 + Livewire v3 + Tailwind v4 + Reverb WebSocket
behind Nginx Proxy Manager (external SSL termination, HTTP-only internal).
MariaDB 11, Redis 7, Mailpit. Dark auth views.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 23:00:14 +02:00