diff --git a/client/ui/frontend/src/components/typography/HelpText.tsx b/client/ui/frontend/src/components/typography/HelpText.tsx index 7f70a352d..707710668 100644 --- a/client/ui/frontend/src/components/typography/HelpText.tsx +++ b/client/ui/frontend/src/components/typography/HelpText.tsx @@ -5,13 +5,15 @@ type Props = { children?: ReactNode; margin?: boolean; className?: string; + disabled?: boolean; }; -export const HelpText = ({ children, margin = true, className }: Props) => ( +export const HelpText = ({ children, margin = true, className, disabled = false }: Props) => ( diff --git a/client/ui/frontend/src/components/typography/Label.tsx b/client/ui/frontend/src/components/typography/Label.tsx index 5c093eba4..6976ec1e9 100644 --- a/client/ui/frontend/src/components/typography/Label.tsx +++ b/client/ui/frontend/src/components/typography/Label.tsx @@ -10,13 +10,19 @@ const labelVariants = cva( type LabelProps = ComponentPropsWithoutRef & VariantProps & { as?: "label" | "div"; + disabled?: boolean; }; export const Label = forwardRef(function Label( - { className, as = "label", children, ...props }, + { className, as = "label", disabled = false, children, ...props }, ref, ) { - const classes = cn(labelVariants(), className, "select-none"); + const classes = cn( + labelVariants(), + className, + "select-none transition-all duration-300", + disabled && "opacity-30 pointer-events-none", + ); if (as === "div") { return ( diff --git a/client/ui/frontend/src/contexts/DebugBundleContext.tsx b/client/ui/frontend/src/contexts/DebugBundleContext.tsx index c623effc1..6d29cfdc2 100644 --- a/client/ui/frontend/src/contexts/DebugBundleContext.tsx +++ b/client/ui/frontend/src/contexts/DebugBundleContext.tsx @@ -3,7 +3,7 @@ import { Connection as ConnectionSvc, Debug as DebugSvc } from "@bindings/servic import type { DebugBundleResult } from "@bindings/services/models.js"; import i18next from "@/lib/i18n"; import { errorDialog, formatErrorMessage } from "@/lib/errors.ts"; -import { useProfile } from "@/contexts/ProfileContext.tsx"; +import { startConnection } from "@/lib/connection.ts"; const NETBIRD_UPLOAD_URL = "https://upload.debug.netbird.io/upload-url"; const TRACE_LOG_FILE_COUNT = 5; @@ -56,14 +56,21 @@ const setLogLevelBestEffort = async (level: string) => { } }; -type LevelState = { original: string; raised: boolean }; +const stopCaptureBestEffort = async () => { + try { + await DebugSvc.StopBundleCapture(); + } catch { + // empty + } +}; -const runTracePhase = async ( +type LevelState = { original: string; raised: boolean }; +type CaptureState = { started: boolean }; + +const raiseToTrace = async ( signal: AbortSignal, level: LevelState, setStage: (s: DebugStage) => void, - target: { profileName: string; username: string }, - traceMinutes: number, ) => { setStage({ kind: "preparing-trace" }); try { @@ -75,7 +82,9 @@ const runTracePhase = async ( throwIfAborted(signal); await DebugSvc.SetLogLevel({ level: TRACE_LOG_LEVEL }); level.raised = true; +}; +const cycleConnection = async (signal: AbortSignal, setStage: (s: DebugStage) => void) => { throwIfAborted(signal); setStage({ kind: "reconnecting" }); try { @@ -84,14 +93,10 @@ const runTracePhase = async ( // empty } throwIfAborted(signal); - await ConnectionSvc.Up(target); - - const totalSec = Math.max(1, Math.min(30, traceMinutes)) * 60; - for (let remaining = totalSec; remaining > 0; remaining--) { - setStage({ kind: "capturing", remainingSec: remaining, totalSec }); - await sleep(1000, signal); - } + await startConnection(undefined, signal); +}; +const restoreLogLevel = async (level: LevelState, setStage: (s: DebugStage) => void) => { setStage({ kind: "restoring-level" }); try { await DebugSvc.SetLogLevel({ level: level.original }); @@ -101,13 +106,25 @@ const runTracePhase = async ( } }; +const waitCaptureWindow = async ( + signal: AbortSignal, + setStage: (s: DebugStage) => void, + totalSec: number, +) => { + for (let remaining = totalSec; remaining > 0; remaining--) { + setStage({ kind: "capturing", remainingSec: remaining, totalSec }); + await sleep(1000, signal); + } +}; + const useDebugBundle = () => { - const { activeProfile, username } = useProfile(); const [anonymize, setAnonymize] = useState(false); const [systemInfo, setSystemInfo] = useState(true); const [upload, setUpload] = useState(true); - const [trace, setTrace] = useState(false); + const [trace, setTrace] = useState(true); + const [capture, setCapture] = useState(false); const [traceMinutes, setTraceMinutes] = useState(1); + const [capturePackets, setCapturePackets] = useState(true); const [stage, setStage] = useState({ kind: "idle" }); const [lastBundlePath, setLastBundlePath] = useState(""); const abortRef = useRef(null); @@ -129,16 +146,43 @@ const useDebugBundle = () => { const uploadUrl = upload ? NETBIRD_UPLOAD_URL : ""; const level: LevelState = { original: DEFAULT_LOG_LEVEL, raised: false }; + const pcap: CaptureState = { started: false }; + const totalSec = Math.max(1, Math.min(30, traceMinutes)) * 60; + const hasWindow = capture && totalSec > 0; try { if (trace) { - await runTracePhase( - signal, - level, - setStage, - { profileName: activeProfile, username }, - traceMinutes, - ); + await raiseToTrace(signal, level, setStage); + } + throwIfAborted(signal); + + if (capture) { + await cycleConnection(signal, setStage); + } + throwIfAborted(signal); + + if (hasWindow && capturePackets) { + try { + // Mirror the CLI's safety margin: window + 30s, server caps at 10m. + await DebugSvc.StartBundleCapture(totalSec + 30); + pcap.started = true; + } catch { + // empty + } + } + throwIfAborted(signal); + + if (hasWindow) { + await waitCaptureWindow(signal, setStage, totalSec); + } + + if (pcap.started) { + await stopCaptureBestEffort(); + pcap.started = false; + } + + if (level.raised) { + await restoreLogLevel(level, setStage); } throwIfAborted(signal); @@ -161,10 +205,13 @@ const useDebugBundle = () => { }); } catch (e) { if (isAbort(e)) { + setStage({ kind: "cancelling" }); + if (pcap.started) await stopCaptureBestEffort(); if (level.raised) await setLogLevelBestEffort(level.original); setStage({ kind: "idle" }); return; } + if (pcap.started) await stopCaptureBestEffort(); setStage({ kind: "idle" }); await errorDialog({ Title: i18next.t("settings.error.debugBundleTitle"), @@ -191,8 +238,12 @@ const useDebugBundle = () => { setUpload, trace, setTrace, + capture, + setCapture, traceMinutes, setTraceMinutes, + capturePackets, + setCapturePackets, stage, isRunning, lastBundlePath, diff --git a/client/ui/frontend/src/lib/connection.ts b/client/ui/frontend/src/lib/connection.ts new file mode 100644 index 000000000..0ef313c18 --- /dev/null +++ b/client/ui/frontend/src/lib/connection.ts @@ -0,0 +1,112 @@ +import { Events } from "@wailsio/runtime"; +import { Connection, WindowManager } from "@bindings/services"; +import i18next from "@/lib/i18n"; +import { errorDialog, formatErrorMessage } from "@/lib/errors.ts"; + +export const EVENT_BROWSER_LOGIN_CANCEL = "browser-login:cancel"; +export const EVENT_TRIGGER_LOGIN = "trigger-login"; + +let connectionInFlight = false; + +export async function startConnection(onSettled?: () => void, signal?: AbortSignal): Promise { + if (connectionInFlight) { + onSettled?.(); + return; + } + if (signal?.aborted) { + onSettled?.(); + return; + } + connectionInFlight = true; + + let cancelled = false; + let offCancel: (() => void) | undefined; + let offSignal: (() => void) | undefined; + let connectError: unknown; + + try { + const result = await Connection.Login({ + profileName: "", + username: "", + managementUrl: "", + setupKey: "", + preSharedKey: "", + hostname: "", + hint: "", + }); + + if (signal?.aborted) cancelled = true; + + if (!cancelled && result.needsSsoLogin) { + const uri = result.verificationUriComplete || result.verificationUri; + if (uri) { + try { + await WindowManager.OpenBrowserLogin(uri); + } catch (e) { + console.error(e); + } + } + + const cancelPromise = new Promise((resolve) => { + offCancel = Events.On(EVENT_BROWSER_LOGIN_CANCEL, () => { + cancelled = true; + resolve(); + }); + if (signal) { + const onAbort = () => { + cancelled = true; + resolve(); + }; + if (signal.aborted) { + onAbort(); + } else { + signal.addEventListener("abort", onAbort); + offSignal = () => signal.removeEventListener("abort", onAbort); + } + } + }); + + 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(() => {}); + } + } + + if (!cancelled && signal?.aborted) cancelled = true; + + if (!cancelled) { + await Connection.Up({ profileName: "", username: "" }); + } + } catch (e) { + WindowManager.CloseBrowserLogin().catch(console.error); + if (!cancelled) connectError = e; + } finally { + offCancel?.(); + offSignal?.(); + connectionInFlight = false; + onSettled?.(); + } + + if (connectError !== undefined) { + await errorDialog({ + Title: i18next.t("connect.error.loginTitle"), + Message: formatErrorMessage(connectError), + }); + return; + } + + if (cancelled && signal) { + throw new DOMException("aborted", "AbortError"); + } +} diff --git a/client/ui/frontend/src/modules/main/MainConnectionStatusSwitch.tsx b/client/ui/frontend/src/modules/main/MainConnectionStatusSwitch.tsx index c6faef404..96b8a4021 100644 --- a/client/ui/frontend/src/modules/main/MainConnectionStatusSwitch.tsx +++ b/client/ui/frontend/src/modules/main/MainConnectionStatusSwitch.tsx @@ -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 { - 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((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)); diff --git a/client/ui/frontend/src/modules/settings/SettingsPage.tsx b/client/ui/frontend/src/modules/settings/SettingsPage.tsx index 473f406ef..fb0661bc2 100644 --- a/client/ui/frontend/src/modules/settings/SettingsPage.tsx +++ b/client/ui/frontend/src/modules/settings/SettingsPage.tsx @@ -52,7 +52,7 @@ export const SettingsPage = () => { className={"flex-1 min-h-0 overflow-hidden"} > -
+
diff --git a/client/ui/frontend/src/modules/settings/SettingsSection.tsx b/client/ui/frontend/src/modules/settings/SettingsSection.tsx index 49809a7cb..eec84df10 100644 --- a/client/ui/frontend/src/modules/settings/SettingsSection.tsx +++ b/client/ui/frontend/src/modules/settings/SettingsSection.tsx @@ -20,7 +20,7 @@ export const SectionGroup = ({ export const SettingsBottomBar = ({ children }: { children: ReactNode }) => ( <> -
+
- - }} /> - - -
-
- - - {t("settings.troubleshooting.duration.help")} - -
-
- - setTraceMinutes(Math.max(1, Math.min(30, Number(e.target.value) || 1))) - } - customSuffix={t("settings.troubleshooting.duration.suffix")} - disabled={!trace} - /> + +
+ +
+
+ + + {t("settings.troubleshooting.duration.help")} + +
+
+ + setTraceMinutes( + Math.max(1, Math.min(30, Number(e.target.value) || 1)), + ) + } + customSuffix={t("settings.troubleshooting.duration.suffix")} + disabled={!capture} + /> +
@@ -297,14 +310,10 @@ const stageLabel = ( t: (key: string, options?: Record) => 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": diff --git a/client/ui/i18n/locales/de/common.json b/client/ui/i18n/locales/de/common.json index 0ce523606..b4ffaaf01 100644 --- a/client/ui/i18n/locales/de/common.json +++ b/client/ui/i18n/locales/de/common.json @@ -698,9 +698,6 @@ "settings.troubleshooting.section.title": { "message": "Debug-Paket" }, - "settings.troubleshooting.intro": { - "message": "Ein Debug-Paket hilft dem NetBird-Support bei der Untersuchung von Verbindungsproblemen.
Es ist eine .zip-Datei mit Logs, Systemdetails und Debug-Informationen Ihres Geräts." - }, "settings.troubleshooting.anonymize.label": { "message": "Sensible Informationen anonymisieren" }, @@ -717,19 +714,31 @@ "message": "Paket an NetBird-Server hochladen" }, "settings.troubleshooting.upload.help": { - "message": "Lädt das Paket sicher hoch und gibt einen Upload-Schlüssel zurück. Teilen Sie den Schlüssel über GitHub oder Slack mit dem NetBird-Support, anstatt die Datei direkt anzuhängen." + "message": "Gibt einen Upload-Schlüssel zurück, den Sie mit dem NetBird-Support teilen können." }, "settings.troubleshooting.trace.label": { - "message": "Trace-Logs erfassen" + "message": "Trace-Logs aktivieren" }, "settings.troubleshooting.trace.help": { - "message": "Erhöht das Logging auf TRACE und schaltet NetBird kurz aus und wieder ein, um Verbindungs-Logs zu erfassen. Das vorherige Level wird nach Erstellung des Pakets wiederhergestellt." + "message": "Hebt das Log-Level auf TRACE an und stellt es danach wieder her." + }, + "settings.troubleshooting.capture.label": { + "message": "Aufzeichnungssitzung" + }, + "settings.troubleshooting.capture.help": { + "message": "Stellt die Verbindung neu her und wartet, damit Sie das Problem reproduzieren können." + }, + "settings.troubleshooting.packets.label": { + "message": "Netzwerkpakete aufzeichnen" + }, + "settings.troubleshooting.packets.help": { + "message": "Speichert eine .pcap-Datei des Netzwerkverkehrs während der Aufzeichnung." }, "settings.troubleshooting.duration.label": { "message": "Aufzeichnungsdauer" }, "settings.troubleshooting.duration.help": { - "message": "Wie lange Trace-Logs vor der Paketerstellung erfasst werden sollen." + "message": "Wie lange die Aufzeichnungssitzung läuft." }, "settings.troubleshooting.duration.suffix": { "message": "Minute(n)" @@ -770,17 +779,11 @@ "settings.troubleshooting.uploadFailed": { "message": "Upload fehlgeschlagen. Das Paket wurde trotzdem lokal gespeichert." }, - "settings.troubleshooting.stage.preparingTrace": { - "message": "Wechsel zu Trace-Logging…" - }, "settings.troubleshooting.stage.reconnecting": { "message": "NetBird wird neu verbunden…" }, "settings.troubleshooting.stage.capturing": { - "message": "Logs werden erfasst" - }, - "settings.troubleshooting.stage.restoring": { - "message": "Vorheriges Log-Level wird wiederhergestellt…" + "message": "Debug-Logs werden erfasst" }, "settings.troubleshooting.stage.bundling": { "message": "Debug-Paket wird erstellt…" diff --git a/client/ui/i18n/locales/en/common.json b/client/ui/i18n/locales/en/common.json index 45abc0a98..4dc324f0d 100644 --- a/client/ui/i18n/locales/en/common.json +++ b/client/ui/i18n/locales/en/common.json @@ -931,10 +931,6 @@ "message": "Debug bundle", "description": "Section heading: Debug bundle." }, - "settings.troubleshooting.intro": { - "message": "A debug bundle helps NetBird support investigate connection problems.
It's a .zip file with logs, system details and debug information from your device.", - "description": "Intro paragraph on the Troubleshoot tab. Contains an HTML '
' line break — keep it exactly. '.zip' is a file extension — keep it." - }, "settings.troubleshooting.anonymize.label": { "message": "Anonymize Sensitive Information", "description": "Toggle label: anonymize sensitive information in the bundle." @@ -956,23 +952,39 @@ "description": "Toggle label: upload the bundle to NetBird servers." }, "settings.troubleshooting.upload.help": { - "message": "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.", - "description": "Helper text for uploading the bundle. 'GitHub' and 'Slack' are brands — keep them." + "message": "Returns an upload key to share with NetBird support.", + "description": "Helper text for uploading the bundle." }, "settings.troubleshooting.trace.label": { - "message": "Capture Trace Logs", - "description": "Toggle label: capture trace logs. 'TRACE' is a log level." + "message": "Enable Trace Logs", + "description": "Toggle label: raise daemon log level to TRACE while the bundle is built. 'TRACE' is a log level." }, "settings.troubleshooting.trace.help": { - "message": "Raises logging to TRACE and cycles NetBird up and down to capture connection logs. The previous level is restored after the bundle is built.", - "description": "Helper text explaining trace capture restarts NetBird briefly. 'TRACE' — keep as-is." + "message": "Raises the log level to TRACE and restores it after.", + "description": "Helper text for the trace toggle. 'TRACE' is a log level — keep as-is." + }, + "settings.troubleshooting.capture.label": { + "message": "Capture Session", + "description": "Toggle label: open a capture session — reconnect NetBird, wait a duration, optionally record packets." + }, + "settings.troubleshooting.capture.help": { + "message": "Reconnects and waits so you can reproduce the issue.", + "description": "Helper text for the master Capture Session toggle." + }, + "settings.troubleshooting.packets.label": { + "message": "Capture Network Packets", + "description": "Toggle label: capture packets to a .pcap during the capture session." + }, + "settings.troubleshooting.packets.help": { + "message": "Saves a .pcap of network traffic during the capture window.", + "description": "Helper text for the packet recording toggle. '.pcap' is a file extension — keep it." }, "settings.troubleshooting.duration.label": { "message": "Capture Duration", "description": "Label for the trace-capture duration field." }, "settings.troubleshooting.duration.help": { - "message": "How long to capture trace logs before generating the bundle.", + "message": "How long the capture session runs.", "description": "Helper text for the capture duration." }, "settings.troubleshooting.duration.suffix": { @@ -1027,22 +1039,14 @@ "message": "Upload failed. The bundle is still saved locally.", "description": "Shown when upload failed (no specific reason) but the bundle was saved locally." }, - "settings.troubleshooting.stage.preparingTrace": { - "message": "Switching to trace logging…", - "description": "Progress stage: switching to trace logging. Ends with an ellipsis." - }, "settings.troubleshooting.stage.reconnecting": { "message": "Reconnecting NetBird…", "description": "Progress stage: reconnecting NetBird. Ends with an ellipsis." }, "settings.troubleshooting.stage.capturing": { - "message": "Capturing logs", + "message": "Capturing debug logs", "description": "Progress stage: capturing logs. {elapsed} and {total} are time values (e.g. 0:30 / 2:00); keep both." }, - "settings.troubleshooting.stage.restoring": { - "message": "Restoring previous log level…", - "description": "Progress stage: restoring the previous log level. Ends with an ellipsis." - }, "settings.troubleshooting.stage.bundling": { "message": "Generating debug bundle…", "description": "Progress stage: generating the debug bundle. Ends with an ellipsis." diff --git a/client/ui/i18n/locales/es/common.json b/client/ui/i18n/locales/es/common.json index 293608341..a8a6227a3 100644 --- a/client/ui/i18n/locales/es/common.json +++ b/client/ui/i18n/locales/es/common.json @@ -698,9 +698,6 @@ "settings.troubleshooting.section.title": { "message": "Paquete de diagnóstico" }, - "settings.troubleshooting.intro": { - "message": "Un paquete de diagnóstico ayuda al soporte de NetBird a investigar problemas de conexión.
Es un archivo .zip con registros, detalles del sistema e información de depuración de su dispositivo." - }, "settings.troubleshooting.anonymize.label": { "message": "Anonimizar información sensible" }, @@ -717,19 +714,31 @@ "message": "Subir el paquete a los servidores de NetBird" }, "settings.troubleshooting.upload.help": { - "message": "Sube el paquete de forma segura y devuelve una clave de subida. Comparta la clave con el soporte de NetBird por GitHub o Slack en lugar de adjuntar el archivo directamente." + "message": "Devuelve una clave de subida para compartir con el soporte de NetBird." }, "settings.troubleshooting.trace.label": { - "message": "Capturar registros de seguimiento" + "message": "Activar registros de seguimiento" }, "settings.troubleshooting.trace.help": { - "message": "Eleva el registro a TRACE y reinicia NetBird para capturar los registros de conexión. El nivel anterior se restaura después de generar el paquete." + "message": "Eleva el nivel de registro a TRACE y lo restaura después." + }, + "settings.troubleshooting.capture.label": { + "message": "Sesión de captura" + }, + "settings.troubleshooting.capture.help": { + "message": "Vuelve a conectar y espera para que pueda reproducir el problema." + }, + "settings.troubleshooting.packets.label": { + "message": "Capturar paquetes de red" + }, + "settings.troubleshooting.packets.help": { + "message": "Guarda un .pcap del tráfico de red durante la sesión de captura." }, "settings.troubleshooting.duration.label": { "message": "Duración de la captura" }, "settings.troubleshooting.duration.help": { - "message": "Cuánto tiempo capturar registros de seguimiento antes de generar el paquete." + "message": "Cuánto tiempo se ejecuta la sesión de captura." }, "settings.troubleshooting.duration.suffix": { "message": "min" @@ -770,17 +779,11 @@ "settings.troubleshooting.uploadFailed": { "message": "Error en la subida. El paquete sigue guardado localmente." }, - "settings.troubleshooting.stage.preparingTrace": { - "message": "Cambiando al registro de seguimiento…" - }, "settings.troubleshooting.stage.reconnecting": { "message": "Reconectando NetBird…" }, "settings.troubleshooting.stage.capturing": { - "message": "Capturando registros" - }, - "settings.troubleshooting.stage.restoring": { - "message": "Restaurando el nivel de registro anterior…" + "message": "Capturando registros de depuración" }, "settings.troubleshooting.stage.bundling": { "message": "Generando el paquete de diagnóstico…" diff --git a/client/ui/i18n/locales/fr/common.json b/client/ui/i18n/locales/fr/common.json index d61b8bd75..31d87060f 100644 --- a/client/ui/i18n/locales/fr/common.json +++ b/client/ui/i18n/locales/fr/common.json @@ -698,9 +698,6 @@ "settings.troubleshooting.section.title": { "message": "Lot de diagnostic" }, - "settings.troubleshooting.intro": { - "message": "Un lot de diagnostic aide l’assistance NetBird à étudier les problèmes de connexion.
C’est un fichier .zip contenant des journaux, des détails système et des informations de diagnostic provenant de votre appareil." - }, "settings.troubleshooting.anonymize.label": { "message": "Anonymiser les informations sensibles" }, @@ -717,19 +714,31 @@ "message": "Envoyer le lot aux serveurs NetBird" }, "settings.troubleshooting.upload.help": { - "message": "Envoie le lot de manière sécurisée et renvoie une clé d’envoi. Partagez la clé avec l’assistance NetBird via GitHub ou Slack plutôt que de joindre directement le fichier." + "message": "Renvoie une clé d’envoi à partager avec l’assistance NetBird." }, "settings.troubleshooting.trace.label": { - "message": "Capturer les journaux trace" + "message": "Activer les journaux trace" }, "settings.troubleshooting.trace.help": { - "message": "Augmente la journalisation au niveau TRACE et redémarre NetBird pour capturer les journaux de connexion. Le niveau précédent est rétabli une fois le lot généré." + "message": "Élève le niveau de journalisation à TRACE, puis le rétablit ensuite." + }, + "settings.troubleshooting.capture.label": { + "message": "Session de capture" + }, + "settings.troubleshooting.capture.help": { + "message": "Se reconnecte et attend pour que vous puissiez reproduire le problème." + }, + "settings.troubleshooting.packets.label": { + "message": "Capturer les paquets réseau" + }, + "settings.troubleshooting.packets.help": { + "message": "Enregistre un .pcap du trafic réseau pendant la session de capture." }, "settings.troubleshooting.duration.label": { "message": "Durée de capture" }, "settings.troubleshooting.duration.help": { - "message": "Durée de capture des journaux trace avant de générer le lot." + "message": "Durée d’exécution de la session de capture." }, "settings.troubleshooting.duration.suffix": { "message": "min" @@ -770,17 +779,11 @@ "settings.troubleshooting.uploadFailed": { "message": "Échec de l’envoi. Le lot reste enregistré localement." }, - "settings.troubleshooting.stage.preparingTrace": { - "message": "Passage à la journalisation trace…" - }, "settings.troubleshooting.stage.reconnecting": { "message": "Reconnexion de NetBird…" }, "settings.troubleshooting.stage.capturing": { - "message": "Capture des journaux" - }, - "settings.troubleshooting.stage.restoring": { - "message": "Rétablissement du niveau de journalisation précédent…" + "message": "Capture des journaux de débogage" }, "settings.troubleshooting.stage.bundling": { "message": "Génération du lot de diagnostic…" diff --git a/client/ui/i18n/locales/hu/common.json b/client/ui/i18n/locales/hu/common.json index 00150a104..c2f7eaf78 100644 --- a/client/ui/i18n/locales/hu/common.json +++ b/client/ui/i18n/locales/hu/common.json @@ -698,9 +698,6 @@ "settings.troubleshooting.section.title": { "message": "Hibakeresési csomag" }, - "settings.troubleshooting.intro": { - "message": "A hibakeresési csomag segít a NetBird támogatásnak a kapcsolati problémák kivizsgálásában.
Egy .zip fájl, amely naplókat, rendszerinformációkat és hibakeresési adatokat tartalmaz az eszközéről." - }, "settings.troubleshooting.anonymize.label": { "message": "Érzékeny információk anonimizálása" }, @@ -717,19 +714,31 @@ "message": "Csomag feltöltése a NetBird szerverekre" }, "settings.troubleshooting.upload.help": { - "message": "Biztonságosan feltölti a csomagot, és visszaad egy feltöltési kulcsot. Ossza meg a kulcsot a NetBird támogatással a GitHubon vagy Slacken keresztül a fájl közvetlen csatolása helyett." + "message": "Egy feltöltési kulcsot ad vissza, amelyet megoszthat a NetBird támogatással." }, "settings.troubleshooting.trace.label": { - "message": "Trace naplók rögzítése" + "message": "Trace naplók engedélyezése" }, "settings.troubleshooting.trace.help": { - "message": "TRACE szintre emeli a naplózást, és újraindítja a NetBird kapcsolatot a kapcsolati naplók rögzítéséhez. Az előző szint a csomag elkészülte után visszaáll." + "message": "TRACE szintre emeli a naplózást, majd utána visszaállítja." + }, + "settings.troubleshooting.capture.label": { + "message": "Rögzítési munkamenet" + }, + "settings.troubleshooting.capture.help": { + "message": "Újra csatlakozik és vár, hogy reprodukálhassa a problémát." + }, + "settings.troubleshooting.packets.label": { + "message": "Hálózati csomagok rögzítése" + }, + "settings.troubleshooting.packets.help": { + "message": "A rögzítés ideje alatt elmenti a hálózati forgalom .pcap fájlját." }, "settings.troubleshooting.duration.label": { "message": "Rögzítés időtartama" }, "settings.troubleshooting.duration.help": { - "message": "Mennyi ideig rögzítse a trace naplókat a csomag elkészítése előtt." + "message": "Mennyi ideig fusson a rögzítési munkamenet." }, "settings.troubleshooting.duration.suffix": { "message": "perc" @@ -770,17 +779,11 @@ "settings.troubleshooting.uploadFailed": { "message": "Feltöltés sikertelen. A csomag továbbra is el van mentve helyileg." }, - "settings.troubleshooting.stage.preparingTrace": { - "message": "Váltás trace naplózásra…" - }, "settings.troubleshooting.stage.reconnecting": { "message": "NetBird újracsatlakoztatása…" }, "settings.troubleshooting.stage.capturing": { - "message": "Naplók rögzítése" - }, - "settings.troubleshooting.stage.restoring": { - "message": "Korábbi napló szint visszaállítása…" + "message": "Hibakeresési naplók rögzítése" }, "settings.troubleshooting.stage.bundling": { "message": "Hibakeresési csomag generálása…" diff --git a/client/ui/i18n/locales/it/common.json b/client/ui/i18n/locales/it/common.json index b6aacbe66..232a25522 100644 --- a/client/ui/i18n/locales/it/common.json +++ b/client/ui/i18n/locales/it/common.json @@ -698,9 +698,6 @@ "settings.troubleshooting.section.title": { "message": "Pacchetto di debug" }, - "settings.troubleshooting.intro": { - "message": "Un pacchetto di debug aiuta il supporto NetBird a indagare sui problemi di connessione.
È un file .zip con log, dettagli di sistema e informazioni di debug del suo dispositivo." - }, "settings.troubleshooting.anonymize.label": { "message": "Anonimizza informazioni sensibili" }, @@ -717,19 +714,31 @@ "message": "Carica il pacchetto sui server NetBird" }, "settings.troubleshooting.upload.help": { - "message": "Carica il pacchetto in modo sicuro e restituisce una chiave di caricamento. Condivida la chiave con il supporto NetBird tramite GitHub o Slack invece di allegare direttamente il file." + "message": "Restituisce una chiave di caricamento da condividere con il supporto NetBird." }, "settings.troubleshooting.trace.label": { - "message": "Acquisisci log di traccia" + "message": "Abilita log di traccia" }, "settings.troubleshooting.trace.help": { - "message": "Aumenta il livello di log a TRACE e riavvia NetBird per acquisire i log di connessione. Il livello precedente viene ripristinato dopo la creazione del pacchetto." + "message": "Aumenta il livello di log a TRACE e lo ripristina al termine." + }, + "settings.troubleshooting.capture.label": { + "message": "Sessione di acquisizione" + }, + "settings.troubleshooting.capture.help": { + "message": "Si riconnette e attende per consentirle di riprodurre il problema." + }, + "settings.troubleshooting.packets.label": { + "message": "Acquisisci pacchetti di rete" + }, + "settings.troubleshooting.packets.help": { + "message": "Salva un .pcap del traffico di rete durante la sessione di acquisizione." }, "settings.troubleshooting.duration.label": { "message": "Durata acquisizione" }, "settings.troubleshooting.duration.help": { - "message": "Per quanto tempo acquisire i log di traccia prima di generare il pacchetto." + "message": "Per quanto tempo viene eseguita la sessione di acquisizione." }, "settings.troubleshooting.duration.suffix": { "message": "min." @@ -770,17 +779,11 @@ "settings.troubleshooting.uploadFailed": { "message": "Caricamento non riuscito. Il pacchetto è comunque salvato localmente." }, - "settings.troubleshooting.stage.preparingTrace": { - "message": "Passaggio al logging di traccia…" - }, "settings.troubleshooting.stage.reconnecting": { "message": "Riconnessione di NetBird…" }, "settings.troubleshooting.stage.capturing": { - "message": "Acquisizione log" - }, - "settings.troubleshooting.stage.restoring": { - "message": "Ripristino del livello di log precedente…" + "message": "Acquisizione log di debug" }, "settings.troubleshooting.stage.bundling": { "message": "Generazione del pacchetto di debug…" diff --git a/client/ui/i18n/locales/pt/common.json b/client/ui/i18n/locales/pt/common.json index 59919663c..96511f8a1 100644 --- a/client/ui/i18n/locales/pt/common.json +++ b/client/ui/i18n/locales/pt/common.json @@ -698,9 +698,6 @@ "settings.troubleshooting.section.title": { "message": "Pacote de depuração" }, - "settings.troubleshooting.intro": { - "message": "Um pacote de depuração ajuda o suporte do NetBird a investigar problemas de conexão.
É um arquivo .zip com logs, detalhes do sistema e informações de depuração do seu dispositivo." - }, "settings.troubleshooting.anonymize.label": { "message": "Anonimizar informações sensíveis" }, @@ -717,19 +714,31 @@ "message": "Enviar pacote aos servidores do NetBird" }, "settings.troubleshooting.upload.help": { - "message": "Envia o pacote com segurança e retorna uma chave de upload. Compartilhe a chave com o suporte do NetBird via GitHub ou Slack em vez de anexar o arquivo diretamente." + "message": "Retorna uma chave de upload para compartilhar com o suporte do NetBird." }, "settings.troubleshooting.trace.label": { - "message": "Capturar logs de trace" + "message": "Habilitar logs de trace" }, "settings.troubleshooting.trace.help": { - "message": "Eleva o nível de log para TRACE e reinicia o NetBird para capturar os logs de conexão. O nível anterior é restaurado após a criação do pacote." + "message": "Eleva o nível de log para TRACE e o restaura em seguida." + }, + "settings.troubleshooting.capture.label": { + "message": "Sessão de captura" + }, + "settings.troubleshooting.capture.help": { + "message": "Reconecta e aguarda para que você possa reproduzir o problema." + }, + "settings.troubleshooting.packets.label": { + "message": "Capturar pacotes de rede" + }, + "settings.troubleshooting.packets.help": { + "message": "Salva um .pcap do tráfego de rede durante a sessão de captura." }, "settings.troubleshooting.duration.label": { "message": "Duração da captura" }, "settings.troubleshooting.duration.help": { - "message": "Por quanto tempo capturar os logs de trace antes de gerar o pacote." + "message": "Por quanto tempo a sessão de captura é executada." }, "settings.troubleshooting.duration.suffix": { "message": "min" @@ -770,17 +779,11 @@ "settings.troubleshooting.uploadFailed": { "message": "Falha no upload. O pacote continua salvo localmente." }, - "settings.troubleshooting.stage.preparingTrace": { - "message": "Alternando para logs de trace…" - }, "settings.troubleshooting.stage.reconnecting": { "message": "Reconectando o NetBird…" }, "settings.troubleshooting.stage.capturing": { - "message": "Capturando logs" - }, - "settings.troubleshooting.stage.restoring": { - "message": "Restaurando o nível de log anterior…" + "message": "Capturando logs de depuração" }, "settings.troubleshooting.stage.bundling": { "message": "Gerando o pacote de depuração…" diff --git a/client/ui/i18n/locales/ru/common.json b/client/ui/i18n/locales/ru/common.json index da5a39d53..78f6797f1 100644 --- a/client/ui/i18n/locales/ru/common.json +++ b/client/ui/i18n/locales/ru/common.json @@ -698,9 +698,6 @@ "settings.troubleshooting.section.title": { "message": "Отладочный пакет" }, - "settings.troubleshooting.intro": { - "message": "Отладочный пакет помогает поддержке NetBird исследовать проблемы с подключением.
Это .zip-файл с журналами, сведениями о системе и отладочной информацией с вашего устройства." - }, "settings.troubleshooting.anonymize.label": { "message": "Анонимизировать конфиденциальную информацию" }, @@ -717,19 +714,31 @@ "message": "Загрузить пакет на серверы NetBird" }, "settings.troubleshooting.upload.help": { - "message": "Безопасно загружает пакет и возвращает ключ загрузки. Поделитесь ключом с поддержкой NetBird через GitHub или Slack вместо того, чтобы прикреплять файл напрямую." + "message": "Возвращает ключ загрузки, который можно передать поддержке NetBird." }, "settings.troubleshooting.trace.label": { - "message": "Собирать журналы TRACE" + "message": "Включить журналы TRACE" }, "settings.troubleshooting.trace.help": { - "message": "Повышает уровень журналирования до TRACE и перезапускает NetBird (отключение и подключение) для сбора журналов подключения. Прежний уровень восстанавливается после создания пакета." + "message": "Повышает уровень журналирования до TRACE и затем восстанавливает прежний." + }, + "settings.troubleshooting.capture.label": { + "message": "Сеанс записи" + }, + "settings.troubleshooting.capture.help": { + "message": "Переподключается и ожидает, чтобы вы могли воспроизвести проблему." + }, + "settings.troubleshooting.packets.label": { + "message": "Записывать сетевые пакеты" + }, + "settings.troubleshooting.packets.help": { + "message": "Сохраняет .pcap сетевого трафика во время сеанса записи." }, "settings.troubleshooting.duration.label": { "message": "Длительность записи" }, "settings.troubleshooting.duration.help": { - "message": "Как долго собирать журналы TRACE перед созданием пакета." + "message": "Как долго длится сеанс записи." }, "settings.troubleshooting.duration.suffix": { "message": "мин." @@ -770,17 +779,11 @@ "settings.troubleshooting.uploadFailed": { "message": "Не удалось загрузить. Пакет всё равно сохранён локально." }, - "settings.troubleshooting.stage.preparingTrace": { - "message": "Переключение на журналирование TRACE…" - }, "settings.troubleshooting.stage.reconnecting": { "message": "Переподключение NetBird…" }, "settings.troubleshooting.stage.capturing": { - "message": "Сбор журналов" - }, - "settings.troubleshooting.stage.restoring": { - "message": "Восстановление прежнего уровня журналирования…" + "message": "Сбор отладочных журналов" }, "settings.troubleshooting.stage.bundling": { "message": "Создание отладочного пакета…" diff --git a/client/ui/i18n/locales/zh-CN/common.json b/client/ui/i18n/locales/zh-CN/common.json index ed66043e6..11ff668f8 100644 --- a/client/ui/i18n/locales/zh-CN/common.json +++ b/client/ui/i18n/locales/zh-CN/common.json @@ -698,9 +698,6 @@ "settings.troubleshooting.section.title": { "message": "调试包" }, - "settings.troubleshooting.intro": { - "message": "调试包可帮助 NetBird 支持团队排查连接问题。
它是一个 .zip 文件,包含来自您设备的日志、系统详情和调试信息。" - }, "settings.troubleshooting.anonymize.label": { "message": "匿名化敏感信息" }, @@ -717,19 +714,31 @@ "message": "将调试包上传到 NetBird 服务器" }, "settings.troubleshooting.upload.help": { - "message": "安全地上传调试包并返回一个上传密钥。请通过 GitHub 或 Slack 将该密钥分享给 NetBird 支持团队,而不要直接附上文件。" + "message": "返回一个上传密钥,供您分享给 NetBird 支持团队。" }, "settings.troubleshooting.trace.label": { - "message": "捕获跟踪日志" + "message": "启用跟踪日志" }, "settings.troubleshooting.trace.help": { - "message": "将日志级别提升到 TRACE,并使 NetBird 上下线一次以捕获连接日志。调试包生成后将恢复先前的级别。" + "message": "将日志级别提升到 TRACE,之后再恢复原级别。" + }, + "settings.troubleshooting.capture.label": { + "message": "捕获会话" + }, + "settings.troubleshooting.capture.help": { + "message": "重新连接并等待,以便您复现问题。" + }, + "settings.troubleshooting.packets.label": { + "message": "捕获网络数据包" + }, + "settings.troubleshooting.packets.help": { + "message": "在捕获期间将网络流量保存为 .pcap 文件。" }, "settings.troubleshooting.duration.label": { "message": "捕获时长" }, "settings.troubleshooting.duration.help": { - "message": "在生成调试包之前捕获跟踪日志的时长。" + "message": "捕获会话运行的时长。" }, "settings.troubleshooting.duration.suffix": { "message": "分钟" @@ -770,17 +779,11 @@ "settings.troubleshooting.uploadFailed": { "message": "上传失败。调试包仍已保存在本地。" }, - "settings.troubleshooting.stage.preparingTrace": { - "message": "正在切换到跟踪日志记录…" - }, "settings.troubleshooting.stage.reconnecting": { "message": "正在重新连接 NetBird…" }, "settings.troubleshooting.stage.capturing": { - "message": "正在捕获日志" - }, - "settings.troubleshooting.stage.restoring": { - "message": "正在恢复先前的日志级别…" + "message": "正在捕获调试日志" }, "settings.troubleshooting.stage.bundling": { "message": "正在生成调试包…" diff --git a/client/ui/services/debug.go b/client/ui/services/debug.go index 64afe38d0..9b84acd57 100644 --- a/client/ui/services/debug.go +++ b/client/ui/services/debug.go @@ -9,6 +9,9 @@ import ( "path/filepath" "runtime" "strings" + "time" + + "google.golang.org/protobuf/types/known/durationpb" "github.com/netbirdio/netbird/client/proto" "github.com/netbirdio/netbird/version" @@ -113,6 +116,28 @@ func (s *Debug) RegisterUILog(ctx context.Context, path string) error { return err } +func (s *Debug) StartBundleCapture(ctx context.Context, timeoutSeconds int32) error { + cli, err := s.conn.Client() + if err != nil { + return err + } + req := &proto.StartBundleCaptureRequest{} + if timeoutSeconds > 0 { + req.Timeout = durationpb.New(time.Duration(timeoutSeconds) * time.Second) + } + _, err = cli.StartBundleCapture(ctx, req) + return err +} + +func (s *Debug) StopBundleCapture(ctx context.Context) error { + cli, err := s.conn.Client() + if err != nil { + return err + } + _, err = cli.StopBundleCapture(ctx, &proto.StopBundleCaptureRequest{}) + return err +} + func (s *Debug) SetLogLevel(ctx context.Context, lvl LogLevel) error { cli, err := s.conn.Client() if err != nil {