Skip to content

Add IPv6 overlay settings and peer display#594

Open
lixmal wants to merge 13 commits intomainfrom
dashboard-ipv6-setting
Open

Add IPv6 overlay settings and peer display#594
lixmal wants to merge 13 commits intomainfrom
dashboard-ipv6-setting

Conversation

@lixmal
Copy link
Copy Markdown
Contributor

@lixmal lixmal commented Mar 25, 2026

Describe your changes

Dashboard support for the IPv6 overlay feature. Depends on management PR netbirdio/netbird#5698.

  • IPv6 network range input with /48-/112 CIDR validation
  • Per-group IPv6 enabled selector (replaces simple toggle)
  • Peer IPv6 display: table cell, tooltip, detail page, selectors, DeviceCard
  • Routing peers tab shows IPv6 when available
image image image

Issue ticket number and link

Checklist

  • Is it a bug fix
  • Is a typo/documentation fix
  • Is a feature enhancement
  • It is a refactor

Documentation

Select exactly one:

  • I added/updated documentation for this change
  • Documentation is not needed for this change (explain why)

Docs PR URL (required if "docs added" is checked)

Paste the PR link from https://github.com/netbirdio/docs here:

https://github.com/netbirdio/docs/pull/__

Summary by CodeRabbit

Release Notes

  • New Features

    • Added IPv6 address support for peers, including editing, search, and display capabilities.
    • Added IPv6 network settings configuration for managing IPv6 ranges and group assignments.
    • Enabled IPv6 support for SSH remote access connections.
  • Style

    • Adjusted card list item spacing and typography for improved readability.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 25, 2026

📝 Walkthrough

Walkthrough

The PR adds comprehensive IPv6 support across the dashboard by extending peer and account interfaces with optional IPv6 fields, creating a reusable IP edit modal for both IPv4 and IPv6, enhancing peer search and display components to show IPv6 addresses, extending network settings with IPv6 CIDR configuration and group selection, and plumbing IP version through SSH connection flows.

Changes

Cohort / File(s) Summary
Interface & Type Extensions
src/interfaces/Peer.ts, src/interfaces/Account.ts
Added optional ipv6?: string field to Peer interface; extended Account settings.extra with ipv6_enabled_groups?: string[] and network_range_v6?: string.
Peer IP Editing & Display
src/modules/peer/PeerEditIPModal.tsx, src/app/(dashboard)/peer/page.tsx
Introduced new reusable PeerEditIPModal component supporting both IPv4 and IPv6 versions with validation per version; replaced inline edit modal in peer page with new component and added separate state/handlers for each IP version; added EditableValue component for IP display with conditional edit button.
Peer Address Rendering
src/modules/peers/PeerAddressCell.tsx, src/modules/peers/PeerAddressTooltipContent.tsx
Added IPv6 conditional rendering in tooltip with copy-to-clipboard support; formatting adjustments to address cells.
Peer Search & Selection
src/components/PeerGroupSelector.tsx, src/components/PeerSelector.tsx, src/modules/networks/routing-peers/NetworkRoutingPeersTabContent.tsx
Extended search predicates to match against optional ipv6 field (case-insensitive); updated peers search placeholder; appended IPv6 to computed search strings in routing peers context.
Network Settings with IPv6
src/modules/settings/NetworkSettingsTab.tsx
Extended network settings with IPv6 CIDR range input and IPv6-enabled groups selector; deferred rendering until group data loads; added IPv6 fields to change detection, validation, and update payload; conditionally includes network ranges only when modified.
SSH IPv6 Version Support
src/app/(remote-access)/peer/ssh/page.tsx, src/modules/remote-access/ssh/useSSH.ts, src/modules/remote-access/ssh/useSSHQueryParams.ts, src/modules/remote-access/useNetBirdClient.ts
Added ipVersion query parameter plumbing through SSH page → terminal → useSSH → useNetBirdClient; derived dynamic sshHost based on IP version in SSH connection setup; updated URL and localStorage handling for IP version persistence.
PeerProvider Context Update
src/contexts/PeerProvider.tsx
Extended update function to accept optional ipv6?: string prop; forwards to API payload.
Styling & Table Updates
src/components/Card.tsx, src/modules/peers/PeersTable.tsx, src/utils/version.ts
Refined card list item padding and font sizing; added IPv6 column definition to peers table with visibility toggle; minor formatting cleanup.

Sequence Diagram

sequenceDiagram
    actor User
    participant SSHPage as SSH Page
    participant SSHTerminal as SSH Terminal
    participant useSSH as useSSH Hook
    participant NetBirdClient as NetBird Client
    participant Backend as Backend

    User->>SSHPage: Navigate with ipVersion param
    SSHPage->>SSHTerminal: Pass ipVersion query param
    SSHTerminal->>useSSH: Create SSH config with ipVersion
    useSSH->>NetBirdClient: Call createSSHConnection(host, port, username, token, ipVersion)
    NetBirdClient->>Backend: Establish SSH connection with ipVersion
    Backend-->>NetBirdClient: Connection established
    NetBirdClient-->>useSSH: Return connection handle
    useSSH-->>SSHTerminal: SSH connection ready
    SSHTerminal-->>User: Display terminal session
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • Add reverse proxy #552: Both PRs modify src/app/(dashboard)/peer/page.tsx and restructure the peer page implementation with UI and state management changes.
  • Add fine-grained ssh policy #522: Both PRs extend the SSH remote-access flow in src/app/(remote-access)/peer/ssh/page.tsx and SSH-related hooks, with this PR adding IP version plumbing to support version-aware SSH connectivity.

Suggested reviewers

  • heisbrot
  • mlsmaycon

Poem

🐰 IPv6 hops through fields so bright,
Adding v4 and v6 with all its might,
Peer addresses dance with dual-stack care,
SSH knows which version to bear! 🌐

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main objective of the PR: adding IPv6 overlay settings and peer display functionality to the dashboard.
Description check ✅ Passed The description includes all required sections: change overview with feature list and screenshots, issue/dependency reference, proper checklist completion, and documentation section marked as not needed.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dashboard-ipv6-setting

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/contexts/PeerProvider.tsx (1)

26-33: ⚠️ Potential issue | 🟠 Major

Context type definition is missing ipv6 parameter.

The update function signature in the PeerContext type (lines 26-33) does not include ipv6?: string, but the implementation at line 83 accepts it. This type mismatch will cause TypeScript errors for consumers calling update({ ipv6: ... }).

🔧 Proposed fix
     update: (props: {
       name?: string;
       ssh?: boolean;
       loginExpiration?: boolean;
       inactivityExpiration?: boolean;
       approval_required?: boolean;
       ip?: string;
+      ipv6?: string;
     }) => Promise<Peer>;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/contexts/PeerProvider.tsx` around lines 26 - 33, The PeerContext type's
update signature is missing the optional ipv6 field, causing a mismatch with the
implementation that accepts ipv6; update the type definition for update (the
props parameter) to include ipv6?: string so callers can pass { ipv6: ... }
without TypeScript errors and the implementation and type remain consistent
(refer to the update function signature in PeerProvider.tsx / PeerContext).
🧹 Nitpick comments (1)
src/modules/settings/NetworkSettingsTab.tsx (1)

49-51: Prefer ID-based dirty-checking for IPv6 group selection.

Tracking by group names can diverge from persisted data (IDs). Use sorted ID lists for useHasChanges/updateRef so change detection matches the payload field.

♻️ Suggested refactor
-  const ipv6GroupNames = useMemo(
-    () => ipv6EnabledGroups.map((g) => g.name).sort(),
+  const ipv6GroupIds = useMemo(
+    () =>
+      ipv6EnabledGroups
+        .map((g) => g.id)
+        .filter((id): id is string => typeof id === "string" && id.length > 0)
+        .sort(),
     [ipv6EnabledGroups],
   );

   const { hasChanges, updateRef } = useHasChanges([
     customDNSDomain,
     networkRange,
     networkRangeV6,
-    ipv6GroupNames,
+    ipv6GroupIds,
   ]);
...
-          updateRef([customDNSDomain, networkRange, networkRangeV6, ipv6GroupNames]);
+          updateRef([customDNSDomain, networkRange, networkRangeV6, ipv6GroupIds]);

Also applies to: 76-81, 117-117

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/modules/settings/NetworkSettingsTab.tsx` around lines 49 - 51, The code
currently derives ipv6GroupNames via useMemo from ipv6EnabledGroups and uses
those names for dirty-checking/updateRef; change this to compute a sorted list
of group IDs instead (e.g., map to g.id then sort) and use that ID list wherever
useHasChanges or updateRef rely on ipv6GroupNames so change detection matches
persisted payload IDs; apply the same refactor for the other occurrences that
derive IPv6 selections (the blocks around the other useMemo calls and any
updateRef/useHasChanges usage).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/app/`(dashboard)/peer/page.tsx:
- Around line 911-915: Normalize the IPv6 input once before checks: inside the
isDisabled useMemo compute a normalizedIpv6 = trim(ipv6) (and normalizedPeerIpv6
= trim(peer.ipv6) if peer value may contain whitespace) and then use
normalizedIpv6 for the length check and pass normalizedIpv6 to isValidIPv6;
compare normalizedIpv6 to normalizedPeerIpv6 for equality so Save is disabled
for no-op whitespace changes. Apply the same pattern to the other related blocks
handling ipv6 (the validation/save logic around the same peer IPv6 fields) so
all checks consistently use the trimmed value.
- Around line 499-506: The PeerContext update type is missing the ipv6 property
used by the UI; update the PeerContext declaration for the update(props)
parameter (the type defined around PeerContext and its update method) to include
ipv6?: string so calls like update({ ipv6: newIPv6 }) compile; ensure the same
optional ipv6?: string is added to the props parameter type referenced by the
update function (the implementation used around the update(...) method).

In `@src/components/DeviceCard.tsx`:
- Around line 28-37: The useMemo for descriptionText references
resource?.address but resource is missing from the dependency array, which can
cause stale values; update the dependency array for the useMemo that computes
descriptionText to include resource (so the hook depends on description,
address, device, and resource) to ensure the memo recomputes when resource
changes.

In `@src/modules/settings/NetworkSettingsTab.tsx`:
- Around line 84-87: The current construction of updatedSettings sets
ipv6_enabled_groups to ipv6EnabledGroups.map(g => g.id), which can include
undefined since Group.id is optional; filter out missing ids before assigning to
ipv6_enabled_groups so the resulting array is string[] only. Update the code
near updatedSettings / ipv6EnabledGroups to produce an array excluding
falsy/undefined ids (e.g., map then filter or flatMap to only include g.id when
present) so account.settings.ipv6_enabled_groups never contains null/undefined
entries.

---

Outside diff comments:
In `@src/contexts/PeerProvider.tsx`:
- Around line 26-33: The PeerContext type's update signature is missing the
optional ipv6 field, causing a mismatch with the implementation that accepts
ipv6; update the type definition for update (the props parameter) to include
ipv6?: string so callers can pass { ipv6: ... } without TypeScript errors and
the implementation and type remain consistent (refer to the update function
signature in PeerProvider.tsx / PeerContext).

---

Nitpick comments:
In `@src/modules/settings/NetworkSettingsTab.tsx`:
- Around line 49-51: The code currently derives ipv6GroupNames via useMemo from
ipv6EnabledGroups and uses those names for dirty-checking/updateRef; change this
to compute a sorted list of group IDs instead (e.g., map to g.id then sort) and
use that ID list wherever useHasChanges or updateRef rely on ipv6GroupNames so
change detection matches persisted payload IDs; apply the same refactor for the
other occurrences that derive IPv6 selections (the blocks around the other
useMemo calls and any updateRef/useHasChanges usage).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4f9b7d0e-dd66-4c45-85e4-56b16f9277ff

📥 Commits

Reviewing files that changed from the base of the PR and between c784676 and 1ea7efa.

📒 Files selected for processing (12)
  • src/app/(dashboard)/peer/page.tsx
  • src/components/DeviceCard.tsx
  • src/components/PeerGroupSelector.tsx
  • src/components/PeerSelector.tsx
  • src/contexts/PeerProvider.tsx
  • src/interfaces/Account.ts
  • src/interfaces/Peer.ts
  • src/modules/networks/routing-peers/NetworkRoutingPeersTabContent.tsx
  • src/modules/peers/PeerAddressCell.tsx
  • src/modules/peers/PeerAddressTooltipContent.tsx
  • src/modules/settings/NetworkSettingsTab.tsx
  • src/utils/version.ts

Comment thread src/app/(dashboard)/peer/page.tsx Outdated
Comment thread src/app/(dashboard)/peer/page.tsx Outdated
Comment thread src/components/DeviceCard.tsx
Comment thread src/modules/settings/NetworkSettingsTab.tsx
- IPv6 network range input with /48-/112 CIDR validation
- Per-group IPv6 enabled selector (replaces simple toggle)
- Peer IPv6 display: table cell, tooltip, detail page, selectors, search
- Gray out IPv6 for peers whose client does not support it yet
- DeviceCard and routing peers show IPv6 when available
@lixmal lixmal force-pushed the dashboard-ipv6-setting branch from 1ea7efa to 9be1fa7 Compare March 25, 2026 16:45
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (2)
src/app/(dashboard)/peer/page.tsx (2)

499-515: ⚠️ Potential issue | 🔴 Critical

Add ipv6 to the PeerContext update type definition to fix the build.

The pipeline failure indicates that the update function's TypeScript type in PeerContext (around lines 26-33 in src/contexts/PeerProvider.tsx) does not include ipv6?: string, even though the implementation at lines 76-84 does accept it. Add ipv6?: string; to the context type definition to resolve the TypeScript error.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(dashboard)/peer/page.tsx around lines 499 - 515, The PeerContext
type for the update function is missing the ipv6 property; update the
PeerContext (the context type/interface that declares update) so the update
parameter type includes ipv6?: string to match the implementation in
PeerProvider's update function, ensuring callers like the EditIPv6Modal usage
compile without errors.

911-929: ⚠️ Potential issue | 🟡 Minor

Normalize IPv6 input once to avoid validation inconsistencies.

The code compares untrimmed ipv6 with peer.ipv6 for equality checks (lines 912, 919) but uses trimmed values for validation (lines 914, 922). This can allow the Save button to be enabled for whitespace-only changes that are effectively no-ops.

🔧 Proposed fix
 function EditIPv6Modal({ onSuccess, peer }: Readonly<EditIPv6ModalProps>) {
   const [ipv6, setIPv6] = useState(peer.ipv6 || "");
   const [error, setError] = useState("");
+  const trimmedIPv6 = useMemo(() => trim(ipv6), [ipv6]);
+  const peerIPv6 = peer.ipv6 || "";

   const isDisabled = useMemo(() => {
-    if (ipv6 === peer.ipv6) return true;
-    const trimmed = trim(ipv6);
-    return trimmed.length === 0 || !isValidIPv6(trimmed);
-  }, [ipv6, peer.ipv6]);
+    if (trimmedIPv6 === peerIPv6) return true;
+    return trimmedIPv6.length === 0 || !isValidIPv6(trimmedIPv6);
+  }, [trimmedIPv6, peerIPv6]);

   React.useEffect(() => {
     switch (true) {
-      case ipv6 === peer.ipv6:
+      case trimmedIPv6 === peerIPv6:
         setError("");
         break;
-      case !isValidIPv6(trim(ipv6)):
+      case !isValidIPv6(trimmedIPv6):
         setError("Please enter a valid IPv6 address, e.g., fd00:1234::1");
         break;
       default:
         setError("");
         break;
     }
-  }, [ipv6, peer.ipv6]);
+  }, [trimmedIPv6, peerIPv6]);

And update the onClick handler:

-              onClick={() => onSuccess(ipv6.trim())}
+              onClick={() => onSuccess(trimmedIPv6)}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(dashboard)/peer/page.tsx around lines 911 - 929, Normalize the IPv6
input once (e.g., const normalized = trim(ipv6)) and use that single normalized
value everywhere: replace direct uses of ipv6 in the isDisabled useMemo and the
React.useEffect switch with normalized, use isValidIPv6(normalized) for
validation, compare normalized === peer.ipv6 for equality, update dependencies
to include normalized instead of raw ipv6, and update the Save onClick handler
to compare/send normalized to avoid whitespace-only changes enabling the button
or being submitted.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/app/`(dashboard)/peer/page.tsx:
- Around line 499-515: The PeerContext type for the update function is missing
the ipv6 property; update the PeerContext (the context type/interface that
declares update) so the update parameter type includes ipv6?: string to match
the implementation in PeerProvider's update function, ensuring callers like the
EditIPv6Modal usage compile without errors.
- Around line 911-929: Normalize the IPv6 input once (e.g., const normalized =
trim(ipv6)) and use that single normalized value everywhere: replace direct uses
of ipv6 in the isDisabled useMemo and the React.useEffect switch with
normalized, use isValidIPv6(normalized) for validation, compare normalized ===
peer.ipv6 for equality, update dependencies to include normalized instead of raw
ipv6, and update the Save onClick handler to compare/send normalized to avoid
whitespace-only changes enabling the button or being submitted.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 085d848f-07cd-4c56-9ba4-271a59da1730

📥 Commits

Reviewing files that changed from the base of the PR and between 1ea7efa and 9be1fa7.

📒 Files selected for processing (12)
  • src/app/(dashboard)/peer/page.tsx
  • src/components/DeviceCard.tsx
  • src/components/PeerGroupSelector.tsx
  • src/components/PeerSelector.tsx
  • src/contexts/PeerProvider.tsx
  • src/interfaces/Account.ts
  • src/interfaces/Peer.ts
  • src/modules/networks/routing-peers/NetworkRoutingPeersTabContent.tsx
  • src/modules/peers/PeerAddressCell.tsx
  • src/modules/peers/PeerAddressTooltipContent.tsx
  • src/modules/settings/NetworkSettingsTab.tsx
  • src/utils/version.ts
✅ Files skipped from review due to trivial changes (4)
  • src/utils/version.ts
  • src/modules/peers/PeerAddressCell.tsx
  • src/interfaces/Peer.ts
  • src/interfaces/Account.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • src/modules/networks/routing-peers/NetworkRoutingPeersTabContent.tsx
  • src/components/PeerGroupSelector.tsx
  • src/modules/peers/PeerAddressTooltipContent.tsx
  • src/components/PeerSelector.tsx
  • src/contexts/PeerProvider.tsx

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/modules/settings/NetworkSettingsTab.tsx`:
- Line 121: The call to updateRef currently references a nonexistent variable
ipv6GroupNames causing build failure; replace ipv6GroupNames with the correct
ipv6GroupIds so the array matches the one passed into useHasChanges (see
useHasChanges and updateRef usages) — update the invocation
updateRef([customDNSDomain, networkRange, networkRangeV6, ipv6GroupNames]) to
use ipv6GroupIds instead and ensure any surrounding logic still references
ipv6GroupIds consistently.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7987ec0d-216b-4ffa-bfed-d810c3f5a56a

📥 Commits

Reviewing files that changed from the base of the PR and between 9be1fa7 and 492bbbd.

📒 Files selected for processing (2)
  • src/contexts/PeerProvider.tsx
  • src/modules/settings/NetworkSettingsTab.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/contexts/PeerProvider.tsx

Comment thread src/modules/settings/NetworkSettingsTab.tsx Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/modules/settings/NetworkSettingsTab.tsx (1)

156-165: Consider adding validation when clearing an existing IPv6 range.

Unlike the IPv4 validation (lines 139-143), this doesn't warn when a previously-configured IPv6 range is cleared. Clearing network_range_v6 would disable IPv6 for all peers, which may warrant user confirmation or at least a warning message.

If intentional (IPv6 is optional), this is fine as-is; otherwise, consider adding:

💡 Suggested enhancement
 const networkRangeV6Error = useMemo(() => {
-  if (networkRangeV6 == "") return "";
+  if (networkRangeV6 == "") {
+    if (account.settings.network_range_v6) {
+      return "Clearing this will disable IPv6 for all peers";
+    }
+    return "";
+  }
   if (!networkRangeV6.includes(":") || !cidr.isValidCIDR(networkRangeV6)) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/modules/settings/NetworkSettingsTab.tsx` around lines 156 - 165, The IPv6
validation in the useMemo that computes networkRangeV6Error currently returns
empty string when networkRangeV6 == "" and therefore doesn't warn on clearing an
existing value; update the useMemo (networkRangeV6Error) to detect when
networkRangeV6 is empty and return a warning message (e.g., "Clearing IPv6 will
disable IPv6 for all peers — confirm or re-enter a valid IPv6 CIDR") or trigger
the same confirmation flow used for IPv4, while keeping the existing CIDR and
prefix-length checks for networkRangeV6.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/modules/settings/NetworkSettingsTab.tsx`:
- Around line 156-165: The IPv6 validation in the useMemo that computes
networkRangeV6Error currently returns empty string when networkRangeV6 == "" and
therefore doesn't warn on clearing an existing value; update the useMemo
(networkRangeV6Error) to detect when networkRangeV6 is empty and return a
warning message (e.g., "Clearing IPv6 will disable IPv6 for all peers — confirm
or re-enter a valid IPv6 CIDR") or trigger the same confirmation flow used for
IPv4, while keeping the existing CIDR and prefix-length checks for
networkRangeV6.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: eebf455d-2af0-4242-bad5-2bb365f55b2e

📥 Commits

Reviewing files that changed from the base of the PR and between 492bbbd and d156e53.

📒 Files selected for processing (1)
  • src/modules/settings/NetworkSettingsTab.tsx

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/app/(remote-access)/peer/ssh/page.tsx (1)

86-91: Use sshHost consistently in status text.

After introducing sshHost, the loading/disconnected text still uses peer.ip, which can mislead users in IPv6/FQDN mode.

🧩 Suggested refactor
   if (isSSHDisconnected && sshConnectedOnce.current) {
     return (
       <DisconnectedMessage
         username={username}
-        peerIp={peer.ip}
+        peerIp={sshHost}
         onReconnect={handleReconnect}
       />
     );
   }
@@
       {session && <Terminal session={session} onClose={disconnect} />}
       {!isSSHConnected && (
-        <LoadingMessage message={`Connecting to ${username}@${peer.ip}...`} />
+        <LoadingMessage message={`Connecting to ${username}@${sshHost}...`} />
       )}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(remote-access)/peer/ssh/page.tsx around lines 86 - 91, The
status/feedback strings still reference peer.ip instead of the new sshHost,
causing incorrect addresses in IPv6/FQDN mode; find where the UI renders
loading/disconnected status text (the same component using sshHost and the
useEffect that sets document.title) and replace any occurrences of peer.ip in
those status messages with sshHost so the displayed host matches the dialed
address family (keep sshHost in component state/deps like username, peer,
client, sshHost).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/modules/remote-access/ssh/useSSHQueryParams.ts`:
- Around line 23-31: The code reads ip_version from searchParams
(searchParams.get("ip_version")) and stores it into state via setParams without
validation, which can lead to invalid host-selection; update the logic in the
useEffect blocks that hydrate state/router (the block using searchParams.get and
the later block at lines 49-70) to validate ipVersion before using it: accept
only allowed values (e.g., "4" or "6" or your app's canonical tokens), treat any
other value as undefined/null (drop it) and do not include it when calling
setParams or updating the router state; ensure the validation is applied both
where URL params are read and where localStorage values are restored so invalid
ip_version values are ignored early.

---

Nitpick comments:
In `@src/app/`(remote-access)/peer/ssh/page.tsx:
- Around line 86-91: The status/feedback strings still reference peer.ip instead
of the new sshHost, causing incorrect addresses in IPv6/FQDN mode; find where
the UI renders loading/disconnected status text (the same component using
sshHost and the useEffect that sets document.title) and replace any occurrences
of peer.ip in those status messages with sshHost so the displayed host matches
the dialed address family (keep sshHost in component state/deps like username,
peer, client, sshHost).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c5050fe4-45ae-4d8e-b457-5968507a2c47

📥 Commits

Reviewing files that changed from the base of the PR and between d156e53 and c8d22d5.

📒 Files selected for processing (4)
  • src/app/(remote-access)/peer/ssh/page.tsx
  • src/modules/remote-access/ssh/useSSH.ts
  • src/modules/remote-access/ssh/useSSHQueryParams.ts
  • src/modules/remote-access/useNetBirdClient.ts
✅ Files skipped from review due to trivial changes (1)
  • src/modules/remote-access/ssh/useSSH.ts

Comment thread src/modules/remote-access/ssh/useSSHQueryParams.ts
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/modules/peer/PeerEditIPModal.tsx (1)

65-69: ⚠️ Potential issue | 🟡 Minor

Normalize both values before equality checks to avoid no-op saves.

Line 66 and Line 72 compare raw input (ip) to currentIP, but save uses trim(ip) on Line 107. This enables Save for whitespace-only edits and can send unchanged values.

💡 Proposed fix
 export function PeerEditIPModal({
@@
 }: Readonly<PeerEditIPModalProps>) {
   const { title, description, placeholder, errorMessage, validate } =
     config[version];
   const [ip, setIP] = useState(currentIP);
+  const normalizedIP = trim(ip);
+  const normalizedCurrentIP = trim(currentIP);
 
   const isDisabled = useMemo(() => {
-    if (ip === currentIP) return true;
-    const trimmed = trim(ip);
-    return trimmed.length === 0 || !validate(trimmed);
-  }, [ip, currentIP, validate]);
+    if (normalizedIP === normalizedCurrentIP) return true;
+    return normalizedIP.length === 0 || !validate(normalizedIP);
+  }, [normalizedIP, normalizedCurrentIP, validate]);
 
   const error = useMemo(() => {
-    if (ip === currentIP) return "";
-    if (!validate(trim(ip))) return errorMessage;
+    if (normalizedIP === normalizedCurrentIP) return "";
+    if (!validate(normalizedIP)) return errorMessage;
     return "";
-  }, [ip, currentIP, validate, errorMessage]);
+  }, [normalizedIP, normalizedCurrentIP, validate, errorMessage]);
@@
-                onClick={() => onSave(trim(ip))}
+                onClick={() => onSave(normalizedIP)}

Also applies to: 71-75

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/modules/peer/PeerEditIPModal.tsx` around lines 65 - 69, The equality
check in the useMemo for isDisabled compares raw ip to currentIP, but the save
path trims the input; normalize both sides before comparing to avoid no-op
saves: in the isDisabled computation (function/useMemo named isDisabled) compute
a trimmed value once (e.g., const trimmed = trim(ip)) and compare trimmed to
trim(currentIP) (and use trimmed for validate), ensuring the same normalization
is used elsewhere (the save handler that uses trim(ip)); update the dependency
array if you introduce new local variables so the memo stays correct.
🧹 Nitpick comments (1)
src/modules/peer/PeerEditIPModal.tsx (1)

80-81: Set explicit button types in this form modal.

Inside a <form>, unspecified button type can behave as submit and cause unintended submit-on-Enter behavior depending on @components/Button internals. Please set explicit types for Cancel/Save here.

Also applies to: 98-111

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/modules/peer/PeerEditIPModal.tsx` around lines 80 - 81, The form in
PeerEditIPModal.tsx leaves button types unspecified which can make them act as
implicit submit buttons; update the Cancel and Save buttons inside the form (the
Cancel/Save <Button> instances rendered after ModalHeader and in the block
covering lines ~98-111) to include explicit type attributes (type="button" for
Cancel, type="submit" for Save) so Enter/keyboard interactions behave correctly
and only the intended action triggers form submission.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/app/`(dashboard)/peer/page.tsx:
- Around line 535-560: The edit button rendered by the EditableValue component
is icon-only and missing an accessible name; update the EditableValue usage for
the IPv4 and IPv6 entries (where value={peer.ip} with onEdit={() =>
setShowEditIPModal(true)} and value={peer.ipv6} with onEdit={() =>
setShowEditIPv6Modal(true)}) to provide a context-specific aria-label (e.g.,
"Edit IPv4 address" and "Edit IPv6 address") or add a prop (like editAriaLabel
or ariaLabel) to EditableValue and pass the labels through so the icon-only
button is announced correctly by assistive technologies.

---

Duplicate comments:
In `@src/modules/peer/PeerEditIPModal.tsx`:
- Around line 65-69: The equality check in the useMemo for isDisabled compares
raw ip to currentIP, but the save path trims the input; normalize both sides
before comparing to avoid no-op saves: in the isDisabled computation
(function/useMemo named isDisabled) compute a trimmed value once (e.g., const
trimmed = trim(ip)) and compare trimmed to trim(currentIP) (and use trimmed for
validate), ensuring the same normalization is used elsewhere (the save handler
that uses trim(ip)); update the dependency array if you introduce new local
variables so the memo stays correct.

---

Nitpick comments:
In `@src/modules/peer/PeerEditIPModal.tsx`:
- Around line 80-81: The form in PeerEditIPModal.tsx leaves button types
unspecified which can make them act as implicit submit buttons; update the
Cancel and Save buttons inside the form (the Cancel/Save <Button> instances
rendered after ModalHeader and in the block covering lines ~98-111) to include
explicit type attributes (type="button" for Cancel, type="submit" for Save) so
Enter/keyboard interactions behave correctly and only the intended action
triggers form submission.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: eb79f9a2-dcb1-434f-b39e-d4d74d99dca6

📥 Commits

Reviewing files that changed from the base of the PR and between d156e53 and 9f10aab.

📒 Files selected for processing (13)
  • src/app/(dashboard)/peer/page.tsx
  • src/app/(remote-access)/peer/ssh/page.tsx
  • src/components/Card.tsx
  • src/components/PeerGroupSelector.tsx
  • src/components/PeerSelector.tsx
  • src/modules/peer/PeerEditIPModal.tsx
  • src/modules/peers/PeerAddressCell.tsx
  • src/modules/peers/PeerAddressTooltipContent.tsx
  • src/modules/peers/PeersTable.tsx
  • src/modules/remote-access/ssh/useSSH.ts
  • src/modules/remote-access/ssh/useSSHQueryParams.ts
  • src/modules/remote-access/useNetBirdClient.ts
  • src/modules/settings/NetworkSettingsTab.tsx
✅ Files skipped from review due to trivial changes (3)
  • src/modules/peers/PeerAddressCell.tsx
  • src/modules/peers/PeersTable.tsx
  • src/components/Card.tsx
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/modules/remote-access/ssh/useSSH.ts
  • src/modules/peers/PeerAddressTooltipContent.tsx
  • src/components/PeerGroupSelector.tsx
  • src/modules/remote-access/ssh/useSSHQueryParams.ts
  • src/modules/settings/NetworkSettingsTab.tsx
  • src/app/(remote-access)/peer/ssh/page.tsx

Comment on lines +535 to +560
<EditableValue
value={peer.ip}
canEdit={permission.peers.update}
onEdit={() => setShowEditIPModal(true)}
/>
}
/>

{peer.ipv6 && (
<Card.ListItem
copy
tooltip={false}
copyText={"NetBird IPv6 Address"}
label={
<>
<MapPin size={16} className={"shrink-0"} />
NetBird IPv6 Address
</>
}
valueToCopy={peer.ipv6}
value={
<EditableValue
value={peer.ipv6}
canEdit={permission.peers.update}
onEdit={() => setShowEditIPv6Modal(true)}
/>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "*.tsx" -o -name "*.ts" | head -20

Repository: netbirdio/dashboard

Length of output: 897


🏁 Script executed:

rg -l "EditableValue" --type ts --type tsx | head -10

Repository: netbirdio/dashboard

Length of output: 90


🏁 Script executed:

rg -n "function EditableValue|const EditableValue" --type ts --type tsx

Repository: netbirdio/dashboard

Length of output: 90


🏁 Script executed:

fd -t f "page.tsx" | grep peer

Repository: netbirdio/dashboard

Length of output: 215


🏁 Script executed:

rg "function EditableValue|const EditableValue|export.*EditableValue" -A 3

Repository: netbirdio/dashboard

Length of output: 253


🏁 Script executed:

rg "EditableValue" --no-heading | head -30

Repository: netbirdio/dashboard

Length of output: 249


🏁 Script executed:

rg "EditableValue" -l

Repository: netbirdio/dashboard

Length of output: 96


🏁 Script executed:

cat src/app/\(dashboard\)/peer/page.tsx | head -100

Repository: netbirdio/dashboard

Length of output: 3849


🏁 Script executed:

find . -name "*.tsx" -o -name "*.ts" | xargs grep -l "EditableValue" | head -10

Repository: netbirdio/dashboard

Length of output: 98


🏁 Script executed:

wc -l src/app/\(dashboard\)/peer/page.tsx

Repository: netbirdio/dashboard

Length of output: 100


🏁 Script executed:

sed -n '800,850p' src/app/\(dashboard\)/peer/page.tsx

Repository: netbirdio/dashboard

Length of output: 731


🏁 Script executed:

sed -n '525,565p' src/app/\(dashboard\)/peer/page.tsx

Repository: netbirdio/dashboard

Length of output: 1220


🏁 Script executed:

rg "EditableValue" src/app/\(dashboard\)/peer/page.tsx -B 2 -A 5

Repository: netbirdio/dashboard

Length of output: 674


Add an accessible name for the icon-only edit button.

The edit button in EditableValue is icon-only (displaying only a pencil icon) and lacks an aria-label, making it inaccessible to assistive technology users. Distinguishing between IPv4 and IPv6 edit actions requires context-specific labels.

♿ Proposed fix
 function EditableValue({
   value,
   canEdit,
   onEdit,
+  editAriaLabel,
 }: {
   value: string;
   canEdit: boolean;
   onEdit: () => void;
+  editAriaLabel: string;
 }) {
@@
       {canEdit && (
         <button
+          type="button"
+          aria-label={editAriaLabel}
           className="flex w-7 h-7 items-center justify-center gap-2 text-nb-gray-400 hover:text-neutral-100 transition-all hover:bg-nb-gray-800/60 rounded-md cursor-pointer"
           onClick={(e) => {
             e.stopPropagation();
             onEdit();
           }}
         >
@@
               <EditableValue
                 value={peer.ip}
                 canEdit={permission.peers.update}
                 onEdit={() => setShowEditIPModal(true)}
+                editAriaLabel={"Edit NetBird IP Address"}
               />
@@
                 <EditableValue
                   value={peer.ipv6}
                   canEdit={permission.peers.update}
                   onEdit={() => setShowEditIPv6Modal(true)}
+                  editAriaLabel={"Edit NetBird IPv6 Address"}
                 />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(dashboard)/peer/page.tsx around lines 535 - 560, The edit button
rendered by the EditableValue component is icon-only and missing an accessible
name; update the EditableValue usage for the IPv4 and IPv6 entries (where
value={peer.ip} with onEdit={() => setShowEditIPModal(true)} and
value={peer.ipv6} with onEdit={() => setShowEditIPv6Modal(true)}) to provide a
context-specific aria-label (e.g., "Edit IPv4 address" and "Edit IPv6 address")
or add a prop (like editAriaLabel or ariaLabel) to EditableValue and pass the
labels through so the icon-only button is announced correctly by assistive
technologies.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants