docs: add UI mock alignment design spec and implementation plan
Comprehensive spec and 20-task plan to close all gaps between @cameleer/design-system v0.0.2 mocks and the current server UI. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
1625
docs/superpowers/plans/2026-03-23-ui-mock-alignment.md
Normal file
1625
docs/superpowers/plans/2026-03-23-ui-mock-alignment.md
Normal file
File diff suppressed because it is too large
Load Diff
576
docs/superpowers/specs/2026-03-23-ui-mock-alignment-design.md
Normal file
576
docs/superpowers/specs/2026-03-23-ui-mock-alignment-design.md
Normal file
@@ -0,0 +1,576 @@
|
||||
# UI Mock Alignment Design
|
||||
|
||||
**Date:** 2026-03-23
|
||||
**Status:** Reviewed
|
||||
**Scope:** Close all gaps between `@cameleer/design-system` mocks and the cameleer3-server UI
|
||||
|
||||
## Context
|
||||
|
||||
The `@cameleer/design-system` package (v0.0.2) contains fully realized mock pages demonstrating the target UX for the Cameleer3 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`:
|
||||
```sql
|
||||
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>`
|
||||
```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.
|
||||
|
||||
**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`
|
||||
```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).
|
||||
|
||||
**New files:** `cameleer3-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.diagramNodeId` → `PositionedNode.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:
|
||||
```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:
|
||||
|
||||
```
|
||||
┌─────────────────────┬────────────────────┐
|
||||
│ 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)
|
||||
- `cameleer3-server-app/.../controller/AgentMetricsController.java`
|
||||
- `cameleer3-server-app/.../dto/ProcessorMetrics.java`
|
||||
- `cameleer3-server-app/.../dto/AgentMetricsResponse.java`
|
||||
- `cameleer3-server-app/.../dto/MetricBucket.java`
|
||||
- `cameleer3-server-app/.../dto/SetPasswordRequest.java`
|
||||
- `cameleer3-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
|
||||
- `cameleer3-server-app/.../dto/AgentInstanceResponse.java` — add version, capabilities
|
||||
- `cameleer3-server-app/.../controller/AgentRegistrationController.java` — map version/capabilities
|
||||
- `cameleer3-server-app/.../controller/RouteMetricsController.java` — add processor stats method
|
||||
- `cameleer3-server-app/.../controller/UserAdminController.java` — add password reset method
|
||||
- `cameleer3-server-app/.../SecurityConfig.java` — add rule for `GET /api/v1/agents/*/metrics`
|
||||
- `ui/src/main.tsx` or `ui/src/components/LayoutShell.tsx` — add `ToastProvider`
|
||||
- `cameleer3-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
|
||||
Reference in New Issue
Block a user