import { useCallback, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { KeyRound, Pencil, Plus, Trash2, X } from "lucide-react"; import { toast } from "sonner"; import { Button } from "@/components/button"; import { Input } from "@/components/input"; import { PasswordInput } from "@/components/password-input"; import { getErrorMessage } from "@/lib/error-message"; import { createSecretSource, deleteSecretSource, listSecretSources, testSecretSource, updateSecretSource, type SecretSource, } from "@/api/secret-sources-api"; /** One line under a secret field: references are allowed, here is where to set them up. */ export function SecretReferenceHint({ onManage }: { onManage: () => void }) { const { t } = useTranslation(); return (

{t("hosts.secretRefHint")}{" "}

); } type FormState = { id?: string; name: string; baseUrl: string; token: string; shared: boolean; }; const emptyForm: FormState = { name: "", baseUrl: "", token: "", shared: false, }; export function SecretSourceManager({ onClose }: { onClose: () => void }) { const { t } = useTranslation(); const [sources, setSources] = useState([]); const [form, setForm] = useState(null); const [saving, setSaving] = useState(false); const [testing, setTesting] = useState(null); const reload = useCallback(async () => { try { setSources(await listSecretSources()); } catch (e) { toast.error(getErrorMessage(e)); } }, []); useEffect(() => { void reload(); }, [reload]); const setField = (key: K, value: FormState[K]) => setForm((prev) => (prev ? { ...prev, [key]: value } : prev)); const handleSave = async () => { if (!form) return; if ( !form.name.trim() || !form.baseUrl.trim() || (!form.id && !form.token) ) { toast.error(t("hosts.secretSourceRequired")); return; } setSaving(true); try { if (form.id) { await updateSecretSource(form.id, { name: form.name, baseUrl: form.baseUrl, shared: form.shared, ...(form.token ? { token: form.token } : {}), }); } else { await createSecretSource({ name: form.name, baseUrl: form.baseUrl, token: form.token, shared: form.shared, }); } toast.success(t("hosts.secretSourceSaved")); setForm(null); await reload(); } catch (e) { toast.error(getErrorMessage(e)); } finally { setSaving(false); } }; const handleDelete = async (source: SecretSource) => { try { await deleteSecretSource(source.id); toast.success(t("hosts.secretSourceDeleted")); await reload(); } catch (e) { toast.error(getErrorMessage(e)); } }; const handleTest = async (source: SecretSource) => { setTesting(source.id); try { const result = await testSecretSource(source.id); if (result.ok) { toast.success( t("hosts.secretSourceTestOk", { count: result.vaults ?? 0 }), ); } else { toast.error(result.error ?? t("hosts.secretSourceTestFailed")); } } catch (e) { toast.error(getErrorMessage(e)); } finally { setTesting(null); } }; return (
{t("hosts.secretSourcesTitle")}

{t("hosts.secretSourcesDesc")}

{!form && ( <> {sources.map((source) => (
{source.name} {source.shared && ( {t("hosts.secretSourceShared")} )}
{source.baseUrl}
{source.owned && ( <> )}
))} )} {form && (
setField("name", e.target.value)} />
setField("baseUrl", e.target.value)} />
setField("token", e.target.value)} />
)}
); }