"use client"; import { RefreshCw } from "lucide-react"; import { useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { CopyField, SettingsCard, SettingsSection, } from "@/components/settings/settings-parts"; import { Button } from "@/components/ui/button"; import { getNetworkInfo, getVersionInfo, type VersionInfo } from "@/lib/version"; import { cn } from "@/lib/utils"; // Self-hosted update path: pull the new images and restart on the server. const UPDATE_COMMAND = "docker compose pull && docker compose up -d"; function isLocalHost(hostname: string): boolean { return ( hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1" ); } function Badge({ tone, children, }: { tone: "ok" | "update" | "muted"; children: string; }) { return ( {children} ); } export function VersionPanel() { const { t } = useTranslation(); const [info, setInfo] = useState(null); const [loading, setLoading] = useState(true); const [checking, setChecking] = useState(false); const [networkUrls, setNetworkUrls] = useState([]); useEffect(() => { getVersionInfo() .then(setInfo) .catch(() => setInfo(null)) .finally(() => setLoading(false)); }, []); // "Check for updates" — force a fresh, cache-bypassing lookup on the backend. const checkForUpdates = () => { setChecking(true); getVersionInfo(true) .then(setInfo) .catch(() => { /* keep the last known info on a failed manual check */ }) .finally(() => setChecking(false)); }; // The most reliable shareable URL is the one the browser is already using — // unless that's localhost, in which case we fall back to the backend's // detected LAN addresses (accurate when not running in Docker's bridge net). const localShareUrl = useMemo(() => { if (typeof window === "undefined") return null; if (isLocalHost(window.location.hostname)) return null; return `${window.location.protocol}//${window.location.host}`; }, []); useEffect(() => { if (localShareUrl) return; getNetworkInfo() // Guard against an unexpected shape so a missing/garbled response shows the // helpful "open via the server's IP" hint rather than a broken value. .then((n) => setNetworkUrls(Array.isArray(n?.urls) ? n.urls : [])) .catch(() => setNetworkUrls([])); }, [localShareUrl]); const statusBadge = loading || checking ? ( {t("settings.version.checking")} ) : !info || info.latest === null ? ( {t("settings.version.offline")} ) : info.updateAvailable ? ( {t("settings.version.updateAvailable")} ) : ( {t("settings.version.upToDate")} ); return (

{t("settings.version.current")}

{info?.current ?? "—"}

{statusBadge}

{t("settings.version.latest")}

{loading ? t("settings.version.checking") : (info?.latest ?? t("settings.version.offline"))}

{info?.releaseUrl ? ( {t("settings.version.viewRelease")} ) : null}
{localShareUrl ? ( ) : networkUrls.length > 0 ? ( networkUrls.map((url) => ( )) ) : (

{t("settings.version.networkLocalHint")}

)}

{t("settings.version.networkFirewallHint")}

); }