# 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`: ```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` ```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> 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 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:** `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.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 ``` 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) - `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