Skip to content

admin: models explorer dashboard UI + local dev server#821

Merged
benben merged 2 commits into
mainfrom
ben/admin-models-explorer-ui
Jun 29, 2026
Merged

admin: models explorer dashboard UI + local dev server#821
benben merged 2 commits into
mainfrom
ben/admin-models-explorer-ui

Conversation

@benben

@benben benben commented Jun 29, 2026

Copy link
Copy Markdown
Member

What

Adds a generic, read-only models explorer to the control-plane admin dashboard, and a local dev workflow for iterating on the UI without redeploying.

  • Sidebar of every config-store model, grouped Tenants / Config / Runtime (13 persisted models across the config + runtime schemas), each with a live row count.
  • Table of the selected model's rows with derived columns.
  • Detail panel on row click — nested managed-warehouse sub-configs (s3, iceberg, metadata_store, the SecretRefs, …) render as expandable sections.
  • / and /models now serve this as the primary dashboard surface; the legacy per-entity pages stay reachable.

Backend (controlplane/admin/models_api.go, kubernetes tag)

  • GET /api/v1/models → sidebar entries + counts.
  • GET /api/v1/models/:model → rows, columns, count, truncation flag.
  • modelDescriptors() is the single source of truth — add a model there and it appears in the sidebar, listing, and column derivation.
  • Redaction is structural: rows are scanned into the typed model and marshaled via json tags, so columns tagged json:"-" (OrgUser.Password, OrgUserSecret.Ciphertext, DuckLakeConfig.S3SecretKey, the singleton IDs) are dropped. Runtime-schema tables are schema-qualified via RuntimeSchema(); listings capped at modelsRowLimit.

UI (static/models.html, restyled static/login.html)

Mission-control design language (Chakra Petch + IBM Plex Mono, dark HUD, cyan/amber accents). Uses relative /api/v1 paths + cookie auth, with a session-expired gate on 401.

Local UI dev without redeploy (controlplane/admin/devserver/)

A stdlib dev proxy serves static/ off disk and proxies /api,/login,/health to a port-forwarded control plane, injecting the internal secret server-side. Same-origin from the browser → no CORS, secret never reaches page JS, and the embedded UI runs byte-for-byte the same under the proxy.

just ui-port-forward                              # kubectl port-forward 8080
DUCKGRES_INTERNAL_SECRET=<secret> just ui-dev     # http://127.0.0.1:5173 — edit static/ + refresh

Note: the /api/v1/models backend is new, so a CP running this branch must be live for the explorer to load data (local stack via just run-multitenant-kind, or a dev-cluster deploy). Once deployed, UI edits are refresh-only.

Auth

Unchanged — the existing internal-secret token (header / login cookie) gates the whole /api/v1 group and the dashboard pages. Token login is enough "for now" as requested; can be hardened later.

Tests

  • models_api_test.go: no-DB guard pinning the registry + the redaction invariant (sensitive columns must stay json:"-"), plus a Postgres-backed test exercising the real query path including "password hash never appears in the response bytes".
  • tests/e2e-mw-dev/harness.sh::models_explorer_api: live-cluster assertion — sidebar enumeration, counts, runtime-schema listing, 404 on unknown model, and the load-bearing check that org_users listings never expose a password column/field.

Docs

controlplane/admin/README.md (pages, API, redaction invariant, local dev loop) + the CLAUDE.md admin bullet.

🤖 Generated with Claude Code

Add a generic, read-only models explorer to the control-plane admin dashboard:
a sidebar of every config-store model (grouped Tenants / Config / Runtime), a
table of the selected model's rows, and a click-through detail panel where
nested managed-warehouse sub-configs render as expandable sections. "/" and
"/models" now serve this as the primary dashboard surface; the legacy
per-entity pages stay reachable.

Backend (controlplane/admin/models_api.go, kubernetes tag):
- GET /api/v1/models — sidebar entries with live row counts.
- GET /api/v1/models/:model — one model's rows, derived columns, count,
  truncation flag.
- modelDescriptors() is the single source of truth (13 persisted models across
  the config + runtime schemas). Rows are scanned into the typed model and
  marshaled via json tags, so secret-bearing columns tagged json:"-"
  (OrgUser.Password, OrgUserSecret.Ciphertext, DuckLakeConfig.S3SecretKey, the
  singleton IDs) are dropped from the response — never swap the typed scan for a
  raw map scan. Runtime-schema tables are schema-qualified via RuntimeSchema();
  listings are capped at modelsRowLimit.

UI (static/models.html, static/login.html):
- New mission-control design language (Chakra Petch + IBM Plex Mono, dark HUD,
  cyan/amber accents). Login page restyled to match. Uses relative /api/v1
  paths and cookie auth, with a session-expired gate on 401.

Local UI development without redeploy (controlplane/admin/devserver):
- A stdlib dev proxy serves static/ off disk and proxies /api,/login,/health to
  a port-forwarded control plane, injecting the internal secret server-side.
  Same-origin from the browser → no CORS, secret never reaches page JS, and the
  embedded UI runs byte-for-byte the same under the proxy. just recipes
  ui-port-forward + ui-dev wire it up.

Auth is unchanged: the existing internal-secret token (header / login cookie)
gates the whole /api/v1 group and the dashboard pages.

Tests:
- models_api_test.go: a no-DB guard pinning the registry + redaction invariant
  (sensitive columns must stay json:"-"), plus a Postgres-backed test exercising
  the real query path (sidebar counts, config + runtime listings, end-to-end
  "password hash never appears in the response bytes").
- tests/e2e-mw-dev/harness.sh: models_explorer_api assertion against the live
  cluster — sidebar enumeration, counts, runtime-schema listing, 404 on unknown
  model, and the load-bearing check that org_users listings never expose a
  password column/field.

Docs: controlplane/admin/README.md (pages, API, redaction invariant, local dev
loop) and the CLAUDE.md admin bullet.
@github-actions

Copy link
Copy Markdown

Test Impact Plan

Deterministic summary of how this PR changes tests, CI runners, and coverage-risk signals.

Summary

Area Added Changed Deleted
Test files 1 1 0
E2E/journey files 0 1 0
Workflow files 0 0 0

Signals

  • Test cases: +2 / -0
  • Assertions: +25 / -0
  • Skips or known failures added: 0
  • Workflow continue-on-error added: 0
  • Workflow path filters added: 0
  • Test commands removed from justfile: 0
  • E2E/journey retry lines added: 0

Coverage risk: needs review

Warnings

  • E2E or journey files changed (needs review)
    • tests/e2e-mw-dev/harness.sh

Comment thread controlplane/admin/devserver/main.go Fixed
Comment thread controlplane/admin/devserver/main.go Fixed
Replace the join-then-validate path handling in the dev server's static
handler with a constant lookup table keyed by request path. The filename
passed to http.ServeFile now always comes from literal map values, so it can
never derive from client input — closes CodeQL alerts 568/569 and removes the
need for the manual traversal guard.
@benben benben merged commit 8879179 into main Jun 29, 2026
25 of 26 checks passed
@benben benben deleted the ben/admin-models-explorer-ui branch June 29, 2026 08:58
benben added a commit that referenced this pull request Jun 29, 2026
#821 (models explorer UI) merged to main after this branch was cut. Its
models_api.go registered the four config-singleton models this branch deletes,
so the merged tree failed to build (undefined: configstore.GlobalConfig etc).

Merge main in and reconcile:
- Remove the global/ducklake/ratelimit/querylog descriptors from
  models_api.go (and the now-unused modelGroupConfig group).
- Drop them from models_api_test.go (wantKeys + the global-config redaction
  entry) and the harness models_explorer_api sidebar key list.
- Drop the dead Settings nav link + the empty Config sidebar group from
  models.html, and the /settings row from admin/README.md (the /settings page
  and its config API were removed on this branch).

Build (plain + kubernetes), all kubernetes test binaries, and the admin
redaction test pass.
benben added a commit that referenced this pull request Jun 29, 2026
* configstore: remove dead config singletons + unused model fields

Audit-driven cleanup of config-store surface that is persisted/served but never
read to drive runtime behavior. No user-visible behavior change.

Removed (confirmed dead by a full cross-repo audit incl. the Crossplane
compositions in posthog/charts):

- The four cluster-wide singleton config tables — GlobalConfig, DuckLakeConfig,
  RateLimitConfig, QueryLogConfig — and their Snapshot fields, FirstOrCreate
  seeds, snapshot loads, the admin GET/PUT /config/* API (handlers, routes,
  apiStore methods, gormAPIStore impls), and the /settings dashboard page.
  Nothing reads Snapshot.Global/DuckLake/RateLimit/QueryLog; effective config
  comes from CLI flags/env (server.Config) and the per-org ManagedWarehouse
  contract. (DuckLakeConfig was already marked legacy.)

- ManagedWarehouse dead columns: warehouse_database_{region,database_name,
  username}, metadata_store_{engine,region}, worker_identity_service_account_name
  (the worker SA name is computed, not read from config), warehouse_database_state
  (warehouse_database has no provisioning sub-state), and the six per-component
  *_status_message columns (the provisioner only writes the top-level
  status_message). Kept warehouse_database endpoint/port (curated provision
  response) and the per-component *_state fields that drive readiness.

- Runtime coordination dead columns: cp_instances.pod_uid/boot_id (already
  encoded into the cp instance id), worker_records.pod_uid (never written),
  org_connection_queue.canceled_at (cancel is a hard DELETE, never set) — plus
  the ControlPlaneRuntimeTracker podUID/bootID fields and constructor params.

Migration & rollout safety:
- New goose migration 000004 drops the four config tables and the 13 dead
  managed_warehouses columns (with a best-effort Down that recreates them).
- Runtime tables are AutoMigrate'd in a per-deployment schema, so the dead
  runtime columns are dropped in autoMigrateRuntimeTables (schema-qualified,
  DROP COLUMN IF EXISTS, idempotent). This runs before the first heartbeat —
  critical because cp_instances.pod_uid/boot_id were NOT NULL, so a rollout that
  stopped supplying them would otherwise fail every UpsertControlPlaneInstance.

Also updated: seed SQL (k8s + test fixtures), all affected unit/postgres tests
(including the migrations metadata-parity test, now asserting the config tables
are absent at version 4), and the README note about the former DuckLakeConfig
singleton.

Verified: `go build ./...`, `go build -tags kubernetes ./controlplane/... ./cmd/...`,
all kubernetes-tagged test binaries compile, and the configstore + admin
no-docker unit tests pass.

* Reconcile with #821: drop config-singleton models from the explorer

#821 (models explorer UI) merged to main after this branch was cut. Its
models_api.go registered the four config-singleton models this branch deletes,
so the merged tree failed to build (undefined: configstore.GlobalConfig etc).

Merge main in and reconcile:
- Remove the global/ducklake/ratelimit/querylog descriptors from
  models_api.go (and the now-unused modelGroupConfig group).
- Drop them from models_api_test.go (wantKeys + the global-config redaction
  entry) and the harness models_explorer_api sidebar key list.
- Drop the dead Settings nav link + the empty Config sidebar group from
  models.html, and the /settings row from admin/README.md (the /settings page
  and its config API were removed on this branch).

Build (plain + kubernetes), all kubernetes test binaries, and the admin
redaction test pass.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants