import { useEffect, useState } from "react" import { Check, Eye, EyeOff, Loader2, Plus, Save, Trash2 } from "lucide-react" import { apiClient } from "@/services/api/client" interface SecretsEditorProps { /** Current secret references: { ENV_VAR_NAME: "SECRET_FILE_NAME" } */ secrets: Record onSecretsChange: (secrets: Record) => void } interface SecretState { value: string original: string visible: boolean saving: boolean saved: boolean loaded: boolean } export function SecretsEditor({ secrets, onSecretsChange }: SecretsEditorProps) { const [states, setStates] = useState>({}) // Load secret values when the secret list changes useEffect(() => { for (const [envKey, secretName] of Object.entries(secrets)) { if (states[envKey]?.loaded) continue setStates((prev) => ({ ...prev, [envKey]: { value: "", original: "", visible: false, saving: false, saved: false, loaded: false, }, })) apiClient .get<{ value: string }>(`/secrets/${secretName}`) .then((data) => { setStates((prev) => ({ ...prev, [envKey]: { ...prev[envKey], value: data.value, original: data.value, loaded: true }, })) }) .catch(() => { setStates((prev) => ({ ...prev, [envKey]: { ...prev[envKey], loaded: true }, })) }) } }, [Object.keys(secrets).join(",")]) const handleSave = async (envKey: string) => { const s = states[envKey] const secretName = secrets[envKey] if (!s || !secretName || s.value === s.original) return setStates((prev) => ({ ...prev, [envKey]: { ...prev[envKey], saving: true } })) try { await apiClient.put(`/secrets/${secretName}`, { value: s.value }) setStates((prev) => ({ ...prev, [envKey]: { ...prev[envKey], saving: false, saved: true, original: s.value }, })) setTimeout(() => { setStates((prev) => ({ ...prev, [envKey]: { ...prev[envKey], saved: false } })) }, 2000) } catch { setStates((prev) => ({ ...prev, [envKey]: { ...prev[envKey], saving: false } })) } } const handleAdd = () => { const envKey = prompt("Environment variable name (e.g. MY_API_KEY):") if (!envKey) return const secretName = prompt("Secret file name (stored in ~/.castle/secrets/):", envKey) if (!secretName) return onSecretsChange({ ...secrets, [envKey]: secretName }) setStates((prev) => ({ ...prev, [envKey]: { value: "", original: "", visible: true, saving: false, saved: false, loaded: true, }, })) } const handleRemove = (envKey: string) => { const next = { ...secrets } delete next[envKey] onSecretsChange(next) setStates((prev) => { const n = { ...prev } delete n[envKey] return n }) } return (
{Object.entries(secrets).map(([envKey, secretName]) => { const s = states[envKey] const dirty = s ? s.value !== s.original : false return (
{envKey}
setStates((prev) => ({ ...prev, [envKey]: { ...prev[envKey], value: e.target.value }, })) } className="flex-1 bg-black/30 border border-[var(--border)] rounded px-2 py-1 text-xs font-mono focus:outline-none focus:border-[var(--primary)]" />
) })}
) }