mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-20 13:49:07 +02:00
[management,client] 0.75.0 release with new desktop UI (#6473)
- **Wails v3 application** (`client/ui`) with a React + TypeScript + Tailwind frontend replacing the Fyne UI: main connection view, exit-node switcher, networks/peers browser with detail panels, profile management, settings (general, network, SSH, security, troubleshooting, appearance), debug-bundle creation, and a first-run welcome flow. - **Internationalization**: go-i18n bundle with 9 locales (en, de, es, fr, hu, it, pt, ru, zh-CN) shared between the tray and the frontend. - **New system tray** implementation with per-platform theme-aware icons, including a native XEmbed host for Linux (`xembed_tray_linux.c`) and a Linux theme watcher. - **Session handling**: auth session watcher (`client/internal/auth/sessionwatch`), pending login flow, session-expiration dialog and tray notifications, and `netbird login` improvements. - **Daemon API extensions** (`daemon.proto`): status stream subscription, event stream, networks/exit-node selection endpoints, and richer full status — with probe throttling on the daemon side to protect against UI-driven request storms. - **UI preferences store** persisted per profile, autostart management via the daemon (single source of truth in HKCU on Windows). - **Build system**: Taskfile-based builds per platform (macOS, Linux, Windows), Docker cross-compilation images, MSIX/NSIS/nfpm/AppImage packaging, and a new `frontend-ui` CI workflow. Co-authored-by: Zoltan Papp <zoltan.pmail@gmail.com> Co-authored-by: Eduard Gert <kontakt@eduardgert.de> Co-authored-by: braginini <bangvalo@gmail.com> Co-authored-by: Pascal Fischer <32096965+pascal-fischer@users.noreply.github.com> Co-authored-by: riccardom <riccardomanfrin@gmail.com>
This commit is contained in:
co-authored by
Zoltan Papp
Eduard Gert
braginini
Pascal Fischer
riccardom
parent
c9d387bd0d
commit
91acb8147c
@@ -0,0 +1,50 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { AlertTriangleIcon, DownloadIcon } from "lucide-react";
|
||||
import { Browser } from "@wailsio/runtime";
|
||||
import { Button } from "@/components/buttons/Button";
|
||||
import { useStatus } from "@/contexts/StatusContext.tsx";
|
||||
|
||||
const RELEASES_URL = "https://github.com/netbirdio/netbird/releases/latest";
|
||||
|
||||
function openUrl(url: string) {
|
||||
Browser.OpenURL(url).catch(() => globalThis.open(url, "_blank"));
|
||||
}
|
||||
|
||||
export const DaemonOutdatedOverlay = () => {
|
||||
const { t } = useTranslation();
|
||||
const { isDaemonOutdated } = useStatus();
|
||||
|
||||
if (!isDaemonOutdated) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
"wails-draggable fixed inset-0 z-[100] flex cursor-default select-none items-center justify-center bg-nb-gray-950 backdrop-blur-sm"
|
||||
}
|
||||
>
|
||||
<div className={"flex max-w-lg flex-col items-center gap-5 px-8 text-center"}>
|
||||
<div
|
||||
className={
|
||||
"flex h-11 w-11 items-center justify-center rounded-xl border border-nb-gray-900 bg-nb-gray-920 text-amber-500"
|
||||
}
|
||||
>
|
||||
<AlertTriangleIcon size={20} />
|
||||
</div>
|
||||
|
||||
<div className={"flex flex-col items-center gap-1"}>
|
||||
<p className={"text-base font-medium text-nb-gray-50"}>
|
||||
{t("daemon.outdated.title")}
|
||||
</p>
|
||||
<p className={"text-sm text-nb-gray-300"}>{t("daemon.outdated.description")}</p>
|
||||
</div>
|
||||
|
||||
<div className={"wails-no-draggable"}>
|
||||
<Button variant={"primary"} size={"xs"} onClick={() => openUrl(RELEASES_URL)}>
|
||||
<DownloadIcon size={14} />
|
||||
{t("update.card.getInstaller")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,52 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { AlertCircleIcon, BookText } from "lucide-react";
|
||||
import { Browser } from "@wailsio/runtime";
|
||||
import { Button } from "@/components/buttons/Button";
|
||||
import { useStatus } from "@/contexts/StatusContext.tsx";
|
||||
|
||||
const DOCS_URL = "https://docs.netbird.io/how-to/installation";
|
||||
|
||||
function openUrl(url: string) {
|
||||
Browser.OpenURL(url).catch(() => globalThis.open(url, "_blank"));
|
||||
}
|
||||
|
||||
export const DaemonUnavailableOverlay = () => {
|
||||
const { t } = useTranslation();
|
||||
const { isDaemonUnavailable } = useStatus();
|
||||
|
||||
if (!isDaemonUnavailable) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
"wails-draggable fixed inset-0 z-[100] flex cursor-default select-none items-center justify-center bg-nb-gray-950 backdrop-blur-sm"
|
||||
}
|
||||
>
|
||||
<div className={"flex max-w-lg flex-col items-center gap-5 px-8 text-center"}>
|
||||
<div
|
||||
className={
|
||||
"flex h-11 w-11 items-center justify-center rounded-xl border border-nb-gray-900 bg-nb-gray-920 text-red-500"
|
||||
}
|
||||
>
|
||||
<AlertCircleIcon size={20} />
|
||||
</div>
|
||||
|
||||
<div className={"flex flex-col items-center gap-1"}>
|
||||
<p className={"text-base font-medium text-nb-gray-50"}>
|
||||
{t("daemon.unavailable.title")}
|
||||
</p>
|
||||
<p className={"text-sm text-nb-gray-300"}>
|
||||
{t("daemon.unavailable.description")}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className={"wails-no-draggable"}>
|
||||
<Button variant={"secondary"} size={"xs"} onClick={() => openUrl(DOCS_URL)}>
|
||||
<BookText size={14} />
|
||||
{t("daemon.unavailable.docsLink")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
import { type ComponentType } from "react";
|
||||
import { type LucideProps } from "lucide-react";
|
||||
import { cn } from "@/lib/cn";
|
||||
import { SquareIcon } from "@/components/SquareIcon";
|
||||
import { isMacOS } from "@/lib/platform";
|
||||
|
||||
// Knob to shift the centered main-window content up/down together.
|
||||
export const contentVerticalOffset = (): string => (isMacOS() ? "0.6rem" : "-1.4rem");
|
||||
export const contentTop = (base: string) => `calc(${base} + ${contentVerticalOffset()})`;
|
||||
|
||||
type Props = {
|
||||
icon: ComponentType<LucideProps>;
|
||||
title: string;
|
||||
description?: string;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export const EmptyState = ({ icon, title, description, className }: Props) => {
|
||||
return (
|
||||
<div className={cn("py-12 text-center", className)}>
|
||||
<div
|
||||
className={"relative mx-auto flex max-w-sm flex-col items-center justify-start"}
|
||||
style={{ top: contentTop("7.8rem") }}
|
||||
>
|
||||
<SquareIcon icon={icon} className={"mb-3"} />
|
||||
<p className={"mb-1 text-[0.95rem] font-medium text-nb-gray-200"}>{title}</p>
|
||||
{description && <p className={"text-sm text-nb-gray-350"}>{description}</p>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
import { type ComponentType } from "react";
|
||||
import { FunnelXIcon, type LucideProps } from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { EmptyState } from "./EmptyState";
|
||||
|
||||
type Props = {
|
||||
icon?: ComponentType<LucideProps>;
|
||||
title?: string;
|
||||
description?: string;
|
||||
};
|
||||
|
||||
export const NoResults = ({ icon = FunnelXIcon, title, description }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<EmptyState
|
||||
icon={icon}
|
||||
title={title ?? t("common.noResults.title")}
|
||||
description={description ?? t("common.noResults.description")}
|
||||
className={"pointer-events-none relative -top-[3.8rem]"}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
import { GlobeOffIcon } from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { EmptyState } from "./EmptyState";
|
||||
|
||||
export const NotConnectedState = () => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className={"relative top-[3rem] w-full"}>
|
||||
<EmptyState
|
||||
icon={GlobeOffIcon}
|
||||
title={t("notConnected.title")}
|
||||
description={t("notConnected.description")}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user