Compare commits

...

5 Commits

Author SHA1 Message Date
hsiegeln
0c4a6d430e chore: align public-facing assets with cameleer.io
All checks were successful
Build & Publish / publish (push) Successful in 2m16s
Replace personal/internal references in shipped artifacts ahead of
institutionalizing the product. registry.cameleer.io becomes the public
endpoint for the npm package and repository URL; gitea.siegeln.net
remains the internal CI publish target. Mock seed-admin renamed from
"Hendrik Siegeln" to "Ada Sterling" across pages, audit log, RBAC
mocks, and ui-mocks; Java exception namespace switched from
com.cameleer3 to io.cameleer in the route detail mock.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-28 17:35:56 +02:00
hsiegeln
2bf4144380 fix: align sidebar section and footer icon columns, bump v0.1.56
All checks were successful
Build & Publish / publish (push) Successful in 1m48s
SonarQube Analysis / sonarqube (push) Successful in 3m50s
Section header and footer link icons were at different x-positions (9px
vs 15px) and used different wrapper widths (16px vs 18px), producing a
ragged left column where the two rows are visually adjacent.

- Drop horizontal padding from .bottom so .bottomItem sits flush to the
  sidebar edges like .treeSection, yielding a shared 9px effective left
  padding (3px transparent border + 6px inner).
- Rewrite .bottomIcon to mirror .sectionIcon: 16px flex-centered, no
  legacy font-size/text-align (obsolete for Lucide SVG icons).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-16 21:11:38 +02:00
hsiegeln
dba3aa5a85 fix: align sidebar bottomItem padding with treeSection, bump v0.1.55
All checks were successful
Build & Publish / publish (push) Successful in 1m52s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 15:34:27 +02:00
hsiegeln
d775df61e4 feat: remove expand toggle from collapsed sidebar, bump v0.1.54
All checks were successful
Build & Publish / publish (push) Successful in 1m49s
SonarQube Analysis / sonarqube (push) Successful in 2m36s
Collapsed sidebar no longer shows an expand button — clicking any
section icon expands the sidebar and opens that section instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 22:05:10 +02:00
hsiegeln
7c6d383ac9 docs: update CLAUDE.md and COMPONENT_GUIDE.md for chart wrappers and sidebar section props
All checks were successful
Build & Publish / publish (push) Successful in 1m2s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 21:55:22 +02:00
26 changed files with 116 additions and 78 deletions

View File

@@ -1,7 +1,7 @@
<!-- gitnexus:start --> <!-- gitnexus:start -->
# GitNexus — Code Intelligence # GitNexus — Code Intelligence
This project is indexed by GitNexus as **design-system** (1479 symbols, 2371 relationships, 24 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. This project is indexed by GitNexus as **design-system** (1537 symbols, 2409 relationships, 23 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. > If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first.

View File

@@ -37,8 +37,8 @@ Always read `COMPONENT_GUIDE.md` before building any UI feature. It contains dec
### Import Paths ### Import Paths
```tsx ```tsx
import { Button, Input } from '../design-system/primitives' import { Button, Input } from '../design-system/primitives'
import { Modal, DataTable, KpiStrip, SplitPane, EntityList, LogViewer } from '../design-system/composites' import { Modal, DataTable, KpiStrip, SplitPane, EntityList, LogViewer, LineChart, AreaChart, BarChart } from '../design-system/composites'
import type { Column, KpiItem, LogEntry } from '../design-system/composites' import type { Column, KpiItem, LogEntry, ChartSeries } from '../design-system/composites'
import { AppShell } from '../design-system/layout/AppShell' import { AppShell } from '../design-system/layout/AppShell'
import { Sidebar } from '../design-system/layout/Sidebar/Sidebar' import { Sidebar } from '../design-system/layout/Sidebar/Sidebar'
import { SidebarTree } from '../design-system/layout/Sidebar/SidebarTree' import { SidebarTree } from '../design-system/layout/Sidebar/SidebarTree'
@@ -51,18 +51,18 @@ import { ThemeProvider } from '../design-system/providers/ThemeProvider'
This design system is published as `@cameleer/design-system` to the Gitea npm registry. This design system is published as `@cameleer/design-system` to the Gitea npm registry.
### Registry: `https://gitea.siegeln.net/api/packages/cameleer/npm/` ### Registry: `https://registry.cameleer.io/api/packages/cameleer/npm/`
### Setup in a consuming app ### Setup in a consuming app
1. Add `.npmrc` to the project root: 1. Add `.npmrc` to the project root:
``` ```
@cameleer:registry=https://gitea.siegeln.net/api/packages/cameleer/npm/ @cameleer:registry=https://registry.cameleer.io/api/packages/cameleer/npm/
//gitea.siegeln.net/api/packages/cameleer/npm/:_authToken=${GITEA_TOKEN} //registry.cameleer.io/api/packages/cameleer/npm/:_authToken=${REGISTRY_TOKEN}
``` ```
Note: CI pipelines for consuming apps also need this `.npmrc` and a `GITEA_TOKEN` secret to fetch the package during `npm ci`. Note: CI pipelines for consuming apps also need this `.npmrc` and a `REGISTRY_TOKEN` secret to fetch the package during `npm ci`.
2. Install: 2. Install:
@@ -95,7 +95,7 @@ import { Button, AppShell, ThemeProvider } from '@cameleer/design-system'
```tsx ```tsx
// All components from single entry // All components from single entry
import { Button, Input, Modal, DataTable, KpiStrip, SplitPane, EntityList, LogViewer, StatusText, AppShell } from '@cameleer/design-system' import { Button, Input, Modal, DataTable, KpiStrip, SplitPane, EntityList, LogViewer, StatusText, AppShell, LineChart, AreaChart, BarChart } from '@cameleer/design-system'
// Sidebar (compound component — compose your own navigation) // Sidebar (compound component — compose your own navigation)
import { Sidebar, SidebarTree, useStarred } from '@cameleer/design-system' import { Sidebar, SidebarTree, useStarred } from '@cameleer/design-system'
@@ -132,7 +132,7 @@ import camelSvg from '@cameleer/design-system/assets/camel-logo.svg' // simp
<!-- gitnexus:start --> <!-- gitnexus:start -->
# GitNexus — Code Intelligence # GitNexus — Code Intelligence
This project is indexed by GitNexus as **design-system** (1479 symbols, 2371 relationships, 24 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. This project is indexed by GitNexus as **design-system** (1537 symbols, 2409 relationships, 23 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. > If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first.

View File

@@ -54,8 +54,10 @@
### "I need to display data" ### "I need to display data"
- Key metrics → **StatCard** (with optional sparkline/trend) - Key metrics → **StatCard** (with optional sparkline/trend)
- Tabular data → **DataTable** (sortable, paginated) - Tabular data → **DataTable** (sortable, paginated)
- Time series **ThemedChart** with `<Line>` or `<Area>` - Time series (quick) → **LineChart** or **AreaChart** (convenience wrappers with series data)
- Categorical comparison **ThemedChart** with `<Bar>` - Categorical comparison (quick) → **BarChart** (convenience wrapper with series data)
- Time series (custom) → **ThemedChart** with `<Line>` or `<Area>`
- Categorical comparison (custom) → **ThemedChart** with `<Bar>`
- Inline trend → **Sparkline** - Inline trend → **Sparkline**
- Advanced charts (treemap, radar, heatmap, pie, etc.) → **Recharts** with `rechartsTheme` (see [Charting Strategy](#charting-strategy)) - Advanced charts (treemap, radar, heatmap, pie, etc.) → **Recharts** with `rechartsTheme` (see [Charting Strategy](#charting-strategy))
- Event log → **EventFeed** - Event log → **EventFeed**
@@ -109,6 +111,9 @@ Sidebar compound API:
<Sidebar.Section label="str" icon={node} open={bool} onToggle={fn} active={bool}> <Sidebar.Section label="str" icon={node} open={bool} onToggle={fn} active={bool}>
<SidebarTree nodes={[...]} selectedPath="..." filterQuery="..." ... /> <SidebarTree nodes={[...]} selectedPath="..." filterQuery="..." ... />
</Sidebar.Section> </Sidebar.Section>
<Sidebar.Section label="str" icon={node} open={bool} onToggle={fn} position="bottom" maxHeight="200px">
<SidebarTree nodes={[...]} ... />
</Sidebar.Section>
<Sidebar.Footer> <Sidebar.Footer>
<Sidebar.FooterLink icon={node} label="str" onClick={fn} active={bool} /> <Sidebar.FooterLink icon={node} label="str" onClick={fn} active={bool} />
</Sidebar.Footer> </Sidebar.Footer>
@@ -119,6 +124,11 @@ Notes:
- Section headers have no chevron — the entire row is clickable to toggle - Section headers have no chevron — the entire row is clickable to toggle
- The app controls all content — sections, order, tree data, collapse state - The app controls all content — sections, order, tree data, collapse state
- Sidebar provides the frame, search input, and icon-rail collapse mode - Sidebar provides the frame, search input, and icon-rail collapse mode
- `position="bottom"` stacks sections above the footer; a spacer separates top/bottom groups
- `maxHeight` (CSS string) constrains the content area — section header stays visible, children scroll
- Both groups scroll independently when the viewport is short
- Custom thin scrollbars match the dark sidebar aesthetic
- No expand button when collapsed — clicking any section icon expands the sidebar and opens that section
``` ```
### Data page pattern ### Data page pattern
@@ -182,9 +192,38 @@ URL-driven progressive filtering: /agents → /agents/:appId → /agents/:appId/
## Charting Strategy ## Charting Strategy
The design system provides a **ThemedChart** wrapper component that applies consistent styling to Recharts charts. Recharts is bundled as a dependency — consumers do not need to install it separately. The design system provides convenience chart wrappers (**LineChart**, **AreaChart**, **BarChart**) for common use cases, plus a lower-level **ThemedChart** wrapper for full Recharts control. Recharts is bundled as a dependency — consumers do not need to install it separately.
### Usage ### Quick Charts (convenience wrappers)
```tsx
import { LineChart, AreaChart, BarChart } from '@cameleer/design-system'
import type { ChartSeries } from '@cameleer/design-system'
const series: ChartSeries[] = [
{ label: 'CPU', data: [{ x: '10:00', y: 45 }, { x: '10:05', y: 62 }] },
{ label: 'Memory', data: [{ x: '10:00', y: 70 }, { x: '10:05', y: 72 }] },
]
<LineChart series={series} height={200} yLabel="%" />
<AreaChart series={series} height={200} yLabel="%" thresholdValue={85} thresholdLabel="Alert" />
<BarChart series={series} height={200} stacked />
```
| Prop | LineChart | AreaChart | BarChart | Description |
|------|:---------:|:---------:|:--------:|-------------|
| `series` | required | required | required | `ChartSeries[]``{ label, data: { x, y }[], color? }` |
| `height` | optional | optional | optional | Chart height in pixels |
| `width` | optional | optional | optional | Container width in pixels |
| `yLabel` | optional | optional | optional | Y-axis label |
| `xLabel` | optional | optional | optional | X-axis label |
| `className` | optional | optional | optional | Container CSS class |
| `threshold` | `{ value, label }` | — | — | Horizontal reference line |
| `thresholdValue` | — | optional | — | Threshold y-value |
| `thresholdLabel` | — | optional | — | Threshold label |
| `stacked` | — | — | optional | Stack bars instead of grouping |
### Custom Charts (ThemedChart)
```tsx ```tsx
import { ThemedChart, Line, Area, ReferenceLine, CHART_COLORS } from '@cameleer/design-system' import { ThemedChart, Line, Area, ReferenceLine, CHART_COLORS } from '@cameleer/design-system'
@@ -231,6 +270,8 @@ For chart types not covered (treemap, radar, pie, sankey), import from `recharts
| Accordion | composite | Multiple collapsible sections, single or multi-open mode | | Accordion | composite | Multiple collapsible sections, single or multi-open mode |
| Alert | primitive | Page-level attention banner with variant colors | | Alert | primitive | Page-level attention banner with variant colors |
| AlertDialog | composite | Confirmation dialog for destructive/important actions | | AlertDialog | composite | Confirmation dialog for destructive/important actions |
| AreaChart | composite | Convenience area chart wrapper — pass `series` data, get themed chart with fills |
| BarChart | composite | Convenience bar chart wrapper — grouped or `stacked` mode |
| Avatar | primitive | User representation with initials and color | | Avatar | primitive | User representation with initials and color |
| AvatarGroup | composite | Stacked overlapping avatars with overflow count | | AvatarGroup | composite | Stacked overlapping avatars with overflow count |
| Badge | primitive | Labeled status indicator with semantic colors | | Badge | primitive | Labeled status indicator with semantic colors |
@@ -261,7 +302,7 @@ For chart types not covered (treemap, radar, pie, sankey), import from `recharts
| KeyboardHint | primitive | Keyboard shortcut display | | KeyboardHint | primitive | Keyboard shortcut display |
| KpiStrip | composite | Horizontal row of KPI cards with colored left border, trend, subtitle, optional sparkline | | KpiStrip | composite | Horizontal row of KPI cards with colored left border, trend, subtitle, optional sparkline |
| Label | primitive | Form label with optional required asterisk | | Label | primitive | Form label with optional required asterisk |
| ThemedChart | composite | Recharts wrapper with themed axes, grid, and tooltip | | LineChart | composite | Convenience line chart wrapper — pass `series` data, get themed chart with lines |
| LogViewer | composite | Scrollable log output with timestamped, severity-colored monospace entries | | LogViewer | composite | Scrollable log output with timestamped, severity-colored monospace entries |
| MenuItem | composite | Sidebar navigation item with health/count | | MenuItem | composite | Sidebar navigation item with health/count |
| Modal | composite | Generic dialog overlay with backdrop | | Modal | composite | Generic dialog overlay with backdrop |
@@ -298,7 +339,7 @@ For chart types not covered (treemap, radar, pie, sankey), import from `recharts
| Component | Purpose | | Component | Purpose |
|-----------|---------| |-----------|---------|
| AppShell | Page shell: sidebar + topbar + main + optional detail panel | | AppShell | Page shell: sidebar + topbar + main + optional detail panel |
| Sidebar | Composable compound sidebar shell with icon-rail collapse mode. Sub-components: `Sidebar.Header`, `Sidebar.Section`, `Sidebar.Footer`, `Sidebar.FooterLink`. The app controls all content via children — the DS provides the frame. | | Sidebar | Composable compound sidebar shell with icon-rail collapse mode. Sub-components: `Sidebar.Header`, `Sidebar.Section` (supports `position="bottom"` and `maxHeight`), `Sidebar.Footer`, `Sidebar.FooterLink`. The app controls all content via children — the DS provides the frame. |
| SidebarTree | Data-driven tree for sidebar sections. Accepts `nodes: SidebarTreeNode[]` with expand/collapse, starring, keyboard nav, search filter, and path-based selection highlighting. | | SidebarTree | Data-driven tree for sidebar sections. Accepts `nodes: SidebarTreeNode[]` with expand/collapse, starring, keyboard nav, search filter, and path-based selection highlighting. |
| useStarred | Hook for localStorage-backed starred item IDs. Returns `{ starredIds, isStarred, toggleStar }`. | | useStarred | Hook for localStorage-backed starred item IDs. Returns `{ starredIds, isStarred, toggleStar }`. |
| TopBar | Header bar with breadcrumb, search trigger, ButtonGroup status filters, time range selector, theme toggle, environment slot (`ReactNode` — pass a string for a static label or a custom dropdown for interactive selection), user avatar | | TopBar | Header bar with breadcrumb, search trigger, ButtonGroup status filters, time range selector, theme toggle, environment slot (`ReactNode` — pass a string for a static label or a custom dropdown for interactive selection), user avatar |

View File

@@ -1,6 +1,6 @@
{ {
"name": "@cameleer/design-system", "name": "@cameleer/design-system",
"version": "0.1.53", "version": "0.1.56",
"type": "module", "type": "module",
"main": "./dist/index.es.js", "main": "./dist/index.es.js",
"module": "./dist/index.es.js", "module": "./dist/index.es.js",
@@ -21,11 +21,11 @@
"*.css" "*.css"
], ],
"publishConfig": { "publishConfig": {
"registry": "https://gitea.siegeln.net/api/packages/cameleer/npm/" "registry": "https://registry.cameleer.io/api/packages/cameleer/npm/"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://gitea.siegeln.net/cameleer/design-system.git" "url": "https://registry.cameleer.io/cameleer/design-system.git"
}, },
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",

View File

@@ -53,11 +53,6 @@
justify-content: center; justify-content: center;
} }
.sidebarCollapsed .collapseToggle {
top: 52px;
right: 50%;
transform: translateX(50%);
}
.logoImg { .logoImg {
width: 28px; width: 28px;
@@ -449,7 +444,7 @@
.bottom { .bottom {
border-top: 1px solid rgba(255, 255, 255, 0.06); border-top: 1px solid rgba(255, 255, 255, 0.06);
padding: 6px; padding: 6px 0;
flex-shrink: 0; flex-shrink: 0;
margin-top: auto; margin-top: auto;
} }
@@ -458,7 +453,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
gap: 10px; gap: 10px;
padding: 7px 12px; padding: 7px 6px;
border-radius: var(--radius-sm); border-radius: var(--radius-sm);
color: var(--sidebar-muted); color: var(--sidebar-muted);
font-size: 12px; font-size: 12px;
@@ -480,7 +475,9 @@
} }
.bottomIcon { .bottomIcon {
font-size: 13px; display: flex;
width: 18px; align-items: center;
text-align: center; justify-content: center;
width: 16px;
color: var(--sidebar-muted);
} }

View File

@@ -132,8 +132,8 @@ describe('Sidebar compound component', () => {
expect(onCollapseToggle).toHaveBeenCalledTimes(1) expect(onCollapseToggle).toHaveBeenCalledTimes(1)
}) })
// 7. renders expand toggle label when collapsed // 7. hides collapse toggle when sidebar is collapsed
it('renders expand toggle when sidebar is collapsed', () => { it('hides collapse toggle when sidebar is collapsed', () => {
render( render(
<Wrapper> <Wrapper>
<Sidebar collapsed onCollapseToggle={vi.fn()}> <Sidebar collapsed onCollapseToggle={vi.fn()}>
@@ -141,7 +141,8 @@ describe('Sidebar compound component', () => {
</Sidebar> </Sidebar>
</Wrapper>, </Wrapper>,
) )
expect(screen.getByRole('button', { name: /expand sidebar/i })).toBeInTheDocument() expect(screen.queryByRole('button', { name: /collapse sidebar/i })).not.toBeInTheDocument()
expect(screen.queryByRole('button', { name: /expand sidebar/i })).not.toBeInTheDocument()
}) })
// 8. renders search input and calls onSearchChange // 8. renders search input and calls onSearchChange

View File

@@ -3,7 +3,6 @@ import {
Search, Search,
X, X,
ChevronsLeft, ChevronsLeft,
ChevronsRight,
} from 'lucide-react' } from 'lucide-react'
import styles from './Sidebar.module.css' import styles from './Sidebar.module.css'
import { SidebarContext, useSidebarContext } from './SidebarContext' import { SidebarContext, useSidebarContext } from './SidebarContext'
@@ -194,14 +193,14 @@ function SidebarRoot({
className ?? '', className ?? '',
].filter(Boolean).join(' ')} ].filter(Boolean).join(' ')}
> >
{/* Collapse toggle */} {/* Collapse toggle (hidden when collapsed — sections expand on click) */}
{onCollapseToggle && ( {onCollapseToggle && !collapsed && (
<button <button
className={styles.collapseToggle} className={styles.collapseToggle}
onClick={onCollapseToggle} onClick={onCollapseToggle}
aria-label={collapsed ? 'Expand sidebar' : 'Collapse sidebar'} aria-label="Collapse sidebar"
> >
{collapsed ? <ChevronsRight size={14} /> : <ChevronsLeft size={14} />} <ChevronsLeft size={14} />
</button> </button>
)} )}

View File

@@ -27,7 +27,7 @@ export function AdminLayout({ title, children }: AdminLayoutProps) {
{ label: title }, { label: title },
]} ]}
environment="PRODUCTION" environment="PRODUCTION"
user={{ name: 'hendrik' }} user={{ name: 'ada' }}
/> />
<Tabs <Tabs
tabs={ADMIN_TABS} tabs={ADMIN_TABS}

View File

@@ -18,7 +18,7 @@ const day = 24 * hour
export const AUDIT_EVENTS: AuditEvent[] = [ export const AUDIT_EVENTS: AuditEvent[] = [
{ {
id: 'audit-1', timestamp: new Date(now - 0.5 * hour).toISOString(), id: 'audit-1', timestamp: new Date(now - 0.5 * hour).toISOString(),
username: 'hendrik', category: 'USER_MGMT', action: 'CREATE_USER', username: 'ada', category: 'USER_MGMT', action: 'CREATE_USER',
target: 'users/alice', result: 'SUCCESS', target: 'users/alice', result: 'SUCCESS',
detail: { displayName: 'Alice Johnson', roles: ['VIEWER'] }, detail: { displayName: 'Alice Johnson', roles: ['VIEWER'] },
ipAddress: '10.0.1.42', userAgent: 'Mozilla/5.0 Chrome/125', ipAddress: '10.0.1.42', userAgent: 'Mozilla/5.0 Chrome/125',
@@ -46,14 +46,14 @@ export const AUDIT_EVENTS: AuditEvent[] = [
}, },
{ {
id: 'audit-5', timestamp: new Date(now - 3 * hour).toISOString(), id: 'audit-5', timestamp: new Date(now - 3 * hour).toISOString(),
username: 'hendrik', category: 'CONFIG', action: 'UPDATE_THRESHOLD', username: 'ada', category: 'CONFIG', action: 'UPDATE_THRESHOLD',
target: 'thresholds/pool-connections', result: 'SUCCESS', target: 'thresholds/pool-connections', result: 'SUCCESS',
detail: { field: 'maxConnections', oldValue: 50, newValue: 100 }, detail: { field: 'maxConnections', oldValue: 50, newValue: 100 },
ipAddress: '10.0.1.42', userAgent: 'Mozilla/5.0 Chrome/125', ipAddress: '10.0.1.42', userAgent: 'Mozilla/5.0 Chrome/125',
}, },
{ {
id: 'audit-6', timestamp: new Date(now - 4 * hour).toISOString(), id: 'audit-6', timestamp: new Date(now - 4 * hour).toISOString(),
username: 'hendrik', category: 'USER_MGMT', action: 'ASSIGN_ROLE', username: 'ada', category: 'USER_MGMT', action: 'ASSIGN_ROLE',
target: 'users/bob', result: 'SUCCESS', target: 'users/bob', result: 'SUCCESS',
detail: { role: 'EDITOR', method: 'direct' }, detail: { role: 'EDITOR', method: 'direct' },
ipAddress: '10.0.1.42', userAgent: 'Mozilla/5.0 Chrome/125', ipAddress: '10.0.1.42', userAgent: 'Mozilla/5.0 Chrome/125',
@@ -74,7 +74,7 @@ export const AUDIT_EVENTS: AuditEvent[] = [
}, },
{ {
id: 'audit-9', timestamp: new Date(now - 8 * hour).toISOString(), id: 'audit-9', timestamp: new Date(now - 8 * hour).toISOString(),
username: 'hendrik', category: 'USER_MGMT', action: 'CREATE_GROUP', username: 'ada', category: 'USER_MGMT', action: 'CREATE_GROUP',
target: 'groups/developers', result: 'SUCCESS', target: 'groups/developers', result: 'SUCCESS',
detail: { parent: null }, detail: { parent: null },
ipAddress: '10.0.1.42', userAgent: 'Mozilla/5.0 Chrome/125', ipAddress: '10.0.1.42', userAgent: 'Mozilla/5.0 Chrome/125',
@@ -88,7 +88,7 @@ export const AUDIT_EVENTS: AuditEvent[] = [
}, },
{ {
id: 'audit-11', timestamp: new Date(now - 12 * hour).toISOString(), id: 'audit-11', timestamp: new Date(now - 12 * hour).toISOString(),
username: 'hendrik', category: 'CONFIG', action: 'UPDATE_OIDC', username: 'ada', category: 'CONFIG', action: 'UPDATE_OIDC',
target: 'config/oidc', result: 'SUCCESS', target: 'config/oidc', result: 'SUCCESS',
detail: { field: 'autoSignup', oldValue: false, newValue: true }, detail: { field: 'autoSignup', oldValue: false, newValue: true },
ipAddress: '10.0.1.42', userAgent: 'Mozilla/5.0 Chrome/125', ipAddress: '10.0.1.42', userAgent: 'Mozilla/5.0 Chrome/125',
@@ -102,7 +102,7 @@ export const AUDIT_EVENTS: AuditEvent[] = [
}, },
{ {
id: 'audit-13', timestamp: new Date(now - 1 * day - 2 * hour).toISOString(), id: 'audit-13', timestamp: new Date(now - 1 * day - 2 * hour).toISOString(),
username: 'hendrik', category: 'USER_MGMT', action: 'DELETE_USER', username: 'ada', category: 'USER_MGMT', action: 'DELETE_USER',
target: 'users/temp-user', result: 'SUCCESS', target: 'users/temp-user', result: 'SUCCESS',
detail: { reason: 'cleanup' }, detail: { reason: 'cleanup' },
ipAddress: '10.0.1.42', userAgent: 'Mozilla/5.0 Chrome/125', ipAddress: '10.0.1.42', userAgent: 'Mozilla/5.0 Chrome/125',
@@ -116,7 +116,7 @@ export const AUDIT_EVENTS: AuditEvent[] = [
}, },
{ {
id: 'audit-15', timestamp: new Date(now - 1 * day - 6 * hour).toISOString(), id: 'audit-15', timestamp: new Date(now - 1 * day - 6 * hour).toISOString(),
username: 'hendrik', category: 'USER_MGMT', action: 'UPDATE_GROUP', username: 'ada', category: 'USER_MGMT', action: 'UPDATE_GROUP',
target: 'groups/admins', result: 'SUCCESS', target: 'groups/admins', result: 'SUCCESS',
detail: { addedMembers: ['alice'], removedMembers: [] }, detail: { addedMembers: ['alice'], removedMembers: [] },
ipAddress: '10.0.1.42', userAgent: 'Mozilla/5.0 Chrome/125', ipAddress: '10.0.1.42', userAgent: 'Mozilla/5.0 Chrome/125',
@@ -137,7 +137,7 @@ export const AUDIT_EVENTS: AuditEvent[] = [
}, },
{ {
id: 'audit-18', timestamp: new Date(now - 2 * day - 5 * hour).toISOString(), id: 'audit-18', timestamp: new Date(now - 2 * day - 5 * hour).toISOString(),
username: 'hendrik', category: 'CONFIG', action: 'UPDATE_THRESHOLD', username: 'ada', category: 'CONFIG', action: 'UPDATE_THRESHOLD',
target: 'thresholds/latency-p99', result: 'SUCCESS', target: 'thresholds/latency-p99', result: 'SUCCESS',
detail: { field: 'warningMs', oldValue: 500, newValue: 300 }, detail: { field: 'warningMs', oldValue: 500, newValue: 300 },
ipAddress: '10.0.1.42', userAgent: 'Mozilla/5.0 Chrome/125', ipAddress: '10.0.1.42', userAgent: 'Mozilla/5.0 Chrome/125',
@@ -151,7 +151,7 @@ export const AUDIT_EVENTS: AuditEvent[] = [
}, },
{ {
id: 'audit-20', timestamp: new Date(now - 3 * day - 2 * hour).toISOString(), id: 'audit-20', timestamp: new Date(now - 3 * day - 2 * hour).toISOString(),
username: 'hendrik', category: 'USER_MGMT', action: 'ASSIGN_ROLE', username: 'ada', category: 'USER_MGMT', action: 'ASSIGN_ROLE',
target: 'groups/developers', result: 'SUCCESS', target: 'groups/developers', result: 'SUCCESS',
detail: { role: 'EDITOR', method: 'group_assignment' }, detail: { role: 'EDITOR', method: 'group_assignment' },
ipAddress: '10.0.1.42', userAgent: 'Mozilla/5.0 Chrome/125', ipAddress: '10.0.1.42', userAgent: 'Mozilla/5.0 Chrome/125',
@@ -172,7 +172,7 @@ export const AUDIT_EVENTS: AuditEvent[] = [
}, },
{ {
id: 'audit-23', timestamp: new Date(now - 5 * day).toISOString(), id: 'audit-23', timestamp: new Date(now - 5 * day).toISOString(),
username: 'hendrik', category: 'USER_MGMT', action: 'CREATE_ROLE', username: 'ada', category: 'USER_MGMT', action: 'CREATE_ROLE',
target: 'roles/OPERATOR', result: 'SUCCESS', target: 'roles/OPERATOR', result: 'SUCCESS',
detail: { scope: 'custom', description: 'Pipeline operator' }, detail: { scope: 'custom', description: 'Pipeline operator' },
ipAddress: '10.0.1.42', userAgent: 'Mozilla/5.0 Chrome/125', ipAddress: '10.0.1.42', userAgent: 'Mozilla/5.0 Chrome/125',
@@ -186,7 +186,7 @@ export const AUDIT_EVENTS: AuditEvent[] = [
}, },
{ {
id: 'audit-25', timestamp: new Date(now - 6 * day).toISOString(), id: 'audit-25', timestamp: new Date(now - 6 * day).toISOString(),
username: 'hendrik', category: 'USER_MGMT', action: 'CREATE_USER', username: 'ada', category: 'USER_MGMT', action: 'CREATE_USER',
target: 'users/bob', result: 'SUCCESS', target: 'users/bob', result: 'SUCCESS',
detail: { displayName: 'Bob Smith', roles: ['VIEWER'] }, detail: { displayName: 'Bob Smith', roles: ['VIEWER'] },
ipAddress: '10.0.1.42', userAgent: 'Mozilla/5.0 Chrome/125', ipAddress: '10.0.1.42', userAgent: 'Mozilla/5.0 Chrome/125',

View File

@@ -189,7 +189,7 @@ export function UsersTab() {
size="sm" size="sm"
variant="danger" variant="danger"
onClick={() => setDeleteTarget(selected)} onClick={() => setDeleteTarget(selected)}
disabled={selected.username === 'hendrik'} disabled={selected.username === 'ada'}
> >
Delete Delete
</Button> </Button>

View File

@@ -44,8 +44,8 @@ export const MOCK_GROUPS: MockGroup[] = [
export const MOCK_USERS: MockUser[] = [ export const MOCK_USERS: MockUser[] = [
{ {
id: 'usr-1', username: 'hendrik', displayName: 'Hendrik Siegeln', id: 'usr-1', username: 'ada', displayName: 'Ada Sterling',
email: 'hendrik@example.com', provider: 'local', createdAt: '2025-01-15T10:00:00Z', email: 'ada@example.com', provider: 'local', createdAt: '2025-01-15T10:00:00Z',
directRoles: ['ADMIN'], directGroups: ['grp-1'], directRoles: ['ADMIN'], directGroups: ['grp-1'],
}, },
{ {

View File

@@ -318,7 +318,7 @@ export function AgentHealth() {
<TopBar <TopBar
breadcrumb={buildBreadcrumb(scope)} breadcrumb={buildBreadcrumb(scope)}
environment="PRODUCTION" environment="PRODUCTION"
user={{ name: 'hendrik' }} user={{ name: 'ada' }}
/> />
<div className={styles.content}> <div className={styles.content}>

View File

@@ -125,7 +125,7 @@ export function AgentInstance() {
if (!agent) { if (!agent) {
return ( return (
<> <>
<TopBar breadcrumb={[{ label: 'Agents', href: '/agents' }, { label: 'Not Found' }]} environment="PRODUCTION" user={{ name: 'hendrik' }} /> <TopBar breadcrumb={[{ label: 'Agents', href: '/agents' }, { label: 'Not Found' }]} environment="PRODUCTION" user={{ name: 'ada' }} />
<div className={styles.content}> <div className={styles.content}>
<div className={styles.notFound}>Agent instance not found.</div> <div className={styles.notFound}>Agent instance not found.</div>
</div> </div>
@@ -159,7 +159,7 @@ export function AgentInstance() {
{ label: instanceId! }, { label: instanceId! },
]} ]}
environment="PRODUCTION" environment="PRODUCTION"
user={{ name: 'hendrik' }} user={{ name: 'ada' }}
/> />
<div className={styles.content}> <div className={styles.content}>

View File

@@ -8,7 +8,7 @@ export function ApiDocs() {
breadcrumb={[{ label: 'API Documentation' }]} breadcrumb={[{ label: 'API Documentation' }]}
environment="PRODUCTION" environment="PRODUCTION"
user={{ name: 'hendrik' }} user={{ name: 'ada' }}
/> />
<EmptyState <EmptyState
title="API Documentation" title="API Documentation"

View File

@@ -14,7 +14,7 @@ export function AppDetail() {
]} ]}
environment="PRODUCTION" environment="PRODUCTION"
user={{ name: 'hendrik' }} user={{ name: 'ada' }}
/> />
<EmptyState <EmptyState
title="Application Detail" title="Application Detail"

View File

@@ -257,7 +257,7 @@ export function Dashboard() {
: [{ label: 'Applications' }] : [{ label: 'Applications' }]
} }
environment="PRODUCTION" environment="PRODUCTION"
user={{ name: 'hendrik' }} user={{ name: 'ada' }}
/> />
{/* Scrollable content */} {/* Scrollable content */}

View File

@@ -180,7 +180,7 @@ export function ExchangeDetail() {
{ label: id ?? 'Unknown' }, { label: id ?? 'Unknown' },
]} ]}
environment="PRODUCTION" environment="PRODUCTION"
user={{ name: 'hendrik' }} user={{ name: 'ada' }}
/> />
<div className={styles.content}> <div className={styles.content}>
<InfoCallout variant="warning">Exchange "{id}" not found in mock data.</InfoCallout> <InfoCallout variant="warning">Exchange "{id}" not found in mock data.</InfoCallout>
@@ -210,7 +210,7 @@ export function ExchangeDetail() {
{ label: exchange.id }, { label: exchange.id },
]} ]}
environment="PRODUCTION" environment="PRODUCTION"
user={{ name: 'hendrik' }} user={{ name: 'ada' }}
/> />
{/* Scrollable content */} {/* Scrollable content */}

View File

@@ -134,7 +134,7 @@ export function LayoutSection() {
{ label: 'order-ingest' }, { label: 'order-ingest' },
]} ]}
environment="production" environment="production"
user={{ name: 'Hendrik' }} user={{ name: 'ada' }}
> >
<SearchTrigger onClick={() => {}} /> <SearchTrigger onClick={() => {}} />
<AutoRefreshToggle active={true} onChange={() => {}} /> <AutoRefreshToggle active={true} onChange={() => {}} />

View File

@@ -171,7 +171,7 @@ export function RouteDetail() {
{ label: id ?? 'Unknown' }, { label: id ?? 'Unknown' },
]} ]}
environment="PRODUCTION" environment="PRODUCTION"
user={{ name: 'hendrik' }} user={{ name: 'ada' }}
/> />
<div className={styles.content}> <div className={styles.content}>
<InfoCallout variant="warning">Route "{id}" not found in mock data.</InfoCallout> <InfoCallout variant="warning">Route "{id}" not found in mock data.</InfoCallout>
@@ -193,7 +193,7 @@ export function RouteDetail() {
]} ]}
environment="PRODUCTION" environment="PRODUCTION"
user={{ name: 'hendrik' }} user={{ name: 'ada' }}
/> />
{/* Scrollable content */} {/* Scrollable content */}

View File

@@ -412,7 +412,7 @@ export function Routes() {
<TopBar <TopBar
breadcrumb={breadcrumb} breadcrumb={breadcrumb}
environment="PRODUCTION" environment="PRODUCTION"
user={{ name: 'hendrik' }} user={{ name: 'ada' }}
/> />
<div className={styles.content}> <div className={styles.content}>
<div className={styles.refreshIndicator}> <div className={styles.refreshIndicator}>
@@ -456,7 +456,7 @@ export function Routes() {
<TopBar <TopBar
breadcrumb={breadcrumb} breadcrumb={breadcrumb}
environment="PRODUCTION" environment="PRODUCTION"
user={{ name: 'hendrik' }} user={{ name: 'ada' }}
/> />
<div className={styles.content}> <div className={styles.content}>
<div className={styles.refreshIndicator}> <div className={styles.refreshIndicator}>

View File

@@ -1133,8 +1133,8 @@ td {
<div class="topbar-right"> <div class="topbar-right">
<span class="topbar-env">PRODUCTION</span> <span class="topbar-env">PRODUCTION</span>
<div class="topbar-user"> <div class="topbar-user">
<span>hendrik</span> <span>ada</span>
<div class="topbar-avatar">H</div> <div class="topbar-avatar">A</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1338,8 +1338,8 @@ td {
<span class="topbar-env">PRODUCTION</span> <span class="topbar-env">PRODUCTION</span>
<span class="topbar-shift">Shift: Day (06:00-18:00)</span> <span class="topbar-shift">Shift: Day (06:00-18:00)</span>
<div class="topbar-user"> <div class="topbar-user">
<span>hendrik</span> <span>ada</span>
<div class="topbar-avatar">H</div> <div class="topbar-avatar">A</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1525,8 +1525,8 @@ td {
<span class="topbar-env">PRODUCTION</span> <span class="topbar-env">PRODUCTION</span>
<span class="topbar-shift">Shift: Day (06:00-18:00)</span> <span class="topbar-shift">Shift: Day (06:00-18:00)</span>
<div class="topbar-user"> <div class="topbar-user">
<span>hendrik</span> <span>ada</span>
<div class="topbar-avatar">H</div> <div class="topbar-avatar">A</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1439,8 +1439,8 @@ body {
<div class="topbar-right"> <div class="topbar-right">
<span class="topbar-env">PRODUCTION</span> <span class="topbar-env">PRODUCTION</span>
<div class="topbar-user"> <div class="topbar-user">
<span>hendrik</span> <span>ada</span>
<div class="topbar-avatar">H</div> <div class="topbar-avatar">A</div>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -1294,8 +1294,8 @@ tbody tr:hover .drill-arrow { color: var(--amber); }
<span class="topbar-env">PRODUCTION</span> <span class="topbar-env">PRODUCTION</span>
<span class="topbar-shift">Shift: Day (06:00-18:00)</span> <span class="topbar-shift">Shift: Day (06:00-18:00)</span>
<div class="topbar-user"> <div class="topbar-user">
<span>hendrik</span> <span>ada</span>
<div class="topbar-avatar">H</div> <div class="topbar-avatar">A</div>
</div> </div>
</div> </div>
</div> </div>
@@ -2162,7 +2162,7 @@ tbody tr:hover .drill-arrow { color: var(--amber); }
<div class="feed-item"> <div class="feed-item">
<div class="feed-icon feed-warn">&#9651;</div> <div class="feed-icon feed-warn">&#9651;</div>
<div class="feed-content"> <div class="feed-content">
<div class="feed-text"><strong>Route stopped</strong> &mdash; <span class="feed-route">dead-letter-processor</span> manually stopped by operator hendrik</div> <div class="feed-text"><strong>Route stopped</strong> &mdash; <span class="feed-route">dead-letter-processor</span> manually stopped by operator ada</div>
<div class="feed-meta">05:48:22 &middot; 3h 26m ago</div> <div class="feed-meta">05:48:22 &middot; 3h 26m ago</div>
</div> </div>
</div> </div>

View File

@@ -1570,8 +1570,8 @@ tbody tr.row-running { border-left: 3px solid var(--running); }
<span class="topbar-env">PRODUCTION</span> <span class="topbar-env">PRODUCTION</span>
<span class="topbar-shift">Shift: Day (06:00-18:00)</span> <span class="topbar-shift">Shift: Day (06:00-18:00)</span>
<div class="topbar-user"> <div class="topbar-user">
<span>hendrik</span> <span>ada</span>
<div class="topbar-avatar">H</div> <div class="topbar-avatar">A</div>
</div> </div>
</div> </div>
</div> </div>
@@ -2200,7 +2200,7 @@ tbody tr.row-running { border-left: 3px solid var(--running); }
<div class="error-count-label">times</div> <div class="error-count-label">times</div>
</div> </div>
<div class="error-pattern-info"> <div class="error-pattern-info">
<div class="error-exception-class">com.cameleer3.validation.OrderValidationException</div> <div class="error-exception-class">io.cameleer.validation.OrderValidationException</div>
<div class="error-message-preview">Order validation failed: required field 'shippingAddress.postalCode' is null or empty. Validation rule: ADDR-001. Source system: EDI-US.</div> <div class="error-message-preview">Order validation failed: required field 'shippingAddress.postalCode' is null or empty. Validation rule: ADDR-001. Source system: EDI-US.</div>
<div class="error-pattern-meta"> <div class="error-pattern-meta">
<span>Processor: <span class="mono">OrderValidator</span></span> <span>Processor: <span class="mono">OrderValidator</span></span>