mirror of
https://github.com/netbirdio/netbird.git
synced 2026-05-21 08:09:55 +00:00
add update context
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
import { createContext, useContext, useMemo, type ReactNode } from "react";
|
||||
import { Update as UpdateSvc } from "@bindings/services";
|
||||
import { useStatus } from "@/hooks/useStatus";
|
||||
import { UpdateAvailableBanner } from "@/modules/auto-update/UpdateAvailableBanner";
|
||||
|
||||
type ClientVersionContextValue = {
|
||||
updateAvailable: boolean;
|
||||
updateVersion: string | null;
|
||||
triggerUpdate: () => void;
|
||||
};
|
||||
|
||||
const ClientVersionContext = createContext<ClientVersionContextValue | null>(null);
|
||||
|
||||
export const useClientVersion = () => {
|
||||
const ctx = useContext(ClientVersionContext);
|
||||
if (!ctx) {
|
||||
throw new Error("useClientVersion must be used inside ClientVersionProvider");
|
||||
}
|
||||
return ctx;
|
||||
};
|
||||
|
||||
export const ClientVersionProvider = ({ children }: { children: ReactNode }) => {
|
||||
const { status } = useStatus();
|
||||
|
||||
const value = useMemo<ClientVersionContextValue>(() => {
|
||||
const version = (status?.events ?? [])
|
||||
.map((e) => e.metadata?.["new_version_available"])
|
||||
.find((v): v is string => Boolean(v));
|
||||
|
||||
return {
|
||||
updateAvailable: Boolean(version),
|
||||
updateVersion: version ?? null,
|
||||
triggerUpdate: () => {
|
||||
UpdateSvc.Trigger().catch(() => {});
|
||||
},
|
||||
};
|
||||
}, [status]);
|
||||
|
||||
return (
|
||||
<ClientVersionContext.Provider value={value}>
|
||||
{children}
|
||||
<UpdateAvailableBanner />
|
||||
</ClientVersionContext.Provider>
|
||||
);
|
||||
};
|
||||
@@ -1,26 +1,16 @@
|
||||
import { useState } from "react";
|
||||
import { Button } from "@/components/Button";
|
||||
import { useStatus } from "@/hooks/useStatus";
|
||||
import { useClientVersion } from "@/modules/auto-update/ClientVersionContext";
|
||||
import { cn } from "@/lib/cn";
|
||||
import { Update as UpdateSvc } from "@bindings/services";
|
||||
|
||||
// TODO: Shown only when management has auto updates enabled + there are updates available + force updates is disabled
|
||||
export const UpdateAvailableBanner = () => {
|
||||
const { status } = useStatus();
|
||||
const { updateVersion, triggerUpdate } = useClientVersion();
|
||||
const [dismissed, setDismissed] = useState(false);
|
||||
|
||||
if (import.meta.env.DEV) return null;
|
||||
|
||||
const updateVersion = (status?.events ?? [])
|
||||
.map((e) => e.metadata?.["new_version_available"])
|
||||
.find((v): v is string => Boolean(v));
|
||||
|
||||
if (!updateVersion || dismissed) return null;
|
||||
|
||||
const triggerUpdate = () => {
|
||||
UpdateSvc.Trigger().catch(() => {});
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
|
||||
20
client/ui/frontend/src/modules/auto-update/UpdateBadge.tsx
Normal file
20
client/ui/frontend/src/modules/auto-update/UpdateBadge.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { ArrowUpCircleIcon } from "lucide-react";
|
||||
import { cn } from "@/lib/cn";
|
||||
|
||||
type Props = {
|
||||
size?: number;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export const UpdateBadge = ({ size = 15, className }: Props) => {
|
||||
return (
|
||||
<div className={cn("relative flex items-center justify-center", className)}>
|
||||
<span
|
||||
className={
|
||||
"animate-ping absolute inline-flex h-[15px] w-[15px] rounded-full bg-netbird opacity-20 pointer-events-none"
|
||||
}
|
||||
/>
|
||||
<ArrowUpCircleIcon size={size} className={"text-netbird"} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
import { useNavigate } from "react-router-dom";
|
||||
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 navigate = useNavigate();
|
||||
const { updateAvailable } = useClientVersion();
|
||||
|
||||
if (!updateAvailable) return null;
|
||||
|
||||
return (
|
||||
<Tooltip content={"Update Available"}>
|
||||
<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>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,83 @@
|
||||
import { ReactNode } from "react";
|
||||
import { Browser } from "@wailsio/runtime";
|
||||
import { Button } from "@/components/Button";
|
||||
import { useClientVersion } from "@/modules/auto-update/ClientVersionContext";
|
||||
import { cn } from "@/lib/cn";
|
||||
|
||||
function openUrl(url: string) {
|
||||
void Browser.OpenURL(url).catch(() => window.open(url, "_blank"));
|
||||
}
|
||||
|
||||
function formatLastChecked(date: Date) {
|
||||
return date.toLocaleString(undefined, {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
});
|
||||
}
|
||||
|
||||
export function UpdateVersionCard() {
|
||||
const { updateVersion, triggerUpdate } = useClientVersion();
|
||||
|
||||
if (updateVersion) {
|
||||
return (
|
||||
<Card>
|
||||
<div>
|
||||
<Title>Version {updateVersion} is available.</Title>
|
||||
<Link
|
||||
url={`https://github.com/netbirdio/netbird/releases/tag/v${updateVersion}`}
|
||||
>
|
||||
What's new?
|
||||
</Link>
|
||||
</div>
|
||||
<Button variant={"primary"} size={"xs"} onClick={triggerUpdate}>
|
||||
Restart Now
|
||||
</Button>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card className={"max-w-md"}>
|
||||
<div>
|
||||
<Title>Last checked on {formatLastChecked(new Date())}</Title>
|
||||
<Link url={"https://github.com/netbirdio/netbird/releases/latest"}>Changelog</Link>
|
||||
</div>
|
||||
<Button variant={"primary"} size={"xs"} onClick={triggerUpdate}>
|
||||
Check for updates
|
||||
</Button>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
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",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Title({ children }: { children: ReactNode }) {
|
||||
return <p className={"text-sm font-semibold"}>{children}</p>;
|
||||
}
|
||||
|
||||
function Link({ url, children }: { url: string; children: ReactNode }) {
|
||||
return (
|
||||
<button
|
||||
type={"button"}
|
||||
onClick={() => openUrl(url)}
|
||||
className={
|
||||
"text-sm text-netbird hover:underline hover:underline-offset-4 hover:decoration-[0.5px] font-medium"
|
||||
}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import { Browser } from "@wailsio/runtime";
|
||||
import netbirdFull from "@/assets/logos/netbird-full.svg";
|
||||
import pkg from "../../../package.json";
|
||||
import { useStatus } from "@/hooks/useStatus";
|
||||
import { NetBirdVersionCard } from "@/components/NetBirdVersionCard";
|
||||
import { UpdateVersionCard } from "@/modules/auto-update/UpdateVersionCard";
|
||||
import { useAccentTrigger } from "@/modules/settings/SettingsAccent";
|
||||
|
||||
const LEGAL_LINKS: { label: string; url: string }[] = [
|
||||
@@ -40,7 +40,7 @@ export function SettingsAbout() {
|
||||
<p className={"text-sm text-nb-gray-300"}>GUI v{guiVersion}</p>
|
||||
</div>
|
||||
|
||||
<NetBirdVersionCard />
|
||||
<UpdateVersionCard />
|
||||
|
||||
<p className={"text-sm text-nb-gray-300 text-center"}>
|
||||
© {new Date().getFullYear()} NetBird. All Rights Reserved.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Tooltip } from "@/components/Tooltip.tsx";
|
||||
import { VerticalTabs } from "@/components/VerticalTabs.tsx";
|
||||
import { useStatus } from "@/hooks/useStatus";
|
||||
import { UpdateBadge } from "@/modules/auto-update/UpdateBadge.tsx";
|
||||
import { useClientVersion } from "@/modules/auto-update/ClientVersionContext.tsx";
|
||||
import {
|
||||
ArrowUpCircleIcon,
|
||||
BoltIcon,
|
||||
InfoIcon,
|
||||
LifeBuoyIcon,
|
||||
@@ -13,21 +13,11 @@ import {
|
||||
} from "lucide-react";
|
||||
|
||||
export const SettingsNavigationTriggers = () => {
|
||||
const { status } = useStatus();
|
||||
const updateAvailable = (status?.events ?? []).some((e) =>
|
||||
Boolean(e.metadata?.["new_version_available"]),
|
||||
);
|
||||
const { updateAvailable } = useClientVersion();
|
||||
|
||||
const aboutAdornment = updateAvailable ? (
|
||||
<Tooltip content={"Update Available"} side={"right"}>
|
||||
<div className={"relative 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"
|
||||
}
|
||||
/>
|
||||
<ArrowUpCircleIcon size={15} className={"text-netbird"} />
|
||||
</div>
|
||||
<UpdateBadge />
|
||||
</Tooltip>
|
||||
) : undefined;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user