update auto update wordings, add update available into ui

This commit is contained in:
Eduard Gert
2026-05-22 13:04:04 +02:00
parent 577ce6deb5
commit 17a365926d
7 changed files with 99 additions and 143 deletions

View File

@@ -234,10 +234,10 @@
"update.card.versionAvailableInstall": "Version {version} ist zur Installation verfügbar.",
"update.card.whatsNew": "Was ist neu?",
"update.card.installNow": "Jetzt installieren",
"update.card.getInstaller": "Installer herunterladen",
"update.card.lastChecked": "Zuletzt geprüft am {date}",
"update.card.getInstaller": "Herunterladen",
"update.card.autoCheckInterval": "NetBird sucht im Hintergrund nach Updates.",
"update.card.changelog": "Änderungsprotokoll",
"update.card.checkForUpdates": "Nach Updates suchen",
"update.card.onLatestVersion": "Du verwendest die neueste Version",
"update.header.tooltip": "Update verfügbar",
"update.overlay.updatingVersion": "NetBird wird auf v{version} aktualisiert",
"update.overlay.updating": "NetBird wird aktualisiert",

View File

@@ -94,6 +94,7 @@
"header.menu.settings": "Settings",
"header.menu.defaultView": "Default View",
"header.menu.advancedView": "Advanced View",
"header.menu.updateAvailable": "Update Available",
"profile.deregister.title": "Deregister Profile",
"profile.deregister.message": "Are you sure you want to deregister \"{name}\"? You will need to log in again to use it.",
@@ -254,11 +255,11 @@
"update.card.versionAvailableDownload": "Version {version} is available for download.",
"update.card.versionAvailableInstall": "Version {version} is available for install.",
"update.card.whatsNew": "What's new?",
"update.card.installNow": "Install now",
"update.card.getInstaller": "Download installer",
"update.card.lastChecked": "Last checked on {date}",
"update.card.installNow": "Install Now",
"update.card.getInstaller": "Download",
"update.card.autoCheckInterval": "NetBird checks for updates in the background.",
"update.card.changelog": "Changelog",
"update.card.checkForUpdates": "Check for updates",
"update.card.onLatestVersion": "You're on the latest version",
"update.header.tooltip": "Update Available",
"update.overlay.updatingVersion": "Updating NetBird to v{version}",
"update.overlay.updating": "Updating NetBird",

View File

@@ -234,10 +234,10 @@
"update.card.versionAvailableInstall": "A {version} verzió telepítésre elérhető.",
"update.card.whatsNew": "Mi az újdonság?",
"update.card.installNow": "Telepítés most",
"update.card.getInstaller": "Telepítő letöltése",
"update.card.lastChecked": "Utolsó ellenőrzés: {date}",
"update.card.getInstaller": "Letöltés",
"update.card.autoCheckInterval": "A NetBird a háttérben keres frissítéseket.",
"update.card.changelog": "Változásnapló",
"update.card.checkForUpdates": "Frissítések keresése",
"update.card.onLatestVersion": "A legfrissebb verziót használod",
"update.header.tooltip": "Frissítés elérhető",
"update.overlay.updatingVersion": "NetBird frissítése a következőre: v{version}",
"update.overlay.updating": "NetBird frissítése",

View File

@@ -1,6 +1,7 @@
import { useState } from "react";
import { useTranslation } from "react-i18next";
import {
ArrowUpCircleIcon,
Check,
MoreVertical,
PanelsRightBottom,
@@ -19,6 +20,7 @@ import {
} from "@/components/DropdownMenu";
import { IconButton } from "@/components/IconButton";
import { ProfileDropdown } from "@/components/ProfileDropdown";
import { useClientVersion } from "@/modules/auto-update/ClientVersionContext";
import { cn } from "@/lib/cn";
type ViewMode = "default" | "advanced";
@@ -35,12 +37,18 @@ export const Header = () => {
const { t } = useTranslation();
const [menuOpen, setMenuOpen] = useState(false);
const [viewMode, setViewMode] = useState<ViewMode>("default");
const { updateAvailable } = useClientVersion();
const openSettings = () => {
setMenuOpen(false);
void WindowManager.OpenSettings("").catch(() => {});
};
const openAbout = () => {
setMenuOpen(false);
void WindowManager.OpenSettings("about").catch(() => {});
};
const openManageProfiles = () => {
void WindowManager.OpenSettings("profiles").catch(() => {});
};
@@ -67,39 +75,75 @@ export const Header = () => {
<ProfileDropdown onManageProfiles={openManageProfiles} />
</div>
<div className={"flex justify-end wails-no-draggable"}>
<DropdownMenu modal={false} open={menuOpen} onOpenChange={setMenuOpen}>
<DropdownMenuTrigger asChild className={"wails-no-draggable"}>
<IconButton
icon={MoreVertical}
iconClassName={"text-nb-gray-200 wails-no-draggable"}
/>
</DropdownMenuTrigger>
<DropdownMenuContent
align="end"
sideOffset={8}
className="min-w-52 data-[state=closed]:!animate-none data-[state=closed]:!duration-0"
>
<DropdownMenuItem onClick={openSettings}>
<div className="flex items-center gap-2">
<Settings size={14} />
{t("header.menu.settings")}
</div>
</DropdownMenuItem>
<DropdownMenuSeparator />
<ViewModeItem
icon={RectangleVertical}
label={t("header.menu.defaultView")}
selected={viewMode === "default"}
onSelect={() => selectMode("default")}
/>
<ViewModeItem
icon={PanelsRightBottom}
label={t("header.menu.advancedView")}
selected={viewMode === "advanced"}
onSelect={() => selectMode("advanced")}
/>
</DropdownMenuContent>
</DropdownMenu>
<div className={"relative"}>
<DropdownMenu modal={false} open={menuOpen} onOpenChange={setMenuOpen}>
<DropdownMenuTrigger asChild className={"wails-no-draggable"}>
<IconButton
icon={MoreVertical}
iconClassName={"text-nb-gray-200 wails-no-draggable"}
/>
</DropdownMenuTrigger>
<DropdownMenuContent
align="end"
sideOffset={8}
className="min-w-52 data-[state=closed]:!animate-none data-[state=closed]:!duration-0"
>
{updateAvailable && (
<>
<DropdownMenuItem onClick={openAbout}>
<div className="flex items-center gap-2">
<ArrowUpCircleIcon
size={14}
className={"text-netbird"}
/>
<span className={"text-netbird"}>
{t("header.menu.updateAvailable")}
</span>
</div>
</DropdownMenuItem>
<DropdownMenuSeparator />
</>
)}
<DropdownMenuItem onClick={openSettings}>
<div className="flex items-center gap-2">
<Settings size={14} />
{t("header.menu.settings")}
</div>
</DropdownMenuItem>
<DropdownMenuSeparator />
<ViewModeItem
icon={RectangleVertical}
label={t("header.menu.defaultView")}
selected={viewMode === "default"}
onSelect={() => selectMode("default")}
/>
<ViewModeItem
icon={PanelsRightBottom}
label={t("header.menu.advancedView")}
selected={viewMode === "advanced"}
onSelect={() => selectMode("advanced")}
/>
</DropdownMenuContent>
</DropdownMenu>
{updateAvailable && (
<span
className={
"pointer-events-none absolute top-1.5 right-1.5 flex h-2.5 w-2.5 items-center justify-center"
}
>
<span
className={
"absolute inset-0 rounded-full bg-netbird opacity-60 animate-ping"
}
/>
<span
className={
"relative h-1.5 w-1.5 rounded-full bg-netbird"
}
/>
</span>
)}
</div>
</div>
</div>
);

View File

@@ -1,45 +0,0 @@
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { Button } from "@/components/Button";
import { useClientVersion } from "@/modules/auto-update/ClientVersionContext";
import { cn } from "@/lib/cn";
// Shown only when management has auto-update enabled (enforced=true) and the
// daemon has not yet started the installer (installing=false). The
// download-only branch (enforced=false) routes the user to GitHub via the
// tray menu instead; the force-install branch (installing=true) takes over
// with the full-screen UpdatingOverlay.
export const UpdateAvailableBanner = () => {
const { t } = useTranslation();
const { updateVersion, enforced, installing, triggerUpdate } = useClientVersion();
const [dismissed, setDismissed] = useState(false);
if (import.meta.env.DEV) return null;
if (!updateVersion || !enforced || installing || dismissed) return null;
return (
<div
className={cn(
"absolute bottom-4 left-1/2 -translate-x-1/2 z-50",
"w-[calc(100%-2rem)] max-w-xl",
"flex items-center justify-between gap-3",
"rounded-xl border border-nb-gray-800 bg-white backdrop-blur",
"px-2 py-2 shadow-lg",
)}
>
<p className={"text-sm text-nb-gray-900 pr-4 pl-2 font-medium"}>
{t("update.banner.message", { version: updateVersion })}
</p>
<div className={"flex gap-2"}>
<Button variant={"subtle"} size={"xs"} onClick={() => setDismissed(true)}>
{t("update.banner.later")}
</Button>
<Button variant={"primary"} size={"xs"} onClick={triggerUpdate}>
{t("update.banner.installNow")}
</Button>
</div>
</div>
);
};
export default UpdateAvailableBanner;

View File

@@ -1,34 +0,0 @@
import { useNavigate } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { ArrowUpCircleIcon } from "lucide-react";
import { IconButton } from "@/components/IconButton.tsx";
import { Tooltip } from "@/components/Tooltip.tsx";
import { useClientVersion } from "@/modules/auto-update/ClientVersionContext";
export const UpdateHeaderTrigger = () => {
const { t } = useTranslation();
const navigate = useNavigate();
const { updateAvailable } = useClientVersion();
if (!updateAvailable) return null;
return (
<Tooltip content={t("update.header.tooltip")}>
<div className={"relative h-11 w-11 flex items-center justify-center"}>
<span
className={
"animate-ping absolute inline-flex h-[15px] w-[15px] rounded-full bg-netbird opacity-20 pointer-events-none"
}
/>
<IconButton
icon={ArrowUpCircleIcon}
iconClassName={"text-netbird"}
onClick={() =>
navigate("/settings", { state: { tab: "about" } })
}
className={"absolute inset-0"}
/>
</div>
</Tooltip>
);
};

View File

@@ -1,6 +1,7 @@
import { ReactNode } from "react";
import { useTranslation } from "react-i18next";
import { Browser } from "@wailsio/runtime";
import { DownloadIcon, NotepadText } from "lucide-react";
import { Button } from "@/components/Button";
import { useClientVersion } from "@/modules/auto-update/ClientVersionContext";
import { cn } from "@/lib/cn";
@@ -11,17 +12,8 @@ function openUrl(url: string) {
void Browser.OpenURL(url).catch(() => window.open(url, "_blank"));
}
function formatLastChecked(date: Date, locale?: string) {
return date.toLocaleString(locale, {
month: "short",
day: "numeric",
hour: "2-digit",
minute: "2-digit",
});
}
export function UpdateVersionCard() {
const { t, i18n } = useTranslation();
const { t } = useTranslation();
const { updateVersion, enforced, triggerUpdate } = useClientVersion();
if (updateVersion) {
@@ -29,7 +21,7 @@ export function UpdateVersionCard() {
? "update.card.versionAvailableInstall"
: "update.card.versionAvailableDownload";
return (
<Card>
<Card className={"max-w-lg"}>
<div>
<Title>{t(titleKey, { version: updateVersion })}</Title>
<Link
@@ -48,6 +40,7 @@ export function UpdateVersionCard() {
size={"xs"}
onClick={() => openUrl(GITHUB_RELEASES)}
>
<DownloadIcon size={14} />
{t("update.card.getInstaller")}
</Button>
)}
@@ -56,17 +49,14 @@ export function UpdateVersionCard() {
}
return (
<Card className={"max-w-md"}>
<Card className={"max-w-lg"}>
<div>
<Title>
{t("update.card.lastChecked", {
date: formatLastChecked(new Date(), i18n.language),
})}
</Title>
<Link url={GITHUB_RELEASES}>{t("update.card.changelog")}</Link>
<Title>{t("update.card.onLatestVersion")}</Title>
<p className={"text-sm text-nb-gray-300"}>{t("update.card.autoCheckInterval")}</p>
</div>
<Button variant={"primary"} size={"xs"} onClick={triggerUpdate}>
{t("update.card.checkForUpdates")}
<Button variant={"primary"} size={"xs"} onClick={() => openUrl(GITHUB_RELEASES)}>
<NotepadText size={14} />
{t("update.card.changelog")}
</Button>
</Card>
);
@@ -76,7 +66,7 @@ function Card({ children, className }: { children: ReactNode; className?: string
return (
<div
className={cn(
"w-full max-w-md flex items-center justify-between gap-4 rounded-md border border-nb-gray-800 bg-nb-gray-910 px-4 py-3",
"w-full flex items-center justify-between gap-4 rounded-md border border-nb-gray-800 bg-nb-gray-910 px-4 py-3",
className,
)}
>