Files
cameleer-server/docs/superpowers/specs/2026-03-23-ui-mock-alignment-design.md
hsiegeln cb3ebfea7c
Some checks failed
CI / cleanup-branch (push) Has been skipped
CI / build (push) Failing after 18s
CI / docker (push) Has been skipped
CI / deploy (push) Has been skipped
CI / deploy-feature (push) Has been skipped
chore: rename cameleer3 to cameleer
Rename Java packages from com.cameleer3 to com.cameleer, module
directories from cameleer3-* to cameleer-*, and all references
throughout workflows, Dockerfiles, docs, migrations, and pom.xml.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 15:28:42 +02:00

26 KiB

UI Mock Alignment Design

Date: 2026-03-23 Status: Reviewed Scope: Close all gaps between @cameleer/design-system mocks and the cameleer-server UI

Context

The @cameleer/design-system package (v0.0.2) contains fully realized mock pages demonstrating the target UX for the Cameleer monitoring platform. The current server UI was built as a first pass and has significant deviations from these mocks across every page. This spec defines the work to align them.

Out of scope:

  • Business context columns (Order ID, Customer) — not applicable to current data model
  • Application log streaming — agent does not send logs; placeholder only

1. Backend — New Endpoints

1a. Processor Stats Endpoint

GET /api/v1/routes/metrics/processors

Exposes per-processor statistics. The current stats_1m_processor continuous aggregate groups by (bucket, group_name, route_id, processor_type) and lacks processor_id. TimescaleDB continuous aggregates cannot be ALTERed to add GROUP BY columns.

Migration: Add V7__processor_stats_by_id.sql creating a new continuous aggregate stats_1m_processor_detail:

CREATE MATERIALIZED VIEW stats_1m_processor_detail
WITH (timescaledb.continuous) AS
SELECT
  time_bucket('1 minute', start_time) AS bucket,
  group_name,
  route_id,
  processor_id,
  processor_type,
  COUNT(*) AS total_count,
  COUNT(*) FILTER (WHERE status = 'FAILED') AS failed_count,
  SUM(duration_ms) AS duration_sum,
  MAX(duration_ms) AS duration_max,
  approx_percentile(0.99, percentile_agg(duration_ms)) AS p99_duration
FROM processor_executions
GROUP BY bucket, group_name, route_id, processor_id, processor_type;

Leave the original stats_1m_processor intact (used elsewhere).

Controller: Add new method in existing RouteMetricsController.java (shares /api/v1/routes/metrics base path) rather than a separate controller.

Query params:

  • routeId (required) — filter by route
  • appId (optional) — filter by application
  • from / to (optional) — time window, defaults to last 24h

Response: List<ProcessorMetrics>

record ProcessorMetrics(
    String processorId,     // unique processor ID within the route
    String processorType,   // e.g. "to", "process", "choice"
    String routeId,
    String appId,
    long totalCount,
    long failedCount,
    double avgDurationMs,
    double p99DurationMs,
    double errorRate        // failedCount / totalCount
)

Security: VIEWER+ role. Already covered by existing GET /api/v1/routes/** wildcard in SecurityConfig.

1b. Agent Metrics Query Endpoint

GET /api/v1/agents/{agentId}/metrics

Queries the agent_metrics hypertable and returns time-bucketed series.

Query params:

  • names (required) — comma-separated metric names (e.g. jvm.cpu.process,jvm.memory.heap.used)
  • from / to (optional) — time window, defaults to last 1h
  • buckets (optional, default 60) — number of time buckets

Response: AgentMetricsResponse

record AgentMetricsResponse(
    Map<String, List<MetricBucket>> metrics
)

record MetricBucket(
    Instant time,
    double value    // avg within bucket
)

Implementation: Use time_bucket() on agent_metrics.collected_at, grouped by metric_name, averaged by metric_value. Filter by agent_id and optional tags if needed.

Security: VIEWER+ role. Requires new SecurityConfig rule: GET /api/v1/agents/*/metrics (existing /api/v1/agents rule is exact-match only, does not cover sub-paths).

1c. Enrich AgentInstanceResponse

Add fields to existing AgentInstanceResponse:

// existing fields...
String version,           // from AgentInfo.version in registry
Map<String, Object> capabilities  // from AgentInfo.capabilities

These values are already stored in the AgentRegistry's AgentInfo objects. The AgentRegistrationController.listAgents() method just needs to map them into the response DTO.

1d. Password Reset Endpoint

POST /api/v1/admin/users/{userId}/password

The current UpdateUserRequest has no password field. Add a dedicated endpoint for admin password reset.

Request body: SetPasswordRequest

record SetPasswordRequest(
    @NotBlank String password
)

Response: 204 No Content

Implementation: Hash password with same BCrypt encoder used in createUser, update users.password_hash column.

Security: ADMIN role required (same as other user management endpoints).

New files: cameleer-server-app/.../dto/SetPasswordRequest.java; new method in UserAdminController.

2. Dashboard Enhancements

2a. DetailPanel — Errors Section

When the selected execution has a non-null errorMessage:

  • Insert an "Errors" section between Overview and Processors in the DetailPanel
  • Display:
    • Error class: parsed from errorMessage (text before : or first line)
    • Error message: remainder of errorMessage
    • Stack trace: errorStackTrace in a collapsible CodeBlock (content prop)
  • Use Alert variant="error" for the error class/message, Collapsible + CodeBlock for the stack trace

2b. DetailPanel — Route Flow Tab

Add a third tab to the DetailPanel tabs: Overview | Processors | Route Flow

  • Fetch diagram via useDiagramByRoute(execution.groupName, execution.routeId)
  • Render RouteFlow component from design system
  • Overlay execution data: map each ProcessorNode status onto diagram nodes using diagramNodeId
  • Color nodes by status: success (green), failed (red), running (blue)
  • Show duration labels on nodes
  • RouteFlow overlay API: The RouteFlow component accepts execution data to color nodes. During implementation, read the RouteFlow.tsx source in the design system to confirm the exact props interface (likely an overlays or nodeStates prop mapping node IDs to status/duration). Map ProcessorNode.diagramNodeIdPositionedNode.id to connect execution data to diagram nodes.

2c. Stat Card Alignment

Change the 5 stat cards to match mock semantics:

Position Label Value Source
1 Throughput exchanges/s totalCount / timeWindowSeconds from stats
2 Error Rate % failedCount / totalCount * 100 from stats
3 Avg Latency ms avgDurationMs from stats
4 P99 Latency ms p99LatencyMs from stats
5 In-Flight count activeCount from stats

Each card includes:

  • Sparkline from timeseries buckets (existing)
  • Trend arrow: compare current vs prev* fields, show up/down indicator

3. Exchange Detail Enhancements

3a. Correlation Chain

Below the exchange header card, add a "Correlation Chain" section:

  • Data source: POST /search/executions with filter { correlationId: execution.correlationId }
  • Rendering: Horizontal chain of small cards connected by arrows
    • Each card: route name, StatusDot, duration, relative timestamp
    • Current exchange highlighted
    • Click navigates to that exchange (/exchanges/:id)
  • Conditional: Only show section when correlationId is present and search returns > 1 result
  • Limit: Request with limit: 20 to prevent excessive results. If more exist, show "+N more" link
  • Hook: useCorrelationChain(correlationId) — new query hook wrapping the search call

3b. Timeline / Flow Toggle

Above the processor timeline section:

  • Add SegmentedTabs with options: Timeline | Flow
  • Timeline (default): existing ProcessorTimeline component (Gantt view)
  • Flow: RouteFlow component with execution overlay
    • Fetch diagram via useDiagramByRoute(execution.groupName, execution.routeId) (same as 2b)
    • Color nodes by processor status, show duration labels
    • Clicking a processor node in either view selects it and loads its snapshot

3c. Header Enrichment

Add to the exchange header:

  • Processor count: execution.processors.length (or recursive count for nested trees)
  • Display as a stat in the header's right section alongside duration

4. Route Detail Page (NEW)

New page at /routes/:appId/:routeId. Currently this path renders a filtered RoutesMetrics; replace with a dedicated route detail page. The filtered table/chart view from RoutesMetrics is not lost — it is subsumed by the Performance and Recent Executions tabs in the new page, which provide the same data in a richer context alongside the route diagram.

Update router.tsx: the /routes/:appId/:routeId route imports a new RouteDetail component instead of RoutesMetrics. The /routes and /routes/:appId routes remain unchanged (continue to render RoutesMetrics).

4a. Route Header Card

Card displaying:

  • Route name (routeId) and application name (appId)
  • Health status from route catalog (useRouteCatalog() filtered)
  • Exchange count (last 24h)
  • Last seen timestamp
  • Back link to /routes/:appId

4b. Route Diagram + Processor Stats (Side-by-Side)

Two-column grid:

  • Left: RouteFlow component rendering the route diagram
    • Data from useDiagramByRoute(appId, routeId) or useDiagramLayout(contentHash)
  • Right: Processor stats table from new endpoint (1a)
    • DataTable columns: Processor ID, Type, Executions, Avg Duration, P99 Duration, Error Rate
    • Data from useProcessorMetrics(routeId, appId)

4c. Tabbed Section

Tabs component with three tabs:

Performance tab:

  • 2x2 chart grid (same pattern as RoutesMetrics) filtered to this specific route
  • Data from useStatsTimeseries(from, to, routeId, appId)

Recent Executions tab:

  • DataTable showing recent executions for this route
  • Data from useSearchExecutions({ routeId, group: appId, limit: 20, sortField: 'startTime', sortDir: 'desc' })
  • Columns: Status, Execution ID, Duration, Start Time, Error Message
  • Row click navigates to /exchanges/:id

Error Patterns tab:

  • Group failed executions by errorMessage
  • Display: error message (truncated), count, last occurrence timestamp, link to sample execution
  • Data from useSearchExecutions({ routeId, group: appId, status: 'FAILED', limit: 100 }) — client-side grouping by errorMessage

4d. CSS Module

RouteDetail.module.css with classes:

  • .headerCard — surface card, padding, margin-bottom
  • .diagramStatsGrid — 2-column grid
  • .diagramPane / .statsPane — surface cards
  • .tabSection — margin-top
  • .chartGrid — 2x2 grid (reuse pattern from RoutesMetrics)

5. Agent Health Enhancements

5a. DetailPanel (Slide-In)

Add a DetailPanel from the design system, triggered by clicking an instance row in a GroupCard.

Overview tab:

  • Status with StatusDot and Badge
  • Application name, version (from enriched AgentInstanceResponse, section 1c)
  • Uptime (formatted), last heartbeat (relative time)
  • TPS, error rate
  • Active routes / total routes
  • Memory usage: ProgressBar — data from GET /agents/{id}/metrics?names=jvm.memory.heap.used,jvm.memory.heap.max&buckets=1 (single latest bucket gives the most recent averaged value)
  • CPU usage: ProgressBar — data from GET /agents/{id}/metrics?names=jvm.cpu.process&buckets=1 (single latest bucket)

Performance tab:

  • Two LineChart components:
    • Throughput over time (from timeseries stats filtered by agentId, or from agent metrics)
    • Error rate over time

5b. Instance Table Enrichment

Add columns to the instance rows within each GroupCard:

Column Source
Status dot agent.status (existing)
Instance name agent.name (existing)
State badge agent.status (existing)
Uptime agent.uptimeSeconds → formatted "2d 4h" / "15m"
TPS agent.tps (existing)
Error rate agent.errorRate → percentage
Last heartbeat agent.lastHeartbeat → relative "2m ago"
Link Icon button → /agents/:appId/:instanceId

5c. Alert Banners

When a GroupCard contains instances with status DEAD:

  • Show Alert variant="error" at the top of the card body
  • Message: "N instance(s) unreachable" where N is the count of DEAD instances

5d. Stat Card Alignment

Replace current 4 cards (Total, Live, Stale, Dead) with 5 cards matching mock:

Label Value Accent
Total Agents count (subtitle: "N live / N stale / N dead") default
Applications count of unique appIds default
Active Routes sum of activeRoutes across live agents default
Total TPS sum of tps across live agents default
Dead count of dead agents error

5e. Scope Trail

Add breadcrumb below stat cards:

  • All agents view: Agents with live Badge showing "N live"
  • Filtered by app: Agents > {appName} with health Badge (live/stale/dead color)

6. Agent Instance Enhancements

6a. JVM Metrics Charts (3x2 Grid)

Replace current 2-column chart grid with 3x2 grid. All data from new endpoint (1b).

Chart Type Metric Name(s)
CPU Usage AreaChart jvm.cpu.process (0-1 scale, display as %)
Memory (Heap) AreaChart jvm.memory.heap.used + jvm.memory.heap.max (two series)
Throughput AreaChart from useStatsTimeseries filtered by agent (existing)
Error Rate LineChart from useStatsTimeseries filtered by agent (existing)
Thread Count LineChart jvm.threads.count
GC Pauses BarChart jvm.gc.time

Hook: useAgentMetrics(agentId, metricNames[], from, to, buckets) — wraps endpoint 1b.

6b. Process Information Card

Card with key-value pairs:

Key Source
JVM Version agent.capabilities.jvmVersion or parse from registration
Camel Version agent.capabilities.camelVersion
Spring Boot agent.capabilities.springBootVersion
Started agent.registeredAt formatted
Capabilities render as tags: tracing, metrics, diagrams, replay

Data from enriched AgentInstanceResponse (section 1c). If version details aren't in current capabilities, they can be added to agent registration in a future iteration — show what's available.

6c. Stat Card Alignment

Replace current 4 cards with 5 cards matching mock:

Label Value Source
CPU % latest jvm.cpu.process from agent metrics
Memory % latest heap.used / heap.max * 100
Throughput req/s agent.tps
Errors % agent.errorRate
Uptime formatted agent.uptimeSeconds

CPU and Memory require a small fetch from endpoint 1b (latest single value).

6d. Application Log Placeholder

Below the EventFeed card, add an EmptyState component:

  • Title: "Application Logs"
  • Description: "Application log streaming is not yet available"
  • No action button

6e. Version Badge in Scope Trail

Breadcrumb: Agents > {appName} > {instanceName}

  • Add Badge next to instance name showing version (from enriched response)
  • Add StatusDot + status Badge for visual state

7. Admin & Miscellaneous

7a. OIDC Config — Default Roles

Add a "Default Roles" section to the OIDC config page:

  • Display current default roles as Tag components (removable, click X to remove)
  • Input + "Add" Button to add a role
  • Validate against existing roles from useRoles() query
  • Persist via existing OIDC config save endpoint

7b. OIDC Config — ConfirmDialog on Delete

Replace direct delete button with ConfirmDialog:

  • Message: "Delete OIDC configuration? All OIDC users will lose access."
  • Require typing "DELETE" to confirm

7c. Design System Update

Update @cameleer/design-system from ^0.0.1 to ^0.0.2 in ui/package.json.

TopBar onLogout prop: Replace the custom Dropdown + Avatar logout hack in LayoutShell.tsx with the TopBar's new onLogout prop:

<TopBar
  breadcrumb={breadcrumb}
  user={{ name: username }}
  onLogout={handleLogout}
/>

Remove the manual Avatar/Dropdown logout code.

Verification needed during implementation: Confirm that the TopBar v0.0.2 renders a user avatar/menu internally when user + onLogout are provided. If it only renders a bare logout button without the "Signed in as" display, keep the custom Avatar/Dropdown and just wire up the TopBar's onLogout as an additional trigger.

7d. Regenerate schema.d.ts

After backend endpoints are added, regenerate types from the running server:

npm run generate-api:live

This ensures all new DTOs (ProcessorMetrics, AgentMetricsResponse, MetricBucket, enriched AgentInstanceResponse) are accurately typed.

8. RBAC / User Management Overhaul

The current RBAC page is a basic DataTable + Modal CRUD interface. The design system mock implements a split-pane detail-oriented admin panel with rich interactions. This section describes the full rebuild.

8a. Layout — Split-Pane Replaces DataTable

All three tabs (Users, Groups, Roles) adopt the same layout pattern:

┌─────────────────────┬────────────────────┐
│    List Pane (52%)   │  Detail Pane (48%) │
│                      │                    │
│  [Search input]      │  [Selected entity  │
│  [+ Create button]   │   detail view]     │
│                      │                    │
│  [Inline create form]│                    │
│                      │                    │
│  [Scrollable entity  │                    │
│   list with avatars, │                    │
│   badges, tags]      │                    │
│                      │                    │
└─────────────────────┴────────────────────┘

New file: ui/src/pages/Admin/UserManagement.module.css

  • .splitPane — CSS grid 52fr 48fr, full height
  • .listPane — scrollable, border-right
  • .detailPane — scrollable, padding
  • .entityItem / .entityItemSelected — list items with hover/selected states
  • .entityInfo, .entityName, .entityMeta, .entityTags — list item layout
  • .createForm, .createFormActions — inline form styling
  • .metaGrid — key-value metadata layout
  • .sectionTags — tag group with wrap
  • .inheritedNote — small italic annotation text
  • .securitySection / .resetForm — password management styling

Keep existing stat cards above tabs — these are a useful addition not present in the mock.

8b. Users Tab

List pane:

  • Search: Input with search icon, filters across username, displayName, email (client-side)
  • Create button: "+ Add User" opens inline form (not modal)
  • Inline create form:
    • Input: Username (required), Display Name, Email
    • Input: Password (required)
    • Client-side validation: duplicate username check, required fields
    • Cancel + Create buttons
    • Note: Admin-created users are always local. OIDC users are auto-provisioned on first login (no admin creation needed). The create form does not include a provider selector.
  • Entity list: role="listbox", each item role="option" with tabIndex={0}
    • Avatar (initials, size sm)
    • Display name + provider Badge (if not local)
    • Email + group path in meta line
    • Direct roles and groups as small Badge tags
    • Click or Enter/Space to select → populates detail pane

Detail pane (when user selected):

  • Header: Avatar (lg) + Display name (InlineEdit for rename) + Email + Delete button
  • Status: "Active" Tag
  • Metadata grid: User ID (MonoText), Created (formatted date+time), Provider
  • Security section:
    • Local users: masked password display + "Reset password" button → toggles inline form (new password Input + Cancel/Set)
    • OIDC users: InfoCallout "Password managed by identity provider"
  • Group membership:
    • Current groups as removable Tag components
    • MultiSelect dropdown to add groups
    • Warning on removal if inherited roles would be revoked
  • Effective roles:
    • Direct roles: removable Tag (warning color)
    • Inherited roles: dashed Badge with "↑ groupName" source notation (opacity 0.65, non-removable)
    • MultiSelect to add direct roles
    • Note: "Roles with ↑ are inherited through group membership"
  • Delete: ConfirmDialog requiring username to be typed. Self-delete guard (can't delete own account).

API hooks used: useUsers, useUser, useCreateUser, useUpdateUser, useDeleteUser, useAssignRoleToUser, useRemoveRoleFromUser, useAddUserToGroup, useRemoveUserFromGroup, useGroups, useRoles

8c. Groups Tab

List pane:

  • Search: filter by group name
  • Create form: inline with name + parent group Select dropdown (options: "Top-level" + all existing groups)
  • Entity list:
    • Avatar + group name
    • Meta: "Child of {parent}" or "Top-level" + child count + member count
    • Role tags

Detail pane:

  • Header: Group name (InlineEdit for non-built-in) + parent info + Delete button (disabled for built-in Admins group)
  • Metadata: Group ID (MonoText)
  • Parent group: display current parent
  • Members: removable Tag list + MultiSelect to add users. Note: "+ all members of child groups" if applicable
  • Child groups: removable Tag list + MultiSelect to add existing groups as children. Circular reference prevention (can't add ancestor as child)
  • Assigned roles: removable Tag list + MultiSelect to add roles. Warning on removal: "Removing {role} from {group} will affect N member(s). Continue?"
  • Delete: ConfirmDialog. Guard: built-in Admins group cannot be deleted.

API hooks used: useGroups, useGroup, useCreateGroup, useUpdateGroup, useDeleteGroup, useAssignRoleToGroup, useRemoveRoleFromGroup

8d. Roles Tab

List pane:

  • Search: filter by role name
  • Create form: inline with name (auto-uppercase) + description
  • Entity list:
    • Avatar + role name + "system" Badge (if system role)
    • Meta: description + assignment count
    • Tags: assigned groups (success color) + direct users

Detail pane:

  • Header: Role name + description + Delete button (disabled for system roles)
  • Metadata: Role ID (MonoText), scope, type (system/custom — "System role (read-only)")
  • Assigned to groups: view-only Tag list (shows which groups have this role)
  • Assigned to users (direct): view-only Tag list
  • Effective principals: filled Badge (direct assignment) + dashed Badge (inherited via group). Note: "Dashed entries inherit this role through group membership"

API hooks used: useRoles, useRole, useCreateRole, useUpdateRole, useDeleteRole

8e. Shared Patterns

  • Toast notifications for all mutations (create, update, delete, assign, remove) — use useToast from design system
  • Cascade warnings when actions affect other entities (removing role from group, removing user from group with roles)
  • Keyboard accessibility: Enter/Space to select, ARIA roles (listbox, option), aria-selected
  • Mutation button states: disable while in-flight, show spinner
  • ToastProvider: Add ToastProvider from design system to LayoutShell.tsx (or app root in main.tsx) to enable useToast() hook across admin pages
  • Graceful empty states: When agent metrics are unavailable (agent not sending a particular metric), show per-chart empty state rather than crashing. Check metric name existence in response before rendering.

File Impact Summary

New files:

  • ui/src/pages/Routes/RouteDetail.tsx + RouteDetail.module.css
  • ui/src/pages/Admin/UserManagement.module.css
  • ui/src/pages/Admin/UsersTab.tsx
  • ui/src/pages/Admin/GroupsTab.tsx
  • ui/src/pages/Admin/RolesTab.tsx
  • ui/src/api/queries/agent-metrics.ts (useAgentMetrics hook)
  • ui/src/api/queries/processor-metrics.ts (useProcessorMetrics hook)
  • ui/src/api/queries/correlation.ts (useCorrelationChain hook)
  • cameleer-server-app/.../controller/AgentMetricsController.java
  • cameleer-server-app/.../dto/ProcessorMetrics.java
  • cameleer-server-app/.../dto/AgentMetricsResponse.java
  • cameleer-server-app/.../dto/MetricBucket.java
  • cameleer-server-app/.../dto/SetPasswordRequest.java
  • cameleer-server-app/src/main/resources/db/migration/V7__processor_stats_by_id.sql

Modified files:

  • ui/package.json — design system ^0.0.2
  • ui/src/router.tsx — add RouteDetail route
  • ui/src/components/LayoutShell.tsx — TopBar onLogout prop, remove Dropdown/Avatar
  • ui/src/pages/Dashboard/Dashboard.tsx — error section, RouteFlow tab, stat card changes
  • ui/src/pages/Dashboard/Dashboard.module.css — new classes
  • ui/src/pages/ExchangeDetail/ExchangeDetail.tsx — correlation chain, flow toggle, processor count
  • ui/src/pages/ExchangeDetail/ExchangeDetail.module.css — new classes
  • ui/src/pages/Routes/RoutesMetrics.tsx — stat card adjustments
  • ui/src/pages/AgentHealth/AgentHealth.tsx — DetailPanel, table enrichment, alert banners, stat cards, scope trail
  • ui/src/pages/AgentHealth/AgentHealth.module.css — new classes
  • ui/src/pages/AgentInstance/AgentInstance.tsx — 3x2 charts, process info, stat cards, log placeholder, version badge
  • ui/src/pages/AgentInstance/AgentInstance.module.css — new classes
  • ui/src/pages/Admin/RbacPage.tsx — restructured to container with split-pane tabs
  • ui/src/pages/Admin/OidcConfigPage.tsx — default roles, ConfirmDialog
  • ui/src/api/schema.d.ts — regenerated with new types
  • cameleer-server-app/.../dto/AgentInstanceResponse.java — add version, capabilities
  • cameleer-server-app/.../controller/AgentRegistrationController.java — map version/capabilities
  • cameleer-server-app/.../controller/RouteMetricsController.java — add processor stats method
  • cameleer-server-app/.../controller/UserAdminController.java — add password reset method
  • cameleer-server-app/.../SecurityConfig.java — add rule for GET /api/v1/agents/*/metrics
  • ui/src/main.tsx or ui/src/components/LayoutShell.tsx — add ToastProvider
  • cameleer-server-app/.../OpenApiConfig.java — register new DTOs

Backend migration:

  • V7__processor_stats_by_id.sql — new stats_1m_processor_detail continuous aggregate with processor_id grouping