import { useEffect, useMemo, useState } from 'react' import { useQueryClient } from '@tanstack/react-query' import { Check, Copy, Eye, EyeOff, Info, KeyRound, Loader2, Plus, RefreshCw, Save, Search, Trash2, X, } from 'lucide-react' import { EmptyState } from '@/components/EmptyState' import { ServiceIcon } from '@/components/serviceIcons' import type { SecretSummary } from '@/api/aws/secretsmanager.api' import { secretsManagerQueryKeys, useSecretDetailQuery, useSecretsQuery, useSecretValueQuery, } from '@/api/aws/secretsmanager.queries' import { useCreateSecretMutation, useDeleteSecretMutation, usePutSecretValueMutation, } from '@/api/aws/secretsmanager.mutations' import { timeAgo } from '@/lib/utils' import { clientId } from '@/lib/clientId' type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue } type JsonValueKind = 'string' | 'number' | 'boolean' | 'null' | 'json' type SecretKeyValueEntry = { id: string key: string value: string kind: JsonValueKind } type SerializedKeyValueEntries = | { secretString: string } | { error: string } function valueKind(value: JsonValue): JsonValueKind { if (value === null) return 'null' if (Array.isArray(value) || typeof value === 'object') return 'json' if (typeof value === 'number') return 'number' if (typeof value === 'boolean') return 'boolean' return 'string' } function valueText(value: JsonValue): string { return typeof value === 'string' ? value : JSON.stringify(value) } function parseKeyValueEntries(secretString: string): SecretKeyValueEntry[] | null { try { const parsed: unknown = JSON.parse(secretString) if (parsed === null || Array.isArray(parsed) || typeof parsed !== 'object') return null return Object.entries(parsed as Record).map(([key, value], index) => ({ id: `${index}-${key}`, key, value: valueText(value), kind: valueKind(value), })) } catch { return null } } function parseEntryValue(entry: SecretKeyValueEntry): JsonValue | undefined { if (entry.kind === 'string') return entry.value if (entry.kind === 'number') { const value = Number(entry.value) return Number.isFinite(value) ? value : undefined } if (entry.kind === 'boolean') { if (entry.value === 'true') return true if (entry.value === 'false') return false return undefined } try { const value: unknown = JSON.parse(entry.value) if (entry.kind === 'null') return value === null ? null : undefined return value as JsonValue } catch { return undefined } } function serializeKeyValueEntries(entries: SecretKeyValueEntry[]): SerializedKeyValueEntries { const output: Record = Object.create(null) as Record for (const entry of entries) { const key = entry.key if (!key) return { error: 'Every entry needs a key.' } if (Object.prototype.hasOwnProperty.call(output, key)) return { error: `Duplicate key: ${key}` } const value = parseEntryValue(entry) if (value === undefined) { return { error: `Invalid ${entry.kind} value for ${key}.` } } output[key] = value } return { secretString: JSON.stringify(output, null, 2) } } // ─── Create secret form ───────────────────────────────────────────────────────── function CreateSecretForm({ onClose }: { onClose: () => void }) { const qc = useQueryClient() const [name, setName] = useState('') const [description, setDescription] = useState('') const [secretString, setSecretString] = useState('') const [err, setErr] = useState('') const createMut = useCreateSecretMutation({ onSuccess: () => { void qc.invalidateQueries({ queryKey: ['resources', 'secretsmanager'] }) onClose() }, onError: (e) => setErr(e instanceof Error ? e.message : 'Create failed'), }) function handleSubmit(e: React.FormEvent) { e.preventDefault() if (!name.trim()) return setErr('Secret name is required') if (!secretString) return setErr('Secret value is required') setErr('') createMut.mutate({ name: name.trim(), secretString, description: description.trim() || undefined }) } return (
Create secret
{ setName(e.target.value); setErr('') }} autoFocus /> setDescription(e.target.value)} />