Files
cameleer-server/.claude/rules/ui.md
hsiegeln 0fc9c8cb4c
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m21s
CI / docker (push) Successful in 1m6s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 38s
docs(rules): checkpoints live inside Identity grid; HistoryDisclosure retired
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 17:15:05 +02:00

12 KiB
Raw Blame History

paths
paths
ui/**

UI Structure

The UI has 4 main tabs: Exchanges, Dashboard, Runtime, Deployments.

  • Exchanges — route execution search and detail (ui/src/pages/Exchanges/)
  • Dashboard — metrics and stats with L1/L2/L3 drill-down (ui/src/pages/DashboardTab/)
  • Runtime — live agent status, logs, commands (ui/src/pages/RuntimeTab/). AgentHealth supports compact view (dense health-tinted cards) and expanded view (full GroupCard+DataTable per app). View mode persisted to localStorage.
  • Deployments — unified app deployment page (ui/src/pages/AppsTab/)
    • Routes: /apps (list, AppListView in AppsTab.tsx), /apps/new + /apps/:slug (both render AppDeploymentPage).
    • Identity & Artifact section always visible; name editable pre-first-deploy, read-only after. JAR picker client-stages; new JAR + any form edits flip the primary button from Save to Redeploy. Environment fixed to the currently-selected env (no selector).
    • Config sub-tabs: Monitoring | Resources | Variables | Sensitive Keys | Deployment | ● Traces & Taps | ● Route Recording. The four staged tabs feed dirty detection; the live tabs apply in real-time (amber LiveBanner + default ?apply=live on their writes) and never mark dirty.
    • Primary action state machine: SaveUploading… N% (during JAR upload; button shows percent with a tinted progress-fill overlay) → RedeployDeploying… during active deploy. Upload progress sourced from useUploadJar (XHR upload.onprogress → page-level uploadPct state). The button is disabled during uploading and deploying.
    • Checkpoints render as a collapsible CheckpointsTable (default collapsed) inside the Identity & Artifact configGrid as an in-grid row (Checkpoints | ▸ Expand (N) / ▾ Collapse (N)). CheckpointsTable returns a React.Fragment of grid-ready children so the label + trigger align with the other identity rows; when opened, a third grid child spans both columns via grid-column: 1 / -1 so the 7-column table gets full width. Wired through IdentitySection.checkpointsSlotCheckpointDetailDrawer stays in IdentitySection.children because it portals. Columns: Version · JAR (filename) · Deployed by · Deployed (relative timeAgo + user-locale sub-line via new Date(iso).toLocaleString()) · Strategy · Outcome · . Row click opens the drawer. Drawer tabs are ordered Config | Logs with Config as the default. Config panel has Snapshot / Diff vs current view modes. Replica filter in the Logs panel uses DS Select. Restore lives in the drawer footer (forces review). Visible row cap = Environment.jarRetentionCount (default 10 if 0/null); older rows accessible via "Show older (N)" expander. Currently-running deployment is excluded — represented separately by StatusCard. The empty-checkpoints case returns null (no row). The legacy Checkpoints.tsx row-list component is gone.
    • Deployment tab: StatusCard + DeploymentProgress (during STARTING / FAILED) + flex-grow StartupLogPanel (no fixed maxHeight). Auto-activates when a deploy starts. The former HistoryDisclosure is retired — per-deployment config and logs live in the Checkpoints drawer. StartupLogPanel header mirrors the Runtime Application Log pattern: title + live/stopped badge + N entries + sort toggle (↑/↓, default desc) + refresh icon (RefreshCw). Sort drives the backend fetch via useStartupLogs(…, sort) so the 500-line limit returns the window closest to the user's interest; display order matches fetch order. Refresh scrolls to the latest edge (top for desc, bottom for asc). Sort + refresh buttons disable while a refetch is in flight. 3s polling while STARTING is unchanged.
    • Unsaved-change router blocker uses DS AlertDialog (not window.beforeunload). Env switch intentionally discards edits without warning.

Admin pages (ADMIN-only, under /admin/):

  • Sensitive Keys (ui/src/pages/Admin/SensitiveKeysPage.tsx) — global sensitive key masking config. Shows agent built-in defaults as outlined Badge reference, editable Tag pills for custom keys, amber-highlighted push-to-agents toggle. Keys add to (not replace) agent defaults. Per-app sensitive key additions managed via ApplicationConfigController API. Note: AppConfigDetailPage.tsx exists but is not routed in router.tsx.

Key UI Files

  • ui/src/router.tsx — React Router v6 routes
  • ui/src/config.ts — apiBaseUrl, basePath
  • ui/src/auth/auth-store.ts — Zustand: accessToken, user, roles, login/logout
  • ui/src/api/environment-store.ts — Zustand: selected environment (localStorage)
  • ui/src/components/ContentTabs.tsx — main tab switcher
  • ui/src/components/EnvironmentSwitcherButton.tsx + EnvironmentSwitcherModal.tsx — explicit env picker (button in TopBar; DS Modal-based list). Replaces the retired EnvironmentSelector (All-Envs dropdown). When envRecords.length > 0 and the stored selectedEnv no longer matches any env, LayoutShell opens the modal in forced mode (non-dismissible). Switcher pulls env records from useEnvironments() (admin endpoint; readable by VIEWER+).
  • ui/src/components/env-colors.ts + ui/src/styles/env-colors.css — 8-swatch preset palette for the per-environment color indicator. Tokens --env-color-slate/red/amber/green/teal/blue/purple/pink are defined for both light and dark themes. envColorVar(name) falls back to slate for unknown values. LayoutShell renders a 3px fixed top bar in the current env's color (z-index 900, below DS modals).
  • ui/src/components/ExecutionDiagram/ — interactive trace view (canvas)
  • ui/src/components/ProcessDiagram/ — ELK-rendered route diagram
  • ui/src/hooks/useScope.ts — TabKey type, scope inference
  • ui/src/components/StartupLogPanel.tsx — deployment startup log viewer (container logs from ClickHouse, polls 3s while STARTING)
  • ui/src/api/queries/logs.tsuseStartupLogs hook for container startup log polling, useLogs/useApplicationLogs for bounded log search (single page), useInfiniteApplicationLogs for streaming log views (cursor-paginated, server-side source/level filters)
  • ui/src/api/queries/agents.tsuseAgents for agent list, useInfiniteAgentEvents for cursor-paginated timeline stream
  • ui/src/hooks/useInfiniteStream.ts — tanstack useInfiniteQuery wrapper with top-gated auto-refetch, flattened items[], and refresh() invalidator
  • ui/src/components/InfiniteScrollArea.tsx — scrollable container with IntersectionObserver top/bottom sentinels. Streaming log/event views use this + useInfiniteStream. Bounded views (LogTab, StartupLogPanel) keep useLogs/useStartupLogs
  • ui/src/components/SideDrawer.tsx — project-local right-slide drawer (DS has Modal but no Drawer). Portal-rendered, ESC + transparent-backdrop click closes, sticky header/footer, sizes md/lg/xl. Currently consumed only by CheckpointDetailDrawer — promote to @cameleer/design-system once a second consumer appears.

Alerts

  • Sidebar section (buildAlertsTreeNodes in ui/src/components/sidebar-utils.ts) — Inbox, Rules, Silences.
  • Routes in ui/src/router.tsx: /alerts (redirect to inbox), /alerts/inbox, /alerts/rules, /alerts/rules/new, /alerts/rules/:id, /alerts/silences. No redirects for the retired /alerts/all and /alerts/history — stale URLs 404 per the clean-break policy.
  • Pages under ui/src/pages/Alerts/:
    • InboxPage.tsx — single filterable inbox. Filters: severity (multi), state (PENDING/FIRING/RESOLVED, default FIRING), Hide acked toggle (default on), Hide read toggle (default on). Row actions: Acknowledge, Mark read, Silence rule… (duration quick menu), Delete (OPERATOR+, soft-delete with undo toast wired to useRestoreAlert). Bulk toolbar (selection-driven): Acknowledge N · Mark N read · Silence rules · Delete N (ConfirmDialog; OPERATOR+).
    • SilenceRuleMenu.tsx — DS Dropdown-based duration picker (1h / 8h / 24h / Custom…). Used by the row-level and bulk silence actions. "Custom…" navigates to /alerts/silences?ruleId=<id>.
    • RulesListPage.tsx — CRUD + enable/disable toggle + env-promotion dropdown (pure UI prefill, no new endpoint).
    • RuleEditor/RuleEditorWizard.tsx — 5-step wizard (Scope / Condition / Trigger / Notify / Review). form-state.ts is the single source of truth (initialForm / toRequest / validateStep). Seven condition-form subcomponents under RuleEditor/condition-forms/ — including AgentLifecycleForm.tsx (multi-select event-type chips for the six-entry AgentLifecycleEventType allowlist + lookback-window input).
    • SilencesPage.tsx — matcher-based create + end-early. Reads ?ruleId= search param to prefill the Rule ID field (driven by InboxPage's "Silence rule… → Custom…" flow).
    • AlertRow.tsx shared list row; alerts-page.module.css shared styling.
  • Components:
    • NotificationBell.tsx — polls /alerts/unread-count every 30 s (paused when tab hidden via TanStack Query refetchIntervalInBackground: false).
    • AlertStateChip.tsx, SeverityBadge.tsx — shared state/severity indicators.
    • MustacheEditor/ — CodeMirror 6 editor with variable autocomplete + inline linter. Shared between rule title/message, webhook body/header overrides, and (future) Admin Outbound Connection editor (reduced-context mode for URL).
    • MustacheEditor/alert-variables.ts — variable registry aligned with NotificationContextBuilder.java. Add new leaves here whenever the backend context grows.
  • API queries under ui/src/api/queries/: alerts.ts, alertRules.ts, alertSilences.ts, alertNotifications.ts, alertMeta.ts. All env-scoped via useSelectedEnv from alertMeta.
  • CMD-K: buildAlertSearchData in LayoutShell.tsx registers alert and alertRule result categories. Badges convey severity + state. Palette navigates directly to the deep-link path — no sidebar-reveal state for alerts.
  • Sidebar accordion: entering /alerts/* collapses Applications + Admin + Starred (mirrors Admin accordion).
  • Top-nav: <NotificationBell /> is the first child of <TopBar>, sitting alongside SearchTrigger + status ButtonGroup + TimeRangeDropdown + AutoRefreshToggle.

UI Styling

  • Always use @cameleer/design-system CSS variables for colors (var(--amber), var(--error), var(--success), etc.) — never hardcode hex values. This applies to CSS modules, inline styles, and SVG fill/stroke attributes. SVG presentation attributes resolve var() correctly. All colors use CSS variables (no hardcoded hex).
  • Shared CSS modules in ui/src/styles/ (table-section, log-panel, rate-colors, refresh-indicator, chart-card, section-card) — import these instead of duplicating patterns.
  • Shared PageLoader component replaces copy-pasted spinner patterns.
  • Design system components used consistently: Select, Tabs, Toggle, Button, LogViewer, Label — prefer DS components over raw HTML elements. LogViewer renders optional source badges (container, app, agent) via LogEntry.source field (DS v0.1.49+).
  • Environment slugs are auto-computed from display name (read-only in UI).
  • Brand assets: @cameleer/design-system/assets/ provides camel-logo.svg (currentColor), cameleer-{16,32,48,192,512}.png, and cameleer-logo.png. Copied to ui/public/ for use as favicon (favicon-16.png, favicon-32.png) and logo (camel-logo.svg — login dialog 36px, sidebar 28x24px).
  • Sidebar generates /exchanges/ paths directly (no legacy /apps/ redirects). basePath is centralized in ui/src/config.ts; router.tsx imports it instead of re-reading <base> tag.
  • Global user preferences (environment selection) use Zustand stores with localStorage persistence — never URL search params. URL params are for page-specific state only (e.g. ?text= search query). Switching environment resets all filters and remounts pages.