Commit Graph
607 Commits
Author SHA1 Message Date
Juan DenisandClaude Opus 4.8 7d22c2cbcf feat(ui): move env var creation into a modal, always store as secret
Adding a variable happened through an inline row that competed with the list
for attention; it now opens a dedicated modal that resets its own state on open.

Drops the is_secret toggle. Env vars are stored as secrets unconditionally, so
offering a choice implied a public mode that does not exist.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 01:16:39 -04:00
Juan DenisandClaude Opus 4.8 bc3711f32d fix(ui): stop detail-page tabs jumping, unify them as segmented bars
PageTopbar measured overflow against a region that grew and shrank with the
page title, so a title change shoved the tabs sideways on first paint. The nav
is now the flex-fill measurement region, right-aligned so the title can resize
without moving the tabs, with overflow hidden to suppress the pre-measure flash.

The tab strips themselves were a transparent underline that dissolved into the
page background. Service detail, applications and WordPress detail now share one
treatment — pills inside a bordered surface, active pill filled — so every
detail page's nav reads the same. Service detail also gets a consistent vertical
rhythm between the KPI band, the info grid and the full-width panels, fixing the
KPI strip being glued to the section below it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 01:16:39 -04:00
Juan DenisandClaude Opus 4.8 9639288494 refactor(extensions): extract seven builtins to standalone repos
Analytics, CrowdSec, DNS Server, GPU Monitor, Kubernetes, Mail Server and
Automations (tramo) now live in their own repositories and install from the
registry as versioned, sha256-pinned zips. Their sources leave
builtin-extensions/, their pre-bundled frontends leave frontend/src/plugins/,
and registry_index.json gains a `bundled: false` entry for each so the
Marketplace offers them as normal registry installs.

The panel keeps only the wiring: comments at the former mount points explain
where each surface now comes from, and the gpu auto-install gate in
extension_migration.py is dropped since a host-probe can no longer decide to
re-vendor an extension that is not in this tree.

Also untracks backend/app/plugins/serverkit-{gpu,k8s,tramo}/, which were the
panel's own install artifacts rather than source. Committing those shipped a
second copy of code that now lives in three other repos, guaranteed to drift.
.gitignore now excludes the install path for every extracted slug on both the
backend and frontend side, so a local install can never be committed back.

Four tests from test_tramo_extension.py exercise panel core rather than the
extension (the retired /api/v1/workflows blueprint, the plan-45 event emitters
ported to EventService, and the docker_service double-emit fix); they move to
test_tramo_panel_core.py and stay here. The rest of the extension suites go
with their repos.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 01:16:16 -04:00
Juan DenisandClaude Opus 4.8 8a9aa03d8f chore(frontend): drop unused eslint-disable in capture-skeletons script
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 19:23:59 -04:00
Juan DenisandClaude Opus 4.8 3333ede87b feat(frontend): runtime self-captured skeleton bones (plan 50 phase 3)
Add useSelfCapturedBones(key, {ready}): after a page's real content renders it
snapshots the region into bones (shared src/utils/snapshotBones.js) and caches
them in localStorage under sk-bones:<key>, versioned so stale layouts are purged;
the next load replays a pixel-accurate skeleton. First-ever visit has no cache and
falls back to the composed skeleton — no regression.

SkeletonBoundary now forwards its ref to the measured container, and the SSL page
is wired as the demonstration. The capture script (phase 2) is DRY'd to inject the
same shared snapshotBones source rather than an inline copy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 19:20:19 -04:00
Juan DenisandClaude Opus 4.8 67142efd13 feat(frontend): dev-time skeleton bone capture + bones replay (plan 50 phase 2)
Add an accuracy path for skeletons: a dev-only `npm run capture:skeletons` tool
(scripts/capture-skeletons.mjs) drives a headless, logged-in dev session, measures
designated page regions into boneyard-format bones ({x,y,w,h,r}; x/w %, y/h px),
and bakes them to frontend/src/skeletons/*.json as static assets. Nothing browser-
or capture-related ships in the product.

SkeletonBoundary gains an optional `bones` prop that replays a captured layout via
a new `renderBones` mapper — SCSS-classed absolutely-positioned placeholders reusing
the shared .skeleton look (shimmer + reduced-motion). boneyard-js is added as a
devDependency for parity/format reference (never imported by shipped code). Ships a
README runbook and an example.json documenting the format.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 19:15:46 -04:00
Juan DenisandClaude Opus 4.8 c687ea4dc4 feat(frontend): consolidate skeletons + overlay SkeletonBoundary (plan 50 phase 1)
Deprecate the Tailwind `ui/skeleton` shim (deleted; its two call sites moved to
the SCSS `Skeleton` primitive with no raw Tailwind sizing classes) and add
`SkeletonBoundary`, which keeps real content in flow and paints the skeleton over
its exact box while loading — inheriting the real dimensions so there is no
cumulative layout shift when data arrives. Falls back to intrinsic size when the
data-gated children are still absent (first load), overlays once content is
present (refresh).

Migrated the worst hand-guessed loaders through the boundary: SSL certificates,
WordPress list + detail page, the WordPress activity feed, and all 11 WordPress
detail tabs. Skeleton shimmer now honors prefers-reduced-motion across the legacy
wp-* skeleton families too (specificity-proof override), and loading regions set
aria-busy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 19:11:55 -04:00
Juan DenisandClaude Opus 4.8 d6e8c737dc feat(analytics): WordPress + nginx tracker wiring (Phase 5)
- wp_integration.py: writes a ServerKit Analytics mu-plugin into a managed WP
  site. Docker sites (the default template uses a named volume, not a bind mount)
  get the file via `docker exec tee` into /var/www/html/wp-content/mu-plugins;
  bare-metal sites via a host write + www-data chown. Idempotent, remove-on-
  disable, feature-detects serverkit-wordpress, remove_all_injections on
  uninstall.
- nginx_integration.py: injects an idempotent, marker-guarded sub_filter block
  (rewrites </body>) into a managed app's vhost, following the WAF include-
  injection precedent. Validates with nginx -t and REVERTS the file if the test
  fails (never leaves nginx broken); reloads (never restarts).
- analytics.py: admin POST/DELETE /sites/<id>/inject/{wordpress,nginx}.
- AnalyticsTab.jsx: feature-detects the extension and shows a banner linking to
  the persistent dashboard, keeping the on-demand log view as a complement.
- 18 new tests (mu-plugin PHP, WP inject/idempotent/docker-exec/remove/
  remove-all, wordpress_available, nginx block insert/strip/idempotent/revert-
  on-invalid/remove, admin-gated endpoints). 71 total. lint + build green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 02:38:39 -04:00
Juan DenisandClaude Opus 4.8 0def464703 feat(analytics): dashboard frontend, six tabs (Phase 4)
- AnalyticsPage: route-driven tab shell (Overview/Pages/Referrers/Devices/
  Realtime/Sites), loads the site list, topbar site picker for report tabs,
  zero-sites empty state.
- OverviewTab: KPI tiles + recharts area chart (visitors+pageviews) + top
  pages/referrers DataTables + live 30-min counter.
- PagesTab / ReferrersTab: range picker + sorted DataTable.
- DevicesTab: device/browser/OS/country tables in a responsive grid.
- RealtimeTab: active-visitor + pageview counters, minutes selector, recent-hit
  table, 15s polling.
- SitesTab: site CRUD (Modal add form, ConfirmDialog rotate/delete, enable
  toggle) + tracking-snippet modal (copy + outbound-link opt-in).
- RangePicker + format utils; isolated analytics.scss (CSS custom props +
  neutral rgba, namespaced under .analytics-page).
- Synced into frontend/src/plugins/serverkit-analytics. npm run lint (0 errors)
  and npm run build both green; sync --check clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 02:25:41 -04:00
Juan DenisandClaude Opus 4.8 cc3626267a feat(analytics): scaffold serverkit-analytics extension (Phase 0)
Native, privacy-first web-analytics builtin extension. Phase 0 lands the
manifest, backend skeleton, and pre-bundled frontend placeholder:

- plugin.json: entry_point analytics:analytics_bp, /api/v1/analytics prefix,
  models/jobs/schedules/config_schema/lifecycle, operations-nav contribution,
  permissions db/network/filesystem.
- backend: analytics_bp (public /ping), config accessor (DEFAULTS + get_cfg),
  lifecycle (seed config defaults, upgrade backfill, injection cleanup on
  uninstall), job handler stubs (rollup/retention_prune/log_tail) fleshed out
  in later phases.
- frontend: AnalyticsPage placeholder, synced into frontend/src/plugins.
- tests: 14 proving tests (manifest validates, refs resolve, config accessor,
  lifecycle seeding, ping route).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 01:46:15 -04:00
Juan Denis 5fd04bbfb0 Add review rules and proxy/auth docs
Introduces a new `.coderabbit.yaml` with ServerKit-specific review guardrails, and expands env/security documentation for trusted proxy headers and per-IP login brute-force throttling. It also adds inline backend notes clarifying `request.remote_addr` behavior under ProxyFix for auth, cron, and pairing paths, and updates the frontend plugin manifest to remove the Tramo and K8s plugin entries.
2026-07-18 01:23:47 -04:00
Juan Denis 7688db181c Refresh ServerKit logo SVG
Replace the old detailed logo SVG with the simplified 32x32 brand mark used elsewhere in the UI. The new asset uses the current accent gradient and white server glyph so the logo stays consistent across the sidebar, auth flows, setup screens, and favicon.
2026-07-15 22:34:43 -04:00
Juan Denis b98265a3d7 Move PageTitleUpdater inside AuthProvider
PageTitleUpdater reads branding values (panelTitle/publicTitle) via useAuth, so it must be rendered as a descendant of AuthProvider. Previously it was placed above the provider tree, causing it to run without auth context.
2026-07-15 22:05:19 -04:00
Juan DenisandClaude Opus 4.8 dc1d8eeda1 fix(icons): replace removed SiSlack with FaSlack (react-icons dropped Slack)
Slack pulled its brand from Simple Icons, so react-icons removed SiSlack (only
SiSlackware, the Linux distro, remains). Under vite 8 / rolldown this is a hard
build error (MISSING_EXPORT) rather than a silent no-op. Swap the Connections-hub
Slack glyph to Font Awesome's stable FaSlack (same react-icons/fa pattern already
used for route53's FaAws). Verified with a vite 8 production build.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 20:20:41 -04:00
Juan Denis dcf05d1cc1 Merge vite 8 upgrade (vite 5→8 + rolldown manualChunks, @vitejs/plugin-react 4→6) 2026-07-15 20:03:01 -04:00
Juan Denis b7911026be Merge plan-47 lean-core (wizard installs + 4 backend extractions + security tab-group) 2026-07-15 15:24:07 -04:00
Juan DenisandClaude Opus 4.8 8ee33ef2c6 fix(favicon): make the per-install tint actually cover the fingerprint URLs
The install-time tint only recolored favicon.svg, but the page also shipped a
fixed raster favicon.ico + .png — and favicon-hash scanners hash /favicon.ico,
which was identical across every install. So the "car-paint" tint didn't change
what those tools cluster on.

- index.html links only the SVG now (drop the raster <link>s).
- Remove the unused favicon-16x16/32x32.png.
- install.sh: after tinting favicon.svg, copy it over favicon.ico too, so a
  blind /favicon.ico fetch also returns the per-install color instead of a fixed
  icon. (SVG bytes under an .ico name — browsers use the linked SVG; this only
  affects what a scanner hashes at that URL.)

Still obscurity/defense-in-depth, not a real gate — but now it does what it says.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 14:04:00 -04:00
Juan DenisandClaude Opus 4.8 3954680c87 fix: apply login layout to the main login form + bind BYO-image legacy port to loopback
Two issues from PR review:

- Login: the AuthLayout wrapper only reached the 2FA and login-link states —
  the primary login form still had a hardcoded auth-container/auth-card, so the
  `login_layout` setting didn't affect the page most users see. Wrap the main
  form in AuthLayout too.
- apps.py: the legacy scalar-port fallback for a BYO-image app omitted `expose`,
  which AppPortService defaults to `public` (binds 0.0.0.0), publishing the port
  past nginx. Default it to `local` (loopback). (Workers set no port, so this
  was latent, but the generic path is now safe.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 13:55:15 -04:00
Juan DenisandClaude Opus 4.8 be0882ca24 chore(frontend): reconcile package-lock with package.json
The lockfile had drifted from package.json (which is why `tramo` — a declared
dep — was never installed and `vite build` failed to resolve `tramo/react`).
`npm install` reconciled node_modules; commit the resulting lockfile so `npm ci`
installs are reproducible and the frontend builds cleanly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 13:17:25 -04:00
Juan DenisandClaude Opus 4.8 882b7bb8e2 feat: brand-neutral public title + per-install favicon tint
Reduce how identifiable a panel is from its unauthenticated surface, without
losing the brand once you're inside.

- Public vs interior title: new `public_title` setting (default "Control
  Panel") drives the sign-in/register/setup headings and the browser tab
  before login (and the static first-paint <title>); `panel_title`
  (ServerKit) still names the tab once signed in. PageTitleUpdater picks by
  route. Both are exposed pre-auth on /auth/setup-status and editable in the
  admin Site Settings → Panel Appearance card.
- Per-install favicon tint (install.sh randomize_favicon): the favicon is a
  plain-text SVG, so each install recolors the tile to one of a curated set of
  legible colors via a string replace — same mark, different color, so a
  favicon-hash comparison across installs differs. No image tooling required;
  if the step can't run, the shipped default color stays. Runs on both fresh
  and release installs; existing installs are untouched.

Defense-in-depth / obscurity only — real protection is access control + auth.
Verified: eslint 0 errors, backend py_compile ok, install.sh `bash -n` ok,
favicon sed recolor confirmed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 13:12:23 -04:00
Juan DenisandClaude Opus 4.8 9a1a1f69a3 feat: configurable panel title + switchable login layouts
Make the panel's identity admin-configurable instead of hardcoded, and let
operators pick a login-page layout — so instances can be branded/varied from
the panel rather than by editing source.

- Settings: new `panel_title` (default "ServerKit") and `login_layout`
  (default "centered") keys via SettingsService (key/value — no migration).
- Exposed pre-auth on the existing GET /auth/setup-status so the login page
  reads them before any authenticated call.
- Frontend threads panelTitle + loginLayout through AuthContext; document.title
  (App.jsx PageTitleUpdater + usePageTitle) and the auth-page headings use the
  configured title, with import.meta.env.VITE_PANEL_TITLE as the build-time
  default for the first paint.
- Login page refactored to an AuthLayout selector with three variants —
  Centered (current), Split-hero, Minimal — chosen by `login_layout`.
- Admin UI: a "Panel Appearance" card (title field + layout dropdown) in the
  admin-only Site Settings tab; saves via the generic settings API and calls
  refreshSetupStatus so the running SPA updates live. Searchable via a new
  settingsIndex entry.

Register/Setup adopt the configured title on their headings. manifest.json
stays static (documented limitation). Verified: eslint clean on all touched
files (0 errors), settings-index guard passes. (The repo's full `vite build`
currently fails on an unrelated pre-existing plugin import, serverkit-tramo →
`tramo/react`, independent of this change.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 13:01:39 -04:00
Juan DenisandClaude Opus 4.8 70027b9fea Revert panel de-branding — keep the ServerKit identity, keep only noindex
Restore the ServerKit name, favicon, manifest, auth-page headings, and
service-worker strings. Genericizing the panel's identity was the wrong
trade: hiding the title/favicon is security-by-obscurity that a real scanner
sees through, and it erased the product identity for almost no benefit. The
pages it touched (login/setup/register) require server or admin access to
reach anyway, so there is nothing to hide there.

The one durable, no-cost measure is kept: the `X-Robots-Tag: noindex`
header (from the prior commit) stays on both nginx server blocks so the
panel isn't indexed by search engines. Real protection lives in access
control + auth, not in the favicon.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 12:40:21 -04:00
Juan DenisandClaude Opus 4.8 7c38e44ba0 chore: drop orphaned old favicon assets under src/assets/favicon
These raster favicons (favicon.ico/.png) were unreferenced dead files — the
app only ever served /favicon.* from public/. Removing them completes the
favicon cleanup from the de-fingerprint change; the served icon is the
generic public/favicon.svg.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 12:36:29 -04:00
Juan DenisandClaude Opus 4.8 70d445da32 harden: de-fingerprint the panel + keep it out of search indexes
Reduce how easily the panel can be enumerated by scanners (Shodan/Censys)
and search engines:

- nginx: emit `X-Robots-Tag: noindex, nofollow, noarchive` on the panel
  server blocks (both the TLS and insecure fallback configs).
- Generic page identity: title "Control Panel" + neutral meta description
  in index.html; the runtime document.title setters (App.jsx, usePageTitle)
  and PWA manifest name/short_name match — so the browser tab and manifest
  no longer say "ServerKit".
- Drop the distinctive favicon (.ico/.png removed, generic favicon.svg) so a
  favicon-hash no longer clusters instances; strip the product name from the
  service worker (cache name, push title, header comment).
- Neutralize the visible auth/splash copy (Login/Register/Setup headings,
  AppLoader label) to a generic name.

Search engines are the easy case; the header is defense-in-depth. Real
hiding still requires access control (IP allowlist / VPN / zero-trust) and
is deployment-specific.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-15 12:32:15 -04:00
Juan DenisandClaude Opus 4.8 176141295f docs(plan47): sync extension manifests, raise ratchet, changelog + docs
Plan 47 Phase 6 (for shipped work). Sync the pre-bundled frontend plugin.json
copies for the four extracted builtins (entry_point/url_prefix + status
description) so builtin-extensions/ and frontend/src/plugins/ agree
(sync-builtin-frontends --check + test_extension_manifests green). Raise the
test-count ratchet 2588 -> 2623. Document the second-speed backend-extraction
recipe in EXTENSIONS.md, add CHANGELOG entries (wizard installs + opt-in
FTP/cloud/remote-access/status), and a README "lean by default" bullet.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 23:05:06 -04:00
Juan DenisandClaude Opus 4.8 a5e19ce6bf feat(security): open the Security tab group to extension contributions
Plan 47 Phase 3a. SECURITY_TABS' TabGroupLayout now passes groupId="security"
and mounts {groupRoutes.security}, so extracted security features (fail2ban,
clamav, …) can contribute tabs + group-nested routes via their manifests and
return as tabs in the exact place users know — the same mechanism the
files/servers/monitoring groups already use. Inert until a security extension
ships; no behavior change on its own.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 22:38:35 -04:00
Juan DenisandClaude Opus 4.8 f58bdb6823 feat(setup): wizard installs recommended extensions; lean-by-default flagships
Plan 47 Phase 1. The onboarding "Recommended for you" step now installs real
extensions matched to the selected use cases instead of rendering decorative
strings:

- Backend use-case -> slug map + resolver (RECOMMENDED_EXTENSIONS_BY_USE_CASE,
  recommend_extensions_for_use_cases) single-sourced and test-guarded against
  dead slugs; exposed via GET /api/v1/plugins/recommendations.
- SetupStepSummary renders default-checked install checkboxes with each
  extension's real display_name/description and installs source-aware on Finish
  (per-item progress, fail-soft: an install failure never blocks setup).
- complete-onboarding persists onboarding_installed_extensions.
- WordPress flagship seeding is now gated on needs_setup(): fresh installs skip
  it (the wizard installs it when the WordPress use case is picked); completed
  -setup/upgraded installs keep it (zero regression). TESTING bypass keeps the
  WordPress suite green; the real gate is covered by dedicated proving tests.

Proving tests: tests/test_onboarding_recommendations.py (12).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 22:11:59 -04:00
Juan DenisandClaude Opus 4.8 c7d81db64e build(deps-dev): upgrade vite 5→8 and @vitejs/plugin-react 4→6
Applies dependabot #68 (resolves the high-severity dev-server vite
advisories) and fixes the release-build break it caused.

Vite 8 bundles with rolldown, which only accepts the FUNCTION form of
`build.rollupOptions.output.manualChunks`. The existing object form
threw "manualChunks is not a function" and failed the Build release
tarball CI job. Convert the vendor grouping to the equivalent
node_modules path-matching function; unmatched modules fall through to
default chunking so shared deps (e.g. d3) still de-duplicate.

Verified: `npm run build` green, `npm audit` 0 vulnerabilities, lint 0
errors, dev server boots on vite 8.1.4, runtime-extension importmap +
vendor shims still injected into dist/index.html.

Note: the lazy TramoEditor chunk grows because rolldown groups the
non-tree-shakeable `import * as simpleIcons from 'simple-icons'` set
(via @tramo/editor) into it. Same bytes as before, still lazy-loaded on
/automations — a chunk-grouping change, not new bloat.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 16:57:11 -04:00
Juan DenisandClaude Opus 4.8 f909103656 Add per-row busy state to Marketplace plugin actions
Track the in-flight plugin id/action so uninstall/update/enable/disable can't be double-fired and each row shows live progress; await loadExtensions() before clearing busy. Includes matching tramo editor and workflow_store tweaks (source + live copies) and tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 06:14:45 -04:00
Juan Denis 6bae6a7a1c Align K8s page with tabgroup layout
Updated both K8sPage copies to use the full-bleed `sk-tabgroup` page shell and content wrappers, including the loading state. This moves warning/empty/main tab content into `sk-tabgroup__content`/`__inner` for consistent spacing and tabbed-page structure, and removes the extra topbar meta text to match the new header pattern.
2026-07-12 04:16:15 -04:00
Juan DenisandClaude Opus 4.8 128143760d chore: sync builtin-extension frontend artifacts (fix CI drift)
Regenerate frontend/src/plugins/<slug>/plugin.json from source via
scripts/sync-builtin-frontends.mjs. Resolves the drift check: k8s was a
missing trailing newline; tramo/wordpress/cloudflare-ops/gpu are the new
featured/feature_score fields normalized to canonical form.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 03:16:28 -04:00
Juan Denis 053d987721 Add Kubernetes plugin backend and manifest entry
Introduces the new `serverkit-k8s` backend plugin with full extension wiring: plugin metadata, model registration, lifecycle hooks, kubectl execution service, cluster CRUD/default handling, connectivity checks, normalized read endpoints (nodes/namespaces/workloads/pods/services/ingresses/logs/overview), and admin mutations (scale/restart/delete/apply). It also registers the plugin in the frontend plugins manifest (including plugin styles) so the Kubernetes page can be surfaced by the plugin system.
2026-07-12 03:06:16 -04:00
Juan DenisandClaude Opus 4.8 4fa318fa8b feat(marketplace): curated featured top-5 + k8s/wordpress covers
- Featured order: Faro (100), Automations (90), WordPress (80),
  Cloudflare Zone Ops (70), GPU Monitor (60). Scores set in each bundled
  extension's manifest (both copies); Faro's lives in the registry index.
- Add illustrated covers for Kubernetes and WordPress (extension-icons +
  resolver rules) so they stop falling back to generic category glyphs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 03:01:33 -04:00
Juan DenisandClaude Opus 4.8 76a2788d24 refactor(ui): rename Marketplace to Extensions
"Marketplace" reads as a store; the page is where you browse and install
extensions, so call it Extensions.

- Nav label + page/tab title + route /marketplace -> /extensions
  (first tab drives the topbar title via TabGroupLayout).
- /marketplace and /marketplace/installed now redirect to /extensions.
- Update command palette, page-context map, NotFound, empty-state copy.
- Keep the internal nav id, CSS classes, component/file names, and the
  backend /api/v1/marketplace/registry endpoints unchanged; palette keeps
  "marketplace" as a search keyword so the old term still finds it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 02:37:17 -04:00
Juan DenisandClaude Opus 4.8 161271dc5d fix(marketplace): surface registry featured flag; distinct mail icons
- registry_service: keep featured/feature_score through normalization so
  registry (bundled:false) extensions like Faro actually float to the top
  and show the Featured ribbon (they were being stripped).
- ExtensionIcons: give the Postfix/Dovecot "Email Server" the envelope and
  the Stalwart "Mail Server" the server-rack icon so they stop looking like
  duplicates ('email' matched before the bare 'mail' rule).
- Drop Faro's logo override so it uses its full-bleed illustrated cover
  (92px) instead of the 44px brand-logo slot.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 02:27:57 -04:00
Juan DenisandClaude Opus 4.8 30fbdb3bf8 feat(marketplace): feature Faro + Cloudflare Zone Ops, add Faro cover
- Add Faro illustrated cover (extension-icons/faro.png) + resolver rule so
  serverkit-faro shows a 3D lighthouse instead of a flat glyph.
- Mark Cloudflare Zone Ops featured (score 95) in both plugin.json copies.
- Add a wordpress.detail.panel PluginSlot so the Faro (and future WP)
  extensions can render on WordPress site pages.
- Sync the registry offline fallback with the Faro v1.0.1 entry (featured).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 02:09:31 -04:00
Juan DenisandClaude Opus 4.8 475c492d19 feat(marketplace): featured Automations with illustrated icon
- Add an illustrated 3D cover icon for the Automations extension
  (assets/extension-icons/automations.png, transparent bg, 256px) and map
  tramo/automation to it in the extension icon resolver.
- Add manifest 'featured' + 'feature_score' (Automations = featured, score 100).
- Sort featured extensions to the top of the Marketplace by score (score is
  hidden; used only for ordering) and show a gold 'Featured' ribbon on the
  cover + a subtle featured border.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 00:21:42 -04:00
Juan DenisandClaude Opus 4.8 483f1fa4c0 feat(automations): side-nav Settings layout (like core/WordPress settings)
Replace the single long scroll of stacked cards with the core settings-layout:
a left sub-nav (Engine / Events & port / Pack secrets) and a content pane that
shows only the selected section. Reuses the global .settings-layout/.settings-nav
classes so it matches core Settings and WordPress. Drops the redundant per-card
Refresh (the topbar Refresh covers it).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 00:10:19 -04:00
Juan DenisandClaude Opus 4.8 5fce53f779 feat(automations): Templates marketplace tab (search, favorites, import)
Promote 'New from template' from a popup to its own Templates tab, built to grow
like a marketplace:
- Responsive card grid (icon, name, description, integration tags) with a
  'Use template' action per card.
- Search box to filter.
- Per-card favorite star; favorites persist in localStorage and float to the
  top (server-side store can replace this later without UI changes).
- 'Import template' action: paste a tramo workflow JSON or load a .json file to
  create it as a new automation.
Removes the old modal picker and its topbar button.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-12 00:01:03 -04:00
Juan DenisandClaude Opus 4.8 049235b12d feat(automations): two-column New automation dialog with trigger picker
Replace the name-only New workflow modal with a two-column dialog: an explainer
panel (badge, mini flow diagram, use-case examples) on the left and, on the
right, the name field plus a 'How should it start?' trigger picker. Choices seed
a valid builtin trigger node (manual / cron / panel-event via webhook on
/sk/events / incoming webhook) so the editor opens with the trigger already
placed. Own copy/wording; collapses to a single column on narrow screens.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 23:51:19 -04:00
Juan DenisandClaude Opus 4.8 f4b8630e3b feat(automations): richer template picker
Replace the three plain text rows with proper cards: a brand-tinted icon tile
(Telegram/GitHub/Discord/generic), bold name, muted description, integration
tags, and a hover chevron. Wider modal (size lg) with an intro line, and the
Close button moves to the modal footer slot.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 23:42:56 -04:00
Juan DenisandClaude Opus 4.8 6f4246bf0e fix(automations): un-nest template-picker styles from .tramo-page
The 'New from template' picker renders inside a <Modal>, which portals to
<body> -- outside the .tramo-page subtree -- so the .tramo-template rules
nested under .tramo-page never matched it. The name/description spans fell back
to inline defaults and ran together. Move .tramo-template-list/.tramo-template
to top-level scope so the portaled modal picks them up (name bold on its own
line, muted description below).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 23:33:25 -04:00
Juan DenisandClaude Opus 4.8 52ea07426f fix(automations): center content in sk-tabgroup__inner (match WordPress)
Add the .sk-tabgroup__inner wrapper WordPress/Domains use inside
.sk-tabgroup__content so the content is padded + max-width centered under the
full-bleed header, instead of sitting edge-to-edge.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 23:23:09 -04:00
Juan DenisandClaude Opus 4.8 801fe3bbf6 fix(automations): full-bleed tab-group header to match WordPress/Domains
The topbar was rendered inside a plain padded page-container, so it floated
inset with no header band. Switch the wrapper to the same classes TabGroupLayout
uses for WordPress/Domains -- 'page-container--full-bleed sk-tabgroup' with the
content in 'sk-tabgroup__content' -- so Automations gets the identical
full-width header strip (bordered band, icon+title+tabs+actions) over centered
content.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 23:20:08 -04:00
Juan DenisandClaude Opus 4.8 18a8248fc1 fix(automations): match the clean Domains/WordPress header layout
Drop the '<icon> tramo . workflows . triggers . integrations' topbar subtext
and the redundant inner card-header on the Workflows/Runs tabs. Actions
(Deploy, New from template, New workflow, Refresh) now live in the PageTopbar
and are contextual to the active tab, so each tab shows a single clean header
over its content -- the same pattern as Domains and WordPress.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 23:13:23 -04:00
Juan DenisandClaude Opus 4.8 bd8842faf4 fix(routing): show a Not Found page for unknown/uninstalled-extension routes
Navigating straight to an extension route whose extension isn't installed
(or any unknown URL) rendered a blank page -- the route was never registered
and there was no catch-all. Add a catch-all '*' route inside the dashboard
shell that renders a NotFound page (icon, the missing path, links to the
dashboard + Marketplace).

Contributed routes load async, so on a hard refresh onto an installed
extension route the catch-all can mount for a beat before the route
registers; NotFound keys off a new contributions __ready flag to show a
loading skeleton until the route set is known, avoiding a 'Not found' flash.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 23:06:03 -04:00
Juan DenisandClaude Opus 4.8 e62d969a8b feat(automations): retire React-Flow Workflow Builder UI (Phase 4 frontend)
The Automations extension (tramo) replaces the drag-and-drop builder.

- Delete builtin-extensions/serverkit-workflows + its mirror, pages/
  WorkflowBuilder.jsx, and all of components/workflow/ (its @xyflow nodes,
  edges, and config panels).
- KEEP @xyflow/react (vendor-flow chunk): pages/AppMap.jsx still uses it.
- Redirect /workflow -> /automations in App.jsx (retired-route pattern).
- Drop the pages/_workflow SCSS partial + its main.scss import.
- Refresh the stale serverkit-workflows comments in DashboardLayout +
  sidebarItems to point at the Automations extension.

Build + lint clean; sync-builtin-frontends --check green (14 managed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 22:27:20 -04:00
Juan DenisandClaude Opus 4.8 8eaf13f2ed build(frontend): add tramo@^0.1.0 dependency for Automations editor
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 22:24:31 -04:00
Juan DenisandClaude Opus 4.8 79be6e082d test(automations): tramo extension suite + host/model refinements
- test_tramo_extension.py: 40 tests (manifest, models+purge, workflow store
  CRUD/materialize/deploy, host argv/control/status/nt-guard, run_sync,
  jobs harvest+health, events bridge + call-back key, blueprint auth/run
  proxy/settings mask/auth-exempt hooks passthrough, Phase 4 ported emitters +
  legacy export + old routes gone).
- host_service: add _is_windows() seam (test platform without touching global
  os.name, which would break pathlib).
- models: dirty detection via deployed_version counter (race-free vs the
  deploy commit's own updated_at onupdate bump).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 22:23:53 -04:00
Juan DenisandClaude Opus 4.8 bde4647fdc feat(k8s): serverkit-k8s extension — manage remote clusters via kubectl
Opt-in builtin extension. Add a cluster by pasting its kubeconfig (stored
encrypted, one row per cluster); the engine shells out to kubectl against a
per-call temp kubeconfig to show cluster health, nodes, namespaces, workloads,
pods with live logs, services, and ingresses, plus scale/restart/delete and
apply-from-paste. Reads need viewer, mutations need admin. Not platform-gated
(kubectl runs anywhere); no cluster needed on the panel host.

Backend: models (ext_serverkit_k8s_clusters), kubectl_service (argv build +
temp-file materialize/cleanup), cluster_service (CRUD + normalizers), k8s
blueprint (/api/v1/k8s), lifecycle. Frontend: K8sPage (7 tabs) mirrored to the
baked plugin tree via sync-builtin-frontends. 30 extension tests green;
manifest/platform suites green; frontend builds; sync-drift clean.

Plan: docs/plans/44_KUBERNETES_EXTENSION_PLAN.md

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 03:41:34 -04:00