49 lines
4.7 KiB
TypeScript
49 lines
4.7 KiB
TypeScript
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>;
|
||
}
|