update debug bundle setting page

This commit is contained in:
Eduard Gert
2026-06-12 12:09:10 +02:00
parent f8ccbb07bb
commit 860be01ebe
18 changed files with 431 additions and 277 deletions
@@ -2,12 +2,16 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { Events } from "@wailsio/runtime";
import { Connection, WindowManager } from "@bindings/services";
import i18next from "@/lib/i18n";
import { ToggleSwitch } from "@/components/switches/ToggleSwitch.tsx";
import { useStatus } from "@/contexts/StatusContext.tsx";
import { useProfile } from "@/contexts/ProfileContext.tsx";
import { cn } from "@/lib/cn.ts";
import { errorDialog, formatErrorMessage } from "@/lib/errors.ts";
import {
startConnection,
EVENT_BROWSER_LOGIN_CANCEL,
EVENT_TRIGGER_LOGIN,
} from "@/lib/connection.ts";
import { CopyToClipboard } from "@/components/CopyToClipboard";
import { TruncatedText } from "@/components/TruncatedText";
import { shortenDns } from "@/lib/formatters";
@@ -16,89 +20,6 @@ import { Check as CheckIcon, ChevronDownIcon, Copy as CopyIcon } from "lucide-re
import * as Popover from "@radix-ui/react-popover";
import netbirdFullLogo from "@/assets/logos/netbird-full.svg";
const EVENT_BROWSER_LOGIN_CANCEL = "browser-login:cancel";
const EVENT_TRIGGER_LOGIN = "trigger-login";
let loginInFlight = false;
// onSettled (re-arm guards) must fire before the error dialog, never gated on it:
// a hanging dialog would silently drop every later login until restart.
async function startLogin(onSettled?: () => void): Promise<void> {
if (loginInFlight) {
onSettled?.();
return;
}
loginInFlight = true;
let cancelled = false;
let offCancel: (() => void) | undefined;
let loginError: unknown;
try {
const result = await Connection.Login({
profileName: "",
username: "",
managementUrl: "",
setupKey: "",
preSharedKey: "",
hostname: "",
hint: "",
});
if (result.needsSsoLogin) {
const uri = result.verificationUriComplete || result.verificationUri;
if (uri) {
try {
await WindowManager.OpenBrowserLogin(uri);
} catch (e) {
console.error(e);
}
}
const cancelPromise = new Promise<void>((resolve) => {
offCancel = Events.On(EVENT_BROWSER_LOGIN_CANCEL, () => {
cancelled = true;
resolve();
});
});
const waitPromise = Connection.WaitSSOLogin({
userCode: result.userCode,
hostname: "",
});
try {
await Promise.race([waitPromise, cancelPromise]);
} finally {
WindowManager.CloseBrowserLogin().catch(console.error);
}
if (cancelled) {
waitPromise.cancel?.();
waitPromise.catch(() => {});
return;
}
}
await Connection.Up({ profileName: "", username: "" });
} catch (e) {
WindowManager.CloseBrowserLogin().catch(console.error);
if (!cancelled) loginError = e;
} finally {
offCancel?.();
loginInFlight = false;
onSettled?.();
}
if (loginError !== undefined) {
await errorDialog({
Title: i18next.t("connect.error.loginTitle"),
Message: formatErrorMessage(loginError),
});
}
}
enum ConnectionState {
Disconnected = "disconnected",
Connecting = "connecting",
@@ -136,7 +57,7 @@ export const MainConnectionStatusSwitch = () => {
if (loginGuard.current) return;
loginGuard.current = true;
setAction("logging-in");
void startLogin(() => {
void startConnection(() => {
loginGuard.current = false;
setAction(null);
refresh().catch((err: unknown) => console.error("refresh after login failed", err));
@@ -52,7 +52,7 @@ export const SettingsPage = () => {
className={"flex-1 min-h-0 overflow-hidden"}
>
<ScrollArea.Viewport className={"h-full w-full"}>
<div className={"py-8 px-7"}>
<div className={"py-6 px-7"}>
<VerticalTabs.Content value={"general"}>
<SettingsGeneral />
</VerticalTabs.Content>
@@ -20,7 +20,7 @@ export const SectionGroup = ({
export const SettingsBottomBar = ({ children }: { children: ReactNode }) => (
<>
<div className={"h-[4rem] shrink-0"} aria-hidden />
<div className={"h-[3.2rem] shrink-0"} aria-hidden />
<div className={"absolute bottom-0 left-0 w-full"}>
<div
className={
@@ -13,7 +13,6 @@ import HelpText from "@/components/typography/HelpText.tsx";
import { Input } from "@/components/inputs/Input";
import { Label } from "@/components/typography/Label";
import { SquareIcon } from "@/components/SquareIcon";
import { cn } from "@/lib/cn";
import { formatRemaining } from "@/lib/formatters";
import type { DebugStage } from "@/contexts/DebugBundleContext";
import { useDebugBundleContext } from "@/contexts/DebugBundleContext";
@@ -32,8 +31,12 @@ export function SettingsTroubleshooting() {
setUpload,
trace,
setTrace,
capture,
setCapture,
traceMinutes,
setTraceMinutes,
capturePackets,
setCapturePackets,
run,
stage,
cancel,
@@ -51,10 +54,6 @@ export function SettingsTroubleshooting() {
return (
<SectionGroup title={t("settings.troubleshooting.section.title")}>
<HelpText className={"-mt-2 mb-2"}>
<Trans i18nKey={"settings.troubleshooting.intro"} components={{ br: <br /> }} />
</HelpText>
<FancyToggleSwitch
value={anonymize}
onChange={setAnonymize}
@@ -79,30 +78,44 @@ export function SettingsTroubleshooting() {
label={t("settings.troubleshooting.trace.label")}
helpText={t("settings.troubleshooting.trace.help")}
/>
<div
className={cn(
"flex items-center gap-6 justify-between",
!trace && "opacity-50 pointer-events-none",
)}
>
<div className={"flex-1 max-w-md"}>
<Label as={"div"}>{t("settings.troubleshooting.duration.label")}</Label>
<HelpText margin={false}>
{t("settings.troubleshooting.duration.help")}
</HelpText>
</div>
<div className={"w-40 shrink-0"}>
<Input
type={"number"}
min={1}
max={30}
value={traceMinutes}
onChange={(e) =>
setTraceMinutes(Math.max(1, Math.min(30, Number(e.target.value) || 1)))
}
customSuffix={t("settings.troubleshooting.duration.suffix")}
disabled={!trace}
/>
<FancyToggleSwitch
value={capture}
onChange={setCapture}
label={t("settings.troubleshooting.capture.label")}
helpText={t("settings.troubleshooting.capture.help")}
/>
<div className={"flex flex-col gap-4"}>
<FancyToggleSwitch
value={capturePackets}
onChange={setCapturePackets}
label={t("settings.troubleshooting.packets.label")}
helpText={t("settings.troubleshooting.packets.help")}
disabled={!capture}
/>
<div className={"flex items-center gap-6 justify-between"}>
<div className={"flex-1 max-w-md"}>
<Label as={"div"} disabled={!capture}>
{t("settings.troubleshooting.duration.label")}
</Label>
<HelpText margin={false} disabled={!capture}>
{t("settings.troubleshooting.duration.help")}
</HelpText>
</div>
<div className={"w-40 shrink-0"}>
<Input
type={"number"}
min={1}
max={30}
value={traceMinutes}
onChange={(e) =>
setTraceMinutes(
Math.max(1, Math.min(30, Number(e.target.value) || 1)),
)
}
customSuffix={t("settings.troubleshooting.duration.suffix")}
disabled={!capture}
/>
</div>
</div>
</div>
@@ -297,14 +310,10 @@ const stageLabel = (
t: (key: string, options?: Record<string, unknown>) => string,
): string => {
switch (stage.kind) {
case "preparing-trace":
return t("settings.troubleshooting.stage.preparingTrace");
case "reconnecting":
return t("settings.troubleshooting.stage.reconnecting");
case "capturing":
return t("settings.troubleshooting.stage.capturing");
case "restoring-level":
return t("settings.troubleshooting.stage.restoring");
case "bundling":
return t("settings.troubleshooting.stage.bundling");
case "uploading":