import { useState } from "react"; import { useTranslation } from "react-i18next"; import { Pencil, Plus, Trash2, X } from "lucide-react"; import { toast } from "sonner"; import { Button } from "@/components/button"; import { Input } from "@/components/input"; import { createVaultProfile, updateVaultProfile, deleteVaultProfile, type VaultProfilePayload, } from "@/main-axios"; import type { VaultProfile } from "@/types/ui-types"; type FormState = VaultProfilePayload & { id?: string }; const emptyForm: FormState = { name: "", vaultAddr: "", vaultNamespace: "", oidcMount: "oidc", oidcRole: "", sshMount: "ssh-client-signer", sshRole: "", validPrincipals: "", keyType: "ssh-ed25519", shared: false, }; function toForm(p: VaultProfile): FormState { return { id: p.id, name: p.name, vaultAddr: p.vaultAddr, vaultNamespace: p.vaultNamespace ?? "", oidcMount: p.oidcMount ?? "oidc", oidcRole: p.oidcRole ?? "", sshMount: p.sshMount ?? "ssh-client-signer", sshRole: p.sshRole, validPrincipals: p.validPrincipals ?? "", keyType: p.keyType ?? "ssh-ed25519", shared: p.shared, }; } export function VaultProfileManager({ profiles, onChanged, onClose, }: { profiles: VaultProfile[]; onChanged: () => void; onClose: () => void; }) { const { t } = useTranslation(); const [form, setForm] = useState(null); const [saving, setSaving] = useState(false); const setField = (k: K, v: FormState[K]) => setForm((p) => (p ? { ...p, [k]: v } : p)); const handleSave = async () => { if (!form) return; if (!form.name.trim() || !form.vaultAddr.trim() || !form.sshRole.trim()) { toast.error(t("hosts.vaultProfileValidationError")); return; } setSaving(true); try { const payload: VaultProfilePayload = { name: form.name.trim(), vaultAddr: form.vaultAddr.trim(), vaultNamespace: form.vaultNamespace?.trim() || null, oidcMount: form.oidcMount?.trim() || null, oidcRole: form.oidcRole?.trim() || null, sshMount: form.sshMount?.trim() || null, sshRole: form.sshRole.trim(), validPrincipals: form.validPrincipals?.trim() || null, keyType: form.keyType?.trim() || null, shared: !!form.shared, }; if (form.id) { await updateVaultProfile(Number(form.id), payload); toast.success(t("hosts.vaultProfileSaved")); } else { await createVaultProfile(payload); toast.success(t("hosts.vaultProfileCreated")); } setForm(null); onChanged(); } catch (e) { toast.error( e instanceof Error ? e.message : t("hosts.vaultProfileSaveFailed"), ); } finally { setSaving(false); } }; const handleDelete = async (p: VaultProfile) => { try { await deleteVaultProfile(Number(p.id)); toast.success(t("hosts.vaultProfileDeleted")); onChanged(); } catch (e) { toast.error( e instanceof Error ? e.message : t("hosts.vaultProfileDeleteFailed"), ); } }; const field = (label: string, key: keyof FormState, placeholder?: string) => (
setField(key, e.target.value as FormState[typeof key])} />
); return (
{t("hosts.vaultManageProfiles")}
{!form && ( <>
{profiles.length === 0 && ( {t("hosts.vaultNoProfiles")} )} {profiles.map((p) => (
{p.name} {p.shared && ( {t("hosts.vaultSharedBadge")} )} {p.vaultAddr}
{p.owned && (
)}
))}
)} {form && (
{field(t("hosts.friendlyNameLabel"), "name", "Production Vault")} {field( t("hosts.vaultAddrLabel"), "vaultAddr", "https://vault:8200", )} {field(t("hosts.vaultNamespaceLabel"), "vaultNamespace", "admin")} {field(t("hosts.vaultOidcMountLabel"), "oidcMount", "oidc")} {field(t("hosts.vaultOidcRoleLabel"), "oidcRole", "default")} {field( t("hosts.vaultSshMountLabel"), "sshMount", "ssh-client-signer", )} {field(t("hosts.vaultSshRoleLabel"), "sshRole", "my-role")} {field( t("hosts.vaultValidPrincipalsLabel"), "validPrincipals", "root,deploy", )}
)}
); }