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.
- 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`:
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>`
```java
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.
-`from` / `to` (optional) — time window, defaults to last 1h
-`buckets` (optional, default 60) — number of time buckets
**Response:** `AgentMetricsResponse`
```java
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`:
```java
// 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`
```java
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).
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.diagramNodeId` → `PositionedNode.id` to connect execution data to diagram nodes.
- **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)
-`.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`:
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 |
- 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:
```tsx
<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:
```bash
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:
- **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
- **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.
- **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"
- **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.