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")}{" "}
{t("hosts.secretSourcesManage")}
);
}
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}
void handleTest(source)}
>
{t("hosts.secretSourceTest")}
{source.owned && (
<>
setForm({
id: source.id,
name: source.name,
baseUrl: source.baseUrl,
token: "",
shared: source.shared,
})
}
>
void handleDelete(source)}
>
>
)}
))}
setForm(emptyForm)}
>
{t("hosts.secretSourceNew")}
>
)}
{form && (
)}
);
}