The relative `to="apps"` didn't resolve correctly. All other legacy
redirects use absolute paths (`to="/apps"`, `to="/runtime"`).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- 7.1: Add deployment status badge (StatusDot + Badge) to AppsTab app
list, sourced from catalog.deployment.status via slug lookup
- 7.3: Add X close button to top-right of exchange detail right panel
in ExchangesPage (position:absolute, triggers handleClearSelection)
- 7.5: PunchcardHeatmap shows "Requires at least 2 days of data"
when timeRangeMs < 2 days; DashboardL1 passes the range down
- 7.6: Command palette exchange results truncate IDs to ...{last8}
matching the exchanges table display
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds amber edit-mode banners to AppConfigDetailPage and both
DefaultResourcesSection/JarRetentionSection in EnvironmentsPage,
matching the existing ConfigSubTab pattern.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- RolesTab: wrap \u00b7 in JS expression {'\u00b7'} so JSX renders the middle dot correctly instead of literal backslash-u sequence
- UsersTab: add confirm password field with mismatch validation, hint text for password policy, and reset on cancel/success
- UserManagement.module.css: add .hintText style for password policy hint
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Throughput chart: divide totalCount by bucket duration (seconds) so Y-axis shows true msg/s instead of raw bucket counts; fixes flat-line appearance when TPS is low but totalCount is large
- Error Rate chart: convert failedCount/totalCount to percentage; change yLabel from "err/h" to "%" to match KPI stat card unit
- Memory chart: add threshold line at jvm.memory.heap.max so chart Y-axis extends to max heap and shows the reference line (spec 5.3)
- Agent state: suppress containerStatus badge when value is "UNKNOWN"; only render it with "Container: <state>" label when a non-UNKNOWN secondary state is present (spec 5.4)
- DashboardTab chartGrid: add pointer-events:none with pointer-events:auto on children so the chart grid overlay does not intercept clicks on the Application Health table rows below (spec 5.5)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Attributes column is now hidden when no exchanges in the current view
have attributes; shown conditionally via hasAttributes check on rows
- Status labels already standardized via statusLabel() in ExchangeHeader
- Agent names truncated to last two hyphen-separated segments via
shortAgentName(); full name preserved as tooltip title
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- formatDuration and formatDurationShort now show Xm Ys for durations >= 60s (e.g. "5m 21s" instead of "321s") and 1 decimal for 1-60s range ("6.7s" instead of "6.70s")
- Exchange ID column shows last 8 chars with ellipsis prefix; full ID on hover, copies to clipboard on click
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Override design system tokens in app root CSS: --text-muted raised to 4.5:1
contrast in both light (#766A5E) and dark (#9A9088) modes; --text-faint dark
mode raised from catastrophic 1.4:1 to 3:1 (#6A6058). Migrate --text-faint
usages on readable text (empty states, italic notes, buttons) to --text-muted.
Raise all 10px and 11px font-size declarations to 12px floor.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
adminFetch called logout() directly on 401/403 responses, which cleared
roles and caused RequireAdmin to redirect to /exchanges while users were
editing forms. Now adminFetch attempts a token refresh before failing,
and RequireAdmin tolerates a transient empty-roles state during refresh.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Return a JSON error body from UserAdminController instead of an empty 400,
and extract API error messages in adminFetch so toasts display the reason.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Detailed step-by-step plan covering critical bug fixes, layout/interaction
consistency, WCAG contrast compliance, data formatting, chart fixes, and
admin polish. Each task includes exact file paths, code snippets, and
verification steps.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Playwright-driven audit of the live UI (build 69dcce2, 60+ screenshots)
covering all pages, CRUD lifecycles, design consistency, and interaction
patterns. Spec defines 8 batches of work: critical bugs, layout
consistency, interaction consistency, contrast/readability, data
formatting, chart fixes, admin polish, and nice-to-have items.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Backend: AgentRegistryService gains findByApplicationAndEnvironment()
and environment-aware addGroupCommandWithReplies() overload.
AgentCommandController and ApplicationConfigController accept optional
environment query parameter. When set, commands only target agents in
that environment. Backward compatible — null means all environments.
Frontend: All command mutations (config update, route control, traced
processors, tap config, route recording) now pass selectedEnv to the
backend via query parameter.
Prevents cross-environment command leakage — e.g., updating config for
prod no longer pushes to dev agents.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add environment parameter to AgentEventsController, AgentEventService,
and ClickHouseAgentEventRepository (filters agent_events by environment)
- Wire selectedEnv to useAgents and useAgentEvents in both AgentHealth
and AgentInstance pages
- Wire selectedEnv to useStatsTimeseries in AgentInstance
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Part A: Environment creation slug is now auto-derived from display name
and shown read-only (matching app creation pattern). Removes manual slug
input.
Part B: All data queries now pass the selected environment to backend:
- Exchanges search, Dashboard L1/L2/L3 stats, Routes metrics, Route
detail, correlation chains, and processor metrics all filter by
selected environment.
- Backend RouteMetricsController now accepts environment parameter for
both route and processor metrics endpoints.
Closes #XYZ
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The DockerEventMonitor only reacted to Docker events. If an event was
missed (e.g., during reconnect or startup race), a DEGRADED deployment
with all replicas healthy would never promote back to RUNNING.
Add a @Scheduled reconciliation (every 30s) that inspects actual
container state and corrects deployment status mismatches.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Full audit of design system adoption, color consistency, inline styles,
layout patterns, and CSS module duplication across the server UI.
Includes 6-phase fix plan.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Prevent removal of last ADMIN role via role unassign, user delete,
or group role removal (returns 409 Conflict)
- Add password policy: min 12 chars, 3/4 character classes, no username
- Add brute-force protection: 5 attempts then 15min lockout, IP rate limit
- Add token revocation on password change via token_revoked_before column
- V9 migration adds failed_login_attempts, locked_until, token_revoked_before
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fixes pre-existing TS2322 where Record<string, string> was not
assignable to the StatusDotVariant union type.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Consolidate 20+ duplicate function definitions across UI components into
three shared util files (format-utils, agent-utils, config-draft-utils).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Part 1 — Config snapshot:
- V8 migration adds resolved_config JSONB to deployments table
- DeploymentExecutor saves the full resolved config at deploy time
- Deployment record includes resolvedConfig for auditability
Part 2 — Composite health StatusDot:
- CatalogController computes composite health from deployment status +
agent health (green only when RUNNING AND agent live)
- CatalogApp includes healthTooltip (e.g. "Deployment: RUNNING,
Agents: live (1 connected)")
- StatusDot added to app detail header with deployment status Badge
- StatusDot added to deployment table rows
- Sidebar passes composite health + tooltip through to tree nodes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Compare app.updatedAt with deployment.deployedAt — if config was
modified after the deployment started, show a primary "Redeploy" button
in the Actions column. Also show a toast hint after saving config:
"Redeploy to apply changes to running deployments."
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Rename cpuShares to cpuRequest (millicores), cpuLimit from cores to
millicores. ResolvedContainerConfig translates to Docker-native units
via dockerCpuShares() and dockerCpuQuota() helpers. Future K8s
orchestrator can pass millicores through directly.
- Fix waitForAnyHealthy to wait for ALL replicas instead of returning
on first healthy one. Prevents false DEGRADED status with 2+ replicas.
- Default app detail to Configuration tab (was Overview)
- Reorder config sub-tabs: Monitoring, Resources, Variables, Traces &
Taps, Route Recording
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Normalize the sidebar selectedPath so the app highlight persists across
tab switches (Dashboard, Runtime, Deployments). Also make sidebar clicks
tab-aware: clicking an app navigates to the current tab's path instead
of always going to /exchanges/.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Consolidate route catalog (agent-driven) and apps table (deployment-
driven) into a single GET /api/v1/catalog?environment={slug} endpoint.
Apps table is authoritative; agent data enriches with live health,
routes, and metrics. Unmanaged apps (agents without App record) appear
with managed=false.
- Add CatalogController merging App records + agent registry + ClickHouse
- Add CatalogApp DTO with deployment summary, managed flag, health
- Change AppController and DeploymentController to accept slugs (not UUIDs)
- Add AppRepository.findBySlug() and AppService.getBySlug()
- Replace useRouteCatalog() with useCatalog() across all UI components
- Navigate to /apps/{slug} instead of /apps/{UUID}
- Update sidebar, search, and all catalog lookups to use slug
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Unify route catalog (agent-driven) and apps table (deployment-driven)
into a single catalog endpoint. Apps table becomes authoritative,
agent data enriches with live health/routes. Slug-based URLs replace
UUIDs for navigation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Redesign DeploymentProgress component: track-based layout with amber
brand color, checkmarks for completed steps, user-friendly labels
(Prepare, Image, Network, Launch, Verify, Activate, Live)
- Delete terminal (STOPPED/FAILED) deployments before creating new ones
for the same app+environment, preventing duplicate rows in the UI
- Update CLAUDE.md with comprehensive key class locations, correct deploy
stages, database migration reference, and REST endpoint summary
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The runtime-base image and all agent Dockerfiles now read
CAMELEER_SERVER_URL instead of CAMELEER_EXPORT_ENDPOINT.
Updated the volume-mode entrypoint override to match.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When CAMELEER_JAR_DOCKER_VOLUME is set, the orchestrator mounts the
named volume at the jar storage path instead of using a host bind mount.
This solves the path translation issue in Docker-in-Docker setups where
the server runs inside a container and manages sibling containers.
The entrypoint is overridden to use the volume-mounted JAR path via
the CAMELEER_APP_JAR env var.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Docker's connectToNetworkCmd needs the network ID (not name) and the
container's network sandbox must be ready. Moving network connection
to DeploymentExecutor where DockerNetworkManager handles ID resolution
and the container is already started.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Nginx defaults to 1MB body size, causing 413 on JAR uploads through
the UI proxy. Matches the Spring Boot multipart limit.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The opacity:0 approach caused the native "Choose File" button to
appear in the accessibility tree and compete for clicks. The clip
pattern properly hides the input while keeping it functional for
programmatic .click().
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Some browsers block programmatic .click() on display:none inputs.
Using position:absolute + opacity:0 keeps the input in the render tree.
Also added type="button" to prevent any form-submission interference.
Applied to both create page and detail view file inputs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Listens to Docker daemon events (die, oom, start, stop) for containers
labeled managed-by=cameleer3-server, updates replica states in Postgres,
and recomputes aggregate deployment status (RUNNING/DEGRADED/FAILED).
Bean is wired in RuntimeOrchestratorAutoConfig via instanceof guard so it
only activates when Docker is available.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extends Deployment with targetState, deploymentStrategy, replicaStates
(List<Map<String,Object>>), and deployStage. Updates withStatus() to
carry the new fields through.
Adds DEGRADED and STOPPING to DeploymentStatus (reordered for lifecycle
clarity). Introduces DeployStage enum for tracking orchestration progress
through PRE_FLIGHT → COMPLETE.
The cameleer-traefik network disables inter-container communication
so app containers cannot reach each other directly — only through
Traefik. Environment networks keep ICC enabled for intra-env comms.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Per-environment "keep last N versions" setting (default 5, null for
unlimited). Nightly scheduled job at 03:00 deletes old versions from
both database and disk, skipping any version that is currently deployed.
Full stack:
- V6 migration: adds jar_retention_count column to environments
- Environment record, repository, service, admin controller endpoint
- JarRetentionJob: @Scheduled nightly, iterates environments and apps
- UI: retention policy editor on admin Environments page with
toggle between limited/unlimited and version count input
- AppVersionRepository.delete() for version cleanup
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New "Default Resource Limits" section in environment detail view with
memory limit/reserve, CPU shares/limit. These defaults apply to new
apps unless overridden per-app.
Added useUpdateDefaultContainerConfig hook for the PUT endpoint.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Environment Variables moved from Resources into a dedicated "Variables"
tab, placed first in the tab order since it's the most commonly needed
config when creating new apps.
Tab order:
- Create page: Variables | Monitoring | Resources
- Detail page: Variables | Monitoring | Traces & Taps | Route Recording | Resources
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
As the user types the app name, the URL builds in real-time:
/{envSlug}/{appSlug}/
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Config sub-tabs are now: Monitoring | Traces & Taps | Route Recording | Resources
(renamed from Agent/Infrastructure, with traces and recording as their own tabs).
Also increase Spring multipart max-file-size and max-request-size to 200MB
to fix HTTP 413 on JAR uploads.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ConfigSubTab now uses inner tabs (Agent / Infrastructure):
- Agent: observability settings, compress success, traces & taps table,
route recording toggles
- Infrastructure: container resources, exposed ports, environment variables
This completes the Config tab consolidation — all features from the
standalone Config page now live in the Deployments tab.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Logs functionality already exists in Runtime tab (AgentHealth/AgentInstance).
Config functionality moved to Deployments tab ConfigSubTab.
Old routes redirect to /runtime and /apps respectively.
Navigation links updated throughout.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Prevents accidental app deletion by requiring the user to type the app
slug before confirming.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace inline create form with a modal that handles the full flow:
- Name → auto-computed slug (editable if needed)
- Environment picker
- JAR file upload
- "Deploy immediately" toggle (on by default)
- Single "Create & Deploy" button runs all three API calls sequentially
with step indicator
After creation, navigates directly to the new app's detail view.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Overview sub-tab:
- Deployments table with env badge, version, status, URL, deployed time
- Actions (Start/Stop) scoped to selected environment; other envs show
"switch env to manage" hint with muted rows
- Versions list with per-env deploy target picker
Configuration sub-tab:
- Read-only by default with Edit mode gate (Cancel/Save banner)
- Agent observability: engine level, payload capture with size unit
selector, log levels, metrics toggle, sampling, replay and route
control (default enabled)
- Container resources: memory/CPU limits, exposed ports as deletable
pills with inline add input
- Environment variables: key-value editor with add/remove
- Reuses existing ApplicationConfig API for agent config push via SSE
Tab renamed from "Apps" to "Deployments" in the tab bar.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The list endpoint on EnvironmentAdminController now overrides the
class-level ADMIN guard with isAuthenticated(), so VIEWERs can see
the environment selector. The LayoutShell merges environments from
both the table and agent heartbeats, so the selector always shows
configured environments even when no agents are connected.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
apps.updated_at already exists from V3. The duplicate ALTER caused
Flyway to fail on startup.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Apps tab visible to OPERATOR+ (hidden for VIEWER), scoped by
sidebar app selection and environment filter
- List view: DataTable with name, environment, updated, created columns
- Detail view: deployments across all envs, version upload with
per-env deploy target, container config form (resources, ports,
custom env vars) with explicit Save
- Memory reserve field disabled for non-production environments
with info hint
- Admin sidebar sorted alphabetically, Applications entry removed
- Old admin AppsPage deleted
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- V5 migration: container_config JSONB + updated_at on apps,
default_container_config JSONB on environments
- App/Environment records updated with new fields
- PUT /apps/{id}/container-config endpoint for per-app config
- PUT /admin/environments/{id}/default-container-config for env defaults
- GET /apps now supports optional environmentId (lists all when omitted)
- AppRepository.findAll() for cross-environment app listing
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- SplitPane layout with environment selector, app list, and detail pane
- Create/delete apps with slug uniqueness validation
- Upload JAR versions with file size display
- Deploy versions and stop running deployments with status badges
- Deployment list auto-refreshes every 5s for live status updates
- Registered at /admin/apps with sidebar entry
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add GET /api/v1/auth/me endpoint returning current user's UserDetail
- Add AboutMeDialog component with role badges and group memberships
- Add userMenuItems prop to TopBar via design-system update
- Wire "About Me" menu item into user dropdown above Logout
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
getDirectRolesForUser filtered on origin='direct', which excluded
roles assigned via claim mapping (origin='managed'). This caused
OIDC users to appear roleless even when claim mappings matched.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When no claim mapping rules are configured or none match the JWT
claims, fall back to assigning the OidcConfig.defaultRoles (e.g.
VIEWER). This restores the behavior that was lost when syncOidcRoles
was replaced with claim mapping.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SplitPane with create/edit/delete, production flag toggle,
enabled/disabled toggle. Follows existing admin page patterns.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Environments now have:
- production (bool): prod vs non-prod resource allocation
- enabled (bool): disabled blocks new deployments
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Local login was blocked when OIDC env vars were present, causing
bootstrap to fail (chicken-and-egg: bootstrap needs local auth to
configure OIDC). The backend now accepts both auth paths; the
frontend/UI decides which login flow to present.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- JAR storage path, base image, Docker network
- Container memory/CPU limits, health check timeout
- Routing mode and domain for Traefik integration
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- EnvironmentAdminController: CRUD under /api/v1/admin/environments (ADMIN)
- AppController: CRUD + JAR upload under /api/v1/apps (OPERATOR+)
- DeploymentController: deploy, stop, promote, logs under /api/v1/apps/{appId}/deployments
- Security rule for /api/v1/apps/** requiring OPERATOR or ADMIN role
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Async container deployment with health check polling
- Stops previous deployment before starting new one
- Configurable memory, CPU, health timeout via application properties
- @EnableAsync on application class for Spring async proxy
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- EnvironmentService: CRUD with slug uniqueness, default env protection
- AppService: CRUD, JAR upload with SHA-256 checksumming
- DeploymentService: create, promote, status transitions
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- EnvironmentRepository, AppRepository, AppVersionRepository, DeploymentRepository
- RuntimeOrchestrator interface with ContainerRequest and ContainerStatus
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- SecurityBeanConfig uses Ed25519SigningServiceImpl.ephemeral() when no jwt-secret
- Fixes pre-existing application context failure in integration tests
- Reverts test jwt-secret from application-test.yml (no longer needed)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- ClaimMappingAdminControllerIT with create+list and delete tests
- Add adminHeaders() convenience method to TestSecurityHelper
- Add jwt-secret to test profile (fixes pre-existing Ed25519 init failure)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- ADMIN-only REST endpoints at /api/v1/admin/claim-mappings
- Full CRUD: list, get by ID, create, update, delete
- OpenAPI annotations for Swagger documentation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- UiAuthController.login returns 404 when OIDC issuer is configured
- JwtAuthenticationFilter skips internal user tokens in OIDC mode (agents still work)
- UserAdminController.createUser and resetPassword return 400 in OIDC mode
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- OidcUserInfo now includes allClaims map from id_token + access_token
- OidcAuthController.callback() calls applyClaimMappings instead of syncOidcRoles
- applyClaimMappings evaluates rules, clears managed assignments, applies new ones
- Supports both assignRole and addToGroup actions
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add clearManagedAssignments, assignManagedRole, addUserToManagedGroup to interface
- Update assignRoleToUser and addUserToGroup to explicitly set origin='direct'
- Update getDirectRolesForUser to filter by origin='direct'
- Implement managed assignment methods with ON CONFLICT upsert
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- GET /api/v1/admin/license returns current license info
- POST /api/v1/admin/license validates and loads new license token
- Requires ADMIN role, validates Ed25519 signature before applying
- OpenAPI annotations for Swagger documentation
- LicenseBeanConfig wires LicenseGate bean with startup validation
- Supports token from CAMELEER_LICENSE_TOKEN env var or CAMELEER_LICENSE_FILE path
- Falls back to open mode when no license or no public key configured
- Add license config properties to application.yml
- JdbcTemplate-based CRUD for claim_mapping_rules table
- RbacBeanConfig wires ClaimMappingRepository and ClaimMappingService beans
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Thread-safe AtomicReference-based license holder
- Defaults to open mode (all features enabled) when no license loaded
- Runtime license loading with feature/limit queries
- Unit tests for open mode and licensed mode
- Evaluates JWT claims against mapping rules
- Supports equals, contains (list + space-separated), regex match types
- Results sorted by priority
- 7 unit tests covering all match types and edge cases
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Validates payload.signature license tokens using Ed25519 public key
- Parses tier, features, limits, timestamps from JSON payload
- Rejects expired and tampered tokens
- Unit tests for valid, expired, and tampered license scenarios
- AssignmentOrigin enum (direct/managed)
- ClaimMappingRule record with match type and action enums
- ClaimMappingRepository interface for CRUD operations
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add origin and mapping_id columns to user_roles and user_groups
- Create claim_mapping_rules table with match_type and action constraints
- Update primary keys to include origin column
- Add indexes for fast managed assignment cleanup
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract inline fontSize/color styles from LogTab, LayoutShell,
UsersTab, GroupsTab, RolesTab, and LevelFilterBar into CSS modules.
Follows project convention of CSS modules over inline styles.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Update @cameleer/design-system to v0.1.38 (12px minimum font size).
Replace all 10px and 11px font sizes with 12px across 25 CSS modules
and 5 TSX inline styles to match the new DS floor.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Expose getDirectRolesForUser on RbacService interface so syncOidcRoles
compares against directly-assigned roles only, not group-inherited ones
- Remove early-return that preserved existing roles when OIDC returned
none — now always applies defaultRoles as fallback
- Update CLAUDE.md and SERVER-CAPABILITIES.md to reflect changes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Logto returns opaque access tokens unless the resource parameter is
included in both the authorization request AND the token exchange.
Append resource to the token endpoint POST body per RFC 8707 so Logto
returns a JWT access token with Custom JWT claims.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Backend holds client_secret and does the token exchange server-side,
making PKCE redundant. Removes code_verifier/code_challenge from all
frontend auth paths and backend exchange method. Eliminates the source
of "grant request is invalid" errors from verifier mismatches.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The OIDC login flow now reads roles from the access_token (JWT) in
addition to the id_token. This fixes role extraction with providers
like Logto that put scopes/roles in access tokens rather than id_tokens.
- Add audience and additionalScopes to OidcConfig for RFC 8707 resource
indicator support and configurable extra scopes
- OidcTokenExchanger decodes access_token with at+jwt-compatible processor,
falls back to id_token if access_token is opaque or has no roles
- syncOidcRoles preserves existing local roles when OIDC returns none
- SPA includes resource and additionalScopes in authorization requests
- Admin UI exposes new config fields
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
extractRoles() only handled List claims (JSON arrays). When rolesClaim
is configured as "scope", the JWT value is a space-delimited string,
which was silently returning [] and falling back to defaultRoles.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Logs received scopes, rolesClaim path, extracted roles, and all claim
keys at each stage of the OIDC auth flow to help debug Logto integration.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Docker build copies package.json before source, so public/ doesn't
exist when npm ci runs postinstall. Use mkdirSync(recursive:true).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
favicon.svg is now copied from @cameleer/design-system/assets on
npm install via postinstall hook. Removed from git tracking
(.gitignore). Updates automatically when DS version changes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fire end-session via fetch(no-cors) instead of window.location redirect.
Always navigate to /login?local regardless of whether end-session
succeeds, preventing broken JSON responses from blocking logout.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace PNG favicons and brand logos with cameleer3-logo.svg from
@cameleer/design-system/assets. Favicon, login dialog, and sidebar
all use the same SVG. Remove PNG favicon files from public/.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
DS now exports ./assets/* — import PNGs directly via Vite instead of
copying to public/. Removes duplicated brand files from public/.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The @cameleer/design-system package.json exports field doesn't include
assets/, causing production build failures. Copy PNGs to public/ and
reference via basePath until DS adds asset exports.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add VITE_APP_VERSION build arg to UI Dockerfile, pass short SHA from
CI docker build step. vite.config.ts truncates to 7 chars so both
CI build and Docker build produce consistent short hashes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Import PNGs via Vite from @cameleer/design-system/assets instead of
copying to public/. Only favicons remain in public/ (needed by HTML).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The SVG uses fill=currentColor (inherits text color). Switch to the
full-color PNG brand icons: 192px for login dialog, 48px for sidebar.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace hand-crafted favicon.svg with official brand assets from
@cameleer/design-system v0.1.32: PNG favicons (16/32px) and
camel-logo.svg for login dialog and sidebar. Update SecurityConfig
public endpoints accordingly. Update documentation for architecture
cleanup (PKCE, OidcProviderHelper, role normalization, K8s hardening,
Dockerfile credential removal, CI deduplication, sidebar path fix).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The standard nginx image requires root to modify /etc/nginx/conf.d
and create /var/cache/nginx directories during startup.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Extract OidcProviderHelper for shared discovery + JWK source construction
- Add SystemRole.normalizeScope() to centralize role normalization
- Merge duplicate claim extraction in OidcTokenExchanger
- Add PKCE (S256) to OIDC authorization flow (frontend + backend)
- Add SecurityContext (runAsNonRoot) to all K8s deployments
- Fix postgres probe to use $POSTGRES_USER instead of hardcoded username
- Remove default credentials from Dockerfile
- Extract sanitize_branch() to shared .gitea/sanitize-branch.sh
- Fix sidebar to use /exchanges/ paths directly, remove legacy redirects
- Centralize basePath computation in router.tsx via config module
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Logout now always redirects to /login?local, either via OIDC
end_session or as a direct fallback, preventing prompt=none
auto-redirect from logging the user back in immediately.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When on Config tab: clicking an app navigates to /config/:appId (shows
that app's config with detail panel). Clicking a route navigates to
/config/:appId (same app config, since config is per-app not per-route).
Clicking Applications header navigates to /config (all apps table).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Config tab now always visible (not just when app selected). Shows all-
app config table at /config, single-app detail at /config/:appId.
Fixed 404 when clicking sidebar nodes while on Config tab — the sidebar
navigation built /config/appId/routeId which had no route. Now falls
back to exchanges tab for route-level navigation from config.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Hide Admin sidebar section for non-ADMIN users
- Add RequireAdmin route guard — /admin/* redirects to / for non-admin
- Move App Config from admin section to main Config tab (per-app,
visible when app selected). VIEWER sees read-only, OPERATOR+ can edit
- Hide diagram node toolbar for VIEWER (onNodeAction conditional)
- Add useIsAdmin/useCanControl helpers to centralize role checks
- Remove App Config from admin sidebar tree
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New input in the Claim Mapping section lets admins configure which
id_token claim is used as the unique user identifier (default: sub).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The OIDC user login ID is now configurable via the admin OIDC setup
dialog (userIdClaim field). Supports dot-separated claim paths (e.g.
'email', 'preferred_username', 'custom.user_id'). Defaults to 'sub'
for backwards compatibility. Throws if the configured claim is missing
from the id_token.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Roles from the id_token's rolesClaim are now diffed against stored
system roles on each OIDC login. Missing roles are added, revoked
roles are removed. Group memberships (manually assigned) are never
touched. This propagates scope revocations from the OIDC provider
on next user login.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
M2M scope mapping now accepts both 'server:admin' and 'admin' (case-
insensitive). OIDC user login role assignment strips the 'server:'
prefix before looking up SystemRole, so 'server:viewer' from the
id_token maps to VIEWER correctly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Without BASE_PATH the redirect fails behind a reverse proxy. Adding
?local prevents the SSO auto-redirect from immediately signing the
user back in after logout.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Logto signs id_tokens with ES384 by default. SecurityConfig already
included it but OidcTokenExchanger only had RS256 and ES256.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When prompt=none fails with consent_required (scopes not yet granted),
retry the OIDC flow without prompt=none so the user can grant consent
once. Uses sessionStorage flag to prevent infinite loops — falls back
to local login if the retry also fails.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When OIDC is configured, the login page automatically redirects to the
provider with prompt=none. If the user has an active OIDC session, they
are signed in without seeing a login page. If the provider returns
login_required (no session), falls back to the login form via ?local.
Users can bypass auto-redirect with /login?local.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Hardcoded /favicon.svg paths skip the <base> tag and fail when served
from a subpath like /server/. Now uses config.basePath in TSX and a
relative href in index.html so the <base> tag resolves correctly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Behind a reverse proxy with strip-prefix (e.g., Traefik at /server/),
the OIDC redirect_uri must include the prefix so the callback routes
back through the proxy. Now uses config.basePath (from <base href>)
instead of hardcoding '/'.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
OidcTokenExchanger fetched the discovery document from the issuerUri
as-is, but the database stores the issuer URI (e.g. /oidc), not the
full discovery URL. Logto returns 404 for the bare issuer path.
SecurityConfig already appended the well-known suffix — now the token
exchanger does the same.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
OidcTokenExchanger cached securityProperties.isOidcTlsSkipVerify() in
the constructor as a boolean field. If Spring constructed the bean
before property binding completed, the cached value was false even when
the env var was set. SecurityConfig worked because it read the property
at call time. Now OidcTokenExchanger stores the SecurityProperties
reference and reads the flag on each call, matching SecurityConfig's
pattern.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Java's automatic redirect following creates new connections that do NOT
inherit custom SSLSocketFactory/HostnameVerifier. This caused the OIDC
discovery fetch to fail on redirect even with TLS_SKIP_VERIFY=true.
Now disables auto-redirect and follows manually with SSL on each hop.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Behind a reverse proxy the browser sends Origin matching the proxy's
public URL, which the single-origin CAMELEER_UI_ORIGIN rejects.
New env var accepts comma-separated origins and takes priority over
UI_ORIGIN, which remains as a backwards-compatible fallback.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Self-signed CA certs on the OIDC provider (e.g. Logto behind a reverse
proxy) cause the login flow to fail because Java's truststore rejects
the connection. This adds an opt-in env var that creates a trust-all
SSLContext scoped to OIDC HTTP calls only (discovery, token exchange,
JWKS fetch) without affecting system-wide TLS.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
config.apiBaseUrl now derives from <base> tag when no explicit config
is set (e.g., /server/api/v1 instead of /api/v1). commands.ts authFetch
prepends apiBaseUrl and uses relative paths.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The second sed matched the just-injected <base href="/server/"> and
rewrote it to <base href="/server/server/">. Since Vite builds with
base: './' (relative paths), the <base> tag alone is sufficient.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When BASE_PATH is set (e.g., /server/), the entrypoint script injects
a <base> tag and rewrites asset paths in index.html. React Router reads
the basename from the <base> tag. Vite builds with relative paths.
Default / for standalone mode (no changes).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When set, fetches JWKs from this URL directly instead of discovering
from the OIDC well-known endpoint. Needed when the public issuer URL
(e.g., https://domain.com/oidc) isn't reachable from inside containers
but the internal URL (http://logto:3001/oidc/jwks) is.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
pg_isready without -U defaults to OS user "root" which doesn't exist
as a PostgreSQL role, causing noisy log entries.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
K8s $(VAR) substitution only resolves env vars defined earlier in the
list. PG_USER and PG_PASSWORD must come before DB_URL.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
LOGTO_ENDPOINT and LOGTO_ADMIN_ENDPOINT are public-facing URLs that
Logto uses for OIDC discovery, issuer URI, and redirects. When behind
a reverse proxy (e.g., Traefik), set these to the external URLs.
Logto requires its own subdomain (not a path prefix).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Explicit spring.datasource.url in YAML takes precedence over the env var,
causing deployed containers to connect to localhost instead of the postgres
service. Now the YAML uses ${SPRING_DATASOURCE_URL:...} so the env var
wins when set. Flyway inherits from the datasource (no separate URL).
Removed CAMELEER_DB_SCHEMA — schema is part of the datasource URL.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Existing deployment has tables in public schema. The new tenant_default
default breaks startup because Flyway sees an empty schema. Override to
public for backward compat; new deployments use the tenant-derived default.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Schema now defaults to tenant_${cameleer.tenant.id} (e.g. tenant_default,
tenant_acme) instead of public. Flyway create-schemas: true ensures the
schema is auto-created on first startup. CAMELEER_DB_SCHEMA env var still
available as override for feature branch isolation. Removed hardcoded
public schema from K8s base and main overlay.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Comprehensive standalone document covering API surface, agent protocol,
security, storage, multi-tenancy, deployment, and configuration — designed
for external systems (like the SaaS orchestration layer) that need to
understand and manage Cameleer3 Server instances.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Environment selector was losing its value on navigation because URL search
params were silently dropped by navigate() calls. Moved to a Zustand store
with localStorage persistence so the selection survives navigation, page
refresh, and new tabs. Switching environment now resets all filters, clears
URL params, invalidates queries, and remounts pages via Outlet key. Also
syncs openapi.json schema with running backend.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
HeartbeatRequest now carries environmentId (cameleer3-common update).
Auto-heal prefers the heartbeat value (most current) over the JWT
claim, ensuring agents recover their correct environment immediately
on the first heartbeat after server restart.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add 'env' claim to agent JWTs (set at registration, carried through
refresh). Auto-heal on heartbeat/SSE now reads environment from the
JWT instead of hardcoding 'default', so agents retain their correct
environment after server restart.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
DS v0.1.31 changes .env wrapper to neutral button style matching
other TopBar controls. Simplified selector CSS to inherit all
font/color properties from the wrapper.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Make the select transparent (no border, no background) so it
inherits the DS .env pill styling (success-colored badge with
mono font). Negative margins compensate for the pill padding.
Dropdown chevron uses currentColor to match the pill text.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use unfiltered agent query to discover environments (avoids circular
filter). Always show selector even with single environment so it's
visible as a label. Default to ['default'] when no agents connected.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Update @cameleer/design-system to v0.1.30 which accepts ReactNode
for the environment prop. Move EnvironmentSelector from standalone
div into TopBar, rendering between theme toggle and user menu.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Backend: Added optional `environment` query parameter to catalog,
search, stats, timeseries, punchcard, top-errors, logs, and agents
endpoints. ClickHouse queries filter by environment when specified
(literal SQL for AggregatingMergeTree, ? binds for raw tables).
StatsStore interface methods all accept environment parameter.
UI: Added EnvironmentSelector component (compact native select).
LayoutShell extracts distinct environments from agent data and
passes selected environment to catalog and agent queries via URL
search param (?env=). TopBar shows current environment label.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds configurable tenant ID (CAMELEER_TENANT_ID env var, default:
"default") and environment as a first-class concept. Each server
instance serves one tenant with multiple environments.
Changes across 36 files:
- TenantProperties config bean for tenant ID injection
- AgentInfo: added environmentId field
- AgentRegistrationRequest: added environmentId field
- All 9 ClickHouse stores: inject tenant ID, replace hardcoded
"default" constant, add environment to writes/reads
- ChunkAccumulator: configurable tenant ID + environment resolver
- MergedExecution/ProcessorBatch/BufferedLogEntry: added environment
- ClickHouse init.sql: added environment column to all tables,
updated ORDER BY (tenant→time→env→app), added tenant_id to
usage_events, updated all MV GROUP BY clauses
- Controllers: pass environmentId through registration/auto-heal
- K8s deploy: added CAMELEER_TENANT_ID env var
- All tests updated for new signatures
Closes#123
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When no exchange is selected, the topology-only diagram now shows
the RouteControlBar above it (if the agent supports routeControl
or replay and the user has OPERATOR/ADMIN role). This fixes a gap
where suspended routes with no recent exchanges had no way to be
resumed from the UI.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The heartbeat now carries capabilities (per protocol v2 update).
On each heartbeat, capabilities are updated in the agent registry.
On auto-heal (server restart), capabilities from the heartbeat
are used instead of empty Map.of(), so the agent's feature flags
(replay, routeControl, logForwarding, etc.) are restored
immediately on the first heartbeat.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Convert ClickHouseUsageTracker and ClickHouseMetricsQueryStore to
use JDBC parameterized queries (? binds) — these query raw tables
without AggregateFunction columns.
Fix lit(String) in RouteMetricsController and ClickHouseStatsStore
to escape backslashes before single quotes. Without this, an input
like \' breaks out of the string literal in ClickHouse (where \
is an escaped backslash). These must remain as literal SQL because
the ClickHouse JDBC 0.9.x driver wraps PreparedStatement in
sub-queries that strip AggregateFunction types, breaking -Merge
combinators.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Running mvn sonar:sonar as a separate invocation skips child
modules. Combining verify and sonar:sonar in a single mvn
command ensures the reactor processes all modules.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Maven sonar plugin auto-detects sources and tests from the POM
module structure. Passing sonar.sources as CLI args caused path
doubling (module-dir/module-dir/src) in multi-module projects.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The standalone sonar-scanner CLI has Java discovery issues in the
build container. Switch to the Maven sonar plugin (same approach
as cameleer3 agent repo), which uses Maven's own JDK. This also
removes the sonar-scanner download/install step entirely.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
sonar-scanner 6.x checks SONAR_SCANNER_JAVA_HOME, not JAVA_HOME.
Despite JAVA_HOME being correct and java being on PATH, the scanner
uses its own env var for Java discovery.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
java binary may not be on PATH directly in the build container.
Derive JAVA_HOME from the jar binary location (which we know works)
and prepend JAVA_HOME/bin to PATH so sonar-scanner can find java.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
sonar-scanner 6.x requires JAVA_HOME or java on PATH. The build
container has Java installed but doesn't export JAVA_HOME, so
derive it from the java binary location.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
jar xf doesn't preserve Unix file permissions from zip entries,
so the sonar-scanner binary lacks the execute bit.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Diagnostics showed ~3,200 tiny inserts per 5 minutes:
- processor_executions: 2,376 inserts (14 rows avg) — one per chunk
- logs: 803 inserts (5 rows avg) — synchronous in HTTP handler
Fix 1: Consolidate processor inserts — new insertProcessorBatches() method
flattens all ProcessorBatch records into a single INSERT per flush cycle.
Fix 2: Buffer log inserts — route through WriteBuffer<BufferedLogEntry>,
flushed on the same 5s interval as executions. LogIngestionController now
pushes to buffer instead of inserting directly.
Also reverts async_insert config (doesn't work with JDBC inline VALUES).
Expected: ~3,200 inserts/5min → ~160 (20x reduction in part creation,
MV triggers, and background merge work).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Diagnostics showed 3,200 tiny inserts per 5 minutes (processor_executions:
2,376 at 14 rows avg, logs: 803 at 5 rows avg), each creating a new part
and triggering MV aggregations + background merges. This was the root cause
of ~400m CPU usage at 3 tx/s.
async_insert=1 with 5s busy timeout lets ClickHouse buffer incoming inserts
and consolidate them into fewer, larger parts before writing to disk.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Increase ingestion flush interval from 500ms to 5000ms to reduce MV merge storms
- Reduce ClickHouse background_schedule_pool_size from 8 to 4
- Rename LIVE/PAUSED badge labels to AUTO/MANUAL across all pages
- Update design system to v0.1.29
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- ChunkAccumulator now extracts inputBody/outputBody/inputHeaders/outputHeaders
from ExecutionChunk.inputSnapshot/outputSnapshot instead of storing empty strings
- Set ClickHouse server log level to warning (was trace by default)
- Update CLAUDE.md to document Ed25519 key derivation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace DB-persisted keypair with deterministic derivation from
CAMELEER_JWT_SECRET via HMAC-SHA256 seed + seeded SHA1PRNG KeyPairGenerator.
Same secret = same key pair across restarts, no private key in the database.
Closes#121
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The keypair was generated ephemerally on each startup, causing agents
to reject all commands after a server restart (signature mismatch).
Now persisted to PostgreSQL server_config table and restored on startup.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Merge all V1-V11 migration scripts into one idempotent init.sql
- Simplify ClickHouseSchemaInitializer to load single file
- Replace route_diagrams projection with in-memory caches:
hashCache (routeId+instanceId → contentHash) warm-loaded on startup,
graphCache (contentHash → RouteGraph) lazy-populated on access
- Eliminates 9M+ row scans on diagram lookups
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ClickHouse 24.12 requires this setting before adding projections to
ReplacingMergeTree tables. Using 'drop' mode which discards the projection
during deduplication merges and rebuilds it afterward.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Alphabetical sort put V10/V11 before V2-V9 ("V11" < "V1_" in ASCII),
causing the route_diagrams projection to run before the table existed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Set com.clickhouse log level to INFO and org.apache.hc.client5 to WARN
- Admin hooks (useUsers/useGroups/useRoles) now only fetch on admin pages,
eliminating AUDIT view_users entries on every UI click
- Add ClickHouse projection on route_diagrams for (tenant_id, route_id,
instance_id, created_at) to avoid full table scans on diagram lookups
- Bump @cameleer/design-system to v0.1.28 (PAUSED mode time range fix,
refreshTimeRange API)
- Call refreshTimeRange before invalidateQueries in PAUSED mode manual
refresh so sidebar clicks use current time window
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
On first click, Dashboard was in non-split mode. The click set
selectedId locally then triggered split view, which remounted
Dashboard — losing the selectedId state.
Added activeExchangeId prop passed from ExchangesPage so the
selection survives the remount. Also syncs via useEffect when
parent changes selection (e.g. correlated exchange navigation).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
useLiveQuery returned enabled:false when paused, which prevented
queries from running at all. Changed to enabled:true always —
PAUSED now means "fetch once, no polling" instead of "don't fetch".
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
After server restart, auto-healed agents register with empty
routeIds. The catalog only looked at agent registry for routes,
so routes and counts disappeared.
Now merges route IDs from ClickHouse stats_1m_route into the
catalog. Also includes apps that only exist in ClickHouse data
(no agent currently registered). Routes and exchange counts
survive server restarts.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When autoRefresh is disabled, sidebar clicks now invalidate all
queries (queryClient.invalidateQueries()), triggering a re-fetch.
This gives users "click to refresh" behavior instead of stale data.
When LIVE mode is on, queries already poll at intervals, so no
invalidation is needed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When the Applications section is already expanded, clicking the
header now navigates to /{tab} (all applications) instead of
collapsing. When collapsed, clicking expands as before.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three related issues caused by in-memory agent registry being empty
after server restart:
1. JwtAuthenticationFilter rejected valid agent JWTs if agent wasn't
in registry — now authenticates any valid JWT regardless
2. Heartbeat returned 404 for unknown agents — now auto-registers
the agent from JWT claims (subject, application)
3. SSE endpoint returned 404 — same auto-registration fix
JWT validation result is stored as a request attribute so downstream
controllers can extract the application claim for auto-registration.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The refresh endpoint required the agent to exist in the in-memory
registry. After server restart the registry is empty, so all refresh
attempts got 404. The refresh token itself is self-contained with
subject, application, and roles — the registry lookup is optional.
Now uses application from the JWT, falling back to registry only
if the agent happens to be registered.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The system.processes query was returning its own row. Added
filter: query NOT LIKE '%system.processes%'
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
AdminLayout was a plain div with padding but no scroll. The parent
<main> has overflow:hidden, so admin page content beyond viewport
height was clipped. Added flex:1, overflow:auto, minHeight:0 to
make AdminLayout a proper scroll container.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
UsageFlushScheduler was a @Component with @ConditionalOnBean, but
ClickHouseUsageTracker is created via @Bean — component scan runs
first, so the condition always evaluated false. Events accumulated
in the WriteBuffer but flush() was never called.
Moved scheduler to @Bean in StorageBeanConfig with the same
@ConditionalOnProperty guard as the tracker.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Icons now reflect event type (UserPlus for registration, Skull
for dead, HeartPulse for recovery, Route for state changes, etc.)
while severity still drives the color. Updated in both
AgentInstance and AgentHealth pages.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Disabled buttons now show reduced opacity (0.35) and muted icon
color instead of just changing the cursor.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Buttons are disabled based on route state: Started disables
Start/Resume, Stopped disables Stop/Suspend/Resume, Suspended
disables Start/Suspend. State looked up from catalog API.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Version injected at build time via VITE_APP_VERSION env var.
CI sets it to branch@sha. Falls back to 'dev' in local dev.
Displayed next to "Cameleer" in the sidebar header.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
collectStarredItems used 'app:' prefix for route keys but
buildAppTreeNodes uses 'route:' prefix. Routes were starred
but never matched in the starred section.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Admin section stays in its fixed position (after Starred, before
Footer). Entering admin mode collapses Applications and Starred
but does not reorder sections.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove Agents and Routes sections from sidebar. Layout is now:
Header (camel logo + Cameleer) → Search → Applications section →
Starred section (when items exist) → Footer (Admin + API Docs).
Admin accordion: clicking Admin navigates to /admin/rbac and
expands Admin section at top while collapsing Applications and
Starred. Clicking Applications exits admin mode.
Removed buildAgentTreeNodes and buildRouteTreeNodes from
sidebar-utils (no longer needed).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pool was hardcoded to 10 connections serving 7 concurrent write
streams + UI reads, causing "too many simultaneous queries" and
WriteBuffer overflow. Pool now defaults to 50 (configurable via
clickhouse.pool-size), flush interval reduced from 1000ms to 500ms.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace local HeartbeatRequest DTO with the shared model from
cameleer3-common. Message types exchanged between server and agent
belong in the common module.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace ACK-based route state inference with agent-reported state.
Heartbeats now carry optional routeStates map, and ROUTE_STATE_CHANGED
events update the registry immediately.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Defines two backward-compatible mechanisms for accurate route state
tracking: heartbeat extension (routeStates map in heartbeat body)
and ROUTE_STATE_CHANGED events for real-time updates. Covers
agent-side detection via Camel EventNotifier, server-side handling,
multi-agent conflict resolution, and migration path.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add routeState field to RouteSummary DTO (null for started, 'stopped'
or 'suspended' for non-default states). Sidebar shows stop/pause icons
and state badge for affected routes in both Apps and Routes sections.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
In-memory registry that infers route state (started/stopped/suspended)
from successful route-control command ACKs. Updates state only when all
agents in a group confirm success.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add CommandGroupResponse and ConfigUpdateResponse types. Switch
useSendGroupCommand and useSendRouteCommand from openapi-fetch to authFetch
returning CommandGroupResponse. Update useUpdateApplicationConfig to return
ConfigUpdateResponse and fix all consumer onSuccess callbacks to access
saved.config.version instead of saved.version.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add addGroupCommandWithReplies() to AgentRegistryService that sends commands
to all LIVE agents in a group and returns CompletableFuture per agent for
collecting replies. Update sendGroupCommand() and pushConfigToAgents() to
wait with a shared 10-second deadline, returning CommandGroupResponse with
per-agent status, timeouts, and overall success. Config update endpoint now
returns ConfigUpdateResponse wrapping both the saved config and push result.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Stop and suspend route commands now show a ConfirmDialog requiring
typed confirmation before dispatch. Start and resume execute
immediately without confirmation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Direct navigation to /admin/* now correctly opens Admin section
and collapses operational sections on first render. Previously
the accordion effect only triggered on route transitions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Review feedback: buildRouteTreeNodes was defined but never rendered.
Added Routes section between Agents and Admin. Removed duplicate
padding on admin pages (AdminLayout handles its own padding).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Search: DS renders dumb input, app owns filterQuery state and
passes it to each SidebarTree. Icon-rail click: fires both
onCollapseToggle and onToggle simultaneously, no navigation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaces the previous "hide sidebar on admin" approach with a
composable compound component design. DS provides shell + building
blocks (Sidebar, Section, Footer, SidebarTree); consuming app
controls all content, section ordering, accordion behavior, and
icon-rail collapse.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Review feedback: breadcrumb memo had an unused isAdminPage branch
(TopBar no longer renders on admin pages). Added aria-label to
icon-only logout button for screen readers.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
AdminLayout gains a self-contained header (Back / Admin / user+logout)
with CSS module styles, replacing the inline padding wrapper. Admin
pages now render fully without the main app chrome.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Pass null as sidebar prop, guard TopBar and CommandPalette with
!isAdminPage, and remove conditional admin padding from main element.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
LIKE is case-sensitive in ClickHouse. Switch to ILIKE for message,
stack_trace, and logger_name searches so queries match regardless
of casing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Recursive case-insensitive highlighting of the search query in
collapsed message, expanded full message, and stack trace. Uses the
project's amber accent color for the highlight mark.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use attributeBadgeColor() (hash-based) instead of "auto" so the same
application name gets the same badge color across all pages.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ClickHouse 24.12 new query analyzer resolves countMerge(total_count)
in the CASE WHEN to the SELECT alias (UInt64) instead of the original
AggregateFunction column when the alias has the same name. Renamed
aliases to tc/fc to avoid the collision.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ClickHouse rejects countMerge() in ORDER BY after GROUP BY because the
column is already finalized to UInt64. Use the SELECT alias instead.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The build container lacks unzip. The JDK jar command handles zip
extraction natively.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The table and materialized view were missing the processor_type column,
causing the RouteMetricsController query to fail and the dashboard
processor metrics table to render empty.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add GET /search/attributes/keys endpoint that queries distinct
attribute key names from ClickHouse using JSONExtractKeys. Attribute
keys appear in the cmd-k Attributes tab alongside attribute value
matches from exchange results.
- SearchIndex.distinctAttributeKeys() interface method
- ClickHouseSearchIndex implementation using arrayJoin(JSONExtractKeys)
- SearchController /attributes/keys endpoint
- useAttributeKeys() React Query hook
- buildSearchData includes attribute keys as 'attribute' category items
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Routes with the same name across different applications (e.g., "route1"
in both QUARKUS-APP and BACKEND-APP) were deduplicated because they
shared the same id (routeId). Use appId/routeId as the id so all
routes appear in cmd-k results.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The searchData useMemo recomputed on every catalog poll cycle because
catalogData got a new array reference even when content was unchanged.
This caused the CommandPalette list to re-render and reset scroll.
Use a ref with deep equality check to keep a stable catalog reference,
only updating when the actual data changes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The command palette renders matchContext via dangerouslySetInnerHTML
expecting HTML with <mark> tags, but extractSnippet() returned plain
text. Wrap the matched term in <mark> tags and escape surrounding
text to prevent XSS.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ID fields (execution_id, correlation_id, exchange_id) should use
exact equality, not LIKE with wildcards. LIKE is only needed for
the _search_text full-text columns.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The _search_text materialized column only contained error messages,
bodies, and headers — not execution_id, correlation_id, exchange_id,
or route_id. Searching by ID via cmd-k returned no results.
- Add ID fields to _search_text in ClickHouse DDL (covered by ngram
bloom filter index)
- Add direct LIKE matches on execution_id, correlation_id, exchange_id
in the text search WHERE clause for faster exact ID lookups
Requires ClickHouse table recreation (fresh install).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sidebar selectedPath now uses sidebarReveal on all tabs, not just
exchanges. This fixes sidebar highlighting on dashboard and runtime.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The chunked ingestion path hardcoded hasTraceData=false because the
execution envelope doesn't carry processor bodies. But the processor
records DO have inputBody/outputBody — we just need to check them.
Track hasTraceData across chunks in PendingExchange and pass it to
MergedExecution when the final chunk arrives or on stale sweep.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The identity rename (application→applicationId) broke search filtering
because the stale schema.d.ts still had 'application' as the field name.
The backend silently ignored the unknown field, returning unfiltered results.
- Regenerate openapi.json and schema.d.ts from live backend
- Fix Dashboard: application→applicationId in search request
- Fix RouteDetail: application→applicationId in search request (2 places)
- LayoutShell: scope command palette search by appId/routeId
- LayoutShell: pass sidebarReveal state on sidebar click navigation
Note for DS team: the Sidebar selectedPath logic (line 5451 in dist)
has a hardcoded pathname.startsWith("/exchanges/") guard. This should
be broadened to simply `S ? S : $.pathname` so sidebarReveal works on
all tabs (dashboard, runtime), not just exchanges.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two fixes:
- Pass sidebarReveal state on sidebar navigation so the design system
can highlight the selected entry (it compares internal /apps/... paths
against this state value, not the browser URL)
- Command palette search now includes scope.appId and scope.routeId
so results are filtered to the current sidebar selection
Note: sidebar highlighting works on the exchanges tab. The design
system's selectedPath logic only checks pathname.startsWith("/exchanges/")
for sidebarReveal — a DS update is needed to support /dashboard/ and
/runtime/ tabs too.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The AgentInstanceResponse backend DTO uses instanceId, displayName,
applicationId, status — but the stale schema.d.ts still had id, name,
application, state. This caused the runtime table to show no data.
- Update schema.d.ts AgentInstanceResponse fields
- Fix AgentHealth: row.id→instanceId, row.name→displayName,
row.application→applicationId, inst.id→instanceId
- Fix AgentInstance: agent.id→instanceId, agent.name→displayName
- Fix ExchangeHeader: agent.id→instanceId, agent.state→status
- Fix LayoutShell search: agent.state→status, agentTps→tps
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rewrite ClickHouse admin to show useful storage metrics instead of
often-empty system.events data. Add active queries section.
- Replace performance endpoint: query system.parts for disk size,
uncompressed size, compression ratio, total rows, part count
- Add /queries endpoint querying system.processes for active queries
- Frontend: storage overview strip, tables with total size, active
queries DataTable
- Fix AgentHealth.tsx type: agentId → instanceId in inline type cast
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Complete the ClickHouse migration by removing all PostgreSQL analytics
code. PostgreSQL now serves only RBAC, config, and audit — all
observability data is exclusively in ClickHouse.
- Delete 6 dead PostgreSQL store classes (executions, stats, diagrams,
events, metrics, metrics-query) and 2 integration tests
- Delete RetentionScheduler (ClickHouse TTL handles retention)
- Remove all 7 cameleer.storage.* feature flags from application.yml
- Remove all @ConditionalOnProperty from ClickHouse beans in StorageBeanConfig
- Consolidate 14 Flyway migrations (V1-V14) into single clean V1 with
only RBAC/config/audit tables (no TimescaleDB, no analytics tables)
- Switch from timescale/timescaledb-ha:pg16 to postgres:16 everywhere
(docker-compose, deploy/postgres.yaml, test containers)
- Remove TimescaleDB check and /metrics-pipeline from DatabaseAdminController
- Set clickhouse.enabled default to true
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Applications, routes within each app, and agents within each app
are now sorted by name using localeCompare.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The backend identity rename (applicationName → applicationId,
agentId → instanceId) was not reflected in the frontend. This caused
drilldown to fail (detail.applicationName was undefined, disabling
the diagram fetch) and various display issues.
Updated schema.d.ts, ExchangeHeader, ExecutionDiagram, Dashboard,
AgentHealth, AgentInstance, LayoutShell, LogTab, InfoTab, DetailPanel,
ExchangesPage, and tracing-store.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tracks authenticated UI user requests to understand usage patterns:
- New ClickHouse usage_events table with 90-day TTL
- UsageTrackingInterceptor captures method, path, duration, user
- Path normalization groups dynamic segments ({id}, {hash})
- Buffered writes via WriteBuffer + periodic flush
- Admin endpoint GET /api/v1/admin/usage with groupBy=endpoint|user|hour
- Skips agent requests, health checks, and data ingestion
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Path containers (EIP_WHEN, EIP_OTHERWISE, etc.) don't have their own
processor records, so they never get an overlay entry. Now inferred
from descendants: green if any descendant executed, red if any failed.
Gated (amber) only when no descendants executed at all.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
A container is only gated (amber) when filterMatched=false or
duplicateMessage=true AND no descendants were executed. Containers
with executed children (split, choice, idempotent that passed) now
correctly show green/red based on their execution status.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CompoundNode now uses execution overlay status to color its header:
failed (red) > completed (green) > default. Previously only used
static type-based color regardless of execution state.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace synthetic wrapper node approach with direct iteration fields:
- ProcessorNode gains iteration (child's index) and iterationSize
(container's total) fields, populated from ClickHouse flat records
- Frontend hooks detect iteration containers from iterationSize != null
instead of scanning for wrapper processorTypes
- useExecutionOverlay filters children by iteration field instead of
wrapper nodes, eliminating ITERATION_WRAPPER_TYPES entirely
- Cleaner data contract: API returns exactly what the DB stores
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
RouteCatalogController, RouteMetricsController, and AgentRegistrationController
had unqualified JdbcTemplate injection, receiving the PostgreSQL template
instead of ClickHouse. The stats queries silently failed (caught exception)
returning 0 counts. Added @Qualifier("clickHouseJdbcTemplate") to all three.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sets TZ=UTC and -Duser.timezone=UTC to guarantee all JVM time operations
use UTC regardless of the container's base image or host configuration.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Timestamp.toString() uses JVM local timezone which can mismatch with
ClickHouse's UTC timezone, causing time-filtered queries to return empty
results. Replaced with DateTimeFormatter.withZone(UTC) in all lit() methods.
Also added warn logging to RouteCatalogController catch blocks to surface
query errors instead of silently swallowing them.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ClickHouse can't rename columns that are part of ORDER BY keys.
Updated V1-V8 DDL files directly with new column names (instance_id,
application_id) and removed V9 migration. Wipe ClickHouse and restart.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Align all internal naming with the agent team's protocol v2 identity rename:
- agentId → instanceId (unique per-JVM identifier)
- applicationName → applicationId (shared app identifier)
- AgentInfo: id → instanceId, name → displayName, application → applicationId
Add SHUTDOWN lifecycle state for graceful agent shutdowns:
- New POST /data/events endpoint receives agent lifecycle events
- AGENT_STOPPED event transitions agent to SHUTDOWN (skips STALE/DEAD)
- New POST /{id}/deregister endpoint removes agent from registry
- Server now distinguishes graceful shutdown from crash (heartbeat timeout)
Includes ClickHouse V9 and PostgreSQL V14 migrations for column renames.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- ElkDiagramRenderer.getElkRoot(): add null guard to prevent NPE
when node is null (SQ java:S2259)
- WriteBuffer: add offerOrWarn() that logs when buffer is full instead
of silently dropping data. ChunkAccumulator now uses this method
so ingestion backpressure is visible in logs (SQ java:S899)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ChunkAccumulator now injects DiagramStore and looks up the content hash
when converting to MergedExecution. Without this, the detail page had
no diagram hash, so the overlay couldn't find the route diagram.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
RouteCatalogController, RouteMetricsController, AgentRegistrationController
all had inline SQL using SUM() on AggregateFunction columns from stats_1m_*
AggregatingMergeTree tables. Replace with countMerge/countIfMerge/sumMerge.
Also fix time_bucket() → toStartOfInterval() and ::double → toFloat64().
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES=false (required
by PROTOCOL.md) and explicit TypeReference<List<ExecutionChunk>> for
array parsing. Without this, batched chunks from ChunkedExporter
(2+ chunks in a JSON array) were silently rejected, causing final:true
chunks to be lost and all exchanges to go stale.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ClickHouseExecutionStore implements ExecutionStore, so the concrete bean
already satisfies the interface — remove redundant wrapper bean. Align
ChunkAccumulator and ExecutionFlushScheduler conditions to
cameleer.storage.executions flag.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add cameleer.storage.executions feature flag (default: clickhouse).
PostgresExecutionStore activates only when explicitly set to postgres.
Add by-seq snapshot endpoint for iteration-aware processor lookup.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dual-mode buildTree: detects seq presence and uses seq/parentSeq linkage
instead of processorId map. Handles duplicate processorIds across
iterations correctly. Old processorId-based mode kept for PG compat.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implements ExecutionStore interface with findById (FINAL for
ReplacingMergeTree), findProcessors (ORDER BY seq), findProcessorById,
and findProcessorBySeq. Write methods unchanged.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add conditional beans for ClickHouseDiagramStore, ClickHouseAgentEventRepository,
and ClickHouseLogStore. All default to ClickHouse (matchIfMissing=true).
PG/OS stores activate only when explicitly configured.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract LogIndex interface from OpenSearchLogIndex. Both ClickHouseLogStore
and OpenSearchLogIndex implement it. Controllers now inject LogIndex.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace partial memory config with full Altinity low-memory guide
settings. Revert container limit from 6Gi back to 4Gi — proper
tuning (mlock=false, reduced caches/pools/threads, disk spill for
aggregations) makes the original budget sufficient.
Switch all storage feature flags to ClickHouse:
- CAMELEER_STORAGE_SEARCH: opensearch → clickhouse
- CAMELEER_STORAGE_METRICS: postgres → clickhouse
- CAMELEER_STORAGE_STATS: already clickhouse
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- ChunkIngestionController: /data/chunks → /data/executions (matches
PROTOCOL.md endpoint the agent actually posts to)
- ExecutionController: conditional on ClickHouse being disabled to
avoid mapping conflict
- Persist originalExchangeId and replayExchangeId from ExecutionChunk
envelope through to ClickHouse (was silently dropped)
- V5 migration adds the two new columns to executions table
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
cameleer3-common removed children, loopIndex, splitIndex,
multicastIndex from ProcessorExecution (flat model only now).
Iteration context lives on synthetic wrapper nodes via processorType.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ClickHouse 24.12 auto-sizes caches from the cgroup limit, leaving
insufficient headroom for MV processing and background merges.
Adds a custom config that shrinks mark/index/expression caches and
caps per-query memory at 2 GiB. Bumps container limit 4Gi → 6Gi.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The V4 DDL had a semicolon inside a comment which caused the
split-on-semicolon logic to produce a comment-only segment that
ClickHouse rejected as empty query. Fixed the comment and made
the initializer strip comment-only segments before execution.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implements StatsStore interface for ClickHouse using AggregatingMergeTree
tables with -Merge combinators (countMerge, countIfMerge, sumMerge,
quantileMerge). Uses literal SQL for aggregate table queries to avoid
ClickHouse JDBC driver PreparedStatement issues with AggregateFunction
columns. Raw table queries (SLA, topErrors, activeErrorTypes) use normal
prepared statements.
Includes 13 integration tests covering stats, timeseries, grouped
timeseries, SLA compliance, SLA counts by app/route, top errors, active
error types, punchcard, and processor stats. Also fixes AggregateFunction
type signatures in V4 DDL (count() takes no args, countIf takes UInt8).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The jdbcTemplate() method was calling dataSource(properties) directly,
creating a new DataSource instance instead of using the Spring-managed
@Primary bean. This caused some repositories to receive the ClickHouse
connection instead of PostgreSQL.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- DDL for executions (ReplacingMergeTree) and processor_executions (MergeTree with seq/parentSeq/iteration)
- ClickHouseExecutionStore with batch INSERT for both tables
- ChunkAccumulator: buffers exchange envelope across chunks, inserts processors immediately, writes execution on final chunk
- ExecutionFlushScheduler drains WriteBuffers to ClickHouse
- ChunkIngestionController: POST /api/v1/data/chunks endpoint
- ClickHouseSearchIndex: ngram-accelerated SQL search implementing SearchIndex interface
- Feature flags: cameleer.storage.search=opensearch|clickhouse
- Uses cameleer3-common ExecutionChunk and FlatProcessorRecord models
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ExecutionFlushScheduler drains MergedExecution and ProcessorBatch write
buffers on a fixed interval and delegates batch inserts to
ClickHouseExecutionStore. Also sweeps stale exchanges every 60s.
ChunkIngestionController exposes POST /api/v1/data/chunks, accepts
single or array ExecutionChunk payloads, and feeds them into the
ChunkAccumulator. Conditional on ChunkAccumulator bean (clickhouse.enabled).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When clickhouse.enabled=true, the ClickHouse JdbcTemplate bean prevents
Spring Boot auto-config from creating the default PG JdbcTemplate.
All PG repositories then get the CH JdbcTemplate and fail with
"Table cameleer.audit_log does not exist".
Fix: explicitly create @Primary DataSource and JdbcTemplate from
DataSourceProperties so PG remains the default for unqualified injections.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
clickhouse-jdbc 0.9.7 rejects async_insert and wait_for_async_insert as
unknown URL parameters. These are server-side settings, not driver config.
Can be set per-query later if needed via custom_settings.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Set CLICKHOUSE_USER/PASSWORD via k8s secret (fixes "disabling network
access for user 'default'" when no password is set)
- Add clickhouse-credentials secret to CI deploy + feature branch copy
- Pass CLICKHOUSE_USERNAME/PASSWORD env vars to server pod
- Make schema initializer non-fatal so server starts even if CH is
temporarily unavailable
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The deploy/clickhouse.yaml manifest was created but not referenced
in the CI workflow. Add kubectl apply between OpenSearch and Authentik.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ClickHouse only has the 'default' database out of the box. The JDBC URL
connects to 'cameleer', so the database must exist before the server starts.
Uses /docker-entrypoint-initdb.d/ init script via ConfigMap.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Testcontainers tests need Docker which isn't available in CI.
Rename to *IT so Surefire skips them (Failsafe runs them with -DskipITs=false).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implements MetricsQueryStore using ClickHouse toStartOfInterval() for
time-bucketed aggregation queries; verified with 4 Testcontainers tests.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
TDD implementation of MetricsStore backed by ClickHouse. Uses native
Map(String,String) column type (no JSON cast), relies on ClickHouse
DEFAULT for server_received_at, and handles null tags by substituting
an empty HashMap. All 4 Testcontainers tests pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds ClickHouseSchemaInitializer that runs on ApplicationReadyEvent,
scanning classpath:clickhouse/*.sql in filename order and executing each
statement. Adds V1__agent_metrics.sql with MergeTree table, tenant/agent
partitioning, and 365-day TTL.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds ClickHouseProperties (bound to clickhouse.*), ClickHouseConfig
(conditional HikariDataSource + JdbcTemplate beans), and extends
application.yml with clickhouse.enabled/url/username/password and
cameleer.storage.metrics properties.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ExchangesPage ignored the exchangeId URL parameter, so selecting an
exchange from the command palette navigated to the right URL but never
displayed the execution overlay. Now derives selection from URL params
as fallback, and LayoutShell passes selectedExchange in state for
exchange/attribute results.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The ExecutionDocument and ExecutionRecord records gained an isReplay
field but the integration tests were not updated, breaking CI.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The X-Cameleer-Replay header is only available when inputSnapshot is
captured (DETAILED/DEEP engine level). The agent always sets
replayExchangeId on RouteExecution, so check that first.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- ElkDiagramRenderer: guard against null containingNode before getElkRoot()
- OpenSearchAdminController: return 503/502 instead of 200 on errors
- DatabaseAdminController: return 503 instead of 200 on connection failure
- SpaForwardController: replace unbound {path} variables with /** wildcards
- WriteBuffer: check offer() return value and log on unexpected rejection
- ApiExceptionHandler: extract getReason() to local var for null safety
- Admin UI pages: handle isError state for disconnected service display
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Detect replayed exchanges via X-Cameleer-Replay header during ingestion,
persist the flag through PostgreSQL and OpenSearch, and surface it in
the dashboard (amber replay icon) and exchange detail chain view.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Exchanges with a _replay attribute now display a small amber
RotateCcw icon between the status dot and route name in the
correlation chain. Tooltip also indicates (replay).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replay audit log now records the agent's reply status (SUCCESS/FAILURE),
message, and error details. Timeout and internal errors are also logged
as FAILURE with the cause.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add dedicated POST /agents/{id}/replay endpoint that uses
addCommandWithReply to wait for the agent ACK (30s timeout).
Returns the actual replay result (status, message, data) instead
of just a delivery confirmation.
Frontend toast now reflects the agent's response: "Replay completed"
on success, agent error message on failure, timeout message if the
agent doesn't respond.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add ROUTE_CONTROL command type and route-control mapping in
AgentCommandController. New RouteControlBar component in the exchange
header shows Start/Stop/Suspend/Resume actions (grouped pill bar) and
a Replay button, gated by agent capabilities and OPERATOR/ADMIN role.
Fix useReplayExchange hook to match protocol section 16: payload now
uses { routeId, exchange: { body, headers }, originalExchangeId, nonce }
instead of the flat { headers, body } format.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- findProcessorInTree now skips non-selected iteration wrappers so
the returned ProcessorNode has data from the correct iteration
- Gate selectedProcessor on overlay presence so processors not
executed in the current iteration don't show in the detail panel
- Header shows "Exchange Details" or "Processor Details" contextually
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CompoundNode (circuit breaker, choice, etc.) now renders at 0.35
opacity when the overlay is active but neither the compound itself
nor any of its diagram descendants appear in the execution overlay.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Render hasTrace/hasTap/status badges inside the node card in both
raw diagram and overlay modes (consistent positioning)
- Pulse only on trace badge in overlay mode when hasTraceData is true
- Fix nodeConfigs to read tracedProcessors from appConfig instead of
never-synced tracing store
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove redundant processor name, status, ID, and duration from the
header bar — all visible in the Info tab and diagram overlay already.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace DetailPanel overlay with direct navigation to
/runtime/:appId/:instanceId on row click. Removes the slide-in panel,
AgentOverviewContent, and AgentPerformanceContent helper components.
The full AgentInstance page already provides all the same data plus
more (charts, routes, logs).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace Recharts ScatterChart with compact SVG grid of small rounded
squares (11x11px, 2px gap). 7 rows (Mon-Sun) x 24 columns (hours).
Color intensity = value relative to max. Transactions = blue scale,
Errors = red scale. Toggle switches between modes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace two separate Transaction/Error punchcard cards with a single
card containing a Transactions/Errors toggle. Uses internal state to
switch between modes without remounting the chart.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add placeholderData to useRouteMetrics and usePunchcard hooks so data
stays stable between refetches instead of going undefined → flicker
- Disable Recharts animation on Treemap (isAnimationActive=false)
- Make .content scrollable (overflow-y: auto, flex: 1, min-height: 0)
so charts below the fold are accessible
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Regenerated schema.d.ts from live backend — now includes slaCompliance
on ExecutionStats/RouteMetrics, filterMatched/duplicateMessage on
ProcessorNode, and all new dashboard endpoints (timeseries/by-app,
timeseries/by-route, punchcard, errors/top, app-settings).
Removed Record<string, unknown> casts that were working around the
stale schema.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- StatusDot: status → variant (correct prop name)
- Badge: color="muted" → color="auto" (valid BadgeColor)
- AreaChart: remove stacked prop (not in AreaChartProps)
- DataTable: remove defaultSort prop (not in DataTableProps)
- TopError → ErrorRow with id field (DataTable requires T extends {id})
- slaCompliance: type assertion for runtime field not in TS schema
- PunchcardHeatmap Scatter shape: proper typing for custom renderer
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace custom SVG chart implementations with Recharts components:
- Treemap: uses Recharts Treemap with custom content renderer for
SLA-colored cells, labels, and click navigation
- PunchcardHeatmap: uses Recharts ScatterChart with custom Rectangle
shape for weekday x hour heatmap grid cells
Both use ResponsiveContainer (no more explicit width/height props) and
rechartsTheme from the design system for consistent tooltip styling.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New exports: rechartsTheme (pre-configured Recharts prop objects matching
design system styling), CHART_COLORS (series color palette), and properly
exported ChartSeries/DataPoint interfaces. No breaking changes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Always use design system CSS variables for colors, never hardcode hex.
Applies to CSS modules, inline styles, and SVG fill/stroke attributes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use var(--amber) and var(--amber-bg) in SVG fill/stroke attributes
instead of hardcoded hex values. SVG presentation attributes resolve
CSS variables correctly, and this respects dark mode theme switching.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use --amber (#C6820E) and --amber-bg (#FDF6E9) from the design system
theme instead of hardcoded #D97706/#FFFBEB.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a filter processor rejects a message (filterMatched=false) or an
idempotent consumer detects a duplicate (duplicateMessage=true), the
compound container turns amber (header, border, body tint).
Also adds red pulsing rings on the failed processor badge (same SMIL
pattern as the teal hasTraceData pulse).
Backend: ProcessorNode gains filterMatched/duplicateMessage fields,
threaded from ProcessorExecution JSON path.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Failed processor nodes now show expanding/fading red rings around the
error badge (same SMIL animation pattern as the teal hasTraceData pulse).
Two staggered circles expand from r=6 to r=14 over 1.5s, making failures
immediately visible in complex route diagrams.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Treemap on left (3fr), two punchcards stacked on right (2fr) using
new .vizRow grid layout. Replaces full-width stacked arrangement.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaced hardcoded width/height on SVG elements with viewBox + width:100%
so both components fill their parent container instead of using fixed pixels.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Treemap: rectangle area = transaction volume, color = SLA compliance
(green→red). Shows apps at L1, routes at L2. Click navigates deeper.
Punchcard heatmap: 7-day rolling weekday x 24-hour grid showing
transaction volume and error patterns. Two side-by-side views
(transactions + errors) reveal temporal clustering.
Backend: new GET /search/stats/punchcard endpoint aggregating
stats_1m_all/app by DOW x hour over rolling 7 days.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add latencyHeatmap prop to ProcessDiagram that colors nodes green→yellow→red
based on their relative contribution to route latency (pctOfRoute). Shows avg
duration label on each node. Threaded through CompoundNode for nested EIP
patterns. Heatmap is active only when no execution overlay is present.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
RouteGraph no longer stores a separate nodes list; getNodes() computes
from root tree. Tests now build proper tree via setRoot() + setChildren()
instead of calling setNodes().
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The agent now sends shallow copies (without children) in the flat nodes
list. Build nodeById map by walking graph.getRoot() tree which preserves
children, falling back to flat list via putIfAbsent for compatibility.
Also adds EIP_FILTER, EIP_IDEMPOTENT_CONSUMER, EIP_RECIPIENT_LIST as
new compound container types per updated DIAGRAMS.md.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Progressive drill-down dashboard following RED method (Rate, Errors,
Duration) with 3 scope levels driven by sidebar selection.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Restores e8039f9. The compound rendering regression was caused by
the agent sending flat nodes without children, not the renderer code.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reverting e8039f9 to diagnose compound rendering regression affecting
all compound types (SPLIT, CHOICE, LOOP, DO_TRY) and error handlers.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Follow the DO_TRY pattern: virtual _CB_MAIN wrapper for main path children,
onFallback rendered as _CB_FALLBACK section with purple dashed border.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The cross-root boundary check in createElkEdges() was too aggressive,
skipping all edges where source and target have different ELK roots.
Compound nodes are their own ELK roots, so valid continuation edges
from the last child inside a compound to the next sibling were lost.
Now allows edges when nodes share a common grandparent or when one
node exits/enters a compound boundary.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Show resolved endpoint URI as teal italic line on diagram nodes
when execution overlay is active
- Enable drill-down for TO and TO_DYNAMIC nodes (not just DIRECT/SEDA)
- Use runtime resolvedEndpointUri from execution overlay for drill-down
when static endpointUri doesn't match
- Increase node height from 50px to 56px to accommodate the third line
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Info tab now reads processor.resolvedEndpointUri instead of hardcoded "-"
- Toolbar buttons highlight in teal/purple when trace/tap is active
- Tooltip changes to "Disable tracing" / "Edit tap" when active
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The toMap() method was missing the has_trace_data field, so it was
never indexed despite being read back in hitToSummary().
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace hand-drawn teardrop paths (looked like plants) with the real
lucide Footprints SVG paths. Configured = bare teal icon, data captured
= white icon in solid teal circle with staggered pulse rings.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Trace data visibility:
- ProcessorNode now includes hasTraceData flag computed from captured
body/headers during tree conversion
- ConfigBadge shows teal for tracing configured, green when data captured
- Search results show green footprints icon for exchanges with trace data
- New has_trace_data column on executions table (V11 migration with backfill)
- OpenSearch documents and ExecutionSummary include the flag
Inline tap configuration:
- Extracted reusable TapConfigModal component from RouteDetail
- Diagram context menu opens tap modal inline instead of navigating away
- Toggle-trace action works immediately with toast feedback
- Modal closes only on ESC, Cancel, Save, or Delete (not backdrop click)
Detail panel tab gating:
- Headers, Input, Output tabs disabled when no data is available
- Works at both exchange and processor level
- Falls back to Info tab when active tab becomes empty
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Each of the ~40 node types now has a distinct, semantically meaningful
lucide icon rendered as crisp SVG paths. Compound node headers also
show their icon left-aligned in the header bar.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
25 rotating cameleer-themed login subtitles picked randomly on each
page load. Also adds the camel logo SVG next to the app name.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace placeholder clock favicon with cameleer camel logo SVG
- Upgrade @cameleer/design-system from v0.1.19 to v0.1.20
- Add minHeight: 0 to main element to complete flex chain for fillHeight DataTable
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Same issue as IngestionService — the ObjectMapper deserializing
processors_json lacked JavaTimeModule, causing Instant parsing to fail
silently and falling back to the broken flat reconstruction.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The ObjectMapper used to serialize the processor tree to JSON lacked
JavaTimeModule, causing Instant fields (startTime, endTime) to fail
silently — processors_json was always null.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fixes iteration overlay corruption caused by flat storage collapsing
duplicate processorIds across loop iterations.
Server:
- Store raw processor tree as processors_json JSONB on executions table
- Detail endpoint serves from processors_json (faithful tree), falls back
to flat record reconstruction for older executions
- V10 migration: processors_json, error categorization (errorType,
errorCategory, rootCauseType, rootCauseMessage), OTel (traceId, spanId),
circuit breaker (circuitBreakerState, fallbackTriggered), drops
erroneous splitDepth/loopDepth columns
- Add all new fields through full ingestion/storage/API chain
UI:
- Fix overlay wrapper filtering: check wrapper type before status filter
- Add new fields to schema.d.ts
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Minimap reflects execution overlay: green for completed, red for failed,
grey for skipped nodes. ENDPOINT nodes are always green when overlay is
active (route entry point, same as main diagram logic).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add resolvedEndpointUri, splitDepth, loopDepth arguments to
ProcessorRecord constructors in TreeReconstructionTest and
PostgresExecutionStoreIT.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wire resolvedEndpointUri through the full chain:
- V9 migration adds resolved_endpoint_uri column
- IngestionService extracts from ProcessorExecution
- PostgresExecutionStore persists and reads the column
- ProcessorNode includes field in detail API response
- UI schema updated for ProcessorNode and PositionedNode
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Server:
- Add endpointUri to PositionedNode (from RouteNode)
- Add fromEndpointUri to RouteSummary (catalog API)
- Catalog controller resolves endpoint URI from diagram store
UI:
- Build endpointRouteMap from catalog's fromEndpointUri field
- Drill-down uses exact match on node.endpointUri against the map
- Remove label parsing heuristics (extractTargetEndpoint, camelToKebab)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove PORT_ALIGNMENT_DEFAULT=BEGIN so NETWORK_SIMPLEX centers edges
at the vertical midpoint of the compound instead of the top.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Edges into/out of compound nodes (DO_TRY, EIP_CHOICE, etc.) now show as
traversed (green) when any descendant node was executed, instead of grey.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Left-align all sections (try_body, doFinally, doCatch) within DO_TRY
- Shrink DO_TRY height to match actual content, removing bottom padding
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Use NETWORK_SIMPLEX placement for vertical centering of root flow nodes
- Skip structural edges from all compound nodes to descendants (not just DO_TRY)
- Reduce DO_TRY section spacing from NODE_SPACING*0.4 to fixed 20px
- Use SVG clipPath for node text instead of character-count truncation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Increase node width (160→220), height (40→50), spacing (90→120)
- Use SVG clipPath for text instead of character-count truncation
- Add UI sources, ESLint report, and sonar-scanner CLI to SonarQube workflow
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Use Sidebar onNavigate callback instead of display:contents click interception
- Use DataTable fillHeight prop instead of manual scroll wrapper divs
- Fix DataTable scroll/pagination by adding overflow:hidden to content container
- Fix left panel in split view to use flex column instead of overflow:auto
- Make error tab stack trace scrollable for large traces
- Add nightly SonarQube workflow with manual trigger support
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Each exchange selection (from table or correlation chain) pushes a
browser history entry with the selected exchange in location.state.
When the user navigates away (to agent details, app scope, etc.) and
presses Back, the previous history entry is restored and the split
view with the diagram reappears exactly as they left it.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Row click no longer navigates to /exchanges/:app/:route/:id which was
changing the search scope. Instead, Dashboard calls onExchangeSelect
callback and ExchangesPage manages the selected exchange as local state.
The search criteria and scope are preserved when selecting an exchange.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Always shows exchange info row: status dot, badge, ID, route, app, duration
- Correlation chain: arrow connectors between nodes, route name + duration per node
- Click on correlated exchange navigates to /exchanges/:app/:route/:exchangeId
- Compact styling with bg-raised background, proper visual hierarchy
- Horizontal scroll for long correlation chains
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New TabKpis component shows scope-aware metrics with trend arrows
aligned right in the content tab bar. Each metric shows current value
and an arrow indicating change vs previous period (green=good, red=bad).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Row click now navigates directly to the split view with diagram.
Removed: DetailPanel, inspect column, unused imports (ExternalLink,
ProcessorTimeline, RouteFlow, useExecutionDetail, useDiagramLayout,
buildFlowSegments).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Sidebar wrapper gets height:100% to fill window
- Route-scoped Exchanges uses same Dashboard table (not compact ExchangeList)
- 50:50 grid split: table on left, diagram on right when route selected
- ContentTabs gets border-bottom and surface background for visibility
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Thin wrapper pages that conditionally render AgentHealth/AgentInstance
and RoutesMetrics/RouteDetail based on URL params for the nav redesign.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Port alignment BEGIN on DO_TRY compounds makes edges attach at the top
instead of center, keeping the main flow level. Post-processing also
stretches all DO_TRY sections (doFinally, doCatch) to match the widest
section's width for visual consistency.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
setPointerCapture on the SVG redirected click/dblclick events away from
node <g> elements, breaking drill-down (double-click) and potentially
click selection. Now only capture the pointer when clicking on empty SVG
space, preserving normal event flow on nodes while keeping drag-to-pan.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ELK's partitioning doesn't reliably order disconnected children within
a compound node. Instead, let ELK lay out freely then re-stack sections
in correct order (try_body → doFinally → doCatch) by adjusting Y
positions in the ELK graph before extraction. This propagates correctly
to both node and edge coordinates via getAbsoluteY().
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Layer constraints (FIRST/LAST) don't work for disconnected components
in ELK's layered algorithm. Replace with invisible edges that chain
try_body → doFinally → doCatch to guarantee correct top-to-bottom order.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The previous fix skipped ALL edges from DO_TRY nodes, which also
removed the continuation edge to the next node in the main flow
(causing LOG nodes to appear disconnected). Now checks if the target
is a descendant of the DO_TRY ELK node — only internal edges are
skipped, continuation edges to the next main flow node are kept.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ELK TB layout places children in insertion order. Now explicitly adds
DO_FINALLY before DO_CATCH so the visual order inside DO_TRY is:
try body (top) → finally → catch blocks (bottom). Also reduces
internal spacing to keep the compound more compact.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Backend: DO_TRY compounds now use a virtual _TRY_BODY wrapper with LR
layout for the try body, while DO_CATCH/DO_FINALLY stack below as
separate sections (TB). Edges from DO_TRY are skipped like route-level
handler edges. Removes ELK-v2 debug logging.
Frontend: _TRY_BODY renders as transparent wrapper, DO_CATCH as red
tinted section, DO_FINALLY as teal section. DO_FINALLY color changed
from red to teal (completion handler, not error).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When pressing Enter in the command palette without explicitly selecting
a result (via arrow keys or mouse), the search query is now applied as
a server-side full-text filter on the Dashboard table. Explicit
selection still navigates to the exchange. Updates design system to
v0.1.18 for the new onSubmit prop.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previously onPointerDown bailed out when the target was inside a node
(data-node-id), blocking pan entirely over nodes and compound groups.
Now panning always starts, and a didPan ref distinguishes drag from
click — node click handlers skip selection when the user was dragging.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds lucide-react and replaces all HTML entity and emoji icons across
the UI with proper SVG icon components. Tree-shaken — only imported
icons are bundled.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Dashboard was fetching 50 results without a status filter and
filtering client-side, causing fewer matches when filtering by error
compared to route-specific pages that filter server-side. Now passes
statusFilters to the OpenSearch query. Backend supports comma-separated
status values for multi-select filters.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root cause: graph.getNodes() is a flat list with duplicates — handler
compound children appear both nested inside their parent AND as
top-level entries. The previous separation tried to filter the flat
list but missed the duplicates, leaving handler children in rootNode.
New approach: walk from graph.getRoot() following non-ERROR edges to
discover main flow nodes. Edges targeting handler compounds (ON_EXCEPTION,
ON_COMPLETION) are not followed. This cleanly separates main flow from
handler sections using the graph's own structure.
Falls back to flat list filtering (old behavior) when graph.getRoot()
is null (legacy/test graphs).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root cause found: RouteGraph.getNodes() is a FLAT list that includes
handler compound children (log8, setBody1, etc.) as top-level entries
alongside the main flow nodes. The handler separation only identified
the compound PARENTS (ON_EXCEPTION) but not their children, so 7
handler children leaked into rootNode as main flow nodes, causing
ELK to place the real main flow at wrong Y positions.
Fix: two-pass separation — first identify handler compounds and
collect ALL descendant IDs, then build mainNodes excluding both
handler compounds AND their descendants.
Debug logging left in temporarily for verification.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The root cause of the Y-offset: ELK places main flow nodes at
arbitrary positions (e.g., y=679) within its root graph, and the
frontend rendered them at those raw positions. Handler sections were
already normalized via shiftNodes, but the main section was not.
Now useDiagramData.ts applies the same normalization to the main
section: computes bounding box, shifts nodes and edges so the section
starts at (0,0). This fixes the Y-offset regardless of what ELK
produces internally.
Removed the backend normalizePositions (was ineffective because handler
nodes at y=12 dominated the global minimum, preventing meaningful shift
of main flow nodes at y=679).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Based on thorough code review, fixes all identified issues:
1. **Y-offset root cause**: Added post-layout normalization that shifts
all positioned nodes and edges so the bounding box starts at (0,0).
ELK can place nodes at arbitrary positions within its root graph;
normalizing compensates regardless of what ELK computes internally.
2. **Bounding box**: Compute from recursively flattened node tree +
edge point bounds. Removes double-counting of compound children
(children have absolute coords, not relative to parent).
3. **SVG double-drawing**: Compound children were drawn both inside
drawCompoundContainer and again in the allNodes loop. Now collects
compound child IDs and skips them in the second pass.
4. **findNode**: Now recurses into children for nested compound lookup.
5. **colorForType**: Removed redundant double-check on EIP_TYPES.
6. **Dead code removed**: routeNodeMap/indexNodeRecursive (populated but
never read), MIN_NODE_WIDTH/CHAR_WIDTH/LABEL_PADDING (unused).
7. **Static initialization**: LayoutMetaDataProvider registration moved
from constructor to static block (runs once, not per instance).
8. **Debug logging removed**: Removed diagnostic System.out.println.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. findCommonParent: replaced with correct lowest common ancestor
algorithm using ancestor set intersection (previous version only
walked from node 'a', not a true LCA)
2. Bounding box: compute totalWidth/totalHeight from actual positioned
node coordinates instead of rootNode.getWidth/Height. The rootNode
dimensions don't account for handler sections in separate ELK roots.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Handler section nodes were positioned relative to rootNode, but they
live under separate handlerRoot ELK graphs. Using getElkRoot() to find
each node's actual root ensures correct absolute coordinates.
This combined with the POLYLINE edge routing should eliminate the
Y-offset misalignment between main flow nodes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Backend:
- Set POLYLINE edge routing on ELK root — eliminates curved/bent edges
between horizontally aligned nodes
- Collect edges from handler section roots (not just main root) so
internal handler edges are included in the layout output
- Use correct root reference for coordinate calculation per edge
Frontend:
- Render ALL edge points as line segments (polylines), not cubic bezier.
ELK bend points are waypoints, not bezier control points — the cubic
bezier interpretation caused false curves.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The tap button in the node toolbar now navigates to
/admin/appconfig?app=<application>&processor=<nodeId>, which
auto-selects the application in the AppConfigPage. The AppConfigPage
reads the ?app query param to open the detail panel for that app.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Handler section ELK roots were missing INCLUDE_CHILDREN, causing
edges between a handler compound and its children to fail with
UnsupportedGraphException (cross-hierarchy edge resolution).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Edges connecting main flow nodes to handler section nodes (ON_EXCEPTION,
ON_COMPLETION) now span different ELK root graphs. ELK throws
UnsupportedGraphException when an edge connects nodes in different
layout hierarchies. Skip these cross-root edges — the frontend doesn't
render them anyway (handler sections are separated visually).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Toggle tracing: "T" → 👣 (footprints — trace = following the path)
- Configure tap: ✎ (pencil) → 🚰 (water tap — tap = intercept the flow)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The flex chain from detailArea → detailPanel → tabContent lacked
min-height: 0, so flex children never shrank below content height
and overflow-y: auto never triggered. Added min-height: 0 and
flex: 1 to propagate the height constraint correctly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ON_EXCEPTION, ON_COMPLETION, and ERROR_HANDLER compounds were included
in the same root ELK graph as the main flow. ELK's layered algorithm
offset the main flow nodes vertically to accommodate the handler
compounds, causing bent arrows between the ENDPOINT and first processor.
Now handler sections get their own independent ELK root graphs. The
frontend already separates and repositions them, so they just need
correct internal layout — not positioning relative to the main flow.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tests were using the old 18-param constructor, missing the 5 new
iteration fields (loopIndex, loopSize, splitIndex, splitSize,
multicastIndex) added in V8 migration.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
No longer needed — the ProcessDiagram is now integrated into
ExchangeDetail via the ExecutionDiagram wrapper.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Added centerOnNodeId prop to ProcessDiagram. When set, the diagram
pans to center the specified node in the viewport. Jump to Error
now selects the failed processor AND centers the viewport on it.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When an onException/error handler section has any executed processors
(overlay entries), it renders with a stronger red tint (8% vs 3%),
a solid red border frame, and a solid divider line. This makes it
easy to identify which handler was triggered when multiple exist.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The execution overlay data maps to the root route's processor IDs. When
drilled into a sub-route, those IDs don't match, causing all nodes to
appear dimmed. Now clears the overlay and shows pure topology when
viewing a sub-route via drill-down.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When drilled into a sub-route, the pre-fetched diagramLayout (loaded by
content hash for the root execution) doesn't contain the sub-route's
diagram. Only use the pre-loaded layout for the root route; fall back to
useDiagramByRoute for drilled-down sub-routes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Make headers tab and timeline tab scrollable when content overflows
- Replace custom <pre> code block with design system CodeBlock component
for body tabs (Input/Output) to match existing styleguide
- Add LINEAR_SEGMENTS node placement strategy to ELK layout to fix
Y-offset misalignment between nodes in left-to-right diagrams
(e.g., ENDPOINT at different Y level than subsequent processors)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Synthesize COMPLETED state for ENDPOINT nodes when overlay is active
(endpoints are route entry points, not in the processor execution tree)
- Move status badge (check/error) inside the card (top-right, below top bar)
to avoid collision with ConfigBadge (TRACE/TAP) badges
- Include ENDPOINT nodes in edge traversal check so the edge from
endpoint to first processor renders as green/traversed
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
React's onWheel is passive by default, so preventDefault() doesn't stop
page scrolling. Attach native wheel listener with { passive: false } via
useEffect instead.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the RouteFlow-based flow view with the new ExecutionDiagram
component which provides execution overlay, iteration stepping, and
an integrated detail panel. The gantt view and all other page sections
remain unchanged.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Composes ProcessDiagram with execution overlay data, exchange summary
bar, resizable splitter, and detail panel into a single root component.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implements the bottom detail panel with processor header bar, tab bar
(Info, Headers, Input, Output, Error, Config, Timeline), and all tab
content components. Info shows processor/exchange metadata in a grid,
Headers fetches per-processor snapshots for side-by-side display,
Input/Output render formatted code blocks, Error extracts exception
types, Config is a placeholder, and Timeline renders a Gantt chart.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
useExecutionOverlay maps processor tree to overlay state map, handling
iteration filtering, sub-route failure detection, and trace data flags.
useIterationState detects compound nodes with iterated children and
manages per-compound iteration selection.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add a left/right stepper widget to compound node headers (LOOP, SPLIT,
MULTICAST) when iteration overlay data is present. Thread executionOverlay,
overlayActive, iterationState, and onIterationChange props through
ProcessDiagram -> CompoundNode -> children and ProcessDiagram ->
ErrorSection -> children so leaf DiagramNode instances render with
execution state (green/red badges, dimming for skipped nodes).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add green solid edges for traversed paths and dashed gray for
not-traversed when execution overlay is active. Includes green
arrowhead marker and overlay threading through CompoundNode and
ErrorSection.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
DiagramNode now accepts executionState and overlayActive props to render
execution status: green tint + checkmark badge for completed nodes, red
tint + exclamation badge for failed nodes, dimmed opacity for skipped
nodes. Duration is shown at bottom-right, and a drill-down arrow appears
for sub-route failures.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Define the execution overlay type system (NodeExecutionState, IterationInfo,
DetailTab) and extend ProcessDiagramProps with optional overlay props. Add
diagramLayout prop so ExecutionDiagram can pass a pre-fetched layout by content
hash, bypassing the internal route-based fetch in useDiagramData.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add iteration fields (loopIndex, loopSize, splitIndex, splitSize,
multicastIndex) to ProcessorNode schema. Add new endpoint path
/executions/{executionId}/processors/by-id/{processorId}/snapshot.
Remove stale diagramNodeId field that was dropped in V6 migration.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add GET /executions/{id}/processors/by-id/{processorId}/snapshot endpoint
that fetches processor snapshot data by processorId instead of positional
index, which is fragile when the tree structure changes. The existing
index-based endpoint remains unchanged for backward compatibility.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add loop_index, loop_size, split_index, split_size, multicast_index
columns to processor_executions table and thread them through the
full storage → ingestion → detail pipeline. These fields enable
execution overlay to display iteration context for loop, split,
and multicast EIPs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Design for overlaying real execution data onto the ProcessDiagram:
- Node status visualization (green OK, red failed, dimmed skipped)
- Per-compound iteration stepping for loops/splits
- Tabbed detail panel (Info, Headers, Input, Output, Error, Config, Timeline)
- Jump to Error with cross-route drill-down
- Backend prerequisites for iteration fields and snapshot-by-id endpoint
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Small overview panel in the bottom-left showing the full diagram
layout with colored node rectangles and an amber viewport indicator.
Click or drag on the minimap to pan the main diagram.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Update design spec with implementation notes covering recursive
compound nesting, edge z-ordering, ON_COMPLETION sections, drill-down
navigation, CSS transform zoom, and HTML overlay toolbar.
Increase SECTION_GAP to 80px for better visual separation between
completion and error handler sections.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Double-click a DIRECT or SEDA node to navigate into that route's
diagram. Breadcrumbs show the route stack and allow clicking back
to any level. Escape key goes back one level.
Route ID resolution handles camelCase endpoint URIs mapping to
kebab-case route IDs (e.g. direct:callGetProduct → call-get-product)
using the catalog's known route IDs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add ON_COMPLETION to backend COMPOUND_TYPES and frontend rendering.
Completion handlers render as teal-tinted sections between the main
flow and error handlers, structurally parallel to onException.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Recursive compound rendering: CompoundNode checks if children are
themselves compound types (WHEN inside CHOICE) and renders them
recursively. Added EIP_WHEN, EIP_OTHERWISE, DO_CATCH, DO_FINALLY
to frontend COMPOUND_TYPES.
- Edge z-ordering: edges are distributed to their containing compound
and rendered after the background rect, so they're not hidden behind
compound containers.
- Error section sizing: normalize error handler node coordinates to
start at (0,0), compute red tint background height from actual
content with symmetric padding for vertical centering.
- Toolbar as HTML overlay: moved from SVG foreignObject to absolute-
positioned HTML div so it stays fixed size at any zoom level. Uses
design system tokens for consistent styling.
- Zoom: replaced viewBox approach with CSS transform on content group.
Default zoom is 100% anchored top-left. Fit-to-view still available
via button.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ELK renderer:
- Add EIP_WHEN, EIP_OTHERWISE, DO_CATCH, DO_FINALLY to COMPOUND_TYPES
so branch body processors nest inside their containers
- Rewrite node creation and result extraction as recursive methods
to support compound-inside-compound (CHOICE → WHEN → processors)
- Use fixed NODE_WIDTH=160 for leaf nodes instead of variable width
Frontend:
- Fix mousewheel crash: capture getBoundingClientRect() before
setState updater (React nulls currentTarget after handler returns)
- Anchor fitToView to top-left instead of centering
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The dev diagram page was calling useRouteCatalog() without time range
params (returned empty) and parsing the wrong response shape (expected
flat {application, routeId} but catalog returns {appId, routes[]}).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New interactive route diagram component with SVG rendering using
server-computed ELK layout coordinates. TIBCO BW5-inspired top-bar
card node style with zoom/pan, hover toolbars, config badges, and
error handler sections below the main flow.
Backend: add direction query parameter (LR/TB) to diagram render
endpoints, defaulting to left-to-right layout.
Frontend: 14-file ProcessDiagram component in ui/src/components/
with DiagramNode, CompoundNode, DiagramEdge, ConfigBadge, NodeToolbar,
ErrorSection, ZoomControls, and supporting hooks. Dev test page at
/dev/diagram for validation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ON_EXCEPTION and ERROR_HANDLER nodes are now treated as compound containers
in the ELK diagram renderer, nesting their children. The frontend
diagram-mapping builds separate FlowSegments for each error handler,
displayed as distinct sections in the RouteFlow component.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add attributes_text flattened field to OpenSearch indexing for both
execution and processor levels. Include in full-text search queries,
wildcard matching, and highlighting. Merge processor-level attributes
into ExecutionSummary. Add 'attribute' category to CommandPalette
(design-system 0.1.17) with per-key-value results in the search UI.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
POST /api/v1/search/executions is a read-only query using POST for the
request body. Skip it in AuditInterceptor to avoid flooding the audit
log with search operations.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
TreeReconstructionTest and PostgresExecutionStoreIT still passed the
removed diagramNodeId parameter. Missed by mvn compile (main only);
caught by mvn verify (test compilation).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Align with cameleer3-common rename: logForwardingLevel → applicationLogLevel
(root logger) and new agentLogLevel (com.cameleer3 logger). Both fields
are on ApplicationConfig, pushed via config-update. UI shows "App Log Level"
and "Agent Log Level" on AppConfig slide-in, AgentHealth config bar, and
AppConfigDetailPage.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Map TRACE to its own 'trace' level instead of grouping with DEBUG,
now that the design system LogViewer supports it natively.
Bump @cameleer/design-system to 0.1.16.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add TRACE option to log forwarding level dropdowns (AppConfig,
AgentHealth), badge color mapping, and log filter ButtonGroups
on all pages that display application logs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Store application_name in route_diagrams at ingestion time (V7 migration),
resolve from agent registry same as ExecutionController. Move
findProcessorRouteMapping from ExecutionStore to DiagramStore using a
JSONB query that extracts node IDs directly from stored RouteGraph
definitions. This makes the mapping available as soon as diagrams are
sent, before any executions are recorded.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Agent now uses Camel processorId as RouteNode.id, eliminating the
nodeId mapping layer. Drop diagram_node_id column (V6 migration),
remove from ProcessorRecord/ProcessorNode/IngestionService/DetailService,
add /processor-routes endpoint for processorId→routeId lookup,
simplify frontend diagram-mapping and ExchangeDetail overlays,
replace N diagram fetches in AppConfigPage with single hook.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add Route column to Traces & Taps table (diagram-based mapping, pending backend fix)
- Make tap badges clickable to navigate to route's Taps tab
- Add edit/save/cancel toolbar with design system Button components
- Move Sampling Rate to last position in settings grid
- Support ?tab= URL param on RouteDetail for direct tab navigation
- Bump @cameleer/design-system to 0.1.15 (DetailPanel overlay + backdrop)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Narrowed panel from 640px to 520px so main table columns stay visible
- Settings grid uses CSS grid (3 columns) for proper wrapping
- Removed unused PanelActions component that caused white footer bar
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaces the separate AppConfigDetailPage route with a 640px-wide
DetailPanel that slides in when clicking a row on the App Config
overview table. All editing functionality (settings, traces & taps,
route recording) is preserved inside the panel.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Picks up LogViewer background fix (removes --bg-inset for consistent
card backgrounds).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaces text "Edit"/"Del" buttons with pencil and trash can icon
buttons matching the style used elsewhere in the UI.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaces the plain checkbox with the design system Toggle component
for consistency with the recording toggle on RouteDetail and
AppConfigDetailPage.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Moved edit pencil and save/cancel actions to sit right after the last
badge field instead of at the start or far right of the config bar.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Moved the pencil edit button after the badge fields and added
margin-left: auto to push it to the far right of the config bar.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New Taps column shows enabled/total count as a badge (e.g. "2/3")
next to the existing Traced column.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Each type option now shows a descriptive tooltip on hover explaining
its purpose: Business Object (key identifiers), Correlation (cross-route
linking), Event (business events), Custom (general purpose).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
crypto.randomUUID() requires a secure context (HTTPS). Since the server
may be accessed via HTTP, use a timestamp + random string ID instead.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaces hardcoded dark-theme hex fallbacks with proper tokens from
tokens.css: --success-bg/--success-border/--success for success and
--error-bg/--error-border/--error for errors. Works in both themes.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The active type option was invisible because --accent-primary doesn't
exist in the design system. Now uses --amber-bg/--amber-deep/--amber
from tokens.css for a clearly visible selected state matching the
brand accent palette.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Updated types now include attributes on ExecutionDetail, ProcessorNode,
and ExecutionSummary from the actual API. Removed stale detail.children
fallback that no longer exists in the schema.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The frontend sends full ISO timestamps (e.g. 2026-03-19T17:55:29Z) but
the controller expected LocalDate (yyyy-MM-dd). This caused null parsing,
which threw NullPointerException in the repository WHERE clause. Changed
to accept Instant directly with sensible defaults (last 7 days).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaces null placeholders with actual getAttributes() calls now that
cameleer3-common SNAPSHOT is resolved with attributes support.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tests were not updated when attributes field was added to ExecutionRecord,
ProcessorRecord, ProcessorDoc, and ExecutionDocument records.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Merge Logging + Observability into unified "Settings" section with
flex-wrap badge grid including new compressSuccess toggle. Merge
Traced Processors with Taps into "Traces & Taps" section showing
capture mode and tap badges per processor. Add "Route Recording"
section with per-route toggles sourced from route catalog. All new
fields (compressSuccess, routeRecording) included in form state
and save payload.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace taps tab placeholder with full DataTable showing all route taps
- Add columns: attribute, processor, expression, language, target, type, enabled toggle, actions
- Add tap modal with form fields: attribute name, processor select, language, target, expression, type selector
- Implement inline enable/disable toggle per tap row
- Add ConfirmDialog for tap deletion
- Add test expression section with Recent Exchange and Custom Payload tabs
- Add save/edit/delete tap operations via application config update
- Add all supporting CSS module classes (no inline styles)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add Toggle for route recording on/off in the route header
- Fetch application config to determine recording state and route taps
- Add Active Taps KPI card showing enabled/total tap counts
- Add Taps tab to the tabbed section with placeholder content
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Shows up to 2 attribute badges (color="auto") per row with a +N overflow
indicator; empty rows render a muted dash. Uses CSS module classes only.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add a Replay button in the exchange header that opens a modal allowing
users to re-send the exchange to a live agent. The modal pre-populates
headers and body from the original exchange input, provides an agent
selector filtered to live agents for the application, and supports
editable header key-value rows with add/remove.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Show route-level attributes as Badge strips in the exchange header
card, and per-processor attributes above the message IN/OUT panels.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add optional `attributes?: Record<string, string>` to ExecutionSummary,
ExecutionDetail, and ProcessorNode in the manually-maintained OpenAPI
schema to reflect the new backend attributes support.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds CompletableFuture-based request-reply mechanism for commands that
need synchronous results. CommandReply record in core, pendingReplies
map in AgentRegistryService, test-expression endpoint on config controller
with 5s timeout. CommandAckRequest extended with optional data field.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
DetailService deserializes attributes JSON from ExecutionRecord/ProcessorRecord and
passes them to ExecutionDetail and ProcessorNode constructors. ExecutionDocument and
ProcessorDoc carry attributes as a JSON string. SearchIndexer passes attributes when
building documents. OpenSearchIndex includes attributes in indexed maps and
deserializes them when constructing ExecutionSummary from search hits.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
IngestionService passes attributes (currently null, pending cameleer3-common update)
to ExecutionRecord and ProcessorRecord. PostgresExecutionStore includes the
attributes column in INSERT and ON CONFLICT UPDATE (with COALESCE), and reads
it back in both row mappers.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds Map<String,String> attributes to ExecutionRecord, ProcessorRecord,
ExecutionDetail, ProcessorNode, and ExecutionSummary. ExecutionStore records
carry attributes as a JSON string; detail/summary models carry deserialized maps.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Covers all 5 new agent features: tap management on RouteDetail, business
attributes display on ExchangeDetail/Dashboard, enhanced replay with
editable payload, per-route recording toggles, and success compression.
Includes backend prerequisites, RBAC matrix, and TypeScript interfaces.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add prefix query parameter to /admin/opensearch/indices endpoint so
the UI can fetch execution and log indices separately. OpenSearch admin
page now shows two card sections: Execution Indices and Log Indices,
each with doc count and size summary. Page restyled with CSS module
replacing inline styles. Delete endpoint also allows log index deletion.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Click a row in the admin App Config table to navigate to a dedicated
detail page at /admin/appconfig/:appId. Shows all config fields as
badges in view mode; pencil toggles to edit mode with dropdowns.
Traced processors are now editable (capture mode dropdown + remove
button per processor). Sections and header use card styling for
visual contrast. OidcConfigPage gets the same card treatment.
List page simplified to read-only badge overview with row click
navigation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Update design system to v0.1.13 where both components scroll to the
top (newest entries) instead of the bottom, matching the descending
sort order used across the UI.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pencil icon and Save/Cancel buttons now appear at the left side of
the AgentHealth config bar, matching the admin overview table where
the edit column is at the start of each row.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace subtle Unicode checkmark/X with proper labeled buttons styled
as primary (Save) and secondary (Cancel) for better visibility.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Settings (log level, engine level, payload capture, metrics) now
display as color-coded badges by default. Clicking the pencil icon
enters edit mode where badges become dropdowns. Save (checkmark)
persists changes and reverts to badge view; cancel discards changes.
Applied consistently on both the admin App Config page and the
AgentHealth config bar.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
DataTable requires rows with an { id: string } constraint. Map
ApplicationConfig to ConfigRow adding id from the application field.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add admin page at /admin/appconfig with a DataTable showing all
application configurations. Inline dropdowns allow editing log level,
engine level, payload capture mode, and metrics toggle directly from
the table. Changes push to agents via SSE immediately.
Also adds a config bar on the AgentHealth page (/agents/:appId) for
per-application config management with the same 4 settings.
Backend: GET /api/v1/config list endpoint, findAll() on repository,
sensible defaults for logForwardingLevel/engineLevel/payloadCaptureMode.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The /routes/catalog endpoint now accepts optional from/to query
parameters instead of hardcoding a 24h window. The UI passes the
global filter time range so sidebar counts match what the user sees.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add a dedicated inspect button column (↗) to navigate to the agent
instance page, consistent with the exchange inspect pattern on the
Dashboard. Row click still opens the detail slide-in panel.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove duplicate in-page breadcrumbs (ExchangeDetail, AgentHealth scope
trail) and improve the global TopBar breadcrumb with semantic labels and
a context-based override for pages with richer navigation data.
- Add BreadcrumbProvider from design system v0.1.12
- LayoutShell: label map prettifies URL segments (apps→Applications, etc.)
- ExchangeDetail: uses useBreadcrumb() to set semantic trail via context
- AgentHealth: remove scope trail, keep live-count badge standalone
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Skip global time range in the logs query key when filtering by
exchangeId (exchange logs are historical, the sliding time window is
irrelevant). Add placeholderData to keep previous results visible
during query key transitions on other pages.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Defensive: use .keyword on the top-level exchangeId field too, in
case indices were created before the explicit keyword mapping was
added to the template.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dynamically mapped string fields in OpenSearch are multi-field
(text + keyword). Term queries require the .keyword sub-field for
exact matching.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Existing log records only have exchangeId inside the mdc object, not
as a top-level indexed field. Use a bool should clause to match on
either exchangeId (new records) or mdc.camel.exchangeId (old records).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Index exchangeId from Camel MDC (camel.exchangeId) as a top-level
keyword field in OpenSearch log indices. Add exchangeId filter to
the log query API and frontend hook. Show a LogViewer on the
ExchangeDetail page filtered to that exchange's logs, with search
input and level filter pills.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The GlobalFilterProvider now recomputes the preset time range every
10s when auto-refresh is on, so timeRange.end stays fresh instead of
being frozen at the moment the preset was clicked.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add the same log + timeline side-by-side layout from AgentInstance to
the AgentHealth page (/agents/{appId}). Includes search input, level
filter pills, sort toggle, and refresh button — matching the instance
page design exactly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Instead of calling refetch() with stale time params, the refresh
buttons now set a toOverride state to new Date().toISOString(). This
flows into the query key, triggering a fresh fetch with the current
time as the upper bound. Both useApplicationLogs and useAgentEvents
hooks accept an optional toOverride parameter.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The CI build runs tsc --noEmit which failed because the ExecutionDetail
type in schema.d.ts was missing the new inputBody/outputBody/inputHeaders/
outputHeaders fields added to the backend DTO.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add inputBody/outputBody/inputHeaders/outputHeaders to ExecutionDetail
DTO so exchange-level bodies are returned by the detail endpoint. Show
"Exchange Input" and "Exchange Output" panels on the detail page when
the data is available.
Fix RouteFlow node click selecting the wrong processor snapshot by
building a flowToTreeIndex mapping that correctly translates flow
display index → diagram node index → processorId → processor tree
index. Previously the diagram node index was used directly as the
processor tree index, which broke when the two orderings differed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove auto-scroll override hack. Add sort order toggle (asc/desc
by time) and manual refresh button to both the application log and
agent events timeline panels on AgentInstance and AgentHealth pages.
Default is descending (newest first); toggling reverses the array.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Give logCard the same max-height and flex layout as timelineCard so
both columns are equal height. Revert .toReversed() so events stay
in DESC order (newest at top). Override EventFeed's auto-scroll-to-
bottom with a requestAnimationFrame that resets scrollTop to 0 after
mount, keeping newest entries visible at the top of both panels.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add GET /api/v1/logs endpoint to query application logs stored in
OpenSearch with filters for application, agent, level, time range,
and text search. Wire up the AgentInstance LogViewer with real data
and an EventFeed-style toolbar (search input + level filter pills).
Fix agent events timeline autoscroll by reversing the DESC-ordered
events so newest entries appear at the bottom where EventFeed
autoscrolls to.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fix admin OpenSearch page always showing "Disconnected" by aligning
frontend field names (reachable/nodeCount/host) with backend DTO.
Update design system to v0.1.10 and adopt the new multi-flow RouteFlow
API — error-handler nodes now render as labeled segments with error
variant instead of relying on legacy auto-separation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Same issue as the CI build — Docker layer cache can serve a stale
cameleer3-common SNAPSHOT.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Maven cache can serve stale cameleer3-common SNAPSHOTs. The -U flag
forces Maven to check the remote registry for updated SNAPSHOTs on
every build.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
LogIndexService in server-core imported LogEntry from cameleer3-common,
but the SNAPSHOT on the registry may not have it yet when the server CI
runs. Moved the dependency to server-app where both the controller and
OpenSearch implementation live.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Agents can now send application log entries in batches via POST /api/v1/data/logs.
Logs are indexed directly into OpenSearch daily indices (logs-{yyyy-MM-dd}) using
the bulk API. Index template defines explicit mappings for full-text search readiness.
New DTOs (LogEntry, LogBatch) added to cameleer3-common in the agent repo.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The findByApplication query only read config_val JSONB, ignoring the
version and updated_at SQL columns. The JSON blob contained version 0
from the original save, so agents saw no config and fell back to defaults.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
LIVE: sidebar clicks trigger initial fetch + polling for the new route.
PAUSED: sidebar clicks navigate but queries are disabled — no fetches
until the user switches back to LIVE.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The npm install @cameleer/design-system@dev was in the same cached layer
as npm ci, so Docker never re-ran it when the registry had a new version.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add highlight field to ExecutionSummary record
- Request highlight fragments from OpenSearch when full-text search is active
- Pass matchContext to command palette for display
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add missing onOpen prop to CommandPalette (fixes Ctrl+K/Cmd+K)
- Wire server-side exchange search with debounced text query
- Use design system dev snapshot from Gitea registry in CI builds
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add application_config table (V4 migration), repository, and REST
controller. GET /api/v1/config/{app} returns config, PUT saves and
pushes CONFIG_UPDATE to all LIVE agents via SSE. UI tracing toggle
now uses config API instead of direct SET_TRACED_PROCESSORS command.
Tracing store syncs with server config on load.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Update design system to 0.1.8 and pass NodeBadge[] to both
ProcessorTimeline and RouteFlow. Traced processors display a
blue "TRACED" badge that updates reactively via Zustand store.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use diagram node ID as fallback processorId when no processor
execution match exists (e.g. error handlers that didn't trigger).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Payload now sends {processors: {id: "BOTH"}} map instead of
{routeId, processorIds[]} array. Tracing state keyed by application
name (global, not per-route) matching agent behavior.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wire getActions on ProcessorTimeline and RouteFlow to send
SET_TRACED_PROCESSORS commands to all agents of the same application.
Tracing state managed via Zustand store with optimistic UI and rollback.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Change Vite proxy pattern from /api to /api/ so /api-docs client
route is not captured and proxied to the backend
- Fix SwaggerUIBundle init: remove empty presets/layout overrides that
crashed the internal persistConfigs function
- Use correct CSS import (swagger-ui.css instead of index.css)
- Add requestInterceptor to auto-attach JWT token to Try-it-out calls
- Add swagger-ui-bundle to optimizeDeps.include for reliable loading
- Remove unused swagger-ui-dist.d.ts type declarations
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add logging to MetricsController: warn on parse failures, debug on
received metrics, buffer depth on 503
- Add GET /api/v1/admin/database/metrics-pipeline diagnostic endpoint
(buffer depth, row count, distinct agents/metrics, latest timestamp)
- Fix BackpressureIT test JSON to match actual MetricsSnapshot schema
(collectedAt/metricName/metricValue instead of timestamp/metrics)
- Upgrade cameleer3-common from 1.0-SNAPSHOT to 0.0.3 (adds engineLevel)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Indeterminate progress bars were misleading when agents don't report
JVM metrics — replaced with plain "N/A" text.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The lock file had "resolved": "../../design-system" from a local
install, causing npm ci in CI to silently skip the package.
Reinstalled from registry to fix the resolved URL.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Build UI step ran npm ci without authenticating to the Gitea npm
registry, causing @cameleer/design-system to fail to resolve. Add
REGISTRY_TOKEN to .npmrc before npm ci.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
DetailPanel now portals itself to #cameleer-detail-panel-root (a div
AppShell places as a sibling of .main in the top-level flex row).
Pages just render <DetailPanel> inline — no manual createPortal,
no context, no prop drilling.
Remove the old #detail-panel-portal div from LayoutShell and the
createPortal wrappers from Dashboard and AgentHealth.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When no OIDC config exists, the backend returns an object with all
null fields (via OidcAdminConfigResponse.unconfigured()). Normalize
all null values to sensible defaults when loading the form instead
of passing nulls through to Input components and .map() calls.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The API returns defaultRoles as null when no roles are configured.
Add null guards on all defaultRoles accesses to prevent .map() crash.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The previous approach used useEffect+context to hoist DetailPanel
content to the AppShell level, but the dependency-free useEffect
caused a re-render loop that broke sidebar navigation.
Replace with createPortal: pages render DetailPanel inline in their
JSX but portal it to a target div (#detail-panel-portal) at the
AppShell level. No state lifting, no re-render loops.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
DetailPanel is a flex sibling that slides in from the right — it must
be rendered at the AppShell level via the detail prop, not inside the
page content. Add DetailPanelContext so pages can push their panel
content up to LayoutShell, which passes it to AppShell.detail.
Applied to Dashboard (exchange detail) and AgentHealth (instance detail).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Upgrade @cameleer/design-system to ^0.1.3 which adds LIVE/PAUSED
toggle to TopBar backed by autoRefresh state in GlobalFilterProvider.
Add useRefreshInterval() hook that returns the polling interval when
auto-refresh is on, or false when paused. Wire it into all query
hooks that use refetchInterval (executions, catalog, agents, metrics,
admin database/opensearch).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
OpenSearch dynamically maps string fields as text with a .keyword
subfield. Sorting on text fields throws an error; only .keyword,
date, and numeric fields support sorting. Add .keyword suffix to
all string sort columns (status, routeId, agentId, executionId,
correlationId, applicationName) while keeping start_time and
duration_ms as-is.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add application_name filter to OpenSearch query builder — sidebar
app selection now correctly filters the exchange list. The
application field was being resolved to agentIds in the controller
but never applied as a query filter in OpenSearch.
Also restore snake_case sort column mapping since the OpenSearch
toMap() serializer uses snake_case field names (start_time, route_id,
etc.), not camelCase.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add executionId and applicationName to allowed sort fields. Fix sort
column mapping to use camelCase field names matching the OpenSearch
ExecutionDocument fields instead of snake_case DB column names. This
was causing sorts on most columns to either silently fall back to
startTime or return empty results from OpenSearch.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Upgrade @cameleer/design-system to v0.1.1 which adds onSortChange
callback to DataTable. Wire it up in Dashboard (exchanges), AuditLog,
and RouteDetail (recent executions) so sorting triggers a new API
request with sortField/sortDir instead of only sorting the current page.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace inline styles with CSS module matching the design system's
LoginForm visual patterns. Uses proper DS class structure (divider,
social section, form fields) while keeping username-based auth
instead of the DS component's email validation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Migrate all page components from the @cameleer/design-system v0.0.3
example UI, replacing mock data with real backend API hooks. This brings
richer visuals (KpiStrip, GroupCard, RouteFlow, ProcessorTimeline,
DateRangePicker, expandable rows) while preserving all existing API
integration, auth, and routing infrastructure.
Pages migrated: Dashboard, RoutesMetrics, RouteDetail, ExchangeDetail,
AgentHealth, AgentInstance, OidcConfig, AuditLog, RBAC (Users/Groups/Roles).
Also enhanced LayoutShell CommandPalette with real search data from catalog.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add engine_level, input_body, output_body, input_headers, output_headers
to the executions INSERT/SELECT/UPSERT and row mapper. Required for
REGULAR mode where route-level payloads exist but no processor records.
Note: requires ALTER TABLE migration to add the new columns.
Extract inputBody/outputBody/inputHeaders/outputHeaders from RouteExecution
snapshots and pass to ExecutionRecord. Maps engineLevel field. Critical for
REGULAR mode where no processor records exist but route-level payloads do.
Adds engineLevel (NONE/MINIMAL/REGULAR/COMPLETE) and inputBody/outputBody/
inputHeaders/outputHeaders to ExecutionRecord so REGULAR mode route-level
payloads are persisted (previously only processor-level records had payloads).
- Add @RequestBody(required=false) CommandAckRequest to ack endpoint for
receiving agent command results (backward compat with old agents)
- Record command results in agent event log via AgentEventService
- Add set-traced-processors to mapCommandType switch
- Inject AgentEventService dependency
<p>Click to select a node (amber highlight ring). Right-click for context menu with tracing/tap/snapshot actions. Clean separation of concerns. Standard desktop UX.</p>
<p>Hover reveals a dark floating icon toolbar above the node. Click still selects. More discoverable than right-click, but can feel cluttered on dense diagrams.</p>
<p>MuleSoft-style: colored icon strip on the left, label + detail on the right. Color encodes node type. Compound nodes (choice, split) use dashed containers.</p>
</div>
</div>
<!-- Option B: Rounded pill with centered icon -->
<p>TIBCO BW5-inspired: white cards with colored top accent bar. Clean, professional, card-like. Compound nodes get a full colored header bar with white title text.</p>
<p>Diagram on top, bottom split into processor list (left) + detail tabs (right). Clicking processor in list or diagram syncs selection. Most information density.</p>
<divclass="pros-cons">
<divclass="pros"><h4>Pros</h4><ul><li>Processor list as navigation</li><li>Full diagram width</li><li>Maximum information density</li></ul></div>
<divstyle="font-family:monospace;font-size:11px;color:#f87171;">Expression evaluation timed out (50ms limit)</div>
</div>
</div>
<divstyle="font-size:10px;color:#6b7280;margin-top:8px;">Evaluated by agent <spanstyle="font-family:monospace;">order-svc-01</span> using Camel's <spanstyle="font-family:monospace;">simple</span> language</div>
@@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
## Project
Cameleer3 Server — observability server that receives, stores, and serves Camel route execution data and route diagrams from Cameleer3 agents. Pushes config and commands to agents via SSE.
Cameleer3 Server — observability server that receives, stores, and serves Camel route execution data and route diagrams from Cameleer3 agents. Pushes config and commands to agents via SSE. Also orchestrates Docker container deployments when running under cameleer-saas.
## Related Project
@@ -14,8 +14,8 @@ Cameleer3 Server — observability server that receives, stores, and serves Came
- Depends on `com.cameleer3:cameleer3-common` from Gitea Maven registry
- Jackson `JavaTimeModule` for `Instant` deserialization
- Communication: receives HTTP POST data from agents, serves SSE event streams for config push/commands
-Maintains agent instance registry with states: LIVE → STALE → DEAD
-Storage: PostgreSQL (TimescaleDB) for structured data, OpenSearch for full-text search
-Security: JWT auth with RBAC (AGENT/VIEWER/OPERATOR/ADMIN roles), Ed25519 config signing, bootstrap token for registration
-OIDC: Optional external identity provider support (token exchange pattern). Configured via admin API, stored in database (`server_config` table)
- Communication: receives HTTP POST data from agents (executions, diagrams, metrics, logs), serves SSE event streams for config push/commands (config-update, deep-trace, replay, route-control)
-Environment filtering: all data queries (exchanges, dashboard stats, route metrics, agent events, correlation) filter by the selected environment. All commands (config-update, route-control, set-traced-processors, replay) target only agents in the selected environment when one is selected. `AgentRegistryService.findByApplicationAndEnvironment()` for environment-scoped command dispatch. Backend endpoints accept optional `environment` query parameter; null = all environments (backward compatible).
-Maintains agent instance registry (in-memory) with states: LIVE -> STALE -> DEAD. Auto-heals from JWT `env` claim + heartbeat body on heartbeat/SSE after server restart (priority: heartbeat `environmentId` > JWT `env` claim > `"default"`). Capabilities and route states updated on every heartbeat (protocol v2). Route catalog falls back to ClickHouse stats for route discovery when registry has incomplete data.
-Multi-tenancy: each server instance serves one tenant (configured via `CAMELEER_TENANT_ID`, default: `"default"`). Environments (dev/staging/prod) are first-class — agents send `environmentId` at registration and in heartbeats. JWT carries `env` claim for environment persistence across token refresh. PostgreSQL isolated via schema-per-tenant (`?currentSchema=tenant_{id}`). ClickHouse shared DB with `tenant_id` + `environment` columns, partitioned by `(tenant_id, toYYYYMM(timestamp))`.
-Storage: PostgreSQL for RBAC, config, and audit; ClickHouse for all observability data (executions, search, logs, metrics, stats, diagrams). ClickHouse schema migrations in `clickhouse/*.sql`, run idempotently on startup by `ClickHouseSchemaInitializer`. Use `IF NOT EXISTS` for CREATE and ADD PROJECTION.
- Logging: ClickHouse JDBC set to INFO (`com.clickhouse`), HTTP client to WARN (`org.apache.hc.client5`) in application.yml
- Security: JWT auth with RBAC (AGENT/VIEWER/OPERATOR/ADMIN roles), Ed25519 config signing (key derived deterministically from JWT secret via HMAC-SHA256), bootstrap token for registration. CORS: `CAMELEER_CORS_ALLOWED_ORIGINS` (comma-separated) overrides `CAMELEER_UI_ORIGIN` for multi-origin setups (e.g., reverse proxy). UI role gating: Admin sidebar/routes hidden for non-ADMIN; diagram toolbar and route control hidden for VIEWER. Read-only for VIEWER, editable for OPERATOR+. Role helpers: `useIsAdmin()`, `useCanControl()` in `auth-store.ts`. Route guard: `RequireAdmin` in `auth/RequireAdmin.tsx`. Last-ADMIN guard: system prevents removal of the last ADMIN role (409 Conflict on role removal, user deletion, group role removal). Password policy: min 12 chars, 3-of-4 character classes, no username match (enforced on user creation and admin password reset). Brute-force protection: 5 failed attempts -> 15 min lockout (tracked via `failed_login_attempts` / `locked_until` on users table). Token revocation: `token_revoked_before` column on users, checked in `JwtAuthenticationFilter`, set on password change.
- OIDC: Optional external identity provider support (token exchange pattern). Configured via admin API/UI, stored in database (`server_config` table). Configurable `userIdClaim` (default `sub`) determines which id_token claim is used as the user identifier. Resource server mode: accepts external access tokens (Logto M2M) via JWKS validation when `CAMELEER_OIDC_ISSUER_URI` is set. `CAMELEER_OIDC_JWK_SET_URI` overrides JWKS discovery for container networking. `CAMELEER_OIDC_TLS_SKIP_VERIFY=true` disables TLS cert verification for OIDC calls (self-signed CAs). Scope-based role mapping via `SystemRole.normalizeScope()` (case-insensitive, strips `server:` prefix): `admin`/`server:admin` -> ADMIN, `operator`/`server:operator` -> OPERATOR, `viewer`/`server:viewer` -> VIEWER. SSO: when OIDC enabled, UI auto-redirects to provider with `prompt=none` for silent sign-in; falls back to `/login?local` on `login_required`, retries without `prompt=none` on `consent_required`. Logout always redirects to `/login?local` (via OIDC end_session or direct fallback) to prevent SSO re-login loops. Auto-signup provisions new OIDC users with default roles. System roles synced on every OIDC login via `syncOidcRoles` — always overwrites directly-assigned roles (falls back to `defaultRoles` when OIDC returns none); uses `getDirectRolesForUser` to avoid touching group-inherited roles. Group memberships are never touched. Supports ES384, ES256, RS256. Shared OIDC logic in `OidcProviderHelper` (discovery, JWK source, algorithm set).
- OIDC role extraction: `OidcTokenExchanger` reads roles from the **access_token** first (JWT with `at+jwt` type, decoded by a separate processor), then falls back to id_token. `OidcConfig` includes `audience` (RFC 8707 resource indicator — included in both authorization request and token exchange POST body to trigger JWT access tokens) and `additionalScopes` (extra scopes for the SPA to request). The `rolesClaim` config points to the claim name in the token (e.g., `"roles"` for Custom JWT claims, `"realm_access.roles"` for Keycloak). All provider-specific configuration is external — no provider-specific code in the server.
- User persistence: PostgreSQL `users` table, admin CRUD at `/api/v1/admin/users`
- Secrets managed in CI deploy step (idempotent `--dry-run=client | kubectl apply`): `cameleer-auth`, `postgres-credentials`, `clickhouse-credentials`
- K8s probes: server uses `/api/v1/health`, PostgreSQL uses `pg_isready -U "$POSTGRES_USER"` (env var, not hardcoded)
- K8s security: server and database pods run with `securityContext.runAsNonRoot`. UI (nginx) runs without securityContext (needs root for entrypoint setup).
- Docker: server Dockerfile has no default credentials — all DB config comes from env vars at runtime
- Always use `@cameleer/design-system` CSS variables for colors (`var(--amber)`, `var(--error)`, `var(--success)`, etc.) — never hardcode hex values. This applies to CSS modules, inline styles, and SVG `fill`/`stroke` attributes. SVG presentation attributes resolve `var()` correctly. All colors use CSS variables (no hardcoded hex).
- Shared CSS modules in `ui/src/styles/` (table-section, log-panel, rate-colors, refresh-indicator, chart-card, section-card) — import these instead of duplicating patterns.
- Design system components used consistently: `Select`, `Tabs`, `Toggle`, `Button`, `LogViewer`, `Label` — prefer DS components over raw HTML elements.
- Environment slugs are auto-computed from display name (read-only in UI).
- Brand assets: `@cameleer/design-system/assets/` provides `camel-logo.svg` (currentColor), `cameleer3-{16,32,48,192,512}.png`, and `cameleer3-logo.png`. Copied to `ui/public/` for use as favicon (`favicon-16.png`, `favicon-32.png`) and logo (`camel-logo.svg` — login dialog 36px, sidebar 28x24px).
- Sidebar generates `/exchanges/` paths directly (no legacy `/apps/` redirects). basePath is centralized in `ui/src/config.ts`; router.tsx imports it instead of re-reading `<base>` tag.
- Global user preferences (environment selection) use Zustand stores with localStorage persistence — never URL search params. URL params are for page-specific state only (e.g. `?text=` search query). Switching environment resets all filters and remounts pages.
## Docker Orchestration
When deployed via the cameleer-saas platform, this server orchestrates customer app containers using Docker. Key components:
- **TraefikLabelBuilder** (`app/runtime/TraefikLabelBuilder.java`) — generates Traefik Docker labels for path-based (`/{envSlug}/{appSlug}/`) or subdomain-based (`{appSlug}-{envSlug}.{domain}`) routing. Supports strip-prefix and SSL offloading toggles.
- **DockerNetworkManager** (`app/runtime/DockerNetworkManager.java`) — manages two Docker network tiers:
-`cameleer-traefik` — shared network; Traefik, server, and all app containers attach here. Server joined via docker-compose with `cameleer3-server` DNS alias.
-`cameleer-env-{slug}` — per-environment isolated network; containers in the same environment discover each other via Docker DNS.
- **DockerEventMonitor** (`app/runtime/DockerEventMonitor.java`) — persistent Docker event stream listener for containers with `managed-by=cameleer3-server` label. Detects die/oom/start/stop events and updates deployment replica states. Periodic reconciliation (@Scheduled every 30s) inspects actual container state and corrects deployment status mismatches (fixes stale DEGRADED with all replicas healthy).
**Blue/green strategy**: when re-deploying, new replicas are started and health-checked before old ones are stopped, minimising downtime.
**Deployment uniqueness**: `DeploymentService.createDeployment()` deletes any STOPPED/FAILED deployments for the same app+environment before creating a new one, preventing duplicate rows.
### JAR Management
- **Retention policy** per environment: configurable maximum number of JAR versions to keep. Older JARs are deleted automatically.
- **Nightly cleanup job** (`JarRetentionJob`, Spring `@Scheduled` 03:00): purges JARs exceeding the retention limit and removes orphaned files not referenced by any app version. Skips versions currently deployed.
- **Volume-based JAR mounting** for Docker-in-Docker setups: set `CAMELEER_JAR_DOCKER_VOLUME` to the Docker volume name that contains the JAR storage directory. When set, the orchestrator mounts this volume into the container instead of bind-mounting the host path (required when the SaaS container itself runs inside Docker and the host path is not accessible from sibling containers).
### nginx / Reverse Proxy
-`client_max_body_size 200m` is required in the nginx config to allow JAR uploads up to 200 MB. Without this, large JAR uploads return 413.
## Disabled Skills
- Do NOT use any `gsd:*` skills in this project. This includes all `/gsd:` prefixed commands.
<!-- gitnexus:start -->
# GitNexus — Code Intelligence
This project is indexed by GitNexus as **cameleer3-server** (5509 symbols, 13919 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely.
> If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first.
## Always Do
- **MUST run impact analysis before editing any symbol.** Before modifying a function, class, or method, run `gitnexus_impact({target: "symbolName", direction: "upstream"})` and report the blast radius (direct callers, affected processes, risk level) to the user.
- **MUST run `gitnexus_detect_changes()` before committing** to verify your changes only affect expected symbols and execution flows.
- **MUST warn the user** if impact analysis returns HIGH or CRITICAL risk before proceeding with edits.
- When exploring unfamiliar code, use `gitnexus_query({query: "concept"})` to find execution flows instead of grepping. It returns process-grouped results ranked by relevance.
- When you need full context on a specific symbol — callers, callees, which execution flows it participates in — use `gitnexus_context({name: "symbolName"})`.
## When Debugging
1.`gitnexus_query({query: "<error or symptom>"})` — find execution flows related to the issue
2.`gitnexus_context({name: "<suspect function>"})` — see all callers, callees, and process participation
3.`READ gitnexus://repo/cameleer3-server/process/{processName}` — trace the full execution flow step by step
4. For regressions: `gitnexus_detect_changes({scope: "compare", base_ref: "main"})` — see what your branch changed
## When Refactoring
- **Renaming**: MUST use `gitnexus_rename({symbol_name: "old", new_name: "new", dry_run: true})` first. Review the preview — graph edits are safe, text_search edits need manual review. Then run with `dry_run: false`.
- **Extracting/Splitting**: MUST run `gitnexus_context({name: "target"})` to see all incoming/outgoing refs, then `gitnexus_impact({target: "target", direction: "upstream"})` to find all external callers before moving code.
- After any refactor: run `gitnexus_detect_changes({scope: "all"})` to verify only expected files changed.
## Never Do
- NEVER edit a function, class, or method without first running `gitnexus_impact` on it.
- NEVER ignore HIGH or CRITICAL risk warnings from impact analysis.
- NEVER rename symbols with find-and-replace — use `gitnexus_rename` which understands the call graph.
- NEVER commit changes without running `gitnexus_detect_changes()` to check affected scope.
Before completing any code modification task, verify:
1.`gitnexus_impact` was run for all modified symbols
2. No HIGH/CRITICAL risk warnings were ignored
3.`gitnexus_detect_changes()` confirms changes match expected scope
4. All d=1 (WILL BREAK) dependents were updated
## Keeping the Index Fresh
After committing code changes, the GitNexus index becomes stale. Re-run analyze to update it:
```bash
npx gitnexus analyze
```
If the index previously included embeddings, preserve them by adding `--embeddings`:
```bash
npx gitnexus analyze --embeddings
```
To check whether embeddings exist, inspect `.gitnexus/meta.json` — the `stats.embeddings` field shows the count (0 means no embeddings). **Running analyze without `--embeddings` will delete any previously generated embeddings.**
> Claude Code users: A PostToolUse hook handles this automatically after `git commit` and `git merge`.
## CLI
| Task | Read this skill file |
|------|---------------------|
| Understand architecture / "How does X work?" | `.claude/skills/gitnexus/gitnexus-exploring/SKILL.md` |
| Blast radius / "What breaks if I change X?" | `.claude/skills/gitnexus/gitnexus-impact-analysis/SKILL.md` |
| Trace bugs / "Why is X failing?" | `.claude/skills/gitnexus/gitnexus-debugging/SKILL.md` |
@@ -21,18 +21,17 @@ mvn clean verify # compile + run all tests (needs Docker for integrati
## Infrastructure Setup
Start PostgreSQL and OpenSearch:
Start PostgreSQL:
```bash
docker compose up -d
```
This starts TimescaleDB (PostgreSQL 16) and OpenSearch 2.19. The database schema is applied automatically via Flyway migrations on server startup.
This starts PostgreSQL 16. The database schema is applied automatically via Flyway migrations on server startup. ClickHouse tables are created by the schema initializer on startup.
> **Note:** The Docker image no longer includes default database credentials. When running via `docker run`, pass `-e SPRING_DATASOURCE_URL=...` etc. The docker-compose setup provides these automatically.
The server starts on **port 8081**. The `CAMELEER_AUTH_TOKEN` environment variable is **required** — the server fails fast on startup if it is not set.
For token rotation without downtime, set `CAMELEER_AUTH_TOKEN_PREVIOUS` to the old token while rolling out the new one. The server accepts both during the overlap window.
@@ -100,13 +105,15 @@ JWTs carry a `roles` claim. Endpoints are restricted by role:
The env-var local user gets `ADMIN` role. Agents get `AGENT` role at registration.
**UI role gating:** The sidebar hides the Admin section for non-ADMIN users. Admin routes (`/admin/*`) redirect to `/` for non-admin. The diagram node toolbar and route control bar are hidden for VIEWER. Config is a main tab (`/config` shows all apps, `/config/:appId` filters to one app with detail panel; sidebar clicks stay on config tab, route clicks resolve to parent app). VIEWER sees read-only, OPERATOR+ can edit.
### OIDC Login (Optional)
OIDC configuration is stored in PostgreSQL and managed via the admin API or UI. The SPA checks if OIDC is available:
@@ -139,7 +146,7 @@ curl -s -X PUT http://localhost:8081/api/v1/admin/oidc \
**Initial provisioning**: OIDC can also be seeded from `CAMELEER_OIDC_*` env vars on first startup (when DB is empty). After that, the admin API takes over.
### Authentik Setup (OIDC Provider)
### Logto Setup (OIDC Provider)
Authentik is deployed alongside the Cameleer stack. After first deployment:
Logto is deployed alongside the Cameleer stack. After first deployment:
1.**Initial setup**: Open`http://192.168.50.86:30950/if/flow/initial-setup/` and create the admin account
- Redirect URIs: `http://192.168.50.86:30090/callback` (or your UI URL)
Logto is proxy-aware via `TRUST_PROXY_HEADER=1`. The `LOGTO_ENDPOINT` and `LOGTO_ADMIN_ENDPOINT` secrets define the public-facing URLs that Logto uses for OIDC discovery, issuer URI, and redirect URLs. When behind a reverse proxy (e.g., Traefik), set these to the external URLs (e.g.,`https://auth.cameleer.my.domain`). Logto needs its own subdomain — it cannot be path-prefixed under another app.
1.**Initial setup**: Open the Logto admin console (the `LOGTO_ADMIN_ENDPOINT` URL) and create the admin account
2.**Create SPA application**: Applications → Create → Single Page App
-Name: `Cameleer UI`
- Redirect URI: your UI URL + `/oidc/callback`
- Note the **Client ID**
3.**Create API Resource**: API Resources → Create
- Name: `Cameleer Server API`
- Indicator: your API URL (e.g., `https://cameleer.siegeln.net/api`)
4.**Configure roles** (optional): Create groups in Authentik and map them to Cameleer roles via the `roles-claim` config. Default claim path is `realm_access.roles`. For Authentik, you may need to customize the OIDC scope to include group claims.
5.**Configure Cameleer**: Use the admin API (`PUT /api/v1/admin/oidc`) or set env vars for initial seeding:
5.**Configure Cameleer OIDC login**: Use the admin API (`PUT /api/v1/admin/oidc`) or set env vars for initial seeding:
CAMELEER_OIDC_TLS_SKIP_VERIFY=true # optional — skip cert verification for self-signed CAs
```
`JWK_SET_URI` is needed when the public issuer URL isn't reachable from inside containers — it fetches JWKS directly from the internal Logto service. `TLS_SKIP_VERIFY` disables certificate verification for all OIDC HTTP calls (discovery, token exchange, JWKS); use only when the provider has a self-signed CA.
### SSO Behavior
When OIDC is configured and enabled, the UI automatically redirects to the OIDC provider for silent SSO (`prompt=none`). Users with an active provider session are signed in without seeing a login form. On first login, the provider may show a consent screen (scopes), after which subsequent logins are seamless. If auto-signup is enabled, new users are automatically provisioned with the configured default roles.
- **Bypass SSO**: Navigate to `/login?local` to see the local login form
- **Subpath deployments**: The OIDC redirect_uri respects `BASE_PATH` (e.g., `https://host/server/oidc/callback`)
- **Role sync**: System roles (ADMIN/OPERATOR/VIEWER) are synced from OIDC scopes on every login — revoking a scope in the provider takes effect on next login. Manually assigned group memberships are preserved.
### User Management (ADMIN only)
@@ -220,6 +247,20 @@ curl -s -X POST http://localhost:8081/api/v1/data/metrics \
curl -s -X POST http://localhost:8081/api/v1/agents/commands \
-H "Content-Type: application/json" \
@@ -324,10 +371,14 @@ curl -s -X POST http://localhost:8081/api/v1/agents/agent-1/commands/{commandId}
**Agent lifecycle:** LIVE (heartbeat within 90s) → STALE (missed 3 heartbeats) → DEAD (5min after STALE). DEAD agents kept indefinitely.
**SSE events:** `config-update`, `deep-trace`, `replay` commands pushed in real time. Server sends ping keepalive every 15s.
**Server restart resilience:** The agent registry is in-memory and lost on server restart. Agents auto-re-register on their next heartbeat or SSE connection — the server reconstructs registry entries from JWT claims (subject, application). Route catalog uses ClickHouse execution data as fallback until agents re-register with full route IDs. Agents should also handle 404 on heartbeat by triggering a full re-registration.
**SSE events:** `config-update`, `deep-trace`, `replay`, `route-control` commands pushed in real time. Server sends ping keepalive every 15s.
**Command expiry:** Unacknowledged commands expire after 60 seconds.
**Route control responses:** Route control commands return `CommandGroupResponse` with per-agent status, response count, and timed-out agent IDs.
### Backpressure
When the write buffer is full (default capacity: 50,000), ingestion endpoints return **503 Service Unavailable**. Already-buffered data is not lost.
@@ -354,6 +405,7 @@ Key settings in `cameleer3-server-app/src/main/resources/application.yml`:
Push to `main` triggers: **build** (UI npm + Maven, unit tests) → **docker** (buildx amd64 for server + UI, push to Gitea registry) → **deploy** (kubectl apply + rolling update).
**Methodology:** Playwright-driven navigation of all major pages (14 screenshots), evaluated by 3 specialist agents: Visual Design, Information Architecture & Usability, Readability & Accessibility.
---
## Executive Summary
The Cameleer3 dashboard has a **distinctive, well-crafted warm amber design language** that stands out in the observability space. The core monitoring pages (Dashboard, Exchange Detail, Routes, Agents) are polished and consistent. The design system provides a solid foundation.
1.**Font sizes too small** — pervasive 10-11px text for critical data impairs reading under stress
2.**Color contrast failures** — `--text-muted` and `--text-faint` fail WCAG AA in both themes
3.**Status indicators rely on color alone** — not accessible for color-blind users
4.**Admin infrastructure pages lag in polish** — Database/OpenSearch use ad-hoc styling
5.**Dashboard is a monitoring display, not yet an incident response tool** — missing error highlighting, per-route error breakdowns, actionable status pages
**Overall Score: 7/10** — Strong foundation, needs targeted fixes for production readiness under stress.
- **[Critical]** Processor timeline label column too narrow — processor names are truncated/illegible. This is the page's primary visualization.
- **[Critical]** No error highlighting in processor timeline — failed processors need red bars/icons. During incidents, engineers must instantly see WHICH processor failed.
- **[Important]** No linkage to route diagram — "View in Route Diagram" would overlay execution on the visual route graph.
- **[Important]** Long exchange ID in breadcrumb is visually heavy — truncate with copy button.
- **[Important]** Header stat labels at 10px uppercase with `--text-muted` — same contrast issue.
### Routes Metrics
- **[Important]** KPI number formatting inconsistent — Dashboard shows "11.742 ms" (decimal + space), Routes shows "11742ms" (no decimal, no space).
- **[Important]** No per-route error rate column — error rate in KPI strip but not broken down per route.
- **[Important]** Charts disconnected from table — clicking a route should filter/highlight its chart data.
- **[Nice-to-have]** No visual comparison between routes (bar chart or heatmap for quick identification of slowest).
### Agent Health
- **[Critical]** Stale/Dead agent visual distinction is too subtle — at 3am, the difference between LIVE and DEAD must scream. Dead agents should have prominent red background or strikethrough, not just `--text-muted`.
- **[Critical]** Agent state dots (green live, amber stale, gray dead) use color alone — no shape variation for color-blind users.
- **[Important]** "2/26" active routes KPI is ambiguous — unit and meaning need to be explicit.
- **[Nice-to-have]** Timeline at bottom takes significant space — consider making it collapsible.
### Agent Instance Detail
- **[Important]** Charts lack threshold/alert lines — CPU at 2% is fine, but where is "concerning"? Configurable thresholds (CPU > 80%, Memory > 90%) would make charts actionable.
- **[Important]** Chart axis labels appear too small.
- **[Nice-to-have]** GC Pauses uses area fill while others use line charts — minor inconsistency.
- **[Nice-to-have]** Six charts in 2x3 grid can create cognitive overload — consider collapsible groups.
### Admin — RBAC
- **[Important]** KPI strip for "Users: 1, Groups: 2, Roles: 4" has too much visual weight — these low-value numbers don't need full stat-card treatment.
- **[Important]** "ADMIN" role badge vs "ADMINS" group badge look identical — different badge styles needed (outlined for groups, filled for roles).
- **[Nice-to-have]** Empty detail panel ("Select a user to view details") needs icon/illustration.
### Admin — Audit Log
- **[Important]** "no data" empty state is uninformative — should explain "No audit events match your filters" with guidance.
- **[Important]** No export functionality — audit logs need CSV/JSON export for compliance.
- **[Important]** Date range filters use raw datetime inputs — inconsistent with dashboard's polished time range pills.
### Admin — OIDC Config
- **[Critical]** "Delete OIDC Configuration" is a destructive action without confirmation dialog — could lock out all SSO users.
- **[Important]** No inline validation — Issuer URL should validate format on blur, required fields need indicators.
- **[Nice-to-have]** No connection test result display area.
### Admin — Database
- **[Important]** Visual treatment inconsistent with rest of app — "Connected" status and pool stats use ad-hoc text, not design system components.
- **[Important]** Page title "Database Administration" implies actions, but page is read-only — rename to "Database Status" or add operations.
- **[Nice-to-have]** Table row counts should be right-aligned for numerical scanning.
### Admin — OpenSearch
- **[Critical]** "Disconnected" status displayed as plain text — needs error styling (red text, error badge, or status banner). Infrastructure disconnection is a critical state.
- **[Important]** "Yellow" cluster health displayed as plain text with no visual hierarchy — same size/weight as version number and node count.
- **[Important]** Indexing pipeline stats use ad-hoc inline format — should use consistent stat-card pattern.
- **[Important]** "Disconnected" + "Yellow" health shown simultaneously is contradictory — if disconnected, clarify whether data is stale.
### Command Palette
- **[Nice-to-have]** No visible keyboard navigation hint for currently selected item.
- **[Nice-to-have]** Empty palette should show recent/frequent items instead of requiring typing.
- Overall well-executed — categories, counts, keyboard hints in footer.
### Dark Mode
- **[Critical]** `--text-muted` (#7A7068) on `--bg-surface` (#242019) is ~2.9:1 — fails WCAG AA. Affects ALL muted labels across every page.
- **[Critical]** `--text-faint` (#4A4238) on `--bg-surface` (#242019) is ~1.4:1 — catastrophically fails WCAG AA. Essentially invisible.
- **[Important]** `--amber` (#D4941E) on `--bg-surface` (#242019) is ~3.6:1 — amber links/active text fail AA.
- **[Important]** KPI sparkline chart lines are harder to read — thin strokes need increased width or brightness.
- **[Important]** Sidebar boundary contrast drops significantly (`--sidebar-bg`#141210 vs `--bg-body`#1A1714 is only ~6 units apart).
- **[Important]** Table row alternation contrast near zero in dark mode.
- **[Nice-to-have]** Amber accent color shift from #C6820E to #D4941E is well-handled.
**Fix:** Change `--text-muted` to **#766A5E** (light) / **#9A9088** (dark). Restrict `--text-faint` to decorative use only or lighten dark variant to #6A6058.
### 2. Font Size Floor
10px text is used for: StatCard labels, overview labels, chain labels, section meta, error class names, detail labels, sidebar tree labels. 11px is used for: table meta, error messages, pagination, toggle buttons, chart titles.
**Fix:** Establish `--font-size-min: 12px` as a design system floor. Update all 10px instances to 12px, all 11px instances to 12px.
### 3. Number/Unit Formatting
Inconsistent across pages:
- Dashboard: "11.742 ms" (decimal + space)
- Routes: "11742ms" (no decimal, no space)
- Dashboard: "1.1 msg/s" vs Agent Instance: "0.1/s"
**Fix:** Create a shared formatting utility enforcing: consistent decimal precision, space before unit, consistent abbreviations.
### 4. KPI Strip Inconsistency
Used on Dashboard, Routes, Agents, Agent Instance (consistent). But RBAC uses oversized cards for trivial counts, and Database/OpenSearch use ad-hoc text rendering.
**Fix:** Admin infra pages should adopt KPI stat strip or a compact-stat component.
### 5. Empty States
Inconsistent handling:
- Audit Log: "no data" in plain gray
- RBAC detail: "Select a user to view details" in gray
- No consistent empty state component with icon + message + CTA
**Fix:** Design system EmptyState component with icon, message, and optional action.
### 6. Status Indicator Accessibility
Color-only status encoding throughout:
- Duration: green (fast), amber (slow), red (breach) — no icons
- Status dots: green (live), amber (stale), gray (dead) — no shapes
- Agent dead state uses `--text-muted` instead of `--error`
**Fix:** Add shape variation (checkmark/triangle/X), increase dot size to 10px minimum, always render text label alongside.
### 7. Sidebar Structure
Same apps listed 3x (under Applications, Agents, Routes) — triples sidebar length and scales poorly.
**Fix:** Unified application-centric tree where expanding an app shows its agents and routes as children.
---
## Prioritized Recommendations
### Critical (fix now)
| # | Recommendation | Impact |
|---|---------------|--------|
| 1 | **Bump `--text-muted` to WCAG AA compliance** — #766A5E (light) / #9A9088 (dark). Single highest-impact fix across all pages. | Fixes majority of contrast failures |
| 2 | **Establish 12px minimum font size** — update all 10px and 11px instances. Especially StatCard labels, overview labels, table meta. | Readable under stress |
| 3 | **Add error highlighting to processor timeline** — red bars, error icons for failed processors. Core debugging view. | Incident response speed |
| 4 | **Make Stale/Dead agent states unmistakable** — full card background color (yellow stale, red dead), prominent badge. Change dead from `--text-muted` to `--error`. | Prevents missed outages |
The Cameleer3 UI is generally well-built with consistent styling, good information density, and a clear layout. However, there are several **Critical** bugs that prevent core CRUD operations from working, and a few **Important** UX issues that reduce clarity and usability.
**Critical issues:** 3
**Important issues:** 7
**Nice-to-have improvements:** 8
---
## 1. Users & Roles (`/server/admin/rbac`)
### What Works Well
- Clean master-detail layout: user list on the left, detail panel on the right
- Summary cards at top (Users: 2, Groups: 1, Roles: 4) provide quick overview
- Tab structure (Users / Groups / Roles) is intuitive
- User detail shows all relevant info: status, ID, created date, provider, password, group membership, effective roles
- Inline role/group management with "+ Add" dropdown and "x" remove buttons
- Search bar for filtering users/groups/roles
- Delete button correctly disabled for the admin user (last-admin guard)
- Group detail shows Top-level, children count, member count, and assigned roles
- Local/OIDC toggle on the user creation form
### Issues Found
#### CRITICAL: User creation fails silently in OIDC mode
- **Location:** "+ Add user" button and create user form
- **Details:** When OIDC is enabled, the backend returns HTTP 400 with an **empty response body** when attempting to create a local user. The UI shows a generic "Failed to create user" toast with no explanation.
- **Root Cause:** `UserAdminController.createUser()` line 92-93 returns `ResponseEntity.badRequest().build()` (no body) when `oidcEnabled` is true.
- **Impact:** The UI still shows the "+ Add user" button and the full creation form even though the operation will always fail. Users fill out the form, click Create, and get a useless error.
- **Fix:** Either (a) hide the "+ Add user" button when OIDC is enabled, or (b) show a clear inline message like "Local user creation is disabled when OIDC is enabled", or (c) return a proper error body from the API.
#### IMPORTANT: Unicode escape shown literally in role descriptions
- **Location:** Roles tab, role description text
- **Details:** Role descriptions display `\u00b7` literally instead of rendering the middle dot character (middle dot).
- **Example:** "Full administrative access \u00b7 0 assignments" should be "Full administrative access - 0 assignments"
- **Screenshot:** `14-roles-tab.png`
#### IMPORTANT: No "Confirm password" field in user creation
- **Location:** "+ Add user" form
- **Details:** The form has Username*, Display name, Email, Password* but no password confirmation field. This increases the risk of typos in passwords.
#### NICE-TO-HAVE: Create button disabled until valid with no inline validation messages
- **Location:** User creation form
- **Details:** The "Create" button is disabled until form is valid, but there are no visible inline error messages explaining what is required. The asterisks on "Username *" and "Password *" help, but there's no indication of password policy requirements (min 12 chars, 3-of-4 character classes).
#### NICE-TO-HAVE: "Select a user to view details" placeholder
- **Location:** Right panel when no user selected
- **Details:** The placeholder text is fine but could be more visually styled (e.g., centered, with an icon).
---
## 2. Audit Log (`/server/admin/audit`)
### What Works Well
- Comprehensive filter system: date range (1h/6h/Today/24h/7d/Custom), user filter, category dropdown, action/target search
- Category dropdown includes all relevant categories: INFRA, AUTH, USER_MGMT, CONFIG, RBAC, AGENT
- Custom date range with From/To date pickers
- Table columns: Timestamp, User, Category, Action, Target, Result
- Color-coded result badges (SUCCESS in green, FAILURE in red)
- Shows my failed user creation attempts correctly logged as FAILURE
- Row count indicator ("179 events") with AUTO/MANUAL refresh
- Pagination with configurable rows per page
### Issues Found
#### IMPORTANT: No export functionality
- **Location:** Audit log page
- **Details:** There is no Export/Download button for audit log data. Compliance requirements typically mandate the ability to export audit logs as CSV or JSON.
#### NICE-TO-HAVE: Audit detail row expansion
- **Location:** Table rows are clickable (cursor: pointer) but clicking doesn't reveal additional details
- **Details:** For entries like "HTTP POST /api/v1/admin/users FAILURE", it would be helpful to see the error response body or request details in an expanded row.
#### NICE-TO-HAVE: Date range filter is independent of the global time selector
- **Location:** Top bar time selector vs. audit log's own time filter
- **Details:** The audit log has its own "Last 1h / 6h / Today / 24h / 7d / Custom" filter, which is separate from the global time range in the header bar. While this provides independence, it could confuse users who expect the global time selector to affect the audit log.
- Each field has a descriptive label and help text (e.g., "RFC 8707 resource indicator sent in the authorization request")
- "Test Connection" button at the top for verification
- "Save" button is clearly visible
- **Excellent** delete protection: "Confirm Deletion" dialog requires typing "delete oidc" to confirm, warns that "All users signed in via OIDC will lose access"
- Enabled/Auto Sign-Up checkboxes with clear descriptions
- Default Roles management with add/remove
### Issues Found
#### IMPORTANT: No unsaved changes indicator
- **Location:** Form fields
- **Details:** If a user modifies a field but navigates away without saving, there is no "You have unsaved changes" warning. This is particularly dangerous for the OIDC configuration since changes could lock users out.
#### NICE-TO-HAVE: Client Secret field is plain text
- **Location:** Client Secret textbox
- **Details:** The Client Secret is a regular text input, not a password/masked field. Since it's sensitive, it should be masked by default with a "show/hide" toggle.
---
## 4. Environments (`/server/admin/environments`)
### What Works Well
- Clean list with search and "+ Add environment" button
- Master-detail layout consistent with Users & Roles
- Environment detail shows: ID, Tier badge (NON-PROD), slug, created date
- Sub-tabs for "Production environment" and "Docker Containers"
- Default Resource Limits section with configurable values
- JAR Retention section with "Edit Policy" button
- "Edit Defaults" button for container defaults
### Issues Found
#### NICE-TO-HAVE: Slug is shown but not labeled clearly
- **Location:** Environment detail panel
- **Details:** The slug "default" appears below the display name "Default" but could benefit from a "Slug:" label for clarity.
---
## 5. Database (`/server/admin/database`)
### What Works Well
- Clear "Connected" status at the top with green styling
- Shows PostgreSQL version string: "PostgreSQL 16.13 on x86_64-pc-linux-musl, compiled by gcc (Alpine 15.2.0) 15.2.0, 64-bit"
- Connection Pool section with Active/Idle/Max counts
- Tables section listing all database tables with rows and sizes
- Consistent styling with the rest of the admin section
### Issues Found
No significant issues found. The page is read-only and informational, which is appropriate.
---
## 6. ClickHouse (`/server/admin/clickhouse`)
### What Works Well
- Clear "Connected" status with version number (26.3.5.12)
- Uptime display: "1 hour, 44 minutes and 29 seconds"
- Key metrics: Disk Usage (156.33 MiB), Memory (1.47 GiB), Compression Ratio (0.104x), Rows (4,875,598), Parts (55), Uncompressed Size (424.02 MiB)
- Tables section listing all ClickHouse tables with engine, rows, and sizes
- Consistent card-based layout
### Issues Found
No significant issues found. Well-presented status page.
---
## 7. Deployments Tab (`/server/apps`)
### What Works Well
- Table layout showing app name, environment, status, and created date
- "+ Create App" button clearly visible
- Clicking an app navigates to a detail page with Configuration and Overrides tabs
- App detail shows environment (DEFAULT), tier (ORACLE), status
- "Create App" full page form with clear Identity & Security, Configuration sections
### Issues Found
#### CRITICAL: Direct URL /server/deployments returns 404 error
- **Location:** `/server/deployments` URL
- **Details:** Navigating directly to `/server/deployments` shows "Unexpected Application Error! 404 Not Found" with a React Router development error ("Hey developer -- You can provide a way better UX than this..."). The Deployments tab is actually at `/server/apps`.
- **Impact:** Users who bookmark or share the URL will see an unhandled error page instead of a redirect to the correct URL.
#### IMPORTANT: Create App page shows full configuration before app exists
- **Location:** `/server/apps/new`
- **Details:** The Create Application page shows Monitoring configuration, Variables, Traces & Taps, and Route Recording sub-tabs with values already populated. This is overwhelming for initial creation -- a simpler wizard-style flow (name + environment first, then configure) would be more intuitive.
#### NICE-TO-HAVE: App deletion flow not easily discoverable
- **Location:** App detail page
- **Details:** There is no visible "Delete App" button on the app detail page. The deletion mechanism is not apparent.
---
## 8. SaaS Platform Pages
### Platform Dashboard (`/platform`)
#### What Works Well
- Clean tenant overview: "Example Tenant" with LOW tier badge
- Three summary cards: Tier (LOW), Status (ACTIVE), License (Active, expires 8.4.2027)
- Tenant Information section with Slug, Status, Created date
- Server Management section with "Open Server Dashboard" button
- Sidebar navigation: Dashboard, License, Open Server Dashboard
#### Issues Found
##### IMPORTANT: "Slug" label missing space
- **Location:** Tenant Information section
- **Details:** Shows "Slugdefault" instead of "Slug: default" -- the label and value run together without separation.
##### NICE-TO-HAVE: "Open Server Dashboard" button appears 3 times
- **Location:** Page header, Server Management section, sidebar bottom
- **Details:** The same action appears in three places on a single page view. One prominent button would suffice.
### Platform License (`/platform/license`)
#### What Works Well
- Clear Validity section: Issued, Expires, Days remaining (365 days badge)
- Features section with Enabled/Disabled badges for each feature
- Limits section: Max Agents, Retention Days, Max Environments
- License Token section with "Show token" button for security
#### Issues Found
##### IMPORTANT: Labels and values lack spacing
- **Location:** Validity section, Limits section
- **Details:** "Issued8. April 2026" and "Max Agents3" -- labels and values run together without separators. Should be "Issued: 8. April 2026" and "Max Agents: 3".
- **Screenshot:** `02-platform-license.png`
---
## 9. Cross-Cutting UX Issues
### CRITICAL: Sporadic auto-navigation to /server/exchanges
- **Location:** Occurs across all admin pages
- **Details:** While interacting with admin pages (Users & Roles, Environments, etc.), the browser occasionally auto-navigates back to `/server/exchanges`. This appears to be triggered by the real-time exchange data stream (SSE). Even when auto-refresh is set to MANUAL, the exchange list continues updating and can cause route changes.
- **Impact:** Users actively editing admin forms can lose their work mid-interaction. This was observed repeatedly during the audit.
- **Root Cause:** Likely a React state update from the SSE exchange stream that triggers a route navigation when the exchange list data changes.
### IMPORTANT: Error toast messages lack detail
- **Location:** Global toast system
- **Details:** Error toasts show generic messages like "Failed to create user" without the specific API error reason. The server returns empty 400 bodies in some cases, and even when it returns error details, they may not be surfaced in the toast.
### NICE-TO-HAVE: Global time range selector persists on admin pages
- **Location:** Top header bar on admin pages (Audit Log, ClickHouse, Database, OIDC, etc.)
- **Details:** The global time range selector (1h/3h/6h/Today/24h/7d) and the status filter buttons (OK/Warn/Error/Running) appear on every page including admin pages where they are not relevant. This adds visual clutter.
### NICE-TO-HAVE: Environment dropdown in header on admin pages
- **Location:** Top header bar, "All Envs" dropdown
- **Details:** The environment selector appears on admin pages where it has no effect (e.g., Users & Roles, OIDC config). It should be hidden or grayed out on pages where it's not applicable.
---
## Summary Table
| # | Severity | Page | Issue |
|---|----------|------|-------|
| 1 | **CRITICAL** | Users & Roles | User creation fails silently in OIDC mode -- form shown but always returns 400 with empty body |
- Custom `.tableHeader` with `padding: 8px 12px` (slightly tighter than shared `tableStyles.tableHeader` which uses `12px 16px`)
-`DataTable` rendered with `flush` and `fillHeight` props
- **NO card wrapper** around the table — it's full-bleed against the background
- **Does NOT import shared `table-section.module.css`** — rolls its own `.tableHeader`, `.tableTitle`, `.tableRight`, `.tableMeta`
**Shared modules used**: NONE. All custom.
**INCONSISTENCY**: Full-bleed table with no card, no container padding. Custom table header styling duplicates shared module patterns with slightly different padding values (8px 12px vs 12px 16px).
**INCONSISTENCY**: No container padding means KPI strip and tables sit flush against the sidebar/edge. The `.errorsSection` duplicates `tableStyles.tableSection` exactly — should import the shared module instead of copy-pasting.
---
### 3. Runtime Tab — Agent Health (`pages/AgentHealth/`)
**Tables**: Uses design-system `DataTable` inside a DS `Card` component for agent group cards. The group cards use custom `.groupGrid` grid layout. No `tableStyles.tableSection` wrapper.
**Cards/sections**: Custom card patterns like `.configBar`, `.eventCard`:
```css
.configBar{
background:var(--bg-surface);
border:1pxsolidvar(--border-subtle);
border-radius:var(--radius-lg);
box-shadow:var(--shadow-card);
padding:12px16px;
margin-bottom:16px;
}
```
**Shared modules used**: `log-panel.module.css`
**INCONSISTENCY**: Uses `padding: 20px 24px 40px` — different from DashboardTab (no padding) and Exchanges (no padding). Custom card patterns duplicate the standard card styling. Does not use `table-section.module.css` or `section-card.module.css`.
**INCONSISTENCY**: Consistent with AgentHealth but inconsistent with DashboardTab and Exchanges. Custom card patterns (processCard, timelineCard) duplicate shared module patterns.
---
### 5. Apps Tab (`pages/AppsTab/`)
**Files**: `AppsTab.tsx`, `AppsTab.module.css`
**Container pattern**:
```css
.container{padding:16px;overflow-y:auto;flex:1;}
```
- **Has padding**: `16px` all around
**Content structure**: Three sub-views (`AppListView`, `AppDetailView`, `CreateAppView`) all wrapped in `.container`.
**Tables**: App list uses `DataTable` directly — no `tableStyles.tableSection` wrapper. Deployment table uses custom `.table` with manual `<table>` HTML (not DataTable).
**Form controls**: Directly on page background with custom grid layout (`.configGrid`). Uses `SectionHeader` from design-system for visual grouping, but forms are not in cards/sections — they sit flat against the `.container` background.
**INCONSISTENCY (user-reported)**: Controls "meshed into background" — correct. Form controls use `SectionHeader` for labels but no `section-card` wrapper. The Tabs component provides visual grouping but the content below tabs is flat. Config grids, toggles, and inputs sit directly on `var(--bg-body)` background via the 16px-padded container. No card/section separation between different config groups. Also uses a manual `<table>` element instead of DataTable for deployments.
**Container pattern**: Inherits AdminLayout padding. Renders bare `<div>`.
**Tables**: Uses `DataTable` directly with NO `tableStyles.tableSection` wrapper. Tables under custom `.section` divs with `.sectionHeading` text labels.
**Cards**: Uses DS `<Card>` for connection pool. Stat strip is a flex layout.
**Shared modules used**: NONE. All custom.
**INCONSISTENCY**: Tables not wrapped in `tableStyles.tableSection`. Uses custom section headings instead of `SectionHeader`. Missing card wrappers around tables. Stat strip uses `flex` layout while other pages use `grid`.
**Container pattern**: Inherits AdminLayout padding. Renders via `SplitPane` (design-system).
**Content**: Uses `SectionHeader`, `SplitPane`, custom meta grids from `UserManagement.module.css`.
**Shared modules used**: Uses `UserManagement.module.css` (shared with RBAC pages)
**INCONSISTENCY**: Does not use `section-card.module.css` for form sections. Config sections use `SectionHeader` without card wrappers. `SplitPane` provides some structure but detail content is flat.
**STATUS**: GOOD. Follows same pattern as OIDC page.
---
### 13. Routes pages (`pages/Routes/`) — NOT ROUTED
These pages (`RoutesMetrics.tsx`, `RouteDetail.tsx`) exist but are NOT in `router.tsx`. They may be deprecated or used as sub-components. `RoutesMetrics` correctly uses shared `tableStyles.tableSection`. `RouteDetail` has many custom card patterns (`.headerCard`, `.diagramPane`, `.statsPane`, `.executionsTable`, `.routeFlowSection`) that duplicate the shared card pattern.
**Finding**: 4 of 7 table-using pages do NOT use the shared `table-section.module.css`. The Exchanges page custom header has padding `8px 12px` vs shared `12px 16px`.
### Form/Control Wrapper Pattern
| Page | Form controls in cards? | Uses `section-card`? |
**Finding**: Only OIDC and AppConfigDetail use `section-card.module.css` for form grouping. Most form pages render controls flat against the page background.
### Duplicated Card Pattern
The following CSS pattern appears in 8+ custom locations instead of importing `section-card.module.css` or `table-section.module.css`:
1.**Exchanges table full-bleed**: `Dashboard.tsx` should wrap its table in `tableStyles.tableSection` and use the shared table header classes instead of custom ones. Custom `.tableHeader` padding (8px 12px) should match shared (12px 16px).
2.**Apps detail flat controls**: `AppsTab.tsx` config sections should wrap form groups in `sectionStyles.section` (from `section-card.module.css`), matching the OIDC page pattern.
3.**Apps deployment table**: Replace manual `<table>` with `DataTable` inside `tableStyles.tableSection`.
### P1 — Padding normalization
4.**Standardize container padding**: Choose ONE pattern for scrollable content areas. Recommended: `padding: 20px 24px 40px` (currently used by Runtime + Admin). Apply to DashboardTab's `.content`. Exchanges is an exception due to its split-view height-filling layout.
5.**DashboardTab.module.css**: Add side padding to `.content`.
### P2 — Shared module adoption
6.**Replace duplicated card patterns**: Import `section-card.module.css` or `table-section.module.css` instead of duplicating the card CSS in:
-`DashboardTab.module.css` (`.errorsSection` -> use `tableStyles.tableSection`)
- Message: `Delete environment "${displayName}"? All apps and deployments in this environment will be removed. This cannot be undone.`
- Confirm text: environment's `slug` (NOT the display name)
- Has `loading` prop
- **Default guard**: Button is `disabled={isDefault}` (cannot delete default environment)
- **Toast on success**: `variant: 'warning'`, title: "Environment deleted"
**NOTE**: The confirm text requires the slug but the message shows the display name. This is intentional (slug is the unique identifier) but differs from Users/Groups/Roles which use the display name.
- **Confirmation**: NONE -- immediate mutation on click
- **Toast on success**: `variant: 'warning'`, title: "Deployment stopped"
**INCONSISTENCY**: Stopping a deployment is a destructive operation that affects live services but has NO confirmation dialog. Route stop/suspend in RouteControlBar uses a ConfirmDialog, but deployment stop does not.
- **Read mode**: Back (ghost) ... Edit (secondary)
- **Edit mode**: Back (ghost) ... Save (default/no variant specified!) | Cancel (secondary)
- **Order when editing**: Save (left) | Cancel (right)
**INCONSISTENCY #1**: Save button has NO `variant` prop set -- it renders as default, not `primary`. Every other Save button uses `variant="primary"`.
**INCONSISTENCY #2**: Button order is REVERSED from every other form. Here it is Save (left) | Cancel (right). Everywhere else it is Cancel (left) | Save (right).
**INCONSISTENCY**: Only page that is always editable with no way to discard changes. Also the only page that shows BOTH a toast AND an inline alert on error.
### 3.6 App Config Detail (AppConfigDetailPage)
- **Edit mode**: Explicit toggle via `Edit` button (Pencil icon) in toolbar
- **Toolbar in edit mode**: Save (unstyled!) | Cancel (secondary)
- **Save button text**: Shows "Saving..." while pending
**INCONSISTENCY**: The AppsTab ConfigSubTab is the only one with a proper unsaved-changes indicator. AppConfigDetailPage (which edits the same data for a different entry point) has no such indicator.
**INCONSISTENCY #1**: Most admin pages use `<Spinner size="md" />` as a bare return. AgentInstance and AppConfigDetailPage use `size="lg"`. DashboardPage and RuntimePage use the `<PageLoader />` component which wraps `<Spinner size="lg" />` in a centered container.
**INCONSISTENCY #2**: OidcConfigPage returns `null` while loading (shows a blank page), unlike every other page.
**INCONSISTENCY #3**: SplitPane detail loading (GroupsTab line 317, RolesTab line 212) uses `<Spinner size="md" />` -- consistent within that context.
The design system provides an `EmptyState` component but it is only used in one place (AgentInstance).
---
## 6. Inconsistency Summary
### HIGH Priority (User-facing confusion)
1.**AppConfigDetailPage button order is reversed** (Save|Cancel instead of Cancel|Save) and Save button has no `variant="primary"`. File: `ui/src/pages/Admin/AppConfigDetailPage.tsx`, lines 311-315.
2.**Deployment Stop has no confirmation dialog**. Stopping a running deployment immediately executes with no confirmation, while stopping/suspending a route shows a ConfirmDialog. File: `ui/src/pages/AppsTab/AppsTab.tsx`, line 672.
3.**Tap deletion is inconsistent**. Deleting from TapConfigModal: no confirmation. Deleting from RouteDetail table: ConfirmDialog. File: `ui/src/components/TapConfigModal.tsx` line 117 vs `ui/src/pages/Routes/RouteDetail.tsx` line 992.
4.**Kill Query has no confirmation and no feedback**. File: `ui/src/pages/Admin/DatabaseAdminPage.tsx`, line 30.
### MEDIUM Priority (Pattern deviations)
5.**Cancel button variant inconsistency**. Create forms use `variant="ghost"` for Cancel. Modal dialogs (TapConfigModal, RouteDetail tap modal) use `variant="secondary"`. File: `ui/src/components/TapConfigModal.tsx` line 255, vs `ui/src/pages/Admin/UsersTab.tsx` line 258.
6.**Removing a role from a user has no confirmation** but removing a group from a user shows an AlertDialog. Both can cascade. File: `ui/src/pages/Admin/UsersTab.tsx`, lines 504-528 vs 588-613.
7.**OIDC Config is always editable with no Cancel/discard**. Every other editable form either has inline-edit (immediate save) or explicit edit mode with Cancel. File: `ui/src/pages/Admin/OidcConfigPage.tsx`.
8.**OIDC Config delete ConfirmDialog missing `loading` prop**. All other delete ConfirmDialogs pass `loading={mutation.isPending}`. File: `ui/src/pages/Admin/OidcConfigPage.tsx`, line 258.
9.**Loading state size inconsistency**. Most pages use `Spinner size="md"`, some use `size="lg"`, some use `PageLoader`, and OidcConfigPage returns `null`. No single standard.
10.**Error toast title format inconsistency**. RBAC pages use "Failed to [verb] [noun]" while AppsTab/AppConfigDetailPage use "[Noun] failed". Should pick one.
### LOW Priority (Minor deviations)
11.**Empty state presentation varies widely**. Five different approaches used. Should standardize on the design system `EmptyState` component or at least a consistent CSS class.
12.**ConfirmDialog confirmText varies between display name and slug**. Users/Groups/Roles use display name; Environments and Apps use slug. This is arguably intentional (slug is the technical identifier) but may confuse users.
13.**OIDC Config shows both toast and inline Alert on error**. No other page shows both simultaneously. File: `ui/src/pages/Admin/OidcConfigPage.tsx`, line 92 (toast) + line 139 (inline Alert).
14.**AppConfigDetailPage Save button text changes to "Saving..."** using string interpolation, while every other page uses the `loading` prop on Button (which shows a spinner). File: `ui/src/pages/Admin/AppConfigDetailPage.tsx`, line 313.
15.**Unsaved changes indicator** only present on AppsTab ConfigSubTab (banner text). AppConfigDetailPage, Environment resource sections, and JAR retention section have no indicator even though they use explicit edit mode.
---
## 7. ConfirmDialog Usage Matrix
| Object | File | Line | confirmText Source | Has `loading`? | Has `variant`? | Has `confirmLabel`? |
**NOTE**: RouteControlBar and RouteDetail set explicit `variant` and `confirmLabel` on ConfirmDialog while all RBAC/admin pages use defaults. This creates visual differences in the confirmation dialogs.
---
## 8. AlertDialog Usage Matrix
| Context | File | Line | Title | Confirm Label | Variant |
- Error tab shows full Java stack trace with Copy button and exception message prominently displayed
- ERR rows in table have clear red status badge with icon
- Correlated exchanges section present (even when none found)
- JSON download button available on the detail view
### Issues Found
**Important:**
- **Exchange ID is raw hex, hard to scan.** The IDs like `96E395B0088AA6D-000000000001ED46` are 33+ characters wide. They push the table columns apart and are hard for humans to parse. Consider truncating with copy-on-click or showing a short hash.
- **Attributes column always shows "--".** Every single exchange row displays "--" in the Attributes column. If no attributes are captured, this column wastes horizontal space. Consider hiding it when empty or showing it only when relevant data exists.
- **Status shows "OK" but detail shows "COMPLETED".** The table status column shows "OK" / "ERR" but the detail panel shows "COMPLETED" / "FAILED". This terminology mismatch is confusing -- pick one convention.
**Nice-to-have:**
- **No breadcrumb update when exchange selected.** The breadcrumb still shows "All Applications" even when viewing a specific exchange detail. Should show: All Applications > sample-app > Exchange 96E39...
- **No action buttons on exchange detail.** No "Replay", "Trace", or "View Route" buttons in the detail view. Users would benefit from contextual actions.
- **Back navigation relies on de-selecting the row.** There is no explicit "Close" or "Back" button on the detail panel.
- **Application Health table row click is blocked by overlapping elements.** Playwright detected `_tableSection` and `_chartGrid` divs intercepting pointer events on the table row. While JavaScript `.click()` works, this means CSS `pointer-events` or `z-index` is wrong -- real mouse clicks may be unreliable depending on scroll position.
- **SLA Compliance 0.0% shows "BREACH" label** in L2 view but no explanation of what the SLA threshold is until you look closely at the latency chart. The SLA threshold (300ms) should be shown next to the KPI, not just in the chart.
- **7-Day Pattern heatmap is flat/empty.** The heatmap shows data only for the current day, making it look broken for a fresh deployment. Consider showing "Insufficient data" when less than 2 days of data exist.
- **"Application Volume vs SLA Compliance" bubble chart** truncates long application names (e.g., "complex-fulfil..." in L2). The chart has limited space for labels.
**Nice-to-have:**
- **No trend arrows on KPI values in L2.** The L1 dashboard shows up/down arrows (all "up"), but L2 KPIs show percentage change text instead. The two levels should be consistent.
- **P99 latency 6695ms is not formatted as seconds.** Values over 1000ms should display as "6.7s" for readability. The L2 view uses raw milliseconds (1345ms) which is also inconsistent with the L1 (6695ms) and the exchange list which does format durations.
- **Throughput numbers use locale-specific formatting.** In the route table: `1.050` (German locale?) vs `14.377` -- these look like decimal numbers rather than thousands. Consider using explicit thousands separator or always using K suffix.
- Clicking an agent row navigates to a rich detail view with 6 charts (CPU, Memory, Throughput, Error Rate, Thread Count, GC Pauses)
- Agent capabilities displayed as badges (LOGFORWARDING, DIAGRAMS, TRACING, METRICS)
- Application Log viewer with level filtering (Error/Warn/Info/Debug/Trace) and auto-scroll
- Timeline shows agent events (CONFIG_APPLIED, COMMAND_SUCCESS) with relative timestamps
### Issues Found
**Critical:**
- **GC Pauses chart X-axis is unreadable.** The chart renders ~60 full ISO-8601 timestamps (`2026-04-09T14:16:00Z` through `2026-04-09T15:15:00Z`) as X-axis labels. These overlap completely and form an unreadable block of text. All other charts use concise numeric labels (e.g., "12", "24"). The GC Pauses chart should use the same time formatting.
**Important:**
- **Agent state shows "UNKNOWN" alongside "LIVE".** The detail view shows both "LIVE" and "UNKNOWN" state indicators. The "UNKNOWN" appears to be a secondary state field (perhaps container state?) but it is confusing to show two conflicting states without explanation.
- **Memory chart shows absolute MB values but no percentage on Y-axis.** The KPI shows "46% / 57 MB / 124 MB" which is great, but the chart Y-axis goes from 0-68 MB which doesn't match the 124 MB limit. The max heap should be indicated on the chart (e.g., as a reference line).
- **Throughput chart Y-axis scale is wildly mismatched.** The KPI shows 2.0 msg/s but the Y-axis goes to 1.2k msg/s, making the actual data appear as a flat line near zero. The Y-axis should auto-scale to the actual data range.
- **Error Rate chart Y-axis shows "err/h"** but the unit inconsistency with the KPI (which shows percentage "1.7%") is confusing.
**Nice-to-have:**
- **"DEAD 0" KPI in the overview is redundant** when "all healthy" text is already shown below it. Consider combining or removing the redundant label.
- **Application Log shows "0 entries"** in the overview but "100 entries" in the agent detail. The overview log may not aggregate across agents, which is misleading.
- Read-only mode with explicit "Edit" button prevents accidental changes
- "Upload JAR" and "Delete App" action buttons are visible
- Create Application form (`/apps/new`) is comprehensive with Identity & Artifact section, deploy toggle, and monitoring sub-tabs
### Issues Found
**Important:**
- **Navigating to `/server/apps` redirected to `/server/apps/new`** on the initial visit, bypassing the apps list. This happened once but not consistently. The default route for the Deployments tab should always be the list view, not the create form.
- **No deployment status/progress visible in the list.** The apps list shows "RUNNING" status only in the detail view. The list should show the deployment status directly (RUNNING/STOPPED/FAILED badge per row).
- **"Updated: 59m ago" is relative time** which becomes stale if the page is left open. Consider showing absolute timestamp on hover.
**Nice-to-have:**
- **Configuration form select dropdowns** (Engine Level, Payload Capture, App Log Level, etc.) all use native HTML selects with a custom `"triangle"` indicator -- this is inconsistent with the design system's `Select` component used elsewhere.
- **"External URL" field shows `/default/.../`** placeholder which is cryptic. Should show the full resolved URL or explain the pattern.
- Search is fast and filters results in real-time (typed "error" -> filtered to 11 results)
- Search term highlighting (yellow background on matched text)
- Keyboard navigation works (ArrowDown moves selection)
- Rich result items: exchange IDs with status, routes with app name and exchange count, applications with agent count
- Escape closes the palette
- Category tabs allow filtering by type
### Issues Found
**Nice-to-have:**
- **Exchange IDs in search results are full hex strings.** The same issue as the exchanges table -- `5EF55FC31352A9A-000000000001F07C` is hard to scan. Show a shorter preview.
- **No keyboard shortcut hints in results.** Results don't show "Enter to open" or "Tab to switch category" -- users must discover these by trial.
- **Category counts don't update when filtering.** When I typed "error", the category tabs still show the original counts (Applications, Exchanges 10, Routes 1, Agents) but some categories become empty. The empty categories should hide or dim.
- Table rows have good contrast (light text on dark background)
- Status badges (OK green, ERR red) remain clearly visible
- Chart lines and data points are visible against dark backgrounds
- KPI cards have distinct dark card backgrounds with readable text
- The dark mode toggle is easy to find (moon icon in header)
- Theme preference persists in localStorage (`cameleer-theme`)
### Issues Found
**Important:**
- **Chart backgrounds appear as opaque dark cards but chart lines may be harder to see.** The throughput and error rate charts use amber/orange lines on dark gray backgrounds -- this is acceptable but not ideal. Consider slightly brighter chart colors in dark mode.
- **Application Volume vs SLA chart** in dashboard: the bubble/bar labels may have low contrast in dark mode (hard to verify at screenshot resolution).
**Nice-to-have:**
- **Sidebar border/separator** between the sidebar and main content area is very subtle in dark mode. A slightly more visible divider would help.
- **Environment badges** (DEFAULT in gold, DEVELOPMENT in orange) are designed for light mode and may look less distinct against the dark background.
---
## 7. Cross-Cutting Interaction Issues
### Status Filter Buttons (OK/Warn/Error/Running)
**Screenshots:**`03-exchanges-error-filtered.png`
**Important:**
- **Error filter works correctly** -- clicking the Error button filters to show only ERR exchanges (447 in the test). The button shows active/pressed state.
- **Filter state is not preserved in URL.** Navigating away and back loses the filter. Consider encoding active filters in the URL query string.
- **KPI strip does not update when filter is active.** When Error filter is active, the KPI strip still shows overall stats (Total 23.4K, Err% 1.9%). It should either update to show filtered stats or clearly indicate it shows overall stats.
- Sort indicator arrow is visible on the column header
- **Sorting is client-side only (within the 50-row page).** With 23K+ exchanges, sorting only the visible page is misleading. Consider either fetching sorted data from the server or clearly labeling "sorted within current page."
### Pagination
- Pagination works: "1-25 of 50", page 1/2, rows per page selector (10/25/50/100)
- Next/Previous page buttons work
- **"50 of 23,485 exchanges" label is confusing.** The "50" refers to the server-side limit (max fetched), not the page size (25). This should read "Showing 1-25 of 23,485" or similar.
### Sidebar App Tree
**Screenshot:**`20-sidebar-expanded.png`
- Expand/collapse works for "sample app"
- Shows all 10 routes with exchange counts (audit-log 5.3k, file-processing 114.2k, etc.)
- Exchange counts use K-suffix formatting which is good
- **Add to starred button is present** (star icon on the app)
### Environment Selector
- Dropdown works: All Envs / default / development
- Switching environment correctly filters data (65K -> 3.5K exchanges)
- Selection persists in localStorage
### Time Range Pills
**Screenshot:**`21-time-range-3h.png`
- Time range pills work (1h, 3h, 6h, Today, 24h, 7d)
- Switching updates data and KPI strip correctly
- Custom date range is shown: "9. Apr. 16:14 -- now" with clickable start/end timestamps
- **Date formatting uses European style** ("9. Apr. 16:14") which is fine but inconsistent with ISO timestamps elsewhere.
---
## 8. Systematic Navigation Bug
**Critical:**
During the audit, the browser consistently auto-redirected from any page to `/server/admin/rbac` (Users & Roles) after interactions involving the Playwright accessibility snapshot tool. This happened:
- After taking snapshots of the exchanges page
- After clicking exchange detail rows
- After interacting with filter buttons
- After attempting to click table rows
The redirect does **not** happen when using only JavaScript-based interactions (`page.evaluate`) without the Playwright snapshot/click methods. The root cause appears to be that the Playwright MCP accessibility snapshot tool triggers focus/click events on sidebar items (specifically "Users & Roles"), causing unintended navigation.
**While this is likely a tool interaction artifact rather than a real user-facing bug**, it reveals that:
1. The sidebar tree items may have overly aggressive focus/activation behavior (activating on focus rather than explicit click)
2. There may be no route guard preventing unexpected navigation when the user hasn't explicitly clicked a sidebar item
Recommend investigating whether keyboard focus on sidebar tree items triggers navigation (it should require Enter/click, not just focus).
---
## Summary of Issues by Severity
### Critical (1)
1.**GC Pauses chart X-axis renders ~60 full ISO timestamps** -- completely unreadable (Runtime > Agent Detail)
### Important (10)
1.**Exchange ID columns are too wide** -- 33-char hex strings push table layout (Exchanges)
2.**Attributes column always shows "--"** -- wastes space (Exchanges)
3.**Status terminology mismatch** -- "OK/ERR" in table vs "COMPLETED/FAILED" in detail (Exchange Detail)
@Schema(description="ClickHouse storage and performance metrics")
publicrecordClickHousePerformanceResponse(
StringdiskSize,
StringuncompressedSize,
doublecompressionRatio,
longtotalRows,
intpartCount,
StringmemoryUsage,
intcurrentQueries
){}
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.