Files
NODEDC_MISSION_CORE/apps/node-agent/ui/src/TailnetAccess.tsx
T

49 lines
4.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useEffect, useState } from "react";
import { ActivityIndicator, Button, SettingsCard, StatusBadge } from "@nodedc/ui-react";
import { desktopAction, networkSetupAvailable } from "./api";
import { tailscaleLabel, useTailscaleStatus } from "./useTailscaleStatus";
interface NetworkResult { action: string; ok: boolean; error?: string; browser_opened?: boolean }
export function TailnetAccess({ failure, revision: hostRevision }: { failure: (error: unknown) => void; revision: string }) {
const [pending, setPending] = useState<string | null>(null);
const [notice, setNotice] = useState("");
const [revision, setRevision] = useState(0);
const { value, checked } = useTailscaleStatus(failure, hostRevision, revision);
useEffect(() => { if (value?.online) setNotice(""); }, [value]);
useEffect(() => {
function completed(event: Event) {
const result = (event as CustomEvent<NetworkResult>).detail;
if (!result || !["install-tailscale", "connect-tailscale"].includes(result.action)) return;
setPending(null);
if (!result.ok) failure(new Error(result.error ?? "Настройка не завершена. Повторите действие."));
setNotice(result.browser_opened ? "Завершите вход в открывшемся браузере. Состояние здесь обновится автоматически." : "");
setRevision(value => value + 1);
}
window.addEventListener("mission-core-network-result", completed);
return () => window.removeEventListener("mission-core-network-result", completed);
}, [failure]);
function perform(action: "install-tailscale" | "connect-tailscale") {
if (pending) return;
setNotice(""); setPending(action);
if (!desktopAction(action)) {
setPending(null); failure(new Error("Откройте установленное приложение Mission Core Node из меню приложений."));
}
}
const label = tailscaleLabel(value, checked);
const canConnect = value?.installed && ["NeedsLogin", "Stopped", "unavailable"].includes(value.state);
const supported = networkSetupAvailable();
return <div className="node-content"><SettingsCard title="Tailscale" description="Частная сеть для удалённого доступа к борту" actions={<StatusBadge tone={value?.online ? "success" : "neutral"}>{label}</StatusBadge>}>
<p className="node-note">Частная сеть для доступа к борту с другого компьютера. Войдите в ту же сеть Tailscale, что и на компьютере оператора. При первом подключении настройки локальной сети сохраняются.</p>
{!checked ? <ActivityIndicator label="Проверяем подключение Tailscale" /> : !value ? <p className="node-note">Не удалось получить состояние. Повторная проверка выполняется автоматически.</p> : <>
{!value.installed && <><p className="node-note">Приложение загрузит проверенный пакет Tailscale и включит его службу. Понадобятся интернет и системное подтверждение.</p><Button disabled={pending !== null || !supported} onClick={() => perform("install-tailscale")}>Установить Tailscale</Button></>}
{canConnect && <Button disabled={pending !== null || !supported} onClick={() => perform("connect-tailscale")}>{value.state === "NeedsLogin" ? "Войти в Tailscale" : "Подключить Tailscale"}</Button>}
{value.state === "NeedsMachineAuth" && <p className="node-note">Администратор вашей сети должен разрешить подключение этого компьютера в Tailscale.</p>}
{value.addresses.length > 0 && <dl className="node-facts"><div><dt>Адреса в Tailscale</dt><dd>{value.addresses.join(" · ")}</dd></div></dl>}
</>}
{!supported && <p className="node-note">Для настройки сети закройте окно и заново откройте установленное приложение из меню приложений. После обновления пакета требуется перезапуск окна.</p>}
{pending && <ActivityIndicator label={pending === "install-tailscale" ? "Подтвердите установку в системном окне. Загружаем и устанавливаем компонент…" : "Подтвердите действие в системном окне. Проверяем подключение…"} />}
{notice && <p className="node-note" role="status">{notice}</p>}
</SettingsCard></div>;
}