From 813ec6904e26b8cec4ea0f52b39fdfd41988e0e8 Mon Sep 17 00:00:00 2001 From: hsiegeln <37154749+hsiegeln@users.noreply.github.com> Date: Tue, 14 Apr 2026 18:23:34 +0200 Subject: [PATCH] feat: add SensitiveKeysPage admin page Co-Authored-By: Claude Sonnet 4.6 --- .../pages/Admin/SensitiveKeysPage.module.css | 39 ++++++ ui/src/pages/Admin/SensitiveKeysPage.tsx | 116 ++++++++++++++++++ 2 files changed, 155 insertions(+) create mode 100644 ui/src/pages/Admin/SensitiveKeysPage.module.css create mode 100644 ui/src/pages/Admin/SensitiveKeysPage.tsx diff --git a/ui/src/pages/Admin/SensitiveKeysPage.module.css b/ui/src/pages/Admin/SensitiveKeysPage.module.css new file mode 100644 index 00000000..9da61cb4 --- /dev/null +++ b/ui/src/pages/Admin/SensitiveKeysPage.module.css @@ -0,0 +1,39 @@ +.page { + display: flex; + flex-direction: column; + gap: var(--space-lg); + max-width: 720px; +} + +.infoBanner { + font-size: var(--font-size-sm); + color: var(--text-secondary); + background: var(--surface-secondary); + padding: var(--space-md); + border-radius: var(--radius-md); + line-height: 1.5; +} + +.pillList { + display: flex; + flex-wrap: wrap; + gap: var(--space-xs); + min-height: 36px; + align-items: center; +} + +.inputRow { + display: flex; + gap: var(--space-sm); + align-items: center; +} + +.inputRow input { + flex: 1; +} + +.footer { + display: flex; + gap: var(--space-sm); + align-items: center; +} diff --git a/ui/src/pages/Admin/SensitiveKeysPage.tsx b/ui/src/pages/Admin/SensitiveKeysPage.tsx new file mode 100644 index 00000000..bbaf68fd --- /dev/null +++ b/ui/src/pages/Admin/SensitiveKeysPage.tsx @@ -0,0 +1,116 @@ +import { useState, useEffect, useCallback } from 'react'; +import { Button, SectionHeader, Tag, Input, Toggle, Label, useToast } from '@cameleer/design-system'; +import { PageLoader } from '../../components/PageLoader'; +import { useSensitiveKeys, useUpdateSensitiveKeys } from '../../api/queries/admin/sensitive-keys'; +import styles from './SensitiveKeysPage.module.css'; +import sectionStyles from '../../styles/section-card.module.css'; + +export default function SensitiveKeysPage() { + const { data, isLoading } = useSensitiveKeys(); + const updateKeys = useUpdateSensitiveKeys(); + const { toast } = useToast(); + + const [draft, setDraft] = useState([]); + const [inputValue, setInputValue] = useState(''); + const [pushToAgents, setPushToAgents] = useState(false); + const [initialized, setInitialized] = useState(false); + + useEffect(() => { + if (data !== undefined && !initialized) { + setDraft(data?.keys ?? []); + setInitialized(true); + } + }, [data, initialized]); + + const addKey = useCallback(() => { + const trimmed = inputValue.trim(); + if (!trimmed) return; + if (draft.some((k) => k.toLowerCase() === trimmed.toLowerCase())) { + toast({ title: 'Duplicate key', description: `"${trimmed}" is already in the list`, variant: 'warning' }); + return; + } + setDraft((prev) => [...prev, trimmed]); + setInputValue(''); + }, [inputValue, draft, toast]); + + const removeKey = useCallback((index: number) => { + setDraft((prev) => prev.filter((_, i) => i !== index)); + }, []); + + const handleKeyDown = useCallback((e: React.KeyboardEvent) => { + if (e.key === 'Enter') { + e.preventDefault(); + addKey(); + } + }, [addKey]); + + function handleSave() { + updateKeys.mutate({ keys: draft, pushToAgents }, { + onSuccess: (result) => { + if (result.pushResult) { + toast({ + title: 'Sensitive keys saved and pushed', + description: `${result.pushResult.total} agent(s) notified, ${result.pushResult.responded} responded`, + variant: result.pushResult.success ? 'success' : 'warning', + }); + } else { + toast({ title: 'Sensitive keys saved', variant: 'success' }); + } + }, + onError: () => { + toast({ title: 'Failed to save sensitive keys', variant: 'error', duration: 86_400_000 }); + }, + }); + } + + if (isLoading) return ; + + return ( +
+ Sensitive Keys + +
+ Agents ship with built-in defaults (Authorization, Cookie, Set-Cookie, X-API-Key, + X-Auth-Token, Proxy-Authorization). Configuring keys here replaces agent defaults for + all applications. Leave unconfigured to use agent defaults. +
+ +
+ +
+ {draft.map((key, i) => ( + removeKey(i)} /> + ))} + {draft.length === 0 && ( + + No keys configured — agents use built-in defaults + + )} +
+ +
+ setInputValue(e.target.value)} + onKeyDown={handleKeyDown} + placeholder="Add key or glob pattern (e.g. *password*)" + /> + +
+
+ +
+ setPushToAgents((e.target as HTMLInputElement).checked)} + label="Push to all connected agents immediately" + /> + +
+
+ ); +}