- Project / Environment hierarchy (C2): Workspace -> Project -> Environment
org model. New Project/Environment models + CRUD APIs, nullable
applications.project_id/environment_id (opt-in, no destructive backfill),
ProjectService.ensure_default, a Projects list + ProjectDetail (env tabs),
a project/environment selector in the create wizard, and sidebar nav.
- Polymorphic shared resources facade (C9): ResourceTag + SharedVariableGroup
/SharedVariable/attachment models keyed by (resource_type, resource_id),
reusing the env-var Fernet encryption; SharedResourceService resolves merged
variables (most-recent attachment wins) with secrets masked. /api/v1/shared
endpoints + reusable TagsPanel/EnvironmentVariablesPanel + a Shared Variables
management page. Existing per-resource tables untouched (facade first).
- PR preview environments (C3): ApplicationPreview + settings models, a
PreviewService with pure domain-templating + reconcile (open PRs vs active
previews) cores and best-effort provisioning (incl. guarded WordPress
file+DB clone), a pull_request webhook endpoint, preview.* jobs, and a
Previews tab on the app detail page.
- Per-server managed proxy stack (C6): opt-in Dockerized Traefik/Caddy as a
Compose stack (host Nginx stays default and superior for PHP/WordPress).
ProxyStack model + ProxyStackService with pure compose/config generators and
versioned config backups, /api/v1/servers/<id>/proxy endpoints, and a Proxy
tab on the server detail page.
Migrations 031-034 add the new tables/columns; the full chain applies cleanly
from an empty DB. Full backend suite: 871 passed, 14 skipped; frontend builds.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Build packs / zero-Dockerfile deploys (C1): new BuildpackService detects the
stack from a cloned repo (Node/Python/Go/PHP/Ruby/Rust/static), produces a
transparent build plan and a generated Dockerfile, and persists the plan +
overrides on the Application. POST /api/v1/buildpacks/{detect,generate}; a
BuildpackPreview shows the detected plan + editable overrides + Dockerfile in
the create wizard and the app Build tab. Builds on the existing build_method
plumbing instead of duplicating it.
- Deployment config snapshots + diff engine (C4): ConfigurationService captures
an immutable, hashed snapshot of resolved config (env keys + masked values,
domains, image/tag, build method, volumes) at every deploy and rollback; new
DeploymentSnapshot model + /api/v1/apps/<id>/snapshots[/diff|/restore]. A
DeploymentTimeline + ConfigDiffModal surface "what changed" and one-click
restore in the app Deploy tab. Secret values are never serialized.
- Template catalog certification (C5 cont.): a whole-catalog validation test
proves all 105 declarative templates conform to the documented schema; fixed
the stale index.json (was 63/105 entries) to list every bundled template.
Migration 030 adds the buildpack columns + deployment_snapshots table. Full
backend suite: 820 passed, 14 skipped; frontend builds clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Four foundational enhancements toward the deploy-code developer experience:
- Container status aggregator (C7): new ContainerStatusService applies a
deterministic priority hierarchy (degraded > restarting > running:unhealthy >
starting > running:healthy > exited > unknown) over an app's containers,
cached with a short TTL. Exposed at /api/v1/status and pushed over a new
container_status Socket.IO channel; Applications page now renders an
aggregated ContainerStatusPill with sub-status tooltip.
- API token scopes + sensitive data masking (C10): require_scope() decorator
enforces fine-grained scopes for API-key requests (pass-through for JWT),
a canonical SCOPES catalog at /api/v1/api-keys/scopes, and a reusable
sensitive_data_filter (mask_sensitive) honoring secrets:read. Frontend gains
a grouped ApiKeyScopesModal picker and per-key scope pills.
- Server onboarding state machine (C8): new onboarding lifecycle
(pending -> validating -> installing_prerequisites -> installing_docker ->
pairing_agent -> ready/failed) with ServerOnboardingService, a
server_onboarding_logs table, servers.onboarding_* columns, REST endpoints,
and an OnboardingWizard surfaced on the server detail overview.
- Declarative template catalog (C5): documented catalog schema and
magic-variable resolution (SERVICE_PASSWORD_/USER_/FQDN_/URL_/BASE64_*)
layered onto the existing YAML template loader without changing behavior for
templates that don't use them; GET /api/v1/templates/catalog/schema.
Migration 029 adds the onboarding table + columns. 60 new tests pass; frontend
builds clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Introduce a ServerKit-managed per-site Fail2ban layer to protect WordPress login endpoints. Adds Fail2banJailService (write-side) to generate filter and per-site jail configs, enable/disable jails, reload fail2ban, and unban IPs (Linux-only, best-effort, ownership-guarded). Wire it into WordPress service lifecycle (enable on site create, remove on delete) and WpSecurityService wrapper methods. Expose API endpoints for status, toggle and unban in backend API. Add unit tests for Fail2banJailService (config rendering, naming/safety, lifecycle, graceful degradation). Frontend: Security tab loads brute-force status, can toggle protection, shows ban counts and unban action; API client and styles updated. NginxService gains canonical per-site log/error path helpers. Docs updated to reflect shipped brute-force jail feature.
Makes the expanded catalog usable end-to-end:
- #24 Auto-domain: templates can opt in with 'auto_domain: true'; on install the app is published at <slug>.<base_domain> via the existing SiteDomainService (HTTPS only if the wildcard cert is already set up — never forces SSL; remote installs skipped).
- #25 Category consolidation: getCategoryIcon now covers the new tags (ai/llm/search/finance/documents/rss/vpn/dns/identity/observability/...).
- #26 Icon strategy: each template ships an inline lucide-style base64 icon with a keyed lucide fallback map; documented in Templates.jsx.
- #27 Update flow: update_app now normalizes list-format variables and re-renders the files: section + bind mounts, so converted/file-based templates (litellm, signoz, posthog) update correctly.
- #28 Community repo: TemplateService.build_repo_index()/export_repo_index() generate the index.json the sync mechanism already expects, exposed via GET /repos/index and POST /repos/index/export.
Roadmap: SERVICES_ROADMAP #24-#28.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds the AI/LLM one-click templates and wires the panel's own AI assistant to a self-hosted Prompture Hub:
- Templates: prompture-hub, ollama-webui, qdrant, chroma, litellm, flowise, langflow, anythingllm, librechat (brand-neutral, auto-port, generated secrets, lucide icons).
- AI provider: new 'prompture-hub' provider routed through Prompture's OpenAI-compatible cachibot driver (hub key + endpoint), so ServerKit AI can call a local hub instead of raw provider keys.
- Featured Prompture Hub + icon fallbacks in the Services UI.
Roadmap: SERVICES_ROADMAP #1-#8.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Introduce a full-screen AppLoader component and corresponding styles, and replace simple loading placeholders across App.jsx with the new loader. Revamp WordPressDetail: add comprehensive skeleton UI (topbar, repo pill, tab strip, KPI/panel/list/activity skeletons), generic PanelSkeleton and OverviewGridSkeleton utilities, and swap multiple tab-loading messages to use the new skeletons. Update layout and wordpress SCSS files to style the app-level loader and the WP detail skeletons. Improves perceived load UX and unifies loading states across the app.
The .ssl-status-* classes were borrowed "from _domains.scss", but that
borrow broke when Domains migrated to MetricCard - leaving the Certbot /
Certificates / Expiring Soon cards as an unstyled vertical stack at the
top-left. Define them locally in _ssl.scss as a 3-up grid of stat cards.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
/dns and /dynamic-dns duplicated the Domains portfolio + drawer and led
with a misleading "0 ServerKit records" view for Cloudflare zones.
Collapse DNS to one surface: both pages now redirect to /domains and
DomainDnsPanel becomes the full records manager.
- Per-record "Make dynamic" on A/AAAA rows (adopt-on-demand ->
createDdnsHost; one-time token via shared DdnsTokenCallout; Dynamic
badge + regenerate/stop)
- Export + Check propagation moved into the drawer; dead "Full DNS page"
button removed
- DOMAIN_TABS trimmed to Domains + SSL; /domains?open=<domain> deep-link
- Relocate shared .dns-rtype + .ddns-token-callout into _domains.scss
- Delete DNSZones.jsx, DynamicDns.jsx, _dns-zones.scss, _dynamic-dns.scss
Frontend-only; DDNS/portfolio backend untouched (20 tests green).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- /domains is one table: app-linked domains + live zones from connected DNS
providers (Cloudflare, ...) with provider badges. New GET /dns/portfolio
(aggregates provider zones, marks which are adopted) and idempotent
POST /dns/adopt (lazy-materialize a managed zone on demand).
- Every row opens one drawer = the unified DNS records surface
(components/domains/DomainDnsPanel): live Cloudflare records via
GET /dns/provider-records (no adopt needed), type badges, proxied=>HTTPS hint,
inline Add record, deep-links to Cloudflare ops / full DNS page. Fixed-layout
table (no horizontal scroll); actions moved into the drawer header.
- Registration expiry/registrar from Cloudflare Registrar + lazy RDAP fallback
(GET /dns/registration), persisted/cached in the new domain_registrations
table and merged into the portfolio so it survives a refresh. New
frontend/src/utils/expiry.js formatExpiry() date util.
- Connections moved to admin-only (Settings Admin group + isAdmin gate;
@admin_required on /api/v1/connections); CF connection card links to Domains.
- Cloudflare client: expose each zone's account_id; add list_registrar_domains.
- Tests: backend/tests/test_dns_portfolio.py.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a Storage tab listing and managing Cloudflare's account-scoped storage
primitives — R2 buckets, KV namespaces, and D1 databases. Management only
(list/create/delete); the object/key/row data planes are out of scope.
- CloudflareClient: r2/buckets, storage/kv/namespaces, d1/database list+create
+delete.
- CloudflareService: combined list_storage (each product fetched independently so
a missing token scope degrades to a per-product error, not a dead tab),
validated create/delete for each. R2 bucket names validated S3-style.
- /api/v1/cloudflare/zones/:id/storage* routes (read any user; writes admin).
- StoragePanel (three generic create/list/delete sections; R2 notes its
S3-compatibility for future backup targeting); api methods; SCSS.
No new tables — wiring an R2 bucket as an S3 backup backend (minting scoped R2
keys) is a deliberate follow-up, noted in the UI.
Tests: aggregate listing + per-product error degradation, R2 name validation,
create/delete, KV/D1 required-field guards (6).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a Tunnels tab for creating and managing Cloudflare Tunnels (cloudflared) —
exposing a local/private service through Cloudflare's edge with no public IP.
Clearly distinct from ServerKit's WireGuard remote-access tunnels (always
labelled "Cloudflare Tunnel" in the UI). Account-scoped; account resolved from
the zone.
- CloudflareTunnel model: records the connector token (encrypted at rest) and
owning connection per (account, tunnel_id).
- CloudflareClient: cfd_tunnel create (config_src=cloudflare) / list
(is_deleted=false) / delete / token / connections / configurations get+put.
- CloudflareService: create (returns + stores the one-time token, yields the
cloudflared install command), list (managed-flagged), delete, fetch install,
and public-hostname routing — sets the ingress config (always re-appends the
required http_status:404 catch-all) and best-effort upserts the proxied CNAME
hostname→<id>.cfargotunnel.com via the shared ownership-guarded DNS path.
- /api/v1/cloudflare/zones/:id/tunnels* routes (read any user; writes admin).
- TunnelsPanel (create form + one-time install box w/ copy, tunnel list with
per-tunnel install reveal and public-hostname add/remove); api methods; SCSS.
Tests: list is_deleted param, create config_src, name required, token stored
encrypted + install string, managed-flagging, delete prunes local row, ingress
built with catch-all last (7).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a Workers tab to the Cloudflare Zone Settings page for deploying edge
scripts and managing Worker routes. Workers are account-scoped, so the owning
account is resolved from the zone — the whole feature reuses the existing
Cloudflare DNS connection (no new credential).
- CloudflareWorker model: records the source ServerKit uploaded per (account,
name) so a worker can be tracked and re-deployed (created via create_all).
- CloudflareClient: zone→account lookup, list/delete scripts, and a stable
multipart PUT /scripts/{name} module upload (the still-beta resource-oriented
Workers API is deliberately avoided per the roadmap's fallback note), plus
zone Worker routes list/add/delete.
- CloudflareService: list (live scripts flagged when ServerKit-managed + routes),
deploy (validated lowercase name, records source, optional route attach),
delete, route add/delete.
- /api/v1/cloudflare/zones/:id/workers* routes (read any user; writes admin).
- WorkersPanel component (deploy form with a starter script, worker list, routes
list); api methods; SCSS.
Tests: multipart upload shape, name validation, deploy records source, list
managed-flagging, delete prunes the local row (5).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a WAF tab to the Cloudflare Zone Settings page for managing custom firewall
rules (the http_request_firewall_custom phase entry-point ruleset).
- CloudflareClient: ruleset list/get + phase-entrypoint create (PUT) + per-rule
POST/PATCH/DELETE.
- CloudflareService: list rules (empty when no custom ruleset yet, distinguished
from an auth error by listing rulesets first), add rule (creates the ruleset on
first use), update/delete, and three one-click presets — lock WordPress admin
to an IP, block common exploit paths, challenge suspicious bots. The IP in the
lock preset is validated with ipaddress so it can't inject expression syntax;
rule actions are constrained to a safe terminal subset.
- /api/v1/cloudflare/zones/:id/waf/* routes (list any user; add/preset/update/
delete admin).
- WafPanel component (preset picker + custom-rule form + active-rules list with
enable toggle and delete); api methods; SCSS.
Tests: ruleset discovery, empty vs auth-error, create-on-first-rule vs append,
action validation, preset IP validation + safe expression, update/delete (10).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add Cloudflare cache management to the Zone Settings page. Purge everything (with
a confirm) or purge specific URLs (one per line, capped at 30 to match Free/Pro
limits); hosts/prefixes/tags are accepted by the API for Enterprise plans and
their plan errors are surfaced verbatim.
- CloudflareClient.purge_cache(zone_id, payload).
- CloudflareService.purge_cache: validates intent (raises on empty), caps files
at 30, normalizes the payload.
- POST /api/v1/cloudflare/zones/:id/purge-cache (admin).
- Actions tab gains a "Purge cache" card (purge-everything button + URL list);
api method; SCSS.
Tests: client payload, purge-everything, 30-file cap, empty-raises, provider
error surfaced (5 tests).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a Cloudflare Zone Settings surface on top of the existing Cloudflare DNS
connection — SSL/TLS, Speed, Caching and Security toggles plus a one-click
"recommended hardening" preset. Reuses the connected DNSProviderConfig
credential and shared CloudflareClient, so auth/encryption-at-rest are not
re-implemented.
Backend:
- CloudflareClient: generic v4 request() helper + zone-settings get/update
(envelope normalized to {success, error?, result?}).
- CloudflareService: resolve zone+credential by ServerKit DNS zone id, curated
setting groups (UI metadata), get/update setting, apply recommended preset
with per-setting reporting.
- /api/v1/cloudflare blueprint: settings GET / GET-one / PATCH + apply-preset
(reads any user, writes admin; mutations captured by the audit fallback).
Frontend:
- CloudflareZoneSettings page (tabbed SSL/TLS · Speed · Caching · Security ·
Actions) with declarative toggle/select/HSTS controls and optimistic save.
- Route /cloudflare/zones/:zoneId, "Cloudflare" link from Cloudflare-managed
DNS zones, api module, SCSS.
Tests: client envelope/zone-settings + service resolution guards, indexing,
and preset reporting (11 tests).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The "common misconfiguration" notices stacked every hint as a banner at the top of
the dashboard while the notification bell sat empty. Now:
- SystemNotices (top banner) shows only urgent notices (error/critical, or an
explicit urgent flag), at most one — the dashboard stays clear.
- The live notices fold into the notification center (bell + /notifications) via the
shared NotificationsContext: they show with their Fix action, bump the unread
badge, and can be dismissed. Dismissal syncs across both surfaces (shared
localStorage).
New util utils/dismissedNotices.js (shared dismiss state + notice->item mapper).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Surfaces sites_dns_mode in the Managed Sites Domain & HTTPS card: a select to choose
Wildcard (point *.base once, every site instant) or Per-site (each new site gets its
own auto-created, ownership-tracked A record). Reads dns_mode from the sites-https
status and saves via updateSystemSetting.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ToastContext wrapped the 2nd arg as { duration }, so the ~30 call sites that pass a
full sonner options object (toast.info(msg, { duration: 4000 })) got it
double-wrapped ({ duration: { duration } }) and their custom durations were silently
dropped. Normalize at the wrapper: a number becomes { duration }, an object passes
through. Backward-compatible with the few numeric callers.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Replace rsync with tar-based copy to avoid missing rsync on Windows/Git Bash
- Exclude backend/venv, backend/.venv, backend/.venv-wsl, and broken WSL symlinks
- Exclude ./backups, backend/dev-data/backups, backend/instance/backups, and scripts/test/output
- Exclude scratch images (PNG/JPEG/JPG), logs, and tmp files from the tarball
- Pack with tar --transform so /opt/serverkit prefix is created without cross-device moves
- Reduce release tarball from 13 GB back to ~6 MB
#7 Surface the Phase 2 backend:
- DnsActivity: a per-connection "Recent changes" feed (action, record, result
badge, error) from GET /dns/changes, expandable on each DNS connection in
Settings -> Connections.
- DNS Zones page: a "ServerKit records / In your provider" toggle; the provider
view (GET /dns/<id>/mirror) lists every live record badged ServerKit vs External,
external records read-only/muted ("ServerKit never modifies external records").
- api: getDnsChanges() + getZoneMirror().
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Phase 0 of the DNS roadmap.
#2 Shared Cloudflare client (app/services/dns/): DnsCredential + DnsRecordSpec +
CloudflareClient. Both DNSProviderService and DNSZoneService delegate to it, so
auth (scoped token / global key), the CAA structured-data wire format, MX/SRV
priority parsing, and idempotent upsert live in one place. Fixes dup-on-recreate,
silent update/delete no-op, and missing proxied/priority on the provider path.
#1 Single credential source: new FK dns_zones.dns_provider_config_id (migration
025) + DNSZoneService._resolve_credential (linked -> auto-discover-by-domain ->
legacy inline-token). create_zone accepts a connection id; link_legacy_zones()
converges inline tokens onto encrypted connections at boot. The /dns create-zone
modal now picks a connection instead of pasting a token. DDNS rides it for free.
Tests: test_dns_cloudflare_client.py, test_dns_credential_resolution.py.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Add GET /api/v1/system/notices endpoint (admin-only)
- Detect missing canonical domain, sites base domain, server public IP, and wildcard HTTPS
- Add SystemNotices React component with dismiss persistence in localStorage
- Render notices inside DashboardLayout so they appear on every page
- Add scoped SCSS for warning/info/error notice banners
Make email handling case-insensitive (queries + model normalization) to prevent duplicate accounts. Add SiteDomainService.panel_origin() helper and use it across services to build public URLs (Gitea, WordPress, Roundcube) and Django ALLOWED_HOSTS. Add optional custom-domain support when creating WordPress sites and propagate domain to Roundcube install. Improve environment domain fallback to prefer panel/base domains over .localhost. Small Dockerfile healthcheck tweak (use 127.0.0.1). Update install/update/build scripts to always (re)create virtualenv locally for release installs. Remove unused frontend CreateSiteModal and expose domain field in WordPress page.
Telemetry: ensure sanitized payloads bigger than the 32KiB threshold are replaced with a {_truncated: True} marker before storage, and add a unit test covering that behavior.
Backend API: adjust imports in secrets_webhooks to use WorkspaceService and include User model.
Frontend: make Sidebar resilient to malformed active_workspace JSON in localStorage (try/catch), and ensure socketService.disconnect() is called when user becomes unauthenticated in NotificationsProvider to clear notifications and unread count.
Files: backend/services/telemetry_service.py, backend/tests/test_telemetry.py, backend/app/api/secrets_webhooks.py, frontend/src/components/Sidebar.jsx, frontend/src/contexts/NotificationsContext.jsx
Rename service settings 'repository' → 'git' (routes, labels, and links) and update App route comment. Reorder sidebar footer so user mini and notification bell share a single row and add accessible user-mini button. Adjust notification panel positioning, add show animation, and responsive layout tweaks. Replace Workspaces card grid with a dense table view: add search, status segment control, bulk select checkboxes, and selection state. Minor SCSS tweaks for sidebar button sizing and WordPress action width. These changes improve discoverability and bulk management UX.
- Rename service Environment tab to Env Vars and settings Environment to Environment Type
- Add Domain & SSL section to service settings with attach domain + Let's Encrypt flow
- Surface primary domain from domains array in service subtitle
- Add prominent repo connection pill to WordPress detail header
- Apply WordPress list layout to Services page (drop KPIs, tabs+search toolbar)
Add /jobs — the single pane to observe and manage all background work, built
on the /api/v1/jobs API. Mirrors the DeliveryLog/Telemetry admin pages.
- Jobs table polls every 5s: status Pills (succeeded/running/pending/failed/
cancelled), kind, owner, attempts, age. Status/kind filters. Per-row Cancel
(pending/running) and Retry (failed/cancelled) with toast feedback. Row
expands to a detail view (payload/result/error/correlation/timing) via getJob.
- Scheduled-jobs table: name, kind, schedule (cron or "every Nh"), next/last run,
with Run-now and Pause/Enable.
- Stat cards (total/running/pending/succeeded/failed) from /jobs/stats.
- Wiring: services/api/jobs.js module bound into ApiService; route + page title
in App.jsx; "Jobs" item in the sidebar "system" group; _jobs.scss imported
in main.scss. Admin-gated client-side via useAuth().isAdmin.
Lint clean (0 errors) and production build passes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add SystemEvent model, TelemetryService, /api/v1 telemetry endpoints, and
a /telemetry UI page. Instrument audit, notification, webhook, backup,
deployment, and monitoring paths. Add correlation_id to Notification,
EventDelivery, and DeploymentJob so related events can be grouped.
- 18 backend tests added and passing
- Frontend production build clean
- NotificationsContext + header bell/dropdown (sidebar + mobile top bar)
with a live unread badge over Socket.IO and a /notifications history page.
- Admin /admin/notifications delivery log (stat cards, status/channel
filters, retry) and an email-provider management UI (add/test/
set-default/delete) for SMTP/SendGrid/Postmark/SES/Mailgun.
- api/notifications.js methods, socket 'notification' forwarding, a timeAgo
util, and themed sk-notif* / sk-dlog / sk-eprov styles.
Builds clean (vite) and lints clean (eslint exit 0).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Enforce max_servers quota on server creation and workspace moves
- Add my_role/my_effective_role to single workspace GET response
- Add Navigation Permissions editor to Workspace Settings
- Store full active workspace object for nav/brand settings
- Update workspace API and service for workspace context handling
- Refine Sidebar and WorkspaceSwitcher components
- Update sidebar navigation items for workspace-aware routing
- Enhance SecretsWebhooks page and API client integration
Sidebar — leaner default + setup wiring:
- Add a "Recommended" preset (Full minus Marketplace, Queue Bus, and
Secrets & Webhooks) and make it the default for unconfigured users, so
fresh installs aren't maximalist. The three demoted items stay fully
routable and one click away via the "Full" view or Customize Sidebar.
- Factor the three into a shared ADVANCED_ITEM_IDS list so every curated
preset hides them consistently.
- Wire the Setup wizard's use-case answer to an initial preset
(presetForUseCases): a single WordPress intent -> Web Hosting, a single
DevOps intent -> Docker/DevOps, anything mixed/general -> Recommended.
Settings — split the 17-item rail:
- Add a two-way "My Account" / "Admin" segmented switch (admin-only) so
personal preferences aren't interleaved with server-wide system
controls. The active group is derived from the tab, so deep links
(e.g. /settings/users) open the right group. About moves into My
Account; the dev/local-dev tools consolidate under Admin.
Verified: eslint (0 errors) and vite build both pass.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Expose Remote Access as a per-server tab and adapt the fleet page for server-scoped views. RemoteAccess now accepts serverId, filters/labels tunnels involving that server, renders inline action button when embedded, disables private-server select for scoped views, and links nodes to per-server Remote Access pages from fleet view. Added tab to ServerDetail when wireguard is supported and updated styles for responsive intro, node links, and a “this server” tag. Small refactors: useMemo/useCallback optimizations and cleaner topbar action handling.
- TargetPicker now renders a static label when only one target option is available,
removing the pointless/disabled dropdown on single-server setups.
- Change Terminal tab route from /terminal/terminal to /terminal/shell and add
a redirect for the old URL.
- Terminal shell tab only lists agents advertising terminal capability, preventing
'Agent not connected' errors on local/WSL dev installs.
- Add per-server creation guard in RemoteTerminal to avoid duplicate session
attempts on remounts or rapid server switches.
Add a POST /security/clamav/start endpoint and SecurityService.start_clamav() to enable+start the ClamAV daemon (tries clamav-daemon and clamd). Add unit tests that mock ServiceControl to verify success/failure and auth. Wire a startClamAV API call into the frontend and extend the Security Overview: richer posture UI, one-click fixes (install, start service, init integrity, update defs), KPIs, icons, and accessibility improvements. Update related styles and file-manager minor styles. This improves operator workflows by allowing a one-click "Start service" posture fix and provides immediate UI feedback when fixes run.
- Standardize toast usage in AgentFleet and FleetMonitor (toast.success/error/info)
- Replace inline full-page spinners with PageLoader in CloudProvision, DNSZones, DynamicDns, Marketplace, ServerTemplates, StatusPages
- Migrate Servers and Domains tables to DataTable
- Migrate DynamicDns create form to FormField
- Adopt SearchFilterBar in Applications
- Adopt CopyButton in DynamicDns token callout
- Replace raw buttons with Button in Servers, Terminal, Docker
- Move Domains error-banner close button inline styles to SCSS class
- New ErrorState, SearchFilterBar, CopyButton utility components
- Add ESLint rules warning on inline styles, raw <button>, and legacy .card divs
- New useOverflowItems hook for tab overflow menus
- Refactor ui/tabs and ds/PageTopbar to share overflow logic
- New PageLoader component for full-page loading states
- New DataTable component with sorting, empty/loading states, custom rows
- New ResourceCard / ResourceList for servers/apps/domains/containers
- New FormField / FormRow wrappers for consistent forms
- New IconButton wrapper for accessible icon-only actions
- Add supporting styles in _design-system.scss and _forms.scss
Bring the Service detail page to parity with the WordPress detail page:
- Routes: add services/:id/:tab and services/:id/:tab/:section so the active
tab and the Settings sub-section live in the URL (shareable, survive refresh).
ServiceDetail drives its tab via useTabParam; SettingsTab reads/writes the
section via useParams + navigate.
- Repository: the Settings -> Repository section now renders the shared
RepoConnectForm (the same component the WordPress Git settings use) instead
of the bespoke GitConnectModal — provider picker (GitHub/GitLab/Bitbucket/
Gitea) with URL fallback and a connected summary + Disconnect. Existing
deploy scripts are preserved on connect. The top 'connect a repository' pill
now navigates to .../settings/repository, and GitConnectModal is removed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A flex <button> keeps its shrink-to-fit intrinsic width, so each settings
nav item's box was sized to its label (e.g. the active 'Git' box only as wide
as the word) instead of the 200px column. Add width:100% (box-sizing is
border-box) so every item's box is the same full width, text still left-aligned.
Applies to both the WordPress (.wp-settings__navitem) and Service
(.svc-settings__navitem) detail settings.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds source-connection support for browsing repositories on GitLab, Gitea, and
Bitbucket (new *RepoPicker components) plus a PathSelector, wired through
RepoConnectForm / GitProviders / ConnectionsHub / providerCatalog and the
sourceConnections + files APIs, backed by additions to the source_connections
API and service.
Bundled from in-progress work in a concurrent session at the user's request.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>