import { useCallback, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { toast } from "sonner"; import { Copy, ExternalLink, FlaskConical, Puzzle, Tag } from "lucide-react"; import { Button } from "@/components/button"; import { FakeSwitch, SectionCard, SettingRow } from "@/components/section-card"; import { getChannelReleases, getUpdateChannel, setUpdateChannel, type ReleaseInfo, type UpdateChannelState, } from "@/api/system-status-api"; import { getPlugins, setAllPluginChannels, setPluginOptions, type PluginSummary, } from "@/api/plugins-api"; import { reportBetaFeedbackUrl } from "@/lib/issue-url"; import { isElectron } from "@/lib/electron"; import { isPrerelease } from "@/plugins/plugin-model"; import { OPEN_PLUGINS_EVENT } from "@/plugins/open-plugins"; const DOCKER_BETA_IMAGE = "ghcr.io/termix-ssh/termix:beta"; const BETA_DOCS_URL = "https://docs.termix.site/install/server/docker#beta-builds"; function onBeta(plugin: PluginSummary): boolean { return plugin.channel === "beta" || isPrerelease(plugin.version); } function ReleaseRow({ label, release, }: { label: string; release: ReleaseInfo | null; }) { const { t } = useTranslation(); return ( {release && ( v{release.version} )} ); } export function UpdatesSettings() { const { t } = useTranslation(); const [channel, setChannel] = useState(null); const [releases, setReleases] = useState<{ stable: ReleaseInfo | null; beta: ReleaseInfo | null; } | null>(null); const [plugins, setPlugins] = useState([]); const [busy, setBusy] = useState(false); const version = import.meta.env.VITE_APP_VERSION || undefined; const loadPlugins = useCallback(async () => { try { setPlugins(await getPlugins()); } catch { setPlugins([]); } }, []); useEffect(() => { getUpdateChannel() .then(setChannel) .catch(() => setChannel(null)); getChannelReleases() .then(setReleases) .catch(() => setReleases({ stable: null, beta: null })); void loadPlugins(); }, [loadPlugins]); const betaPlugins = plugins.filter(onBeta); const runningBetas = plugins .filter((plugin) => isPrerelease(plugin.version)) .map((plugin) => ({ id: plugin.id, version: plugin.version })); const joined = channel?.channel === "beta"; const changeChannel = async (next: boolean) => { setBusy(true); try { setChannel(await setUpdateChannel(next ? "beta" : "stable")); } catch { toast.error(t("settings.updates.saveFailed")); } finally { setBusy(false); } }; const changePlugins = async (action: () => Promise) => { setBusy(true); try { await action(); } catch { toast.error(t("settings.updates.saveFailed")); } finally { await loadPlugins(); setBusy(false); } }; const copyImage = async () => { try { await navigator.clipboard.writeText(DOCKER_BETA_IMAGE); toast.success(t("settings.updates.copied")); } catch { toast.error(t("settings.updates.copyFailed")); } }; return (
} rowId="updates-beta" >

{t("settings.updates.betaIntro")}

void changeChannel(next)} /> {joined && !channel?.runningBeta && (
{t("settings.updates.howToInstall")} {isElectron() ? ( {t("settings.updates.desktopInstall")}{" "} {releases?.beta && ( {t("settings.updates.downloadBeta", { version: releases.beta.version, })} )} ) : ( <> {t("settings.updates.dockerInstall")}
{DOCKER_BETA_IMAGE}
)} {t("settings.updates.learnMore")}
)}
} rowId="updates-releases" > {version ? `v${version}` : "-"} } rowId="updates-plugins" >

{t("settings.updates.pluginBetasIntro")}{" "}

{betaPlugins.map((plugin) => ( {plugin.channel === "beta" && ( )} ))} {plugins.length > 0 && (
)}
); }