improve: redesign SensitiveKeysPage with better layout and information hierarchy
All checks were successful
CI / cleanup-branch (push) Has been skipped
CI / build (push) Successful in 1m29s
CI / docker (push) Successful in 1m11s
CI / deploy-feature (push) Has been skipped
CI / deploy (push) Successful in 37s

Show agent built-in defaults as reference Badge pills, separate editable keys
section with count badge, amber-highlighted push toggle, right-aligned save
button. Fix info text: keys add to defaults, not replace. Add ClaimMapping
controller to CLAUDE.md.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-14 19:03:45 +02:00
parent 9ac8e3604c
commit 92d7f5809b
3 changed files with 125 additions and 35 deletions

View File

@@ -105,6 +105,7 @@ java -jar cameleer3-server-app/target/cameleer3-server-app-1.0-SNAPSHOT.jar
- `MetricsController` — GET /api/v1/metrics, GET /timeseries - `MetricsController` — GET /api/v1/metrics, GET /timeseries
- `DiagramController` — GET /api/v1/diagrams/{id}, POST / - `DiagramController` — GET /api/v1/diagrams/{id}, POST /
- `DiagramRenderController` — POST /api/v1/diagrams/render (ELK layout) - `DiagramRenderController` — POST /api/v1/diagrams/render (ELK layout)
- `ClaimMappingAdminController` — CRUD /api/v1/admin/claim-mappings, POST /test (accepts inline rules + claims for preview without saving)
- `LicenseAdminController` — GET/POST /api/v1/admin/license - `LicenseAdminController` — GET/POST /api/v1/admin/license
**runtime/** — Docker orchestration **runtime/** — Docker orchestration
@@ -219,7 +220,7 @@ The UI has 4 main tabs: **Exchanges**, **Dashboard**, **Runtime**, **Deployments
- Deployment progress: `ui/src/components/DeploymentProgress.tsx` (7-stage step indicator) - Deployment progress: `ui/src/components/DeploymentProgress.tsx` (7-stage step indicator)
**Admin pages** (ADMIN-only, under `/admin/`): **Admin pages** (ADMIN-only, under `/admin/`):
- **Sensitive Keys** (`ui/src/pages/Admin/SensitiveKeysPage.tsx`) — global sensitive key masking config with tag/pill editor, push-to-agents toggle. Per-app additions shown in `AppConfigDetailPage.tsx` with read-only global pills (greyed Badge) + editable per-app pills (Tag with remove). - **Sensitive Keys** (`ui/src/pages/Admin/SensitiveKeysPage.tsx`) — global sensitive key masking config. Shows agent built-in defaults as outlined Badge reference, editable Tag pills for custom keys, amber-highlighted push-to-agents toggle. Keys add to (not replace) agent defaults. Per-app additions shown in `AppConfigDetailPage.tsx` with read-only global pills (greyed Badge) + editable per-app pills (Tag with remove).
### Key UI Files ### Key UI Files
@@ -391,7 +392,7 @@ Mean processing time = `camel.route.policy.total_time / camel.route.policy.count
<!-- gitnexus:start --> <!-- gitnexus:start -->
# GitNexus — Code Intelligence # GitNexus — Code Intelligence
This project is indexed by GitNexus as **cameleer3-server** (6155 symbols, 15501 relationships, 300 execution flows). Use the GitNexus MCP tools to understand code, assess impact, and navigate safely. This project is indexed by GitNexus as **cameleer3-server** (6195 symbols, 15647 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. > If any GitNexus tool warns the index is stale, run `npx gitnexus analyze` in terminal first.

View File

@@ -1,27 +1,71 @@
.page { .page {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: var(--space-lg); gap: var(--space-md);
max-width: 720px; max-width: 720px;
} }
.infoBanner { .sectionTitle {
font-size: var(--font-size-sm); display: flex;
align-items: center;
gap: 6px;
font-size: 13px;
font-weight: 600;
color: var(--text-secondary); color: var(--text-secondary);
background: var(--surface-secondary); letter-spacing: 0.01em;
padding: var(--space-md); }
border-radius: var(--radius-md);
line-height: 1.5; .defaultsList {
display: flex;
flex-wrap: wrap;
gap: 6px;
align-items: center;
}
.hint {
display: flex;
align-items: flex-start;
gap: 6px;
font-size: 12px;
color: var(--text-muted);
line-height: 1.4;
padding-top: 4px;
border-top: 1px solid var(--border-subtle);
}
.hint svg {
flex-shrink: 0;
margin-top: 1px;
}
.keyCount {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 18px;
height: 18px;
padding: 0 5px;
border-radius: 9px;
background: var(--bg-hover);
color: var(--text-muted);
font-size: 12px;
font-weight: 500;
} }
.pillList { .pillList {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: var(--space-xs); gap: 6px;
min-height: 36px; min-height: 36px;
align-items: center; align-items: center;
} }
.emptyState {
color: var(--text-tertiary);
font-size: 12px;
font-style: italic;
}
.inputRow { .inputRow {
display: flex; display: flex;
gap: var(--space-sm); gap: var(--space-sm);
@@ -32,8 +76,20 @@
flex: 1; flex: 1;
} }
.footer { .inputHint {
display: flex; font-size: 12px;
gap: var(--space-sm); color: var(--text-muted);
align-items: center; line-height: 1.4;
}
.pushToggle {
background: color-mix(in srgb, var(--amber) 8%, transparent);
border: 1px solid color-mix(in srgb, var(--amber) 20%, transparent);
border-radius: var(--radius-md);
padding: 8px 12px;
}
.saveRow {
display: flex;
justify-content: flex-end;
} }

View File

@@ -1,10 +1,16 @@
import { useState, useEffect, useCallback } from 'react'; import { useState, useEffect, useCallback } from 'react';
import { Button, SectionHeader, Tag, Input, Toggle, Label, useToast } from '@cameleer/design-system'; import { Shield, Info } from 'lucide-react';
import { Button, SectionHeader, Tag, Badge, Input, Toggle, useToast } from '@cameleer/design-system';
import { PageLoader } from '../../components/PageLoader'; import { PageLoader } from '../../components/PageLoader';
import { useSensitiveKeys, useUpdateSensitiveKeys } from '../../api/queries/admin/sensitive-keys'; import { useSensitiveKeys, useUpdateSensitiveKeys } from '../../api/queries/admin/sensitive-keys';
import styles from './SensitiveKeysPage.module.css'; import styles from './SensitiveKeysPage.module.css';
import sectionStyles from '../../styles/section-card.module.css'; import sectionStyles from '../../styles/section-card.module.css';
const AGENT_DEFAULTS = [
'Authorization', 'Cookie', 'Set-Cookie',
'X-API-Key', 'X-Auth-Token', 'Proxy-Authorization',
];
export default function SensitiveKeysPage() { export default function SensitiveKeysPage() {
const { data, isLoading } = useSensitiveKeys(); const { data, isLoading } = useSensitiveKeys();
const updateKeys = useUpdateSensitiveKeys(); const updateKeys = useUpdateSensitiveKeys();
@@ -65,24 +71,46 @@ export default function SensitiveKeysPage() {
if (isLoading) return <PageLoader />; if (isLoading) return <PageLoader />;
const isConfigured = draft.length > 0;
return ( return (
<div className={styles.page}> <div className={styles.page}>
<SectionHeader>Sensitive Keys</SectionHeader> <SectionHeader>Sensitive Keys</SectionHeader>
<div className={styles.infoBanner}> {/* Agent defaults reference */}
Agents ship with built-in defaults (Authorization, Cookie, Set-Cookie, X-API-Key, <section className={sectionStyles.section}>
X-Auth-Token, Proxy-Authorization). Configuring keys here replaces agent defaults for <div className={styles.sectionTitle}>
all applications. Leave unconfigured to use agent defaults. <Shield size={14} />
</div> <span>Agent built-in defaults</span>
</div>
<div className={styles.defaultsList}>
{AGENT_DEFAULTS.map((key) => (
<Badge key={key} label={key} variant="outlined" />
))}
</div>
<div className={styles.hint}>
<Info size={12} />
<span>
{isConfigured
? 'Your keys below are added to these defaults for all applications.'
: 'These headers are masked automatically. Add keys below to extend the list.'}
</span>
</div>
</section>
{/* Editable keys */}
<section className={sectionStyles.section}>
<div className={styles.sectionTitle}>
<span>Global sensitive keys</span>
{isConfigured && <span className={styles.keyCount}>{draft.length}</span>}
</div>
<div className={sectionStyles.section}>
<Label>Global sensitive keys</Label>
<div className={styles.pillList}> <div className={styles.pillList}>
{draft.map((key, i) => ( {draft.map((key, i) => (
<Tag key={`${key}-${i}`} label={key} onRemove={() => removeKey(i)} /> <Tag key={`${key}-${i}`} label={key} onRemove={() => removeKey(i)} />
))} ))}
{draft.length === 0 && ( {!isConfigured && (
<span style={{ color: 'var(--text-tertiary)', fontSize: 'var(--font-size-sm)' }}> <span className={styles.emptyState}>
No keys configured agents use built-in defaults No keys configured agents use built-in defaults
</span> </span>
)} )}
@@ -99,18 +127,23 @@ export default function SensitiveKeysPage() {
Add Add
</Button> </Button>
</div> </div>
</div> <div className={styles.inputHint}>
Supports exact header names and glob patterns. Per-app additions can be configured on each application's settings page.
</div>
<div className={styles.footer}> <div className={styles.pushToggle}>
<Toggle <Toggle
checked={pushToAgents} checked={pushToAgents}
onChange={(e) => setPushToAgents((e.target as HTMLInputElement).checked)} onChange={(e) => setPushToAgents((e.target as HTMLInputElement).checked)}
label="Push to all connected agents immediately" label="Push to all connected agents immediately"
/> />
<Button variant="primary" onClick={handleSave} loading={updateKeys.isPending}> </div>
Save <div className={styles.saveRow}>
</Button> <Button variant="primary" onClick={handleSave} loading={updateKeys.isPending}>
</div> Save
</Button>
</div>
</section>
</div> </div>
); );
} }