Files
cameleer-server/.claude/rules/ui.md
hsiegeln 414f7204bf feat(alerting): AGENT_LIFECYCLE condition kind with per-subject fire mode
Allows alert rules to fire on agent-lifecycle events — REGISTERED,
RE_REGISTERED, DEREGISTERED, WENT_STALE, WENT_DEAD, RECOVERED — rather
than only on current state. Each matching `(agent, eventType, timestamp)`
becomes its own ackable AlertInstance, so outages on distinct agents are
independently routable.

Core:
- New `ConditionKind.AGENT_LIFECYCLE` + `AgentLifecycleCondition` record
  (scope, eventTypes, withinSeconds). Compact ctor rejects empty
  eventTypes and withinSeconds<1.
- Strict allowlist enum `AgentLifecycleEventType` (six entries matching
  the server-emitted types in `AgentRegistrationController` and
  `AgentLifecycleMonitor`). Custom agent-emitted event types tracked in
  backlog issue #145.
- `AgentEventRepository.findInWindow(env, appSlug, agentId, eventTypes,
  from, to, limit)` — new read path ordered `(timestamp ASC, insert_id
  ASC)` used by the evaluator. Implemented on
  `ClickHouseAgentEventRepository` with tenant + env filter mandatory.

App:
- `AgentLifecycleEvaluator` queries events in the last `withinSeconds`
  window and returns `EvalResult.Batch` with one `Firing` per row.
  Every Firing carries a canonical `_subjectFingerprint` of
  `"<agentId>:<eventType>:<tsMillis>"` in context plus `agent` / `event`
  subtrees for Mustache templating.
- `NotificationContextBuilder` gains an `AGENT_LIFECYCLE` branch that
  exposes `{{agent.id}}`, `{{agent.app}}`, `{{event.type}}`,
  `{{event.timestamp}}`, `{{event.detail}}`.
- Validation is delegated to the record compact ctor + enum at Jackson
  deserialization time — matches the existing policy of keeping
  controller validators focused on env-scoped / SQL-injection concerns.

Schema:
- V16 migration generalises the V15 per-exchange discriminator on
  `alert_instances_open_rule_uq` to prefer `_subjectFingerprint` with a
  fallback to the legacy `exchange.id` expression. Scalar kinds still
  resolve to `''` and keep one-open-per-rule. Duplicate-key path in
  `PostgresAlertInstanceRepository.save` is unchanged — the index is
  the deduper.

UI:
- New `AgentLifecycleForm.tsx` wizard form with multi-select chips for
  the six allowed event types + `withinSeconds` input. Wired into
  `ConditionStep`, `form-state` (validation + defaults: WENT_DEAD,
  300 s), and `enums.ts` options. Tests in `enums.test.ts` pin the
  new option array.
- `alert-variables.ts` registers `{{agent.app}}`, `{{event.type}}`,
  `{{event.timestamp}}`, `{{event.detail}}` leaves for the new kind,
  and extends `agent.id`'s availability list to include `AGENT_LIFECYCLE`.

Tests (all passing):
- 5 new JSON-roundtrip cases on `AlertConditionJsonTest` (positive +
  empty/zero/unknown-type rejection).
- 5 new evaluator unit tests on `AgentLifecycleEvaluatorTest` (empty
  window, multi-agent fingerprint shape, scope forwarding, missing env).
- `NotificationContextBuilderTest` switch now covers the new kind.
- 119 alerting unit tests + 71 UI tests green.

Docs: `.claude/rules/{core,app,ui}` and CLAUDE.md migration list updated.
2026-04-21 14:52:08 +02:00

7.0 KiB

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 — app management, JAR upload, deployment lifecycle (ui/src/pages/AppsTab/)
    • Config sub-tabs: Monitoring | Resources | Variables | Traces & Taps | Route Recording
    • Create app: full page at /apps/new (not a modal)
    • Deployment progress: ui/src/components/DeploymentProgress.tsx (7-stage step indicator)

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/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

Alerts

  • Sidebar section (buildAlertsTreeNodes in ui/src/components/sidebar-utils.ts) — Inbox, All, Rules, Silences, History.
  • Routes in ui/src/router.tsx: /alerts, /alerts/inbox, /alerts/all, /alerts/history, /alerts/rules, /alerts/rules/new, /alerts/rules/:id, /alerts/silences.
  • Pages under ui/src/pages/Alerts/:
    • InboxPage.tsx — user-targeted FIRING/ACK'd alerts with bulk-read.
    • AllAlertsPage.tsx — env-wide list with state-chip filter.
    • HistoryPage.tsx — RESOLVED alerts.
    • 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.
    • 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.