Commit Graph
755 Commits
Author SHA1 Message Date
Juan DenisandClaude Opus 4.8 ad5c9b6bf0 feat(wordpress): attach a custom domain with auto-DNS
Turns "point myblog.com at this site" into one action that consumes the URL-swap
tool. New DNSProviderService.find_zone_for_domain (longest zone-suffix match
across connected providers) + ensure_a_record upsert the A record via Cloudflare/
Route53, or return the exact record to add when nothing manages the zone.

WordPressService.attach_custom_domain orchestrates it: auto-create DNS (server IP
from SiteDomainService), optionally obtain a Let's Encrypt cert (the host vhost is
written first so certbot --nginx can validate), then migrate the site URL to the
domain via change_site_url. External steps degrade to warnings; only a requested
migration that errors aborts.

POST /wordpress/sites/<id>/domain (admin). Frontend: "Add Domain" button +
AttachDomainModal — enter a domain, optional HTTPS, and the result shows the
auto-created record or the exact A record to add manually.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 00:30:50 -04:00
Juan DenisandClaude Opus 4.8 57a7b7b3e6 feat(wordpress): URL-swap tool — safe, previewable site URL changes
Changing a WordPress site's URL is not a single UPDATE: the URL is embedded
across wp_options, post content, and PHP-serialized blobs whose byte-length
prefixes break under a naive REPLACE. This adds a first-class swap tool built on
WP-CLI search-replace (serialization-safe), with the rails to make it routine:

- preview_url_change: dry-run that reports per-pair replacement counts, no writes
- change_site_url: backs the database up first, rewrites with --skip-columns=guid
  (full-URL + host-only pairs so scheme/protocol-relative refs are covered),
  updates home/siteurl, flushes the object cache + permalinks, then re-points the
  primary Domain row and nginx vhost. Rolls the DB back from the backup on
  failure. keep_old_redirect keeps the old host resolving so WordPress 301s it.
- POST /sites/<id>/url/preview and /sites/<id>/url (admin-gated)
- "Change URL" modal on the site detail page: live dry-run preview, backup
  notice, keep-old-address toggle, then apply.

_write_app_vhost is factored out of _provision_routing and shared by the
re-point path. The pure-SQL search-replace in db_sync_service remains the
offline-dump fallback only.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 00:19:16 -04:00
Juan DenisandClaude Opus 4.8 1a0214f11d feat(wordpress): publish new sites at a real subdomain, not localhost:port
create_site installed WordPress at http://localhost:<port>, which WordPress
bakes in as its canonical home/siteurl — reachable on the box but useless as a
public site URL. New SiteDomainService resolves <slug>.<base_domain> (config
SITES_BASE_DOMAIN: lvh.me in dev, opt-in in prod, empty falls back to the old
localhost URL). create_site now installs at that host and provisions a primary
Domain row + nginx reverse-proxy vhost (_provision_routing, best-effort so a box
without nginx warns instead of failing). Teardown removes the vhost; clone_site
URL rewriting is generalized via _canonical_site_url so cloning stays correct
with real hostnames.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 00:01:51 -04:00
Juan DenisandClaude Opus 4.8 a6f66b2976 fix(databases): distinguish a connection failure from an empty database
docker_mysql_get_tables returned [] on both a genuinely empty database and a
container/auth failure, so the explorer rendered "Empty" either way and silently
hid broken connections. It now returns {connected, tables, error}; the API
passes that through, and the source tree shows "No tables yet" for a connected
but empty database vs the real reason when the container exec/auth fails.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 00:01:41 -04:00
Juan DenisandClaude Opus 4.8 0abed0efa1 redesign(dashboard): admin-gated recent-activity feed
Wire the previously-unused Feed/FeedItem ds primitives to the existing /admin/activity/feed endpoint as a Recent Activity panel beside Applications (span 8 + span 4). Admin-gated via AuthContext isAdmin so non-admins neither see it nor trigger the 403; actions map to tone+icon, with relative timestamps and a link to the audit log.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 03:18:30 -04:00
Juan DenisandClaude Opus 4.8 e1a2a68359 redesign(security): fail2ban per-jail KPI cards
Fetch each active jail's status (getFail2banJailStatus) and render a per-jail card grid (Banned / Failed / Total banned) above the banned-IPs table, matching the prototype. Per-jail counts already existed behind the /fail2ban/jails/<jail> endpoint — the tab just never called it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 03:12:10 -04:00
Juan DenisandClaude Opus 4.8 a3f4676ad9 redesign(wordpress): functional plugin/theme card grids
Replace the dense plugin/theme tables with the prototype's card grids. Plugins: ServiceTile avatar + name/slug + version + update-flag, and a working activate/deactivate Switch. Themes: preview placeholder + Active pill + Activate. Wires the already-existing backend WP-CLI endpoints via new activatePlugin/deactivatePlugin/activateTheme api methods (these were never backend-blocked — only the frontend api methods were missing).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 03:12:10 -04:00
Juan DenisandClaude Opus 4.8 80bdd21375 redesign(files): owner column + flush inline preview pane
Add an Owner column to the list view (header + FileRow cell + grid). Convert the file preview from a fixed overlay-drawer into a flush inline 3rd pane within the file-manager body via an opt-in 'inline' prop on PreviewDrawer (the shared .preview-drawer overlay used by Terminal/Docker is untouched). All preview features, tree, uploads, bulk and context-menu preserved.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:57:47 -04:00
Juan DenisandClaude Opus 4.8 b08c7970e3 redesign(security): overview posture gauge + dense integrity table
OverviewTab gains a ScoreGauge posture ring + hardening checklist computed client-side from real signals (ClamAV state, file-integrity baseline, recent integrity changes, notifications) — no invented backend number. IntegrityTab's plain path lists become a dense sk-dtable with change-kind state chips (modified/deleted/new/permission). Fail2ban per-jail cards left out (payload has no per-jail counts).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:57:47 -04:00
Juan DenisandClaude Opus 4.8 fb0fd1cf44 redesign(domains): inline DNS records table in the drawer
The domain drawer punted DNS to a /dns link; now it loads the domain's zone records and renders them inline as a dense sk-dtable (colored .dns-rtype chip + mono name/value + TTL), reusing the DNSZones records API. Handles loading/empty/no-zone/error; the Manage DNS records link is kept for full editing.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:57:47 -04:00
Juan DenisandClaude Opus 4.8 1feb6ef336 redesign(dashboard): server-switcher dropdown on title
Replace the static hostname h1 + separate select with a .srv-switch button that opens a popover server list (status dot + name + provider/IP meta + active check), matching the prototype env-menu. Reuses the existing server-change handler verbatim; shown even for a single server. (Recent-activity feed stays backend-blocked.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:57:47 -04:00
Juan DenisandClaude Opus 4.8 3eeb0611b9 redesign(wordpress): list subtitle, filter counts, search, gradient tiles
Add a count subtitle, per-filter counts on the status SegControl, a client-side site search, and per-site ServiceTile gradient avatars (replacing the flat static Globe). Bulk-select, tag filter, status pills and links are preserved; enriched columns (plugins/visits/uptime) stay backend-blocked.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:49:36 -04:00
Juan DenisandClaude Opus 4.8 fd341b4d63 redesign(services): restyle new-service method picker as a grid
Convert the vertical 3-card mode strip into the prototype's .method-grid (icon tile + title + sub, accent border/wash on the selected card, hover lift). All three create flows and form logic are unchanged; Docker-image/from-scratch stay backend-blocked.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:49:36 -04:00
Juan DenisandClaude Opus 4.8 83cab5af28 redesign(git): add repository-connect hint banner
Add the prototype's .git-hint accent-gradient explainer banner atop the Git page (icon tile + headline + sub). The providers OAuth grid remains backend-blocked and is intentionally not added.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:49:36 -04:00
Juan DenisandClaude Opus 4.8 e3a9208172 redesign(marketplace): add horizontal category chip row
Add a quick-scan .cat-chips row above the browse grid (All + per-category), wired to the existing category filter. The right sidebar list and select are preserved (additive).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:49:36 -04:00
Juan DenisandClaude Opus 4.8 69cb44dbd5 redesign(email): show status dots on DNS presence pills
DKIM/SPF/DMARC presence pills passed dot={false}, rendering as flat chips; the prototype dtable uses dot+label status pills. Drop dot={false} so the colored status dot shows. A 3rd pending/verifying state remains backend-blocked (API reports boolean presence only).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:43:43 -04:00
Juan DenisandClaude Opus 4.8 67ae2ae8ab redesign: settings nav accent-wash + rail width parity
Settings nav active item used a heavy solid accent fill; switch to the subtle accent-bg wash + accent-bright text/icon used by .sk-topbar__tab (theme-safe in light and dark). Tighten the nav column 240->206px and widen the workflow palette rail 212->248px to match the prototype.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:43:43 -04:00
Juan DenisandClaude Opus 4.8 a8f77bc97b redesign(cron): migrate page header to PageTopbar
Cron Jobs still used the legacy .page-header (plain h1 + subtitle). Swap to the shared PageTopbar (icon + title + actions) used by the other migrated pages, matching the prototype's topbar.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:43:43 -04:00
Juan DenisandClaude Opus 4.8 dfc628e13b redesign(fleet): render fleet tables with sk-dtable
Agent Fleet and Fleet Monitor used the generic .table style while every other migrated list page uses the dense .sk-dtable look (mono uppercase headers, token row borders). Swap the className so they match their siblings under the Servers tab group.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 02:43:43 -04:00
Juan DenisandClaude Fable 5 b2fad9528a docs: clarify agent.key + session-token notes; record audit remediation (L2/L4-L7)
- SECURITY.md: spell out that agent.key encryption is off-host/tamper
  protection, not confidentiality against local root; 0600 perms are the real
  control (L2).
- agent_registry.py: reword the per-message session-token "TODO" to an accepted
  limitation (defence-in-depth, not a bug) (L7).
- AGENT_AUDIT.md: record H1/H2/H3 and L1-L7 status, with L4 (cap OR-merge),
  L5 (single-worker), L6 (windows .bat) noted as accepted by-design/documented.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 18:35:52 -04:00
Juan DenisandClaude Fable 5 df67ce782b fix(ui): remove dead agent-version management buttons (audit L3)
The Agent Versions tab rendered an "Add Version" button and per-row Edit/Delete
buttons with no handlers — there are no edit/delete endpoints. Remove the dead
controls (and now-unused Plus/Trash2 imports); the version list is unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 18:35:43 -04:00
Juan DenisandClaude Fable 5 75db466096 fix(pairing): require developer role for claim/lookup (audit L1)
Pairing /claim creates a fleet server and /lookup reveals a pending agent's
details; both were @jwt_required() only, so any authenticated user (incl. a
read-only viewer) could use them. Gate both with @developer_required to match
POST /servers. Test: viewer 403, developer clears the gate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 18:35:43 -04:00
Juan DenisandClaude Fable 5 a34064d15a fix(plugins): make plugin/template features fail honestly, not silently (audit H2)
agent_plugin_service and server_template_service dispatched agent commands
through get_agent_gateway() — a function that does not exist — inside a bare
try/except. Every call raised ImportError, was swallowed, and left DB rows stuck
in 'installing'/'checking'/'remediating' forever while the UI implied the
feature worked. The agent also has no handlers for these commands.

- Plugin install now reports STATUS_ERROR with "Agent-side plugin support is not
  implemented yet" instead of hanging in 'installing'.
- Template drift-check/remediate report 'unknown' + an explanatory
  drift_report.error.
- Local-only ops (disable, uninstall, configure) just drop the dead dispatch.
- test_agent_features_honest.py: 3 tests asserting honest states.

Implementing the feature for real (agent handlers + sandbox) remains future
work; this stops it from lying in the meantime.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 18:35:35 -04:00
Juan DenisandClaude Fable 5 db4b403a59 fix(agent): fail-closed checksum verification in the self-updater (audit H1)
The updater could install an unverified binary three ways: it skipped
verification when no checksums URL was given, returned success when no checksum
line matched the platform, and ignored the HTTP status of the checksums fetch
(a 404 body parsed to an empty map → skipped). Since the updater runs as the
agent process (root on packaged installs), that was a code-execution primitive
that bypassed every feature gate.

- Refuse to install without a checksums URL.
- Extract a pure, fail-closed matchChecksum: empty/unparseable body, no entry
  for this GOOS/GOARCH, or a mismatch all error (was a silent skip).
- Check the checksums fetch HTTP status.
- requireSecureURL rejects non-HTTPS update/checksum URLs (dev override via
  SERVERKIT_INSECURE_TLS).
- updater_test.go: 6 tests covering match/mismatch/no-entry/empty/case/https.

Cryptographic release signing remains the recommended next layer.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 18:35:27 -04:00
Juan DenisandClaude Fable 5 089b75526e fix(agent): accept registration token from env to keep it out of argv (M5)
The installers ran `serverkit-agent register --token <token>`, putting the
registration token in the process argument list — world-readable via `ps` /
/proc/<pid>/cmdline while register runs. The documented curl|bash one-liner also
landed the token in shell history.

- register now falls back to SERVERKIT_REGISTRATION_TOKEN /
  SERVERKIT_CONNECTION_STRING / SERVERKIT_SERVER_URL when the flags are absent
  (a process's environment is readable only by its owner and root).
- install.sh passes the token via env (drops the old `eval`) and accepts it from
  the environment so it need not appear on the curl line.
- install.ps1 passes the token to the child via env, and makes -Token optional
  with the same env fallback.

Verified: agent reads the env token at runtime (register proceeds past flag
validation to the network call); install.sh `bash -n` and install.ps1 parser
both clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 17:52:59 -04:00
Juan DenisandClaude Fable 5 6eb4471829 fix(servers): require developer role for agent self-update (audit H3)
POST /<id>/agent/update was @jwt_required() only, so a read-only viewer could
trigger agent binary replacement + service restart across the whole fleet —
inconsistent with every other state-changing server route, which is
@developer_required. Add @developer_required and a test proving a viewer gets
403 while a developer clears the gate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 17:33:29 -04:00
Juan DenisandClaude Fable 5 0bc30b62ef ci: run agent Go checks and backend pytest on push/PR (addresses audit M4)
Nothing ran the agent's tests or the backend suite automatically, so breakage
was only found by hand. Add two workflows:

- agent-ci.yml: go vet + go test on linux, plus a cross-compile build matrix
  (linux amd64/arm64, windows amd64) to catch platform-specific breakage at
  PR time. darwin is excluded — fyne.io/systray needs the macOS Cocoa toolchain
  and can't cross-compile from Linux (it's built on a mac at release time).
- backend-ci.yml: install requirements and run the full pytest suite, including
  the new end-to-end agent command-loop test.

Verified locally: go vet clean, all three cross-builds succeed, agent unit
tests and the full backend suite (136 passed) are green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 17:31:26 -04:00
Juan DenisandClaude Fable 5 43fc900944 test(agent): end-to-end test for the panel<->agent command loop
The agent subsystem had unit tests but nothing proving the whole protocol
works. This drives the REST poll transport (/connect, /poll, /result) — plain
HTTP that shares the WebSocket gateway's auth, registry, and command-routing
code — through the full loop: a correctly-signed agent authenticates, registers,
receives a command the panel issued via send_command (run on a background
thread since it blocks on the result), and round-trips the result back to the
waiter. Uses a temp-file SQLite DB so the thread and request handlers share
committed rows. 4 tests, all green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 17:31:26 -04:00
Juan DenisandClaude Fable 5 b57e436432 docs: add agent subsystem security & completeness audit
Severity-ranked, file:line-referenced audit of the agent fleet (Go agent,
gateway/registry/long-poll, pairing, panel→agent APIs, install/packaging, UI,
docs/CI) plus a remediation-status section recording the M1/M2/M3 fixes and the
M2 correction (compose allowlisting was already enforced at the handler layer).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 01:34:02 -04:00
Juan DenisandClaude Fable 5 315d443305 fix(agent): run packaged DEB/RPM agent as an unprivileged service account
The packaged systemd units ran the agent as User=root with a self-contradictory
ProtectSystem=strict, while the script installer (install.sh) runs a dedicated,
hardened serverkit-agent account. Align the packages with install.sh:

- User/Group=serverkit-agent; NoNewPrivileges=yes, ProtectSystem=strict,
  ProtectHome=yes, PrivateTmp=yes, ReadWritePaths for config/log/data.
- Create the account (deb postinst / rpm %pre), add it to the docker group,
  create+chown the data dirs, and re-chown the config dir on (re)install so an
  existing root-owned agent.key becomes readable after upgrade.
- ExecStartPre=+chown fixes credential ownership at start regardless of how the
  operator ran `register`; install docs now use `sudo -u serverkit-agent`.

Tradeoff: packaged agents match install.sh's capability set (Docker + metrics +
file access) and no longer do systemd/package management or self-update by
default (both need root). Disabling self-update also routes updates through the
OS package manager's signed path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 01:34:02 -04:00
Juan DenisandClaude Fable 5 41983cc3a9 docs(agent): clarify compose path allowlist is enforced at the handler layer
validateProjectPath is only a syntactic check (absolute, no `..`). The actual
restriction to operator-configured roots is the symlink-resolved AllowedPaths
allowlist run by every compose handler (a.validateFileAccess) before these
Compose* methods are called. Document that contract so a future caller can't
mistake validateProjectPath for the authorization boundary.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 01:34:01 -04:00
Juan DenisandClaude Fable 5 fd825276b9 fix(sockets): authorize socket connections by role; gate terminal streams
The Socket.IO connect handler only checked that the JWT decoded — it never
confirmed the account still exists or is active, and recorded no identity. The
generic join_room and subscribe_terminal handlers then performed no
authorization, so a read-only viewer (or a deactivated account with an
unexpired token) could attach to a live PTY stream room and watch a privileged
shell.

- connect: resolve the JWT identity to a live, active User and stash
  {user_id, role} per sid; reject missing/deactivated accounts.
- subscribe_terminal and join_room (for _terminal: rooms): require
  admin/developer, matching the @developer_required on terminal creation.
- disconnect: drop the per-sid identity record.

Job-progress and cloudflared-login streaming (non-terminal server rooms) are
unaffected — that data is already reachable over REST by any authenticated user.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 01:33:44 -04:00
Juan DenisandClaude Fable 5 58dc9f9c4d build(agent): darwin cross-builds non-fatal in build-all
fyne.io/systray needs CGO on darwin, so the macOS steps can only succeed
on a Mac; with CGO_ENABLED=0 they always fail and aborted build-all
before... actually after linux/windows, but with a dirty exit. Mark them
non-fatal (and use CGO_ENABLED=1 so they work where they can) so
build-all completes cross-host - darwin release builds come from the
Agent Release workflow anyway.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 00:49:04 -04:00
Juan DenisandClaude Fable 5 5ceb2d83b7 fix(ui): serialize terminal input + demo chrome for the Terminal tab
Verified the S8.3 terminal live end-to-end (WSL agent -> gateway ->
browser xterm) and fixed what the test surfaced:

- RemoteTerminal sent every keystroke as its own HTTP POST; parallel
  POSTs reach the agent out of order, so fast typing arrived scrambled
  ("echo" -> "ehe..."). Input is now queued and flushed sequentially,
  coalescing keys that arrive while a chunk is in flight.
- Terminal page header -> PageTopbar (icon + mono meta), matching every
  other migrated page.
- term-shell rail rows: server icon + right-aligned glowing status dot
  (demo con-target); shell pane now sits flush against the rail with
  RemoteTerminal's drawer chrome flattened (demo con-main).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 00:36:45 -04:00
Juan DenisandClaude Fable 5 9e0f063d0a fix(agent): restore Linux build + register flag panic
Found while standing up a Linux agent to test the panel terminal:

- agentui/pairer.go calls isServiceInstalled/waitForServiceStopped which
  only existed in platform_windows.go, so the agent did not compile for
  Linux at all (its primary target). platform_other.go now stubs both:
  isServiceInstalled() returns false so pairing skips the SCM stop/start
  dance entirely on non-Windows.
- register defined -c as shorthand for --connection-string, colliding
  with the global -c/--config persistent flag; pflag panics on shorthand
  redefinition, so `serverkit-agent register` crashed on every platform
  at HEAD. Shorthand dropped.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 00:36:32 -04:00
Juan DenisandClaude Fable 5 b2e4ac7c60 feat: interactive Terminal tab on /terminal over agent PTY (S8.3)
The "Terminal & Logs" page had no terminal. Now a Terminal tab hosts the
xterm console (RemoteTerminal) against paired agent servers, behind a
demo-style target rail (.term-shell: agent list with glowing status
dots, offline rows disabled, first online server auto-selected).

RemoteTerminal was built but never mountable - it imported a
nonexistent SocketContext and listened for a terminal:<sid> event that
nothing emits. Fixed the full output path:

- backend sockets.py: new subscribe_terminal/unsubscribe_terminal
  handlers join the browser into the agent gateway stream room
  (server_<id>_terminal:<session>) after validating the session via
  TerminalService.get_session
- RemoteTerminal: rewired to services/socket.js; consumes server_stream
  events filtered by the session channel (agent PTY -> gateway ->
  xterm), re-subscribes on socket reconnect, unsubscribes on unmount

VALID_TABS gains terminal (appended - logs stays the default landing
since the shell needs a connected agent; panel-host PTY remains a S5
gap, TerminalService rejects local).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 23:55:39 -04:00
Juan DenisandClaude Fable 5 1cbaa6fa27 feat(ui): Databases SQL editor gutter + highlighting, click opens console (S8.1)
- new databases/SqlEditor.jsx: the demo .sql-editor as a working editor -
  transparent <textarea> overlaid on a scroll-synced highlighted <pre>
  (identical font metrics), line-number gutter, SQL keyword/string/
  number/comment tints; wrap=off + horizontal scroll sync
- ConsoleTab swaps its bare textarea for SqlEditor (focus/shortcuts/
  read-only badge unchanged); dead .dbx-editor styles replaced by
  .dbx-sqled
- UX: single-clicking a database node in the tree now opens its SQL
  console directly (previously only via right-click context menu) and
  expands its tables; collapsing stays on the chevron so re-clicks do
  not fold the tree

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 23:34:51 -04:00
Juan DenisandClaude Fable 5 7647fde208 feat(ui): Docker per-container accent dots (S8.6)
The demo gives each container a hue-hashed identity dot in the name cell
(.dot-ico); the live table only had a green/gray status dot duplicating
the Status pill. The dot is now deterministic per container name, glows
when running, dims when stopped.

Also closes the S8.6 audit items: KPI icon tone-wash was a false alarm
(rules + tone props verified correct); Projects KPI skipped (labels only
exist on per-container inspect, count would cost an extra fetch per
target); logs modal->drawer stays blocked on the S6.3 decision.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 23:09:20 -04:00
Juan DenisandClaude Fable 5 ad2d3015cb feat(ui): Files parity - syntax-tinted preview + quick access (S8.5)
- new file-manager/highlight.js: the demo hlCode line tinting (escape
  first, then kw/str/num/cmt/var token spans; PHP + JS keyword sets)
- PreviewDrawer read view -> .pv-code with line-number gutter on
  --bg-code; Edit mode keeps the textarea; >4000-line files fall back to
  the plain read-only textarea
- Quick access rail group above Folders (Sites /var/www, Stack
  /opt/serverkit, Web config /etc/nginx, Logs /var/log)
- PageTopbar meta shows the browse target (agent name / "panel host")
- breadcrumb chevron icons -> demo mono "/" separators
- ext-badge on image thumbs always visible (was hover-only)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 23:06:53 -04:00
Juan DenisandClaude Fable 5 8ac61a7f1e feat(ui): WP list bulk-select + detail posture gauge & PHP version (S8.4)
List (demo bulkbar, real APIs, no bulk route needed):
- header + per-row checkboxes with accent is-selected rows
- bulkbar fans out per site: Start (unarchive), Stop (archive), Update
  core, Backup (snapshot), Clear cache - each scoped by an applies()
  filter so e.g. Stop only hits running sites; failure counts toasted

Detail:
- Security tab gains the demo posture panel: ScoreGauge ring + checklist
  computed from 3 real signals (integrity checksums clean, WP_DEBUG off,
  no critical/high vulnerabilities); checks that have not run stay out of
  the score instead of faking it
- header subtitle now shows PHP version (site.application.php_version)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 18:46:45 -04:00
Juan DenisandClaude Fable 5 6e3d0f5155 feat(ui): Services list -> dense sk-dtable + SegControl + src badges (S8.2)
The list was a flex-row layout with three native selects - the main reason
it did not read as the demo. Now:

- rows -> .sk-dtable in a surface card: checkbox gutter, Service (type
  tile + label sub), Source badge (GitHub/git icon + owner/name from
  deploy_repo_url), Domain (mono), Status Pill (amber deploying/building,
  red failed), Last Deploy, right-aligned lucide action icons
- status select -> SegControl (All/Running/Stopped); Type + Sort selects
  kept (capability superset over the demo)
- bulk-select + row-select preserved (sk-dtable is-selected)
- narrow screens drop Source/Domain/Last Deploy columns
- dead flex-row/list-header/status-dot/repo-pill SCSS removed

CPU/Memory/Server columns omitted (no list payload - S5 gap).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 11:15:04 -04:00
Juan DenisandClaude Fable 5 8b49fd2255 docs: mark S8.7 Workspaces done in the parity map
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 10:11:51 -04:00
Juan DenisandClaude Fable 5 2312cf4448 feat(ui): WorkspaceDetail page + demo workspace cards (S8.7)
The demo's missing /workspaces/:id detail view, built on real data only:

- new pages/WorkspaceDetail.jsx: back-link + site-header hero (brand tile,
  active-workspace / status Pill, mono slug-members-since meta), Workspace
  info card (sk-info-row) + live Resources count strip, accent-underline
  tabs (Servers / Services / Sites / Members) over sk-dtable rows with
  Manage links, move-in/out pickers, member add/remove, per-app Share ACL
  modal, Set active (localStorage scope + reload, mirrors
  WorkspaceSwitcher), Settings edit modal (updateWorkspace), Archive/
  Restore + admin Delete
- list page slimmed to the demo card: clickable card -> detail, "Open >"
  footer, .ws-card.active accent wash for the scoped workspace; the
  members/resources/sharing modals moved into the detail page
- route /workspaces/:id added

Deferred (S5 backend): plan/billing, aggregated CPU/MEM usage bars,
member last_active.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 10:11:25 -04:00
Juan DenisandClaude Fable 5 a97f8a4215 feat(ui): Databases parity pass 2 - flush console + semantic tints (S8.1)
The demo is a flush full-height 3-pane console; the live page was padded
floating cards, which is why it did not read as the new design.

- de-cardify: .db-explorer padding/gaps -> 0; toolbar/tree-panel/
  workspace/statusbar lose radius+outer borders, join on 1px edges
- ResultsGrid: demo cellEl semantic tints (NULL italic-ghost, emails/URLs
  + positive statuses green, negative amber)
- tab strip: demo + new-tab pill (opens SQL console on selected source)
- result count number bold green

Follow-up (S8.1): SQL editor line-number gutter + keyword highlighting.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 02:37:40 -04:00
Juan DenisandClaude Fable 5 e0e660087e fix(ui): retint Agent Fleet + Fleet Monitor to design tokens (S8.0)
These two pages were never migrated: hard-coded Tailwind light-palette
utilities (bg-gray-200, bg-yellow-50, bg-blue-600, bg-white modal) made
the /servers -> /fleet jump read as a different app and broke dark theme.

- StatCard/StatsGrid -> MetricCard KPI strip; progress bars -> ds Gauge
- status Badges -> Pill (versions, rollouts, heatmap, alerts, anomalies,
  search, discovery)
- new token homes in _fleet-monitor.scss: .fleet-kpis/.fleet-warnrow/
  .fleet-ico/.fleet-statbox/.fleet-predbox/.fleet-modal/.fleet-statrow
- heatmap buckets + forecast chart strokes -> theme vars
- _utilities.scss: shim text-green-500/red-500/yellow-600/yellow-700

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 02:37:29 -04:00
Juan DenisandClaude Fable 5 c31ad144d3 docs: re-audit vs new design extract - add S8 parity-gap map
Pass-1 restyle stops short of real parity on several pages; S8 ranks the
gaps per page (Agent Fleet/Fleet Monitor never migrated, Databases still
cardified, Services list structure diverged, Terminal IA, WP list, etc.)
and sets the execution order.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 02:37:17 -04:00
Juan Denis 8bf6488b9c docs: redesign restyle COMPLETE - mark Applications/overlays/sweeps done 2026-06-11 02:12:17 -04:00
Juan DenisandClaude Fable 5 6f5df6db6e refactor(styles): promote .sk-state primitive + final design-system sweeps
(1) New %sk-state placeholder + .sk-state class in _design-system.scss
(mono uppercase state chip, 7 tone modifiers); the 5 page-local copies
(.mon-state/.sec-state/.inc-state+.inc-impact/.git-chip/.bk-type) now
@extend it - class names and JSX untouched, compiled output verified
equivalent. (2) Dead connectionColors export removed from
connectionRules.js (zero consumers). (3) _email.scss globals evicted:
.status-badge deleted (its unique content was dead variants + a phantom
::before dot artifact in two text badges - strict improvement),
.empty-state moved to _empty-state.scss via an html-prefixed bridge that
preserves the cascade over page-partial copies. (4) TokenModal blocks in
_servers.scss re-tokened (--bg-code scripts/connection string, eyebrow
labels, green status dot) - no other selectors in the file changed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 02:11:49 -04:00
Juan DenisandClaude Fable 5 7b1e9849ab feat(ui): restyle the infra overlays (logs dock, SSH terminal, container logs)
Map item #21, bottom-dock IA kept (S6.3 stays open). LogsDrawer: sk-
drawer-style head (icon tile, mono source sub, live-dot line counter),
log well on --bg-code, mobile fix (dock was orphan-offset 250px with the
sidebar off-canvas). RemoteTerminal: previously UNSTYLED (classes had no
stylesheet; old VS Code palette in an embedded <style> tag) - now has a
proper token home in _logs-drawer.scss; xterm theme retinted to the new
palette incl. the v5 selectionBackground key fix (v4 selection key was
silently ignored). ContainerLogs: icon-tile header + mono sub; its
styles live in _wordpress-pipeline.scss (CONTAINER LOGS section only).
All socket/resize/follow/cleanup behavior byte-identical.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 02:11:49 -04:00
Juan DenisandClaude Fable 5 96641a1855 feat(ui): restyle the Applications pages + modernize the base app-* chrome
The last old-look pages. Base app-* classes in _applications.scss now
carry the new spec directly (gradient detail header + accent wash, mono
breadcrumbs/eyebrow panel headers, sk-info-row k/v grids, accent tab
strip) - property-aligned with the .wp-detail-page overrides so WP detail
stays identical and WordPressProject inherits the new chrome for free.
Applications list: PageTopbar + MetricCard KPI strip + dense sk-dtable
(type tiles, Pills, Gauge resource rows, SegControl filter); page no
longer borrows docker-* classes. ApplicationDetail: tinted header tile +
Pill/EnvTag, deploy/build Badges -> Pills, consoles on --bg-code; dead
blocks with zero consumers dropped. Also fixes pre-existing lint errors
(empty catches, unescaped entities). NOTE: /apps currently redirects to
/services so the list page is orphaned - restyled for consistency.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 02:11:48 -04:00