mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-29 14:45:24 +00:00
feat: load plugin frontends at runtime and remove plugin ids from the shell
This commit is contained in:
1 parent
49f0286a14
commit
e88d5a52e3
721 files changed
+54265
-44416
No files matched your search
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* The tailnet status shown on the Tailscale settings page.
|
||||
*
|
||||
* Confirms the saved key actually works, which is the question anyone has just
|
||||
* after entering one. Connecting to a device stays in the Tailscale rail panel;
|
||||
* a settings page is for configuring, not for operating.
|
||||
*
|
||||
* Registered by legacy-settings-components.ts until A7 lets the plugin's own
|
||||
* bundle register it.
|
||||
*/
|
||||
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useTranslation } from "@termix/plugin-sdk/frontend";
|
||||
import { Loader2, RefreshCw } from "lucide-react";
|
||||
import { Button } from "@/components/button";
|
||||
import type { SettingsComponentProps } from "@termix/plugin-sdk/frontend";
|
||||
import { getTailscaleDevices } from "./tailscale-api";
|
||||
|
||||
type Status =
|
||||
| { kind: "loading" }
|
||||
| { kind: "no-key" }
|
||||
| { kind: "error" }
|
||||
| { kind: "ok"; count: number };
|
||||
|
||||
export function TailscaleDevicesStatus({ running }: SettingsComponentProps) {
|
||||
const { t } = useTranslation();
|
||||
const [status, setStatus] = useState<Status>({ kind: "loading" });
|
||||
|
||||
const load = useCallback(async () => {
|
||||
if (!running) {
|
||||
setStatus({ kind: "no-key" });
|
||||
return;
|
||||
}
|
||||
setStatus({ kind: "loading" });
|
||||
try {
|
||||
const result = await getTailscaleDevices();
|
||||
if (!result.hasApiKey) {
|
||||
setStatus({ kind: "no-key" });
|
||||
return;
|
||||
}
|
||||
if (result.error) {
|
||||
setStatus({ kind: "error" });
|
||||
return;
|
||||
}
|
||||
setStatus({ kind: "ok", count: result.devices?.length ?? 0 });
|
||||
} catch {
|
||||
setStatus({ kind: "error" });
|
||||
}
|
||||
}, [running]);
|
||||
|
||||
useEffect(() => {
|
||||
void load();
|
||||
}, [load]);
|
||||
|
||||
const label = (key: string) => t(key);
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between gap-3 py-3">
|
||||
<div className="flex flex-col gap-0.5 min-w-0 flex-1">
|
||||
<span className="text-sm font-medium leading-snug">
|
||||
{label("settings.devices.label")}
|
||||
</span>
|
||||
<span className="text-xs text-muted-foreground leading-snug">
|
||||
{status.kind === "loading" && label("settings.devices.checking")}
|
||||
{status.kind === "no-key" && label("settings.devices.noKey")}
|
||||
{status.kind === "error" && label("settings.devices.error")}
|
||||
{status.kind === "ok" &&
|
||||
t("settings.devices.count", {
|
||||
count: status.count,
|
||||
})}
|
||||
</span>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="h-7 text-xs shrink-0"
|
||||
disabled={!running || status.kind === "loading"}
|
||||
onClick={() => void load()}
|
||||
>
|
||||
{status.kind === "loading" ? (
|
||||
<Loader2 className="size-3 animate-spin" />
|
||||
) : (
|
||||
<RefreshCw className="size-3" />
|
||||
)}
|
||||
<span className="ml-1.5">{label("settings.devices.refresh")}</span>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user