import { useEffect, useState } from "react"; import { useTranslation, type SshAuthEditorProps, } from "@termix/plugin-sdk/frontend"; import { Select2 } from "@termix/plugin-sdk/ui"; import { getTailscaleDevices } from "./tailscale-api"; interface Device { id: string; name: string; hostname: string; addresses: string[]; os: string; lastSeen: string; } function tailnetAddress(device: Device): string { return ( device.addresses.find((address) => address.startsWith("100.")) ?? device.addresses[0] ?? "" ); } /** * The host editor's "tailscale" auth method: pick a tailnet device and its * address fills the host's IP. */ export function TailscaleAuthEditor({ form, setField }: SshAuthEditorProps) { const { t } = useTranslation(); const [devices, setDevices] = useState([]); const [hasApiKey, setHasApiKey] = useState(false); const [loading, setLoading] = useState(true); const [failed, setFailed] = useState(false); useEffect(() => { let cancelled = false; setLoading(true); setFailed(false); getTailscaleDevices() .then((result) => { if (cancelled) return; setDevices(result?.devices ?? []); setHasApiKey(result?.hasApiKey ?? false); setFailed(!!result?.error); }) .catch(() => { if (!cancelled) setFailed(true); }) .finally(() => { if (!cancelled) setLoading(false); }); return () => { cancelled = true; }; }, []); const ip = String(form.ip ?? ""); return (
{t("hosts.tailscaleDocsLink")}
{loading ? (

{t("hosts.tailscaleLoadingDevices")}

) : failed ? (

{t("hosts.tailscaleDeviceLoadFailed")}

) : !hasApiKey ? (

{t("hosts.tailscaleNoApiKey")}

) : devices.length === 0 ? (

{t("hosts.tailscaleNoDevices")}

) : ( <> device.addresses.includes(ip))?.id ?? "" } onChange={(event) => { const device = devices.find( (item) => item.id === event.target.value, ); if (device) setField("ip", tailnetAddress(device)); }} > {devices.map((device) => ( ))}

{t("hosts.tailscaleDeviceAutoFill")}

)}
); }