Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/interfaces/Job.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,14 @@ export interface Workload {
// Parameters for bundle job
export interface BundleJobParameters {
anonymize: boolean;
// Anonymization level applied when anonymize is enabled. "default" keeps
// internal IP ranges readable; "strict" also anonymizes them. Omitted or
// empty resolves to the default on the peer.
anonymize_level?: "default" | "strict";
bundle_for: boolean;
bundle_for_time: number;
log_file_count: number;
// Upload service URL the peer requests an upload URL from. Empty selects the
// default upload server.
upload_url?: string;
}
78 changes: 76 additions & 2 deletions src/modules/jobs/CreateDebugJobModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
FileText,
PlusCircle,
Shield,
UploadCloud,
} from "lucide-react";
import { useMemo, useState } from "react";
import { useSWRConfig } from "swr";
Expand All @@ -19,6 +20,13 @@ import {
} from "@/components/modal/Modal";
import ModalHeader from "@/components/modal/ModalHeader";
import { notify } from "@/components/Notification";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/Select";
import Separator from "@/components/Separator";
import { Workload } from "@/interfaces/Job";
import { useApiCall } from "@/utils/api";
Expand All @@ -36,10 +44,15 @@ export function CreateDebugJobModalContent({ peerID, onSuccess }: Props) {
const [bundleForTime, setBundleForTime] = useState<string>("");
const [logFileCount, setLogFileCount] = useState<string>("10");
const [anonymize, setAnonymize] = useState<boolean>(false);
const [anonymizeLevel, setAnonymizeLevel] = useState<"default" | "strict">(
"default",
);
const [uploadUrl, setUploadUrl] = useState<string>("");

const isValid = useMemo(() => {
let validBundleFor = true;
let validLogFileCount = true;
let validUploadUrl = true;

const logFileCountNumber = Number(logFileCount);
const bundleForTimeNumber = Number(bundleForTime);
Expand All @@ -50,8 +63,18 @@ export function CreateDebugJobModalContent({ peerID, onSuccess }: Props) {

validLogFileCount = logFileCountNumber >= 1 && logFileCountNumber <= 1000;

return validLogFileCount && validBundleFor;
}, [bundleForTime, logFileCount]);
const trimmedUploadUrl = uploadUrl.trim();
if (trimmedUploadUrl) {
try {
const parsed = new URL(trimmedUploadUrl);
validUploadUrl = parsed.protocol === "https:" && parsed.host !== "";
} catch {
validUploadUrl = false;
}
}

return validLogFileCount && validBundleFor && validUploadUrl;
}, [bundleForTime, logFileCount, uploadUrl]);

const createDebugJob = async () => {
notify({
Expand All @@ -64,11 +87,13 @@ export function CreateDebugJobModalContent({ peerID, onSuccess }: Props) {
type: "bundle",
parameters: {
anonymize,
anonymize_level: anonymize ? anonymizeLevel : undefined,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Omit the default anonymization level.

When anonymize is enabled and the user keeps the Default selection, this sends "default" instead of omitting anonymize_level. This prevents the peer from applying its own default.

Proposed fix
-              anonymize_level: anonymize ? anonymizeLevel : undefined,
+              anonymize_level:
+                anonymize && anonymizeLevel !== "default"
+                  ? anonymizeLevel
+                  : undefined,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
anonymize_level: anonymize ? anonymizeLevel : undefined,
anonymize_level:
anonymize && anonymizeLevel !== "default"
? anonymizeLevel
: undefined,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/modules/jobs/CreateDebugJobModal.tsx` at line 90, Update the payload
construction around anonymizeLevel so anonymize_level is omitted when anonymize
is enabled but the user leaves the Default selection; only send an explicit
anonymization level for non-default choices, while preserving omission when
anonymization is disabled.

bundle_for: bundleForTimeEnabled,
bundle_for_time: bundleForTimeEnabled
? Number(bundleForTime)
: undefined,
log_file_count: logFileCount ? Number(logFileCount) : 10,
upload_url: uploadUrl.trim() ? uploadUrl.trim() : undefined,
},
},
})
Expand Down Expand Up @@ -172,6 +197,55 @@ export function CreateDebugJobModalContent({ peerID, onSuccess }: Props) {
}
helpText="Remove sensitive information (IP addresses, domains etc.) before creating the debug bundle."
/>

{/* Anonymization Level */}
{anonymize && (
<div className="flex justify-between gap-6">
<div className={"max-w-[300px]"}>
<Label>Anonymization Level</Label>
<HelpText>
Default keeps internal (private) IP ranges readable; Strict also
anonymizes private, CGNAT and link-local addresses.
</HelpText>
</div>

<Select
value={anonymizeLevel}
onValueChange={(v) => setAnonymizeLevel(v as "default" | "strict")}
>
<SelectTrigger className="w-[220px]">
<div className="flex items-center gap-3">
<Shield size={15} className="text-nb-gray-300 shrink-0" />
<SelectValue placeholder="Select level..." />
</div>
</SelectTrigger>
<SelectContent>
<SelectItem value="default">Default</SelectItem>
<SelectItem value="strict">Strict</SelectItem>
</SelectContent>
</Select>
</div>
)}

{/* Upload URL */}
<div className="flex justify-between gap-6">
<div className={"max-w-[300px]"}>
<Label>Upload URL (optional)</Label>
<HelpText>
Service the peer requests an upload URL from. Leave empty to use
the default upload server. Must be an https URL.
</HelpText>
</div>

<Input
type="text"
placeholder={"https://upload.debug.netbird.io"}
value={uploadUrl}
onChange={(e) => setUploadUrl(e.target.value)}
maxWidthClass="w-[220px]"
customPrefix={<UploadCloud size={16} className="text-nb-gray-300" />}
/>
</div>
</div>

<ModalFooter className="items-center">
Expand Down
Loading