Restore combined Peers page with server and device filters#668
Conversation
📝 WalkthroughWalkthroughIntroduces a new ChangesPeer Kind Feature End-to-End
Sequence Diagram(s)sequenceDiagram
participant User
participant PeersPage
participant PeersProvider
participant PeersTable
participant peerKind
User->>PeersPage: navigate to /peers
PeersPage->>PeersPage: check isRestricted via usePermissions()
alt is restricted
PeersPage->>User: render PeersBlockedView + SetupModalContent
else normal
PeersPage->>PeersProvider: wrap PeersView
PeersProvider->>PeersTable: provide peers + users context
PeersTable->>peerKind: matchesPeerTableKind(peer, enabledKind)
peerKind-->>PeersTable: boolean match result
PeersTable->>PeersTable: filter kindFilteredPeers, compute headingCountLabel
User->>PeersTable: toggle kind ButtonGroup
PeersTable->>PeersTable: setEnabledKinds, reset selection, jump to page 0
PeersTable->>User: render filtered rows + count label
end
sequenceDiagram
participant User
participant PeerDetailPage
participant PeerProvider
participant SWR
User->>PeerDetailPage: select peer kind via PeerKindSelect
PeerDetailPage->>PeerDetailPage: derive hasPeerKind, selectedPeerKind, inferredPeerKind
User->>PeerDetailPage: save peer kind
PeerDetailPage->>PeerProvider: update({ kind })
PeerProvider->>PeerProvider: build payload, conditionally add kind
PeerProvider-->>PeerDetailPage: updated Peer
PeerDetailPage->>SWR: mutate /peers/{id} and /peers
SWR-->>PeerDetailPage: revalidated data
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint install failed due to a network error. 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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
src/app/(dashboard)/peers/page.tsx (1)
43-49: ⚡ Quick winReduce join complexity in peers-to-users mapping.
At Line 45-Line 48, each peer does a linear
users.find(...), making thisO(peers * users). Build a user-id map once and doO(1)lookups to keep table rendering stable on larger accounts.Suggested refactor
const peersWithUser = useMemo(() => { if (!peers || !users) return undefined; + const usersById = new Map(users.map((u) => [u.id, u])); return peers.map((peer) => ({ ...peer, - user: users.find((u) => u.id === peer.user_id), + user: usersById.get(peer.user_id), })); }, [peers, users]);🤖 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/app/`(dashboard)/peers/page.tsx around lines 43 - 49, The peersWithUser useMemo is inefficient because it performs a linear search using users.find() for each peer, resulting in O(peers * users) complexity. Fix this by first creating a Map keyed by user ID from the users array before the peers.map() operation, then replace the users.find() call with a direct O(1) Map lookup to maintain stable table rendering performance on larger datasets.
🤖 Prompt for all review comments with 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.
Inline comments:
In `@src/app/`(dashboard)/peer/page.tsx:
- Around line 641-665: The Card.ListItem for "Peer Type" is currently hidden
entirely when hasPeerKind is false, but it should instead remain visible to show
the inferred peer type in read-only mode for older backends. Remove the
conditional wrapper {hasPeerKind && (...)} around the Card.ListItem so the row
is always rendered. The conditional logic inside the value prop (which shows
PeerKindSelect when permission.peers.update is true and PeerKindValue otherwise)
will properly handle showing the read-only inferred type for older backends that
don't support peer kind updates.
In `@src/app/`(dashboard)/peers/page.tsx:
- Around line 100-103: The InlineLink component instances that use
target="_blank" are missing the rel="noopener noreferrer" attribute, which
exposes the application to reverse-tabnabbing attacks. Add the rel="noopener
noreferrer" attribute to each InlineLink component where target="_blank" is
specified (there are multiple instances in the peers/page.tsx file). This
prevents the opened page from accessing the window.opener property and
potentially redirecting the original page.
---
Nitpick comments:
In `@src/app/`(dashboard)/peers/page.tsx:
- Around line 43-49: The peersWithUser useMemo is inefficient because it
performs a linear search using users.find() for each peer, resulting in O(peers
* users) complexity. Fix this by first creating a Map keyed by user ID from the
users array before the peers.map() operation, then replace the users.find() call
with a direct O(1) Map lookup to maintain stable table rendering performance on
larger datasets.
🪄 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: e2da1355-5c64-4975-a150-25321c19c7b1
📒 Files selected for processing (11)
src/app/(dashboard)/peer/page.tsxsrc/app/(dashboard)/peers/page.tsxsrc/components/table/DataTable.tsxsrc/components/table/DataTableHeadingPortal.tsxsrc/contexts/PeerProvider.tsxsrc/interfaces/Peer.tssrc/layouts/Navigation.tsxsrc/modules/common-table-rows/ActiveInactiveRow.tsxsrc/modules/peers/PeerNameCell.tsxsrc/modules/peers/PeersTable.tsxsrc/modules/peers/peerKind.ts
| {hasPeerKind && ( | ||
| <Card.ListItem | ||
| tooltip={false} | ||
| label={ | ||
| <> | ||
| <MonitorSmartphoneIcon size={16} className={"shrink-0"} /> | ||
| Peer Type | ||
| </> | ||
| } | ||
| value={ | ||
| permission.peers.update ? ( | ||
| <PeerKindSelect | ||
| value={selectedPeerKind} | ||
| inferredKind={inferredPeerKind} | ||
| onChange={handleSavePeerKind} | ||
| /> | ||
| ) : ( | ||
| <PeerKindValue | ||
| value={selectedPeerKind} | ||
| inferredKind={inferredPeerKind} | ||
| /> | ||
| ) | ||
| } | ||
| /> | ||
| )} |
There was a problem hiding this comment.
Show inferred peer type on older backends instead of hiding the entire row.
This block hides “Peer Type” when supportsPeerKind(peer) is false. That drops fallback visibility for older backends; the editor should be hidden there, but inferred type should still be shown read-only.
Proposed fix
- {hasPeerKind && (
- <Card.ListItem
- tooltip={false}
- label={
- <>
- <MonitorSmartphoneIcon size={16} className={"shrink-0"} />
- Peer Type
- </>
- }
- value={
- permission.peers.update ? (
- <PeerKindSelect
- value={selectedPeerKind}
- inferredKind={inferredPeerKind}
- onChange={handleSavePeerKind}
- />
- ) : (
- <PeerKindValue
- value={selectedPeerKind}
- inferredKind={inferredPeerKind}
- />
- )
- }
- />
- )}
+ <Card.ListItem
+ tooltip={false}
+ label={
+ <>
+ <MonitorSmartphoneIcon size={16} className={"shrink-0"} />
+ Peer Type
+ </>
+ }
+ value={
+ hasPeerKind && permission.peers.update ? (
+ <PeerKindSelect
+ value={selectedPeerKind}
+ inferredKind={inferredPeerKind}
+ onChange={handleSavePeerKind}
+ />
+ ) : (
+ <PeerKindValue
+ value={selectedPeerKind}
+ inferredKind={inferredPeerKind}
+ />
+ )
+ }
+ />📝 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.
| {hasPeerKind && ( | |
| <Card.ListItem | |
| tooltip={false} | |
| label={ | |
| <> | |
| <MonitorSmartphoneIcon size={16} className={"shrink-0"} /> | |
| Peer Type | |
| </> | |
| } | |
| value={ | |
| permission.peers.update ? ( | |
| <PeerKindSelect | |
| value={selectedPeerKind} | |
| inferredKind={inferredPeerKind} | |
| onChange={handleSavePeerKind} | |
| /> | |
| ) : ( | |
| <PeerKindValue | |
| value={selectedPeerKind} | |
| inferredKind={inferredPeerKind} | |
| /> | |
| ) | |
| } | |
| /> | |
| )} | |
| <Card.ListItem | |
| tooltip={false} | |
| label={ | |
| <> | |
| <MonitorSmartphoneIcon size={16} className={"shrink-0"} /> | |
| Peer Type | |
| </> | |
| } | |
| value={ | |
| hasPeerKind && permission.peers.update ? ( | |
| <PeerKindSelect | |
| value={selectedPeerKind} | |
| inferredKind={inferredPeerKind} | |
| onChange={handleSavePeerKind} | |
| /> | |
| ) : ( | |
| <PeerKindValue | |
| value={selectedPeerKind} | |
| inferredKind={inferredPeerKind} | |
| /> | |
| ) | |
| } | |
| /> |
🤖 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/app/`(dashboard)/peer/page.tsx around lines 641 - 665, The Card.ListItem
for "Peer Type" is currently hidden entirely when hasPeerKind is false, but it
should instead remain visible to show the inferred peer type in read-only mode
for older backends. Remove the conditional wrapper {hasPeerKind && (...)} around
the Card.ListItem so the row is always rendered. The conditional logic inside
the value prop (which shows PeerKindSelect when permission.peers.update is true
and PeerKindValue otherwise) will properly handle showing the read-only inferred
type for older backends that don't support peer kind updates.
| <InlineLink | ||
| href={"https://docs.netbird.io/how-to/add-machines-to-your-network"} | ||
| target={"_blank"} | ||
| > |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate InlineLink implementation
fd -i 'InlineLink' src
# Inspect component behavior around target/rel propagation
rg -n -C4 'target|rel|noopener|noreferrer' src --iglob '*InlineLink*'Repository: netbirdio/dashboard
Length of output: 1154
🏁 Script executed:
cat -n src/components/InlineLink.tsxRepository: netbirdio/dashboard
Length of output: 2035
🏁 Script executed:
sed -n '95,140p' src/app/\(dashboard\)/peers/page.tsxRepository: netbirdio/dashboard
Length of output: 1527
Add rel="noopener noreferrer" to protect against reverse-tabnabbing.
The InlineLink component does not automatically set the rel attribute when target="_blank" is used. Both instances at lines 100–103 and 130–133 in peers/page.tsx need explicit rel="noopener noreferrer" to prevent reverse-tabnabbing attacks.
Either:
- Update the InlineLink component to automatically include
rel="noopener noreferrer"whentarget="_blank"is present, or - Add
rel="noopener noreferrer"directly to eachInlineLinkthat usestarget="_blank".
🤖 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/app/`(dashboard)/peers/page.tsx around lines 100 - 103, The InlineLink
component instances that use target="_blank" are missing the rel="noopener
noreferrer" attribute, which exposes the application to reverse-tabnabbing
attacks. Add the rel="noopener noreferrer" attribute to each InlineLink
component where target="_blank" is specified (there are multiple instances in
the peers/page.tsx file). This prevents the opened page from accessing the
window.opener property and potentially redirecting the original page.
Issue ticket number and link
Closes #663
Issue #663 reports that Dashboard v2.39.0 reduced peer-list usability by splitting the previous unified Peers view into two separate navigation destinations: User Devices and Servers. That split made it impossible to see all peers in one sortable/searchable/filterable table, forcing users to move back and forth between two pages when they needed a complete view of their network.
This PR restores
/peersas the canonical combined Peers page while preserving the ability to focus the list by peer type. Instead of separate sidebar destinations, the Peers page now shows both Servers and Devices in one table by default and provides inline Servers / Devices toggles for quick visibility filtering. This directly addresses the issue request: “go back to a full list with ‘User Devices’ and ‘Servers’ toggles to filter visibility instead.”The previous classification logic is still useful as a default because peers enrolled with setup keys are likely servers most of the time, and peers enrolled through SSO are likely user devices most of the time. However, that assumption is not always correct. Some setup-key peers are regular devices, and some SSO-enrolled peers may function as servers or shared infrastructure. Treating enrollment method as the only source of truth makes the experience clunky, especially once users need to organize or audit mixed environments from the Peers page. This PR keeps that inference as the automatic fallback, but prepares the dashboard to respect an explicit backend-provided peer type when available.
The change also keeps the combined view practical:
Peersitem now links directly to/peersinstead of opening only the old split page dropdown./peerspage shows one combined list of all peers by default.X Peers,X Server Peers, orX Device Peers./peers, matching the restored combined page instead of sending users back into the old split routes.This PR also includes dashboard support for an optional backend-provided peer classification field. When the management API exposes
peer.kind, the peer detail page can show/edit the peer type. When connected to an older backend that does not return that field, the editor is hidden and the dashboard falls back to the existing inference logic. That keeps the dashboard backward-compatible while allowing newer backends to support explicit peer classification.Documentation
Select exactly one:
This change restores and improves the dashboard Peers list behavior requested in issue #663. The relevant guidance is already presented in the UI through the Peers heading tooltip, which preserves the explanatory Servers and Devices wording from the previously split sections. No external documentation page currently describes the split Peers navigation or the new table toggles, and this PR does not require users to follow a new setup procedure.
Summary by CodeRabbit
New Features
UI/UX Improvements