import { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; import { Button } from "@/components/button"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/dialog"; import { getCredentials, getHostAuthOverride, setHostAuthOverride, } from "@/main-axios"; import type { Credential, Host } from "@/types/ui-types"; import { AUTH_PROTOCOL_METADATA, type AuthOverrideProtocol, } from "@/types/auth-protocols"; import { mapCredentials } from "./HostManagerData"; import { getConnectedRemoteApi } from "@/lib/remote-server-api"; export function HostAuthOverrideModal({ open, onOpenChange, host, protocol, }: { open: boolean; onOpenChange: (open: boolean) => void; host: Host; protocol: AuthOverrideProtocol; }) { const { t } = useTranslation(); const [credentials, setCredentials] = useState([]); const [selectedId, setSelectedId] = useState(""); const [initialId, setInitialId] = useState(""); const [loading, setLoading] = useState(false); const [loadError, setLoadError] = useState(false); const [saving, setSaving] = useState(false); const overrideState = host.authOverrides?.[protocol]; const ownerAuthShared = overrideState?.ownerAuthShared ?? (protocol === "ssh" ? !!host.shareSshAuth : false); // A shared host's copy on a linked desktop: its override lives on the // server, against the server's own credentials. const sharedCopySyncId = host.sharedCopy ? (host.syncId ?? null) : null; useEffect(() => { if (!open) return; let cancelled = false; setLoading(true); setLoadError(false); const credentialsRequest = sharedCopySyncId ? getConnectedRemoteApi().then((api) => { if (!api) throw new Error("The linked server is not reachable"); return api.get("/credentials").then((response) => response.data); }) : getCredentials(); Promise.all([ credentialsRequest, getHostAuthOverride(Number(host.id), protocol, sharedCopySyncId), ]) .then(([credentialResult, overrideResult]) => { if (cancelled) return; const nextCredentials = mapCredentials(credentialResult); const nextId = overrideResult.credentialId == null ? "" : String(overrideResult.credentialId); setCredentials(nextCredentials); setSelectedId(nextId); setInitialId(nextId); }) .catch(() => { if (!cancelled) setLoadError(true); }) .finally(() => { if (!cancelled) setLoading(false); }); return () => { cancelled = true; }; }, [host.id, open, protocol, sharedCopySyncId]); const handleSave = async () => { setSaving(true); try { const credentialId = selectedId ? Number(selectedId) : null; await setHostAuthOverride( Number(host.id), protocol, credentialId, sharedCopySyncId, ); toast.success( credentialId === null ? t( ownerAuthShared ? "hosts.sharing.authOverrideClearedToShared" : "hosts.sharing.authOverrideCleared", ) : t("hosts.sharing.authOverrideSaved"), ); window.dispatchEvent(new CustomEvent("termix:hosts-changed")); onOpenChange(false); } catch { toast.error(t("hosts.sharing.authOverrideSaveError")); } finally { setSaving(false); } }; return ( event.stopPropagation()} onPointerDown={(event) => event.stopPropagation()} > {t("hosts.sharing.authOverrideTitleProtocol", { protocol: AUTH_PROTOCOL_METADATA[protocol].label, })} {t( ownerAuthShared ? "hosts.sharing.authOverrideDescriptionShared" : "hosts.sharing.authOverrideDescriptionPrivate", { host: host.name }, )} {loading ? (

{t("common.loading")}

) : loadError ? (

{t("hosts.sharing.authOverrideLoadError")}

) : (
{credentials.length === 0 && (

{t("hosts.sharing.authOverrideNoCredentials")}

)} {overrideState?.required && selectedId === "" && (

{t("hosts.sharing.authOverrideRequired")}

)}

{t("hosts.sharing.authOverridePrivateHint")}

)}
); }