Files
netbird/client/ui/frontend/src/modules/settings/SettingsTroubleshooting.tsx
Eduard Gert 1932b76f5b update stuff
2026-05-13 16:28:51 +02:00

254 lines
9.0 KiB
TypeScript

import type { ReactNode } from "react";
import { FolderOpen } from "lucide-react";
import { Debug as DebugSvc } from "@bindings/services";
import type { DebugBundleResult } from "@bindings/services/models.js";
import { Button } from "@/components/Button";
import FancyToggleSwitch from "@/components/FancyToggleSwitch";
import HelpText from "@/components/HelpText.tsx";
import { Input } from "@/components/Input";
import { Label } from "@/components/Label";
import { StatusPanel } from "@/components/StatusPanel";
import { cn } from "@/lib/cn";
import type { DebugStage } from "@/modules/debug-bundle/useDebugBundle.ts";
import { useDebugBundleContext } from "@/modules/debug-bundle/useDebugBundleContext.ts";
import { SectionGroup } from "@/modules/settings/SettingsSection.tsx";
export function SettingsTroubleshooting() {
const {
anonymize,
setAnonymize,
systemInfo,
setSystemInfo,
upload,
setUpload,
trace,
setTrace,
traceMinutes,
setTraceMinutes,
run,
stage,
cancel,
reset,
} = useDebugBundleContext();
if (stage.kind === "done") {
return (
<DoneResult
result={stage.result}
uploaded={stage.uploadAttempted}
onClose={reset}
/>
);
}
if (stage.kind !== "idle") {
return <ProgressSection stage={stage} onCancel={cancel} />;
}
return (
<SectionGroup title={"Debug bundle"}>
<HelpText className={"-mt-2 mb-2"}>
A debug bundle helps NetBird support investigate connection problems. <br /> It's a
.zip file with logs, system details and debug information from your device.
</HelpText>
<FancyToggleSwitch
value={anonymize}
onChange={setAnonymize}
label={"Anonymize Sensitive Information"}
helpText={"Hides public IP addresses and non-NetBird domains from logs."}
/>
<FancyToggleSwitch
value={systemInfo}
onChange={setSystemInfo}
label={"Include System Information"}
helpText={"Include OS, kernel, network interfaces, and routing tables."}
/>
<FancyToggleSwitch
value={upload}
onChange={setUpload}
label={"Upload Bundle to NetBird Servers"}
helpText={
"Securely uploads the bundle and returns an upload key. Share the key with NetBird support over GitHub or Slack instead of attaching the file directly."
}
/>
<FancyToggleSwitch
value={trace}
onChange={setTrace}
label={"Capture Trace Logs"}
helpText={
"Raises logging to TRACE and cycles NetBird up and down to capture connection logs. The previous level is restored after the bundle is built."
}
/>
<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"}>Capture Duration</Label>
<HelpText margin={false}>
How long to capture trace logs before generating the bundle.
</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={"Minute(s)"}
disabled={!trace}
/>
</div>
</div>
<BottomBar>
<Button variant={"primary"} size={"md"} onClick={run}>
Create Bundle
</Button>
</BottomBar>
</SectionGroup>
);
}
function ProgressSection({ stage, onCancel }: { stage: DebugStage; onCancel: () => void }) {
const cancelling = stage.kind === "cancelling";
return (
<StatusPanel
variant={"loading"}
title={stageLabel(stage)}
description={
"Collecting logs, system details, and connection state. This usually takes a moment — keep this window open until it completes."
}
actions={
<Button variant={"secondary"} size={"xs"} onClick={onCancel} disabled={cancelling}>
{cancelling ? "Cancelling…" : "Cancel"}
</Button>
}
/>
);
}
function DoneResult({
result,
uploaded,
onClose,
}: {
result: DebugBundleResult;
uploaded: boolean;
onClose: () => void;
}) {
const showKey = uploaded && Boolean(result.uploadedKey);
const uploadFailed = uploaded && !result.uploadedKey;
const onRevealPath = () => {
if (!result.path) return;
void DebugSvc.RevealFile(result.path).catch(() => {});
};
return (
<StatusPanel
variant={"success"}
title={showKey ? "Debug bundle successfully uploaded!" : "Bundle saved"}
description={
showKey
? "Share the upload key below with NetBird support. A local copy was also saved on your device."
: "Your debug bundle has been saved locally."
}
actions={
<>
<Button variant={"secondary"} size={"xs"} onClick={onClose}>
Close
</Button>
{showKey ? (
<Button variant={"primary"} size={"xs"} copy={result.uploadedKey}>
Copy Key
</Button>
) : (
result.path && (
<Button variant={"primary"} size={"xs"} onClick={onRevealPath}>
<FolderOpen size={12} />
Open Folder
</Button>
)
)}
</>
}
>
<div className={"w-full max-w-xs mx-auto flex flex-col gap-3"}>
{showKey && <Input value={result.uploadedKey} readOnly copy />}
{result.path && !showKey && (
<Input
value={result.path}
readOnly
customSuffix={
<button
type={"button"}
onClick={onRevealPath}
className={"pointer-events-auto hover:text-white transition-all"}
aria-label={"Open file location"}
>
<FolderOpen size={16} />
</button>
}
/>
)}
{uploadFailed && (
<div
className={
"rounded-md border border-red-500/30 bg-red-500/10 px-3 py-2 text-xs text-red-300"
}
>
Upload failed
{result.uploadFailureReason ? `: ${result.uploadFailureReason}` : "."} The
bundle is still saved locally.
</div>
)}
</div>
</StatusPanel>
);
}
function BottomBar({ children }: { children: ReactNode }) {
return (
<div className={"absolute bottom-0 left-0 w-full"}>
<div
className={
"w-full flex justify-end gap-3 px-8 py-5 border-t border-nb-gray-900 bg-nb-gray-935"
}
>
{children}
</div>
</div>
);
}
const stageLabel = (stage: DebugStage): string => {
switch (stage.kind) {
case "preparing-trace":
return "Switching to trace logging…";
case "reconnecting":
return "Reconnecting NetBird…";
case "capturing": {
const fmt = (s: number) => `${Math.floor(s / 60)}:${String(s % 60).padStart(2, "0")}`;
return `Capturing logs — ${fmt(
stage.totalSec - stage.remainingSec,
)} / ${fmt(stage.totalSec)}`;
}
case "restoring-level":
return "Restoring previous log level…";
case "bundling":
return "Generating debug bundle…";
case "uploading":
return "Uploading to NetBird…";
case "cancelling":
return "Cancelling…";
default:
return "";
}
};