ui(deploy): extract SensitiveKeysTab component
Pure presentational tab receiving SensitiveKeysFormState via value/onChange. Calls useSensitiveKeys() internally to show global baseline (readonly). Local useState for the new-key input buffer. Reuses skStyles from SensitiveKeysPage.module.css for consistent pill/badge layout. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
import { useState } from 'react';
|
||||
import { Badge, Button, Input, Tag } from '@cameleer/design-system';
|
||||
import { Shield, Info } from 'lucide-react';
|
||||
import { useSensitiveKeys } from '../../../../api/queries/admin/sensitive-keys';
|
||||
import type { SensitiveKeysFormState } from '../hooks/useDeploymentPageState';
|
||||
import skStyles from '../../../Admin/SensitiveKeysPage.module.css';
|
||||
|
||||
const AGENT_DEFAULTS = [
|
||||
'Authorization',
|
||||
'Cookie',
|
||||
'Set-Cookie',
|
||||
'X-API-Key',
|
||||
'X-Auth-Token',
|
||||
'Proxy-Authorization',
|
||||
];
|
||||
|
||||
interface Props {
|
||||
value: SensitiveKeysFormState;
|
||||
onChange: (next: SensitiveKeysFormState) => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export function SensitiveKeysTab({ value, onChange, disabled }: Props) {
|
||||
const [newKey, setNewKey] = useState('');
|
||||
const { data: globalKeysConfig } = useSensitiveKeys();
|
||||
const globalKeys = globalKeysConfig?.keys ?? [];
|
||||
|
||||
function addKey() {
|
||||
const v = newKey.trim();
|
||||
if (v && !value.sensitiveKeys.some((k) => k.toLowerCase() === v.toLowerCase())) {
|
||||
onChange({ sensitiveKeys: [...value.sensitiveKeys, v] });
|
||||
setNewKey('');
|
||||
}
|
||||
}
|
||||
|
||||
function removeKey(index: number) {
|
||||
onChange({ sensitiveKeys: value.sensitiveKeys.filter((_, i) => i !== index) });
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className={skStyles.sectionTitle}>
|
||||
<Shield size={14} />
|
||||
<span>Agent built-in defaults</span>
|
||||
</div>
|
||||
<div className={skStyles.defaultsList}>
|
||||
{AGENT_DEFAULTS.map((key) => (
|
||||
<Badge key={key} label={key} variant="outlined" />
|
||||
))}
|
||||
</div>
|
||||
|
||||
{globalKeys.length > 0 && (
|
||||
<>
|
||||
<hr style={{ border: 'none', borderTop: '1px solid var(--border-subtle)', margin: '10px 0' }} />
|
||||
<div className={skStyles.sectionTitle}>
|
||||
<span>Global keys (enforced)</span>
|
||||
<span className={skStyles.keyCount}>{globalKeys.length}</span>
|
||||
</div>
|
||||
<div className={skStyles.defaultsList}>
|
||||
{globalKeys.map((key) => (
|
||||
<Badge key={key} label={key} color="auto" variant="filled" />
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<hr style={{ border: 'none', borderTop: '1px solid var(--border-subtle)', margin: '10px 0' }} />
|
||||
<div className={skStyles.sectionTitle}>
|
||||
<span>Application-specific keys</span>
|
||||
{value.sensitiveKeys.length > 0 && (
|
||||
<span className={skStyles.keyCount}>{value.sensitiveKeys.length}</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={skStyles.pillList}>
|
||||
{value.sensitiveKeys.map((k, i) => (
|
||||
<Tag
|
||||
key={`${k}-${i}`}
|
||||
label={k}
|
||||
onRemove={() => !disabled && removeKey(i)}
|
||||
/>
|
||||
))}
|
||||
{value.sensitiveKeys.length === 0 && (
|
||||
<span className={skStyles.emptyState}>
|
||||
No app-specific keys — agents use built-in defaults
|
||||
{globalKeys.length > 0 ? ' and global keys' : ''}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={skStyles.inputRow}>
|
||||
<Input
|
||||
value={newKey}
|
||||
onChange={(e) => setNewKey(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
addKey();
|
||||
}
|
||||
}}
|
||||
placeholder="Add key or glob pattern (e.g. *password*)"
|
||||
disabled={disabled}
|
||||
/>
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
disabled={disabled || !newKey.trim()}
|
||||
onClick={addKey}
|
||||
>
|
||||
Add
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className={skStyles.hint}>
|
||||
<Info size={12} />
|
||||
<span>
|
||||
The final masking configuration is: agent defaults + global keys + app-specific keys.
|
||||
Supports exact header names and glob patterns.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user