mirror of
https://github.com/netbirdio/netbird.git
synced 2026-08-30 11:31:29 +02:00
update auto update wordings, add update available into ui
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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,
|
||||
)}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user