104 Commits
Author SHA1 Message Date
Juan Denis f9d0610452 chore: remove agent code now living in serverkit-agent repo
The Go agent has moved to its own repository:
https://github.com/jhd3197/serverkit-agent

Changes:
- Delete agent/ directory and all source, build, and packaging files
- Delete .github/workflows/agent-ci.yml and agent-release.yml
- Delete scripts/test/agent-test.ps1 (agent now builds in separate repo)
- Remove committed dist/serverkit-agent-windows-amd64.exe build artifact
- Update docs/INSTALLATION.md and docs/README.md links to point to the new repo
- Add migration notes to docs/AGENT_AUDIT.md, docs/REMOTE_ACCESS_ROADMAP.md,
  SECURITY_AUDIT.md, and docs/plans/AGENT_FLEET_PHASES_3-4.md

Panel agent API endpoints under /api/v1/servers/agent/ and /api/v1/agent/
are retained — they are the control-plane contract with the standalone agent.

Verified:
- backend: pytest (371 passed, 12 skipped)
- frontend: npm run lint (0 errors)
2026-06-21 03:30:57 -04:00
Juan DenisandClaude Opus 4.8 467ebc51b0 chore(docs): track regenerated UI screenshots
Drop the stray /docs/screenshots ignore that overrode the existing negation (.gitignore:117-118), so the 23 mock-data README screenshots are committed assets again, and add the freshly regenerated set (ServiceDetail header selector updated in the local capture skill).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-21 02:21:47 -04:00
Juan DenisandClaude Opus 4.8 6d8d8dfa31 docs(i18n): sync es/zh-CN/pt READMEs with the new meaningful features
The localized READMEs are older, slimmer variants missing several sections the
English README has, so each picked up the feature entries that map to existing
sections (Manual & Zip deploys, Container Lifecycle, SSL/TLS hardening, S3/B2
browsing, Passkeys/WebAuthn, WAF, GPU monitoring) plus the full set of new
completed roadmap items, the Tech Stack security row, and a NEW_FEATURES link.

Features whose parent section doesn't exist in a given translation (Remote
Service Tunnels, Connections, WordPress Publishing, Dynamic DNS, Secrets &
Webhook Gateway, CVE/SBOM, Encrypted Secrets at Rest) are represented in that
file's roadmap rather than fabricating new sections. No Layout Switcher
(UI-only) line added, consistent with the English README.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 17:23:13 -04:00
Juan DenisandClaude Opus 4.8 ae177d789c docs: catch README/CHANGELOG/feature reference up to the unreleased dev work
The dev branch is 23 commits ahead of origin with many user-facing features that
were never reflected in the docs. Bring them current.

- README: add WAF, container CVE/SBOM (grype/syft), passkeys/WebAuthn, dynamic
  DNS, GPU monitoring, container lifecycle (image updates / auto-sleep /
  auto-scale), secrets + inbound webhook gateway, backup encryption, optional
  hardened TLS, encrypted secrets at rest, and the layout switcher to the
  Features list, Roadmap, and Tech Stack; link NEW_FEATURES.md.
- CHANGELOG: add the above to [Unreleased] and a new Security subsection
  (CVE/SBOM, optional/hardened TLS + auto-CAA + HSTS gating, encryption at rest).
- NEW_FEATURES.md: add §6 documenting the SSL/TLS hardening follow-up
  (02afdec..a09bf0f) and renumber Known follow-ups to §7.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 17:08:24 -04:00
Juan DenisandClaude Opus 4.8 1f700cea06 docs: endpoint & page reference for this session's features
Reviewer-facing summary of the new endpoints, pages, models, config flags,
and operational notes for the DDNS / image-update / auto-sleep / auto-scale /
GPU / backup-encryption / WAF features and the layout switcher.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 15:34:55 -04:00
Juan DenisandClaude Opus 4.8 733f866f1c feat(wordpress): per-site PHP-fatals analytics from the WP_DEBUG log (#25)
Finish #25's deferred PHP-fatals signal, sourced from the WP_DEBUG log
(/tmp/wp-debug.log) that #30's Security toggle writes — not the access log
(which can't carry it):

- get_php_errors: one bounded docker exec (marker-guarded tail -c, 15s single-
  worker timeout) distinguishes logging-off / no-log from on-but-clean, then
  parses [time] PHP <Level>: <msg> into severity counts (fatal/warning/notice/
  deprecated; Parse + Recoverable-fatal fold into fatal) + the 25 most-recent
  entries (messages capped, stack-trace lines skipped).
- Merged into GET /sites/<id>/analytics as php_errors; a PHP errors panel added
  to the Analytics tab with an 'enable WP_DEBUG on the Security tab' empty state.
- Supplies the per-site error signal #27's error-spike rule wanted.

7 new tests (classification, parse/skip-traces, off/empty/unavailable, capping);
full backend suite 218 passed / 12 skipped; frontend lint-clean + production build.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 22:06:58 -04:00
Juan DenisandClaude Opus 4.8 a6a214c245 feat(wordpress): wp-content/full-site zip import with zip-slip-safe extraction (#17)
Finish #17's deferred files half on top of the .sql MVP. After the DB import +
search-replace, an optional wp-content/full-site .zip is extracted and copied
into the container:

- _safe_extract_zip rejects any member escaping the temp dir BEFORE writing
  (zip-slip: ../ traversal, deep traversal, absolute paths) + bad-zip guard.
- _resolve_wp_content_dir handles the 3 layouts: zip OF wp-content, zip WITH
  wp-content/ at root, and full-site zip one wrapper folder deep.
- _import_wp_content_zip docker cp's the resolved tree into
  /var/www/html/wp-content and chowns it to www-data. Non-fatal: DB import still
  succeeds; response carries a warning + wp_content_imported flag.
- Route accepts an optional wp_content multipart file (.zip, own temp, cleaned
  up); client importSite({wpContentFile}) + a file input in the import modal.

11 new tests (zip-slip rejection, layouts, docker cp/chown flow mocked); full
backend suite 211 passed / 12 skipped; frontend lint-clean + production build.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 21:59:59 -04:00
Juan DenisandClaude Opus 4.8 aaf99212cb feat(wordpress): durable per-site PHP limit writes + wire the PHP tab (#24)
Finish #24's deferred limit-WRITE side, Docker-correct for wordpress:*-apache
(Apache + mod_php):

- set_php_limits: whitelist + re.fullmatch-validate each directive (fullmatch so
  a newline-smuggled extra ini line can't pass the $ anchor), write a host-side
  php-conf/zz-serverkit.ini and bind-mount that single file into conf.d so it
  survives container recreate. File written before the mount is added; new mount
  applies via compose up, content-only change via restart so php.ini reloads.
  Partial updates merge; mount injected once.
- Route POST /sites/<id>/php/limits (admin), client setPhpLimits, and an Edit
  PHP Limits panel in the PHP tab (inputs seeded from live values, partial save).
- Fix latent bug: PhpTab (the #24 read panel) was built but never wired into
  WordPressDetail (no tab button / render branch) — the whole PHP panel was
  invisible. Added 'php' to VALID_TABS + a tab + render branch.

7 new tests; full backend suite 200 passed / 12 skipped; frontend lint-clean +
production build.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 21:43:12 -04:00
Juan DenisandClaude Opus 4.8 4a637c7181 docs(wordpress): split #34 into an executable per-site edge + scaling spec
#34 is infra-blocked and unverifiable on the Windows dev host, so spec it
rather than build it blind. A capability sweep found the load-balancing,
rate-limit, proxy-cache, per-vhost-log, wildcard-TLS, and S3-upload primitives
already exist (nginx_advanced_service / nginx_service / advanced_ssl_service /
storage_provider_service) — so #34 is wiring + a public base-domain + stateless
WP, not greenfield nginx.

Adds docs/HORIZONTAL_SCALING_SPEC.md: a shared keystone (K0-K4) that also
unblocks #21/#15-TLS/#8/#30-jail/#25, then #34 proper (H0-H4), each phase a
separately verifiable PR with a Windows-vs-real-host split. Links it from the
roadmap #34 entry.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 21:09:42 -04:00
Juan DenisandClaude Opus 4.8 fa73b217ef feat(workspace): scope domains/databases + reconcile workspace roles (#33)
Finish #33's two named workspace-scoping slices, backend-only (no schema
migration, no frontend changes):

- Domains: get_domains scopes through the parent Application via scope_query
  (own + granted, workspace-narrowed); also surfaces granted apps' domains,
  closing a gap vs the per-domain routes. Admin-no-context path unchanged.
- Databases: the app-linked /docker/databases enumeration scopes via
  scope_query; host-level MySQL/PG/SQLite stay global system resources
  (documented inline).
- Role reconciliation: WorkspaceService.effective_role + can_write_in_workspace
  intersect global User.role with per-workspace WorkspaceMember.role,
  narrow-only (a workspace role can only reduce capability; system admin never
  capped). Enforced at apps/servers create + reassign seams (viewer -> 403,
  only when a workspace context is active). Reconciled role surfaced on
  GET /workspaces (my_role/my_effective_role).

6 new tests; full backend suite 193 passed / 12 skipped.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 21:00:57 -04:00
Juan DenisandClaude Opus 4.8 60a676025a Fix wildcard HTTPS reading encrypted DNS creds raw + add encryption tests
Auditing the encryption-at-rest work surfaced one missed read site: sites_https_service
passed the DNS provider api_key/api_secret straight to the cert issuer, but those are
now Fernet-encrypted at rest — so DNS-01 wildcard issuance would have received ciphertext
and failed.

- Add DNSProviderService.decrypted_credentials() as the public, canonical way to read a
  DNS provider's usable creds (nothing reads .api_key raw anymore); use it in
  sites_https_service.
- Add tests/test_provider_secret_encryption.py — 8 proving tests: ciphertext-at-rest +
  plaintext-on-read for DNS / cloud / storage / registrar, the idempotent legacy-plaintext
  migration, and the wildcard path now receiving decrypted creds (the regression above).

pytest: 17/17 pass (8 new + 9 existing test_sites_https.py). Full suite: 187 passed; the
lone failure (test_agent_poll_e2e command roundtrip) is a pre-existing flake — the agent
registry in-memory singleton leaks across tests in that file (passes solo), unrelated here.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 19:55:57 -04:00
Juan DenisandClaude Opus 4.8 fd2313077f Polish connections: domain-expiry alerts, S3 image preview, encryption-health banner
- Domain-expiry notifications: a daily scheduler (RegistrarService.notify_expiring)
  fires through the notification channels when a registrar domain crosses 30/14/7/1
  days or expires, de-duped via a small state file so each crossing alerts once.
- S3 image preview: ImageThumb is now S3-aware (loads objects via short-lived
  presigned URLs); threaded through FileCard / PreviewDrawer / FileManager.
- Encryption-health banner: the Connections hub consumes GET /api/v1/connections
  and warns if any credential is not encrypted at rest (hidden in the happy path)
  — the registry's first real consumer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 19:43:00 -04:00
Juan DenisandClaude Opus 4.8 bbe6bee267 Unify credential encryption + add a read-only connection registry (#18)
- Encrypt the last plaintext credential store: CloudProvider.api_key_encrypted is
  now Fernet-encrypted on write and decrypted in _auth_headers, with an idempotent
  encrypt_legacy_secrets() migration run at startup. All five stores (source, DNS,
  cloud, registrar, storage) now encrypt identically via app.utils.crypto.
- Add ConnectionRegistry + GET /api/v1/connections: one normalized, secret-free
  list of every connected external account (the "what's connected" source of
  truth). Frontend api.getAllConnections().
- The full write-side "one model replaces five" migration is deliberately not done
  — it would risk five working subsystems for no user-facing benefit. The uniform
  encryption + read registry deliver #18's practical value.

Verified by booting create_app('testing') and running the registry live (returns
a list; /api/v1/connections present in the URL map).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 10:47:59 -04:00
Juan DenisandClaude Opus 4.8 d157fc514d Encrypt provider secrets at rest + add Namecheap registrar (#16, #17)
Encryption-at-rest (#16/#17) for the two provider stores still in plaintext:
- crypto.py: add decrypt_secret_safe (decrypt with plaintext fallback) + is_encrypted.
- DNS providers: encrypt api_key/api_secret on write; decrypt at point-of-use via
  _api_key/_api_secret in the Cloudflare/Route53/DigitalOcean/GoDaddy builders.
- Storage: encrypt secrets in save_config; decrypt in get_config.
- Idempotent encrypt_legacy_secrets() migrations run on startup, encrypting any
  pre-existing plaintext in place. Legacy values keep working via the decrypt
  fallback, so there is no flag day.

Namecheap registrar:
- RegistrarConnection.config_json column for non-secret extras (username, client_ip).
- RegistrarService: Namecheap XML domains.getList (portfolio + expiry), test, and
  MM/DD/YYYY -> ISO date parsing.
- Frontend: namecheap tile is live; the registrar form is provider-aware (GoDaddy
  key+secret vs Namecheap key + username + whitelisted IP).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 10:33:09 -04:00
Juan DenisandClaude Opus 4.8 845df9959e Add S3 bucket browsing to the Files app (#14, #15)
- backend: StorageProviderService.s3_* (browse/read/write/upload/delete/
  presigned-get) + /api/v1/files/s3/* routes, reusing the configured backup
  bucket — no new credentials.
- frontend: an "S3 bucket" target in the File Manager. UI paths stay slash-rooted
  and map to S3 keys server-side, so breadcrumbs/history work unchanged; virtual
  folders from prefixes; presigned-URL download. Folders/rename/permissions are
  disabled and the local-only sidebar is hidden on S3. TargetPicker gained a
  backward-compatible extraOptions prop.
- docs: mark #14/#15 done (Phase 4).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 10:21:39 -04:00
Juan DenisandClaude Opus 4.8 7b6086934d Finalize Connections hub: complete #8 SMTP relay docs + lint cleanup
Follow-up to the connections work already committed in 2e1bdbc + c1d4653
(swept in by a concurrent commit). Commits the two remaining edits:

- docs/CONNECTIONS_ROADMAP.md: mark #8 (SMTP relay) done — a real Postfix
  smarthost (relayhost + SASL) with an OS-agnostic smtplib connection test.
- ConnectProviderModal: drop the unused `provider` arg from EmailBody (lint).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 09:23:06 -04:00
Juan Denis c1d4653f6c Add registrars, GitLab, DO/Godaddy DNS and SMTP relay
Introduce a broad Connections expansion: add RegistrarConnection model, RegistrarService and /api/v1/registrars endpoints to surface domain portfolios and expiry. Add EmailRelayService and API routes plus Postfix relay wiring to configure/test/disable outbound SMTP. Extend DNSProviderService with DigitalOcean and GoDaddy implementations and helpers. Add GitLab support to source connection service and routes. Frontend: expand Connections hub, provider catalog, modal bodies, provider icons, RegistrarPortfolio component, API client methods and styles. Add docs/CONNECTIONS_ROADMAP.md describing roadmap and intent.
2026-06-15 09:19:56 -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 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 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 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 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 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 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 Denis 98f41294b1 docs: mark WP detail restyle done; record flat-tabs decision (S6.2) 2026-06-11 01:30:17 -04:00
Juan Denis aa6981c6c5 docs: mark Git/Workflow/Backups body restyles done; record resolved is_active gap 2026-06-11 00:49:00 -04:00
Juan Denis 0530d531ab docs: mark Cron/Terminal/Workspaces/Email/Security/StatusPages body restyles done 2026-06-11 00:19:16 -04:00
Juan Denis 7e272492ef docs: mark FTP/Monitoring/ServerDetail/ServiceDetail+Templates body restyles done 2026-06-10 23:46:10 -04:00
Juan Denis 819630b02a docs: mark FileManager body restyle done in the redesign map 2026-06-10 23:15:43 -04:00
Juan DenisandClaude Opus 4.8 b758fda2fd feat(ui): restyle the DNS Zones + SSL bodies to the new look
Finish the Domains trio visually:
- DNS Zones: records table -> dense .sk-dtable; record type rendered as a colored .dns-rtype chip (A/CNAME/MX/TXT/NS/SOA…); content cell uses .sk-cell-mono. Drop unused Badge import.
- SSL Certificates: per-cert status Badge -> Pill (green Valid / amber Expiring). Drop unused Badge import.
Both already themed from Phase 0; these add the redesign's chip/pill language.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 00:25:09 -04:00
Juan DenisandClaude Opus 4.8 506945c44b feat(ui): restyle the Services list to a KPI strip + dense table
StatStrip -> MetricCard KPI strip (Running/Stopped/Total/Last Deploy with tone icons + subs); per-row status -> Pill; list header -> mono uppercase dense, list card on surface/border-soft tokens. All existing logic kept: search/type/status/sort filters, multi-select + bulk start/stop/restart, repo pill, per-row action buttons, and wordpress/service navigation. Drop now-unused StatStrip/Stat import.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 23:57:55 -04:00
Juan DenisandClaude Opus 4.8 d18d20ce36 feat(ui): restyle the WordPress site list to a dense table
Convert the .wp-site-card grid into a dense .sk-dtable: Site tile + name/port, Environments, Version (mono), Status (Pill green/gray), tag chips, and Open-Site/WP-Admin links. Add a status SegControl (All/Running/Stopped) alongside the existing tag filter; row click still opens the site detail. Omitted SSL/plugins/visits/uptime/server columns (not in the get_sites payload — no list backend) per restyle-first. Drop an unused Badge import.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 20:11:22 -04:00
Juan DenisandClaude Opus 4.8 e2faf9dc6e feat(ui): restyle the Servers list table to the demo's dense look
Visual restyle of the existing .servers-table (no column/capability changes — bulk-select, group, telemetry, and row actions all kept): mono uppercase headers, and thin token-colored telemetry gauges (CPU=accent-bright, RAM=cyan, Disk=green). Region/Load columns omitted (mock-only, no backend field) per restyle-first.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 19:18:28 -04:00
Juan DenisandClaude Opus 4.8 e2968b2197 feat(ui): migrate the WordPress group to the top-bar layout (sidebar now flat)
Final sidebar sub-menu moved to the demo's top-bar structure (REDESIGN_MAP §6 decision 3):
- Add shared WORDPRESS_TABS (WordPress / Pipeline); render PageTopbar on WordPress (sites list) and WordPressProjects (pipeline).
- Remove the wordpress subItems from the sidebar — NO sidebar item has a sub-menu anymore; the rail is fully flat, matching the demo.
- WordPress list: both headers (loading + main) swapped; Import Site + Create Site actions kept.
- Pipeline list header swapped (title 'Pipeline').
- The Pipeline tab is now always visible (was wpInstalled-gated in the sidebar).
Six infra groups total now use the top-bar layout: Domains, Servers, Services, Files, Monitoring, Marketplace, WordPress. Body restyles still pending per group.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 19:07:35 -04:00
Juan DenisandClaude Opus 4.8 7cc27cb82f feat(ui): migrate Monitoring + Marketplace groups to the top-bar layout
Final two sidebar sub-menus moved to the demo's top-bar structure (REDESIGN_MAP §6 decision 3):
- Monitoring group: shared MONITOR_TABS (Monitoring/Status Pages); PageTopbar on both pages (Refresh + Start/Stop + Create-Page actions kept).
- Marketplace group: shared MARKET_TABS (Marketplace/Downloads); PageTopbar replaces the marketplace hero; Downloads header swapped (Import ZIP + Refresh kept).
- Remove the monitoring + marketplace subItems from the sidebar.
Only WordPress (Pipeline) still has a sidebar sub-menu; everything else is now flat + top-bar. Body restyles still pending per group.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 18:35:21 -04:00
Juan DenisandClaude Opus 4.8 faa6355c75 feat(ui): migrate the Files page group to the top-bar layout
Move Files + FTP to the demo's top-bar structure (REDESIGN_MAP §6 decision 3):
- Add shared FILE_TABS (Files / FTP Server); render PageTopbar on both pages.
- Remove the files subItems from the sidebar (route /ftp unchanged, reachable from the tab).
- FileManager: top bar added as the first child of the full-bleed fullscreen browser (already a flex column; body keeps filling) — the toolbar, tree, preview, upload tray and all file ops are untouched.
- FTPServer: .page-header swapped for PageTopbar; the conditional install/test/start/stop/restart actions moved verbatim into the top bar.
- Fix a pre-existing unescaped-apostrophe lint error in FTPServer.
Follow-up: restyle the FileManager + FTP bodies.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 16:26:55 -04:00
Juan DenisandClaude Opus 4.8 c6d44ac502 feat(ui): restyle New Service mode chooser to the demo's method cards
Restyle .new-service-page__mode-card into the demo's centered method-card look (icon chip on top, title, description, corner check on the active card) and tokenize the old teal/indigo active-state literals. GitHub-OAuth repo picker, manifest detection, and the template flow are all preserved. Follow-up: template-catalog grid + Docker-Image/From-Scratch methods.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 16:14:08 -04:00
Juan DenisandClaude Opus 4.8 4e95c2257f feat(ui): migrate the Services page group to the top-bar layout
Move Services + its sub-pages to the demo's top-bar structure (REDESIGN_MAP §6 decision 3):
- Add shared SERVICE_TABS (Services/New Service/Templates/Deploy Activity); render PageTopbar on all 4 group pages so the tab group persists.
- Remove the services subItems from the sidebar (routes /services/new, /templates, /deployments unchanged, reachable from the tabs).
- Services: header -> PageTopbar (meta shows total/running; New Service action kept).
- NewService: drop the now-redundant breadcrumb; Connections action kept.
- Templates: search box moved into the top-bar actions.
- Deployments: New Service + Live/Paused + Refresh actions kept.
Follow-up: restyle the bodies (NewService method-cards + template catalog, Services dense table, ServiceDetail tabs).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 13:07:48 -04:00
Juan DenisandClaude Opus 4.8 8e61ad08d8 feat(ui): migrate the Servers page group to the top-bar layout
Move Servers + its 4 sub-pages to the demo's top-bar structure (REDESIGN_MAP §6 decision 3):
- Add shared SERVER_TABS (Servers/Agent Fleet/Fleet Monitor/Cloud Servers/Config Templates) and render PageTopbar on all 5 pages so the tab group persists.
- Remove the servers subItems from the sidebar (routes unchanged, reachable from the tabs).
- Main Servers page: top bar added ABOVE the full-bleed ops workspace (container -> column); the fleet-health rail, status/group filtering, workbar, bulk actions and pairing are fully preserved — no capability removed.
- Sub-pages (AgentFleet/FleetMonitor/CloudProvision/ServerTemplates): .page-header swapped for PageTopbar, action buttons preserved.
- Fix a pre-existing unescaped-quote lint error in FleetMonitor.
Follow-up: restyle the Servers list body to the demo's dense table + ServerDetail tabs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 04:30:51 -04:00
Juan DenisandClaude Opus 4.8 0eab32096b feat(ui): share the top-bar tab group across Domains/DNS/SSL
Finish the domain page group so the Domains/DNS Zones/SSL tabs persist on all three:
- Extract DOMAIN_TABS to components/domains/domainTabs.jsx; Domains imports it.
- DNSZones + SSLCertificates pages now render the shared <PageTopbar tabs={DOMAIN_TABS}> in place of their old headers (actions preserved).
- Fix a pre-existing unescaped-apostrophe lint error in SSLCertificates.
DNS/SSL page bodies (lists/forms) are not yet restyled — headers only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 04:03:29 -04:00
Juan DenisandClaude Opus 4.8 68695a779c feat(ui): migrate Domains to the demo's top-bar layout
First infra page moved to the demo structure (REDESIGN_MAP §6 decision 3):
- Add shared PageTopbar ds primitive (icon + title + routed sub-nav + actions).
- Domains: top bar with Domains/DNS Zones/SSL tabs (replaces the removed sidebar sub-menu), KPI strip (MetricCard), segmented filter (SegControl), .sk-dtable list, and a detail Drawer with SSL/linked-site/status specs + all actions (visit/verify/SSL/delete). Add-domain + enable-SSL modals preserved.
- Remove the domains subItems from sidebarItems.js (/dns, /ssl routes unchanged, reachable from the top-bar tabs).
- Omit registrar/DNSSEC/nameservers columns (no WHOIS backend yet) per restyle-first.
Follow-up: give DNSZones + SSLCertificates pages the same top bar so the tab group persists.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 03:46:46 -04:00
Juan DenisandClaude Opus 4.8 94877ffeca docs: add redesign change map
Per-view change map for the UI redesign: thesis, modularity contract, Phase 0 foundation, shared primitives, per-page deltas, backend gap register, decisions (incl. infra pages adopt the demo's top-bar layout, not sidebar sub-menus), and phased sequence.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 03:35:41 -04:00
Juan DenisandClaude Opus 4.8 6f70eb9ad1 feat(workspaces): make python.py grant-aware — every app blueprint now covered (#33)
Re-reading python.py showed every mutating endpoint is independently
@admin_required (runs before the shared get_app_or_404 gate), so that gate only
governs the 4 read GETs (packages/env/status/gunicorn-config). Pointing it at
can_access_app safely opens those to grantees while the admin-only mutations stay
untouched — no per-method split needed.

With this, every app-related blueprint is grant-aware. Also fixes the int-vs-str
identity comparison in the helper.

Verified: a python read-gate test (stranger 403, grantee passes), full suite
132 passed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-05 23:08:44 -04:00