Skip to content

Add fine-grained ssh policy#522

Merged
heisbrot merged 3 commits intomainfrom
feature/ssh-authorized-users
Dec 30, 2025
Merged

Add fine-grained ssh policy#522
heisbrot merged 3 commits intomainfrom
feature/ssh-authorized-users

Conversation

@heisbrot
Copy link
Copy Markdown
Contributor

@heisbrot heisbrot commented Dec 29, 2025

Summary by CodeRabbit

  • New Features

    • NetBird SSH protocol support with policy-based SSH access and SSH user mappings
    • Inline policy editing via modals and interactive policy lists
    • Clickable group/resource navigation and new peer/resource count badges
    • New provider icons (Jumpcloud, OIDC)
  • UI/UX Improvements

    • Better loading indicators across group detail sections and user pages (Tabs)
    • Badge size variants and refined tooltips/hover interactions
    • Improved peer/user links and counts display
  • Bug Fixes

    • Fixed table inset/layout inconsistencies
    • Updated SSH version messaging and guidance

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Dec 29, 2025

📝 Walkthrough

Walkthrough

useGroupDetails now returns { groupDetails, isLoading }; isLoading is propagated to all group detail sections. Major SSH support added (netbird-ssh) with new SSH UI components, authorized_groups support in policies, version check for SSH protocol, and multiple UI/navigation enhancements (group redirects, badges, HoverCard, badges, and policy modal workflows).

Changes

Cohort / File(s) Summary
Group details loading
src/modules/groups/details/useGroupDetails.ts, src/app/(dashboard)/group/page.tsx, src/modules/groups/details/Group{Users,Peers,Policies,Resources,NetworkRoutes,Nameservers,SetupKeys}Section.tsx
useGroupDetails now returns { groupDetails, isLoading }; propagated isLoading into all Group*Section components and updated page tab/count logic.
SSH protocol & policies
src/interfaces/Policy.ts, src/modules/access-control/*, src/modules/access-control/ssh/*, src/modules/peer/*, src/modules/peers/*, src/modules/remote-access/*, src/utils/version.ts, src/modules/remote-access/useNetBirdClient.ts
Added Protocol "netbird-ssh", AuthorizedGroups, version check isNetbirdSSHProtocolSupported (0.61.0), SSH UI components (SSHAccessType, SSHAuthorizedGroups, SSHUsernameSelector), SSH-aware access control modal and hook changes (sshAccessType, sshAuthorizedGroups), and updated remote-access connect logic to prefer netbird-ssh when supported.
Policy modal workflows & providers
src/contexts/PoliciesProvider.tsx, src/modules/networks/NetworkProvider.tsx, src/modules/networks/resources/ResourcePolicyCell.tsx, src/modules/posture-checks/table/cells/PostureCheckPolicyUsageCell.tsx, src/modules/access-control/table/*
Added openEditPolicyModal to contexts; modal-driven policy edit flows; interactive policy lists with edit actions; permissions-gated edit icons in AccessControl sources/destinations cells.
Group / badge / navigation UX
src/components/ui/GroupBadge.tsx, src/components/ui/GroupBadgeIcon.tsx, src/components/ui/MultipleGroups.tsx, src/modules/networks/routing-peers/NetworkRoutingPeerName.tsx, src/modules/routes/RoutePeerCell.tsx, src/app/(dashboard)/peer/page.tsx
GroupBadge gained redirect props (redirectToGroupPage, redirectGroupTab, textClassName); MultipleGroups replaced Tooltip with HoverCard, added showResources/showUsers/redirectGroupTab and UserCountStack; GroupBadgeIcon supports Jumpcloud/OIDC icons; Peer links to user profile added.
Count/badge components
src/components/ui/PeerCountBadge.tsx, src/components/ui/ResourceCountBadge.tsx, src/components/PeerGroupSelector.tsx
New PeerCountBadge and ResourceCountBadge components; PeerGroupSelector refactored to use PeerCounter/UserCountStack, shows empty counts as 0.
Tabs / UI primitives / styling
src/components/{Badge,DropdownMenu,HoverCard,Tooltip,Tabs,Label,PortSelector}.tsx, src/components/ui/TruncatedText.tsx
Badge size variant added; TabsList gains hidden prop; Tooltip colors adjusted (removed dark variants); HoverCard component added and used; TruncatedText switched tooltip implementation; PortSelector click now prevents propagation.
Users & user page changes
src/app/(dashboard)/team/user/page.tsx, src/modules/users/UserPeersSection.tsx, src/modules/users/HorizontalUsersStack.tsx
User page refactored to Tabs (access tokens/peers), isServiceUser flag used throughout; added UserPeersSection; HorizontalUsersStack adds isAllGroup prop and improved fallbacks.
Peer/SSH management UI
src/modules/peer/PeerSSHToggle.tsx, src/modules/peer/PeerSSHInstructions.tsx, src/modules/peer/PeerSSHPolicyModal.tsx, src/modules/peer/PeerSSHPolicyInfo.tsx, src/modules/remote-access/ssh/SSHTooltip.tsx, src/modules/remote-access/ssh/SSHButton.tsx
Richer SSH policy workflows: tooltip/modal lists, updated instructional copy referencing v0.61.0, PeerSSHPolicyModal now uses netbird-ssh, SSH enablement considers peer.local_flags.server_ssh_allowed.
Interfaces & helpers
src/interfaces/Peer.ts, src/utils/helpers.ts, src/utils/version.ts
Added Peer.local_flags (PeerLocalFlags), singularize gained showZero param, added isNetbirdSSHProtocolSupported(version).
Tables / layout minor changes
src/modules/route-group/NetworkRoutesTable.tsx, src/modules/setup-keys/SetupKeysTable.tsx, src/modules/dns-nameservers/table/NameserverGroupTable.tsx, src/modules/groups/details/GroupNetworkRoutesSection.tsx
Several DataTable insets hard-coded to false for consistent layout; GroupNetworkRoutesTableColumns export removed; NetworkRoutes/SetupKeys/Nameserver tables receive isLoading support.
Misc & import reorderings
numerous files (imports reordered)
Mostly import ordering changes across multiple files; small prop signature immutability additions (Readonly) in a few components.

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant UI as Dashboard UI
    participant PoliciesCtx as Policies Context
    participant AccessModal as AccessControl Modal
    participant SSHComp as SSH subcomponents
    participant API as Backend API

    User->>UI: Open policy editor / select policy
    UI->>PoliciesCtx: openEditPolicyModal(policy, initialTab)
    PoliciesCtx->>AccessModal: render with policy
    AccessModal->>SSHComp: if protocol == netbird-ssh show SSH UI
    alt SSH - limited access
        User->>SSHComp: configure authorized users per group
        SSHComp->>AccessModal: set sshAuthorizedGroups
    end
    User->>AccessModal: Save
    AccessModal->>API: submit policy (includes authorized_groups when netbird-ssh)
    API-->>PoliciesCtx: policy saved
    PoliciesCtx->>UI: close modal, refresh policies
Loading
sequenceDiagram
    participant PeerUI as Peer Dashboard
    participant PolicySvc as Policy list fetch
    participant Modal as Policy Modal
    participant API as Backend

    PeerUI->>PolicySvc: fetch policies relevant to peer (filter by netbird-ssh & target)
    PolicySvc-->>PeerUI: return policies
    alt no policies
        PeerUI->>User: show Create SSH Policy CTA
        User->>Modal: open create modal (protocol=netbird-ssh)
    else policies exist
        PeerUI->>User: show Active Policies badge (clickable list)
        User->>Modal: open selected policy for edit
    end
    Modal->>API: save policy changes
    API-->>PeerUI: policy updated (refresh)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • lixmal
  • braginini

Poem

🐰
I hopped through code with nimble paws,
Netbird SSH now knows new laws,
Badges point where users roam,
Loading flags bring states back home,
A rabbit's patch — a joyful cause.

Pre-merge checks and finishing touches

❌ 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%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding fine-grained SSH policy control with authorized user groups per source group.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/ssh-authorized-users

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 590405c and ce1f8b4.

📒 Files selected for processing (1)
  • src/modules/groups/details/useGroupDetails.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build_n_push
🔇 Additional comments (2)
src/modules/groups/details/useGroupDetails.ts (2)

116-125: LGTM! Correctly includes all loading states in the composite.

This is a necessary fix—without isLoadingResources and isNetworksLoading, the groupDetails could be computed prematurely with incomplete network resource data.


127-155: Clean refactor of the return API.

The new { groupDetails, isLoading } return shape is a cleaner hook pattern that:

  • Prevents partial data exposure by returning null during loading
  • Allows consumers to handle loading state consistently across all sections

The useMemo dependencies are complete.


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: 8

Caution

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

⚠️ Outside diff range comments (1)
src/modules/groups/details/useGroupDetails.ts (1)

116-124: Missing isNetworksLoading in loading state computation.

The isLoading flag combines multiple loading states but omits isNetworksLoading (from line 46-47), even though networks is used in linkedNetworkResources computation. This could cause the UI to render with incomplete data while networks are still loading.

🔎 Proposed fix
   const isLoading =
     isGroupsLoading ||
     isPoliciesLoading ||
     isNameserversLoading ||
     isRoutesLoading ||
     isSetupKeysLoading ||
     isUsersLoading ||
     isPeerLoading ||
-    isLoadingResources;
+    isLoadingResources ||
+    isNetworksLoading;
🧹 Nitpick comments (20)
src/app/(dashboard)/network-routes/page.tsx (1)

19-19: Consider moving the import to maintain conventional ordering.

The Callout import is positioned after the hook imports, breaking the standard convention of grouping all imports at the top of the file. For consistency and readability, consider moving it with the other @components imports (lines 3-7).

🔎 Suggested fix
 import Breadcrumbs from "@components/Breadcrumbs";
+import { Callout } from "@components/Callout";
 import InlineLink from "@components/InlineLink";

And remove from line 19:

 import useGroupedRoutes from "@/modules/route-group/useGroupedRoutes";
-import { Callout } from "@components/Callout";
src/modules/activity/ActivityDescription.tsx (1)

206-212: LGTM! Clean implementation of user creation activity.

The handler correctly displays the username, email, and creator attribution with a sensible fallback to "NetBird" when the initiator is unknown. The pattern is consistent with other user activity handlers like user.invite.

Minor: Optional chaining on event is unnecessary

Since event is a required prop, event?.initiator_name can be simplified to event.initiator_name:

-      was created by <Value>{event?.initiator_name || "NetBird"}</Value>
+      was created by <Value>{event.initiator_name || "NetBird"}</Value>

This matches the pattern used elsewhere in the file (e.g., line 417).

src/utils/helpers.ts (1)

247-252: Consider explicit undefined check for clarity.

The showZero parameter adds useful zero-state display control. However, the truthy check if (!count) treats 0, undefined, and null identically. This may be intentional, but consider whether an explicit check for count === undefined || count === null would better express the intent and avoid potential confusion when count is explicitly 0.

Current behavior:

  • singularize("items", 0, true)"0 items"
  • singularize("items", 0)"items" (no zero shown by default)
  • singularize("items", undefined, true)"0 items"
🔎 Alternative implementation with explicit checks
 export const singularize = (
   word: string,
   count?: number,
   showZero?: boolean,
 ) => {
-  if (!count) return showZero ? `0 ${word}` : word;
+  if (count === undefined || count === null) return word;
+  if (count === 0) return showZero ? `0 ${word}` : word;
   if (word.endsWith("ies") && count === 1) {
     return count + " " + word.slice(0, -3) + "y";
   } else if (word.endsWith("s") && count === 1) {
     return count + " " + word.slice(0, -1);
   }
   return count + " " + word;
 };

This makes the handling of undefined/null vs. 0 more explicit.

src/app/(dashboard)/peer/page.tsx (1)

151-155: Consider using group ID or a constant for "All" group filtering.

Filtering by the string literal "All" may be fragile if the group name changes or varies by locale. Consider using a group ID check or a shared constant if one exists in the codebase.

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

93-97: LGTM with optional refactor suggestion.

The protocol-aware routing logic correctly switches to netbird-ssh when supported (version >= 0.61.0). The implementation is consistent in both handleReconnect and the initial connect effect.

Consider extracting the duplicated rule-building logic into a helper function to reduce repetition:

🔎 Optional refactor to reduce duplication
const buildRoutingRules = (peerVersion: string, port: string): string[] => {
  const aclPort = isNativeSSHSupported(peerVersion) ? "22022" : port;
  const protocol = isNetbirdSSHProtocolSupported(peerVersion)
    ? "netbird-ssh"
    : "tcp";
  return [`${protocol}/${aclPort}`];
};

Also applies to: 119-123

src/modules/users/UserPeersSection.tsx (1)

23-32: Consider server-side filtering for peers if available.

The component fetches all peers via /peers and filters client-side by user_id. While this works, it could be inefficient with many peers. If an endpoint like /users/${user.id}/peers exists or can be added, it would improve performance and reduce data transfer.

💡 Example refactor if user-specific endpoint exists
- const { data: peers, isLoading: isPeersLoading } =
-   useFetchApi<Peer[]>("/peers");
+ const { data: userPeers, isLoading: isPeersLoading } =
+   useFetchApi<Peer[]>(`/users/${user.id}/peers`);

- const userPeers = useMemo(() => {
-   return (
-     peers?.filter((peer) => {
-       return peer?.user_id === user.id;
-     }) || []
-   );
- }, [user, peers]);

Note: Only apply this if the backend supports such an endpoint.

src/components/ui/PolicyDirection.tsx (1)

35-42: Consider guarding onChange to avoid redundant calls.

The useEffect calls onChange("in") whenever protocol === "netbird-ssh", even if value is already "in". This could trigger unnecessary re-renders or state updates in parent components.

🔎 Proposed fix
  useEffect(() => {
    if (protocol === "netbird-ssh") {
-     onChange("in");
+     if (value !== "in") onChange("in");
      return;
    }
-   if (disabled) onChange("bi");
+   if (disabled && value !== "bi") onChange("bi");
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [disabled, protocol]);

Note: This would require adding value to the dependency array, which changes the behavior. Alternatively, keep current logic if the parent handles idempotent state updates gracefully.

src/app/(dashboard)/group/page.tsx (1)

176-188: Minor inconsistency: tab pulse animation uses groupDetails === null while sections use isLoading.

The animate-pulse class is conditionally applied when groupDetails === null, but the section components receive isLoading. These might not always be in sync (e.g., isLoading could be false while groupDetails is still null on error, or vice versa during refetch).

Consider using isLoading consistently for the pulse animation to align with how the sections handle loading state.

src/modules/networks/resources/ResourcePolicyCell.tsx (1)

75-78: Early return without explicit null may cause React warning.

When rule is falsy, return; is equivalent to return undefined;. While React handles this, explicitly returning null is cleaner and more intentional for map callbacks.

🔎 Proposed fix
                {assignedPolicies?.map((policy: Policy) => {
                  const rule = policy?.rules?.[0];
-                 if (!rule) return;
+                 if (!rule) return null;
                  return (
src/components/ui/PeerCountBadge.tsx (2)

16-20: Props destructuring drops HTML attributes.

The Props type extends React.HTMLAttributes<HTMLDivElement>, but only group, variant, and className are destructured. Other HTML attributes (e.g., onClick, style, data-*) passed to the component will be silently ignored and not forwarded to the Badge.

🔎 Proposed fix to forward remaining props
 export default function PeerCountBadge({
   group,
   variant = "gray",
   className,
+  ...props
 }: Props) {

Then spread {...props} on the Badge component if forwarding is intended.


24-35: Group lookup by name may be fragile.

The component finds currentGroup by matching group?.name against dropdownOptions. If group names aren't unique or the provided group's name doesn't match any dropdown option, currentGroup will be undefined and peerCount will default to 0. Consider matching by id instead for reliability, or add a fallback to use the passed group directly.

src/modules/peer/PeerSSHToggle.tsx (1)

227-264: Early return without key could be improved.

On line 229, if (!rule) return; returns undefined inside a .map(). While React handles this gracefully, it's cleaner to filter out invalid entries or return null explicitly.

🔎 Proposed fix
                 {assignedPolicies?.map((policy: Policy) => {
                   const rule = policy?.rules?.[0];
-                  if (!rule) return;
+                  if (!rule) return null;
                   return (
src/components/ui/MultipleGroups.tsx (1)

149-161: Potential filter issue when group.id is undefined.

If group.id is undefined, the filter user.auto_groups.includes(group.id as string) casts undefined to "undefined", which likely won't match but isn't the intended behavior. Consider adding a guard.

🔎 Proposed fix
 export const UserCountStack = ({ group }: { group: Group }) => {
   const { users } = useUsers();
-  const usersOfGroup =
-    users?.filter((user) => user.auto_groups.includes(group.id as string)) ||
-    [];
+  const usersOfGroup = group.id
+    ? users?.filter((user) => user.auto_groups.includes(group.id as string)) || []
+    : [];
   return (
src/modules/access-control/useAccessControl.ts (1)

261-286: Misuse of reduce - result ignored, side effects used instead.

The reduce on lines 273-282 mutates the external authorizedGroups variable via side effect but ignores the accumulator and return value. This is confusing and non-idiomatic.

🔎 Proposed fix using forEach
       if (!isEmpty) {
-        Object.entries(sshAuthorizedGroups).reduce(
-          (acc, [groupName, usernames]) => {
-            const group = groups?.find((group) => group.name === groupName);
-            if (group?.id) {
-              authorizedGroups[group.id] = usernames;
-            }
-            return acc;
-          },
-          {} as AuthorizedGroups,
-        );
+        Object.entries(sshAuthorizedGroups).forEach(([groupName, usernames]) => {
+          const group = groups?.find((g) => g.name === groupName);
+          if (group?.id) {
+            authorizedGroups[group.id] = usernames;
+          }
+        });
       } else {
         authorizedGroups = {};
       }
src/modules/access-control/ssh/SSHAuthorizedGroups.tsx (1)

55-55: Return null instead of implicit undefined.

The early return if (accessType === "full") return; returns undefined. For React components, explicitly returning null is preferred for clarity.

🔎 Proposed fix
-  if (accessType === "full") return;
+  if (accessType === "full") return null;
src/modules/access-control/ssh/SSHUsernameSelector.tsx (4)

20-20: Remove unused import.

PostureCheck is imported but never used in this file.

-import { PostureCheck } from "@/interfaces/PostureCheck";

118-126: Minor: Extra space in className.

There's a double space in the className string.

-          className="w-full p-0 shadow-sm  shadow-nb-gray-950"
+          className="w-full p-0 shadow-sm shadow-nb-gray-950"

176-182: Redundant className condition.

Line 180's condition is a subset of line 179's - if values?.length != 0 is true, p-2 is already applied regardless of search. The second condition can never add padding that isn't already present.

                <div
                  className={cn(
                    "flex flex-col gap-2",
                    values?.length != 0 && "p-2",
-                   values?.length != 0 && search && "p-2",
                  )}
                >

221-248: Clarify always-true condition.

Since user is sourced from iterating values, values?.includes(user) will always be true. The checkbox serves to allow deselection, but the code reads misleadingly. Consider simplifying:

                    {values?.map((user) => {
-                     const isSelected = values?.includes(user);
                      return (
                        <CommandItem
                          key={user}
                          value={user.toString()}
                          onSelect={() => {
                            toggle(user);
                            searchRef.current?.focus();
                          }}
                          onClick={(e) => e.preventDefault()}
                        >
                          <div className={"flex items-center gap-2"}>
                            <Badge
                              variant={"gray"}
                              className={"font-normal py-1"}
                            >
                              {user}
                            </Badge>
                          </div>

                          <div
                            className={
                              "text-neutral-500 dark:text-nb-gray-300 font-medium flex items-center gap-2"
                            }
                          >
-                           <Checkbox checked={isSelected} />
+                           <Checkbox checked={true} />
                          </div>
                        </CommandItem>
                      );
                    })}
src/modules/access-control/AccessControlModal.tsx (1)

195-200: Simplify redundant condition.

The check initialTab !== "" is redundant since an empty string is already falsy.

  const [tab, setTab] = useState(() => {
-   if (initialTab && initialTab !== "") return initialTab;
+   if (initialTab) return initialTab;
    if (!cell) return "policy";
    if (cell == "posture_checks") return "posture_checks";
    return "policy";
  });
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b058e66 and 590405c.

📒 Files selected for processing (72)
  • src/app/(dashboard)/group/page.tsx
  • src/app/(dashboard)/network-routes/page.tsx
  • src/app/(dashboard)/peer/page.tsx
  • src/app/(dashboard)/team/user/page.tsx
  • src/app/(remote-access)/peer/rdp/page.tsx
  • src/app/(remote-access)/peer/ssh/page.tsx
  • src/assets/icons/JumpcloudIcon.tsx
  • src/assets/icons/OIDCIcon.tsx
  • src/components/Badge.tsx
  • src/components/DropdownMenu.tsx
  • src/components/HoverCard.tsx
  • src/components/Label.tsx
  • src/components/PeerGroupSelector.tsx
  • src/components/PortSelector.tsx
  • src/components/Tabs.tsx
  • src/components/Tooltip.tsx
  • src/components/ui/GroupBadge.tsx
  • src/components/ui/GroupBadgeIcon.tsx
  • src/components/ui/MultipleGroups.tsx
  • src/components/ui/PeerCountBadge.tsx
  • src/components/ui/PolicyDirection.tsx
  • src/components/ui/ResourceCountBadge.tsx
  • src/components/ui/TruncatedText.tsx
  • src/contexts/PeerProvider.tsx
  • src/contexts/PoliciesProvider.tsx
  • src/interfaces/Peer.ts
  • src/interfaces/Policy.ts
  • src/modules/access-control/AccessControlModal.tsx
  • src/modules/access-control/ssh/SSHAccessType.tsx
  • src/modules/access-control/ssh/SSHAuthorizedGroups.tsx
  • src/modules/access-control/ssh/SSHUsernameSelector.tsx
  • src/modules/access-control/table/AccessControlDestinationsCell.tsx
  • src/modules/access-control/table/AccessControlPortsCell.tsx
  • src/modules/access-control/table/AccessControlSourcesCell.tsx
  • src/modules/access-control/useAccessControl.ts
  • src/modules/activity/ActivityDescription.tsx
  • src/modules/dns-nameservers/table/NameserverDistributionGroupsCell.tsx
  • src/modules/dns-nameservers/table/NameserverGroupTable.tsx
  • src/modules/groups/details/GroupNameserversSection.tsx
  • src/modules/groups/details/GroupNetworkRoutesSection.tsx
  • src/modules/groups/details/GroupPeersSection.tsx
  • src/modules/groups/details/GroupPoliciesSection.tsx
  • src/modules/groups/details/GroupResourcesSection.tsx
  • src/modules/groups/details/GroupSetupKeysSection.tsx
  • src/modules/groups/details/GroupUsersSection.tsx
  • src/modules/groups/details/useGroupDetails.ts
  • src/modules/groups/table/GroupsTable.tsx
  • src/modules/groups/useGroupIdentification.ts
  • src/modules/networks/NetworkProvider.tsx
  • src/modules/networks/resources/ResourceGroupCell.tsx
  • src/modules/networks/resources/ResourcePolicyCell.tsx
  • src/modules/networks/routing-peers/NetworkRoutingPeerName.tsx
  • src/modules/networks/table/NetworkActionCell.tsx
  • src/modules/peer/PeerSSHInstructions.tsx
  • src/modules/peer/PeerSSHPolicyInfo.tsx
  • src/modules/peer/PeerSSHPolicyModal.tsx
  • src/modules/peer/PeerSSHToggle.tsx
  • src/modules/peers/PeerActionCell.tsx
  • src/modules/peers/PeerConnectButton.tsx
  • src/modules/peers/PeerGroupCell.tsx
  • src/modules/posture-checks/table/cells/PostureCheckPolicyUsageCell.tsx
  • src/modules/remote-access/rdp/RDPCredentialsModal.tsx
  • src/modules/remote-access/ssh/SSHButton.tsx
  • src/modules/remote-access/ssh/SSHTooltip.tsx
  • src/modules/remote-access/useNetBirdClient.ts
  • src/modules/route-group/NetworkRoutesTable.tsx
  • src/modules/routes/RoutePeerCell.tsx
  • src/modules/setup-keys/SetupKeysTable.tsx
  • src/modules/users/HorizontalUsersStack.tsx
  • src/modules/users/UserPeersSection.tsx
  • src/utils/helpers.ts
  • src/utils/version.ts
🧰 Additional context used
🧬 Code graph analysis (32)
src/assets/icons/JumpcloudIcon.tsx (1)
src/assets/icons/IconProperties.tsx (2)
  • IconProps (1-5)
  • iconProperties (14-26)
src/modules/access-control/table/AccessControlPortsCell.tsx (1)
src/modules/access-control/useAccessControl.ts (1)
  • parsePortsToStrings (444-457)
src/modules/groups/details/GroupResourcesSection.tsx (1)
src/interfaces/Network.ts (1)
  • NetworkResourceWithNetwork (32-34)
src/modules/networks/resources/ResourceGroupCell.tsx (2)
src/components/ui/MultipleGroups.tsx (1)
  • MultipleGroups (31-135)
src/interfaces/Group.ts (1)
  • Group (1-12)
src/components/ui/TruncatedText.tsx (2)
src/components/Tooltip.tsx (3)
  • Tooltip (68-68)
  • TooltipTrigger (68-68)
  • TooltipContent (68-68)
src/utils/helpers.ts (1)
  • cn (6-8)
src/modules/groups/details/GroupPeersSection.tsx (1)
src/interfaces/Peer.ts (1)
  • Peer (4-34)
src/components/ui/PeerCountBadge.tsx (4)
src/interfaces/Group.ts (1)
  • Group (1-12)
src/contexts/GroupsProvider.tsx (1)
  • useGroups (163-163)
src/components/ui/ResourceCountBadge.tsx (1)
  • ResourceCountBadge (13-33)
src/utils/helpers.ts (2)
  • cn (6-8)
  • singularize (247-259)
src/modules/access-control/ssh/SSHUsernameSelector.tsx (2)
src/utils/helpers.ts (1)
  • cn (6-8)
src/components/Badge.tsx (1)
  • Badge (56-78)
src/modules/access-control/useAccessControl.ts (1)
src/interfaces/Policy.ts (2)
  • AuthorizedGroups (31-31)
  • PolicyRule (14-29)
src/components/ui/ResourceCountBadge.tsx (3)
src/interfaces/Group.ts (1)
  • Group (1-12)
src/components/Badge.tsx (2)
  • BadgeVariants (5-5)
  • Badge (56-78)
src/utils/helpers.ts (2)
  • cn (6-8)
  • singularize (247-259)
src/components/ui/MultipleGroups.tsx (6)
src/interfaces/Group.ts (1)
  • Group (1-12)
src/components/ui/GroupBadge.tsx (1)
  • GroupBadge (26-85)
src/components/ui/ResourceCountBadge.tsx (1)
  • ResourceCountBadge (13-33)
src/components/ui/PeerCountBadge.tsx (1)
  • PeerCountBadge (16-64)
src/contexts/UsersProvider.tsx (1)
  • useUsers (44-44)
src/modules/users/HorizontalUsersStack.tsx (1)
  • HorizontalUsersStack (19-113)
src/components/PeerGroupSelector.tsx (2)
src/interfaces/Group.ts (1)
  • Group (1-12)
src/utils/helpers.ts (1)
  • cn (6-8)
src/components/HoverCard.tsx (2)
src/components/Tooltip.tsx (2)
  • TooltipVariants (14-14)
  • tooltipVariants (16-37)
src/utils/helpers.ts (1)
  • cn (6-8)
src/modules/groups/details/GroupUsersSection.tsx (1)
src/interfaces/User.ts (1)
  • User (3-16)
src/components/Badge.tsx (1)
src/utils/helpers.ts (1)
  • cn (6-8)
src/modules/posture-checks/table/cells/PostureCheckPolicyUsageCell.tsx (7)
src/interfaces/PostureCheck.ts (1)
  • PostureCheck (4-17)
src/contexts/PoliciesProvider.tsx (1)
  • usePolicies (97-99)
src/utils/helpers.ts (1)
  • cn (6-8)
src/components/FullTooltip.tsx (1)
  • FullTooltip (33-108)
src/interfaces/Policy.ts (1)
  • Policy (4-12)
src/assets/icons/CircleIcon.tsx (1)
  • CircleIcon (10-32)
src/components/Badge.tsx (1)
  • Badge (56-78)
src/components/ui/GroupBadgeIcon.tsx (3)
src/modules/groups/useGroupIdentification.ts (1)
  • useGroupIdentification (8-29)
src/assets/icons/JumpcloudIcon.tsx (1)
  • JumpcloudIcon (3-19)
src/assets/icons/OIDCIcon.tsx (1)
  • OIDCIcon (3-27)
src/modules/groups/details/GroupSetupKeysSection.tsx (4)
src/interfaces/SetupKey.ts (1)
  • SetupKey (3-20)
src/contexts/GroupProvider.tsx (1)
  • useGroupContext (329-335)
src/modules/groups/details/GroupDetailsTableContainer.tsx (1)
  • GroupDetailsTableContainer (14-47)
src/modules/setup-keys/SetupKeysTable.tsx (1)
  • SetupKeysTable (128-325)
src/modules/groups/details/GroupNameserversSection.tsx (4)
src/interfaces/Nameserver.ts (1)
  • NameserverGroup (1-11)
src/contexts/GroupProvider.tsx (1)
  • useGroupContext (329-335)
src/modules/groups/details/GroupDetailsTableContainer.tsx (1)
  • GroupDetailsTableContainer (14-47)
src/modules/dns-nameservers/table/NameserverGroupTable.tsx (1)
  • NameserverGroupTable (101-298)
src/app/(remote-access)/peer/ssh/page.tsx (1)
src/utils/version.ts (1)
  • isNetbirdSSHProtocolSupported (93-96)
src/modules/routes/RoutePeerCell.tsx (2)
src/components/ui/GroupBadge.tsx (1)
  • GroupBadge (26-85)
src/components/ui/PeerCountBadge.tsx (1)
  • PeerCountBadge (16-64)
src/assets/icons/OIDCIcon.tsx (1)
src/assets/icons/IconProperties.tsx (2)
  • IconProps (1-5)
  • iconProperties (14-26)
src/modules/access-control/ssh/SSHAccessType.tsx (1)
src/contexts/PermissionsProvider.tsx (1)
  • usePermissions (39-39)
src/modules/networks/routing-peers/NetworkRoutingPeerName.tsx (2)
src/components/ui/GroupBadge.tsx (1)
  • GroupBadge (26-85)
src/components/ui/PeerCountBadge.tsx (1)
  • PeerCountBadge (16-64)
src/app/(remote-access)/peer/rdp/page.tsx (1)
src/utils/version.ts (1)
  • isNetbirdSSHProtocolSupported (93-96)
src/components/Tabs.tsx (2)
src/utils/helpers.ts (1)
  • cn (6-8)
src/components/ScrollArea.tsx (2)
  • ScrollArea (77-77)
  • ScrollBar (79-79)
src/modules/groups/details/GroupPoliciesSection.tsx (3)
src/interfaces/Policy.ts (1)
  • Policy (4-12)
src/modules/groups/details/GroupDetailsTableContainer.tsx (1)
  • GroupDetailsTableContainer (14-47)
src/modules/access-control/table/AccessControlTable.tsx (1)
  • AccessControlTable (181-470)
src/modules/peers/PeerActionCell.tsx (5)
src/contexts/PeerProvider.tsx (1)
  • usePeer (193-193)
src/contexts/PermissionsProvider.tsx (1)
  • usePermissions (39-39)
src/contexts/DialogProvider.tsx (1)
  • useDialog (132-132)
src/components/InlineLink.tsx (1)
  • InlineLink (35-41)
src/components/DropdownMenu.tsx (1)
  • DropdownMenuItem (211-211)
src/components/ui/PolicyDirection.tsx (1)
src/interfaces/Policy.ts (2)
  • PolicyRuleResource (38-41)
  • Protocol (43-43)
src/modules/dns-nameservers/table/NameserverDistributionGroupsCell.tsx (6)
src/interfaces/Nameserver.ts (1)
  • NameserverGroup (1-11)
src/contexts/GroupsProvider.tsx (1)
  • useGroups (163-163)
src/contexts/PermissionsProvider.tsx (1)
  • usePermissions (39-39)
src/interfaces/Group.ts (1)
  • Group (1-12)
src/utils/helpers.ts (1)
  • cn (6-8)
src/components/ui/MultipleGroups.tsx (2)
  • MultipleGroups (31-135)
  • TransparentEditIconButton (137-147)
src/modules/networks/resources/ResourcePolicyCell.tsx (5)
src/modules/networks/NetworkProvider.tsx (1)
  • useNetworksContext (401-407)
src/interfaces/Policy.ts (1)
  • Policy (4-12)
src/interfaces/Group.ts (1)
  • Group (1-12)
src/components/FullTooltip.tsx (1)
  • FullTooltip (33-108)
src/assets/icons/CircleIcon.tsx (1)
  • CircleIcon (10-32)
src/modules/access-control/table/AccessControlSourcesCell.tsx (4)
src/contexts/PermissionsProvider.tsx (1)
  • usePermissions (39-39)
src/utils/helpers.ts (1)
  • cn (6-8)
src/components/ui/MultipleGroups.tsx (2)
  • MultipleGroups (31-135)
  • TransparentEditIconButton (137-147)
src/interfaces/Group.ts (1)
  • Group (1-12)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build_n_push
🔇 Additional comments (96)
src/components/Label.tsx (1)

9-9: LGTM! Spacing adjustment aligns with UI refinements.

The margin-bottom increase from mb-1 to mb-1.5 is a minor spacing refinement that aligns with the broader UI density updates introduced in this PR.

src/modules/remote-access/rdp/RDPCredentialsModal.tsx (1)

1-24: Import refactor looks good.

The reorganization consolidates UI component imports into a cohesive block and maintains correct import sources. All imports are accounted for in the component logic and JSX.

src/components/PeerGroupSelector.tsx (2)

688-711: LGTM! Good refactoring into a reusable component.

The extraction of the peer counter logic into a dedicated component improves maintainability. The conditional hiding logic is sensible—when resources are shown and there are resources but no peers, hiding the "0 Peer(s)" reduces UI clutter.


666-673: UI behavior change: now displays "0 User(s)" instead of rendering nothing.

This makes the interface more consistent by always showing a count, even when zero. The change improves user feedback and aligns with the updated PeerCounter behavior.

src/contexts/PeerProvider.tsx (1)

142-145: The implementation is correct as-is.

The PeerSSHInstructions component is purely instructional UI—it displays SSH setup steps and has a "Finish Setup" button that triggers the callback. No async operations occur within this component, so error handling and user notifications are not applicable here. The callback correctly refreshes peer data and closes the modal after the user confirms setup completion.

src/modules/remote-access/useNetBirdClient.ts (1)

290-295: LGTM! SSH protocol support added correctly.

The addition of "netbird-ssh/22" to the default rules array properly enables NetBird SSH protocol support for temporary access connections, aligning with the broader SSH policy functionality introduced in this PR.

src/components/DropdownMenu.tsx (1)

84-84: LGTM! Dark mode color token updated for consistency.

The update from dark:text-gray-400 to dark:text-nb-gray-300 aligns with the broader theming standardization across the codebase.

src/assets/icons/OIDCIcon.tsx (1)

1-27: LGTM! Well-structured icon component.

The OIDCIcon component follows the established icon pattern, properly uses the iconProperties helper, and maintains type safety with Readonly<IconProps>.

src/assets/icons/JumpcloudIcon.tsx (1)

1-19: LGTM! Consistent icon component implementation.

The JumpcloudIcon component follows the established icon pattern and maintains consistency with other icon components in the codebase.

src/modules/networks/table/NetworkActionCell.tsx (1)

19-19: LGTM! Type safety improvement with readonly props.

Using Readonly<Props> enforces immutability and follows React best practices for prop handling.

src/modules/peers/PeerGroupCell.tsx (2)

28-37: LGTM! Correct exclusion of "All" group.

The filtering logic properly excludes the "All" group from assigned group IDs by returning undefined for "All" groups and filtering out undefined values. The type assertion to string[] is safe after the undefined filter.


45-45: LGTM! Add group button enabled.

The showAddGroupButton={true} prop appropriately enables the add-group functionality in the GroupsRow component UI.

src/modules/peer/PeerSSHInstructions.tsx (3)

21-22: LGTM! Import organization updated.

The reordered imports properly include SegmentedTabs and NetBirdIcon to support the UI enhancements in this component.


102-103: LGTM! Version and messaging updated appropriately.

The update to v0.61.0 and the clarified text accurately reflects the new SSH policy requirements, making the instructions clearer for users.


143-143: LGTM! Improved button label.

"Finish Setup" is more descriptive and better communicates the action than the previous "Confirm & Enable" label.

src/modules/users/HorizontalUsersStack.tsx (4)

7-7: LGTM! New prop added for "All" group handling.

The isAllGroup prop with a sensible default value enables conditional rendering for the "All Users" group scenario.

Also applies to: 16-16, 24-24


28-31: LGTM! Proper memoization of user count text.

The useMemo hook correctly derives the display text based on isAllGroup flag and user count, with appropriate dependencies to prevent unnecessary recalculations.


95-95: LGTM! Defensive fallback for user name.

The fallback to user?.id when user?.name is unavailable prevents potential runtime issues with missing user names.


108-108: LGTM! Dynamic user count display.

Using the memoized userCountText properly reflects the context (either "All Users" or the numeric count) based on the isAllGroup flag.

src/contexts/PoliciesProvider.tsx (2)

27-29: LGTM! State initialization is appropriate.

The three new state variables properly initialize the modal workflow for policy editing.


76-92: LGTM! Modal integration is correct.

The modal properly manages its lifecycle, remounting AccessControlModalContent on each open via the key prop pattern. The cleanup logic appropriately resets currentPolicy on close and success.

src/components/Badge.tsx (2)

35-38: LGTM! Size variants properly implemented.

The new size variants provide flexible badge sizing with appropriate text and padding values. The default and xs presets are well-defined and follow consistent styling patterns.


60-60: LGTM! Size prop integration is correct.

The new size prop is properly defaulted and integrated into the className generation via the variants() call, enabling dynamic sizing based on the prop value.

Also applies to: 68-69

src/modules/peers/PeerActionCell.tsx (2)

37-42: LGTM! SSH button visibility logic is sound.

The memo correctly determines when to show the SSH button based on dashboard and client SSH flags. The logic ensures the button only appears when it's meaningful for the user to interact with SSH settings from the dashboard.


143-159: LGTM! Conditional SSH menu item rendering is correct.

The SSH menu item properly renders only when showSSHButton is true, and the onClick handler appropriately branches between the disable confirmation flow and the enable instructions modal.

src/modules/posture-checks/table/cells/PostureCheckPolicyUsageCell.tsx (1)

83-98: LGTM! Interactive badge and tooltip implementation.

The badge correctly toggles the tooltip on click, and the controlled tooltip state ensures predictable behavior. The click handlers properly prevent event propagation.

src/modules/groups/table/GroupsTable.tsx (1)

22-22: LGTM! Import reordering has no functional impact.

The import statement was reordered without affecting functionality.

src/modules/networks/routing-peers/NetworkRoutingPeerName.tsx (1)

48-54: LGTM! Enhanced navigation with redirect props.

The changes enable direct navigation to the group's peers tab when clicking the group badge, and replace the generic peer badge with a count badge that shows the actual number of peers. This improves the user experience by making the UI more informative and interactive.

src/modules/setup-keys/SetupKeysTable.tsx (1)

171-171: LGTM! Layout unification for consistency.

Changing inset from conditional to constant false unifies the table layout across all rendering contexts, ensuring visual consistency regardless of whether this is rendered on a group page or standalone.

src/modules/peer/PeerSSHPolicyModal.tsx (1)

18-18: Protocol handling for "netbird-ssh" is properly implemented and verified.

The "netbird-ssh" protocol is correctly handled throughout the access control system. When this protocol is selected, port 22 is automatically set in useAccessControl.ts, making the initialPorts parameter unnecessary. The change is safe—port configuration is handled internally for this protocol type, with additional constraints properly applied (direction disabled, resources hidden, authorized groups enabled).

src/modules/route-group/NetworkRoutesTable.tsx (1)

178-178: LGTM: Layout standardization applied.

The inset prop is now consistently set to false regardless of context, aligning with the broader layout standardization pattern applied across table components in this PR.

src/modules/remote-access/ssh/SSHTooltip.tsx (1)

81-82: LGTM: Updated guidance aligns with policy-driven SSH model.

The revised text correctly directs users to create an explicit access control policy for SSH access, which aligns with the policy-driven SSH improvements introduced in this PR.

src/modules/peer/PeerSSHPolicyInfo.tsx (1)

23-24: LGTM: Improved clarity with version-specific guidance.

The updated text references NetBird v0.61.0 and provides clearer guidance on SSH policy requirements, improving user understanding of the new policy-driven SSH model.

src/modules/dns-nameservers/table/NameserverGroupTable.tsx (1)

148-148: LGTM: Consistent layout standardization.

The inset prop is now consistently set to false, matching the layout standardization applied to other table components in this PR.

src/modules/groups/details/GroupUsersSection.tsx (1)

116-121: LGTM: Loading state propagation implemented correctly.

The addition of the isLoading prop enables external control of the loading state, aligning with the broader pattern introduced by the updated useGroupDetails hook. The default value of true is appropriate for showing a loading state until data arrives.

src/components/PortSelector.tsx (1)

142-146: LGTM: Improved event handling prevents unintended side effects.

Adding preventDefault() and stopPropagation() to the port badge click handler is a good defensive coding practice that prevents unintended event propagation and default behaviors, particularly important in the more complex SSH/policy UI interactions introduced in this PR.

src/modules/networks/resources/ResourceGroupCell.tsx (1)

26-30: LGTM! Resource-specific group UI enabled.

The addition of showResources={true} and redirectGroupTab={"resources"} props correctly leverages the enhanced MultipleGroups API to display resource counts and enable direct navigation to the resources tab when clicking group badges.

src/modules/access-control/table/AccessControlPortsCell.tsx (1)

10-10: LGTM! Port parsing logic centralized.

The refactoring to use parsePortsToStrings(rule) eliminates code duplication and removes the lodash dependency from this file. The centralized helper maintains the same behavior (combining port ranges and individual ports, then sorting by start port).

Also applies to: 26-26

src/utils/version.ts (1)

88-96: LGTM! Version check for netbird-ssh protocol support.

The new helper function correctly checks for netbird-ssh protocol support (v0.61.0+) and properly handles the "development" version. The implementation mirrors the existing isNativeSSHSupported pattern.

src/components/Tooltip.tsx (1)

24-33: Verify intentional removal of light mode styling.

The tooltip variants now use only dark theme colors (e.g., bg-nb-gray-940, text-neutral-50) instead of the previous responsive light/dark mode classes (e.g., bg-white dark:bg-nb-gray-940). This means tooltips will always display with dark styling regardless of the user's theme preference.

While dark tooltips are a common pattern even in light-themed UIs, please confirm this change is intentional and aligns with the design system.

src/app/(remote-access)/peer/rdp/page.tsx (1)

88-93: LGTM! Protocol selection with version-based fallback.

The dynamic protocol selection correctly uses netbird-ssh for peers running v0.61.0+ and falls back to tcp for older versions. This ensures backward compatibility while enabling the new netbird-ssh protocol for supported peers.

src/modules/remote-access/ssh/SSHButton.tsx (1)

22-24: LGTM! SSH enablement now includes policy-based allowance.

The updated logic correctly checks both peer?.local_flags?.server_ssh_allowed (policy-based) and peer?.ssh_enabled (existing flag), using OR logic to enable SSH if either condition is met. This aligns with the PR's fine-grained SSH policy support.

src/components/ui/GroupBadgeIcon.tsx (1)

5-7: LGTM! Jumpcloud and OIDC group icon support added.

The implementation correctly integrates JumpCloud and OIDC icons with appropriate size adjustments (size+2 for JumpCloud, no adjustment for OIDC), consistent with the existing pattern for other identity provider icons.

Also applies to: 25-32, 40-43

src/components/Tabs.tsx (1)

42-42: LGTM! Optional tab list hiding capability added.

The new hidden prop allows the tab list to be conditionally hidden while preserving tab content rendering. This is useful for loading states or permission-based UI scenarios. The default value of false maintains backward compatibility.

Also applies to: 49-78

src/modules/routes/RoutePeerCell.tsx (1)

47-54: LGTM!

The refactor to use PeerCountBadge and the new GroupBadge navigation props (redirectToGroupPage, redirectGroupTab) aligns well with the component APIs shown in the relevant code snippets and maintains consistency with similar patterns elsewhere in the PR.

src/interfaces/Policy.ts (2)

28-32: LGTM!

The AuthorizedGroups type and authorized_groups property cleanly extend the policy rule model for SSH authorization. The inline comment documenting the mapping semantics (group_id → local machine usernames) is helpful.


43-43: LGTM!

Adding "netbird-ssh" to the Protocol union type correctly extends the supported protocols for the new SSH feature.

src/modules/groups/useGroupIdentification.ts (1)

12-17: LGTM!

The new isJumpcloudGroup and isOIDCGroup checks follow the established pattern. The refactored isRegularGroup logic (now including JWT groups) and simplified isIntegrationGroup check are clear improvements.

src/modules/access-control/table/AccessControlDestinationsCell.tsx (1)

33-37: LGTM!

The permission-gated edit button with hover styling is a good UX pattern. The canUpdate check ensures the edit affordance only appears for authorized users.

src/modules/groups/details/GroupNameserversSection.tsx (1)

11-19: LGTM!

The isLoading prop propagation with a sensible default of true follows the PR's pattern for unified loading state management across group detail sections.

Also applies to: 24-26

src/interfaces/Peer.ts (1)

33-47: No action needed. All usages of local_flags throughout the codebase (PeerActionCell.tsx, SSHButton.tsx, PeerSSHToggle.tsx) already employ optional chaining (peer?.local_flags?.server_ssh_allowed), which safely handles the optional nature of the property.

src/components/ui/TruncatedText.tsx (2)

1-1: LGTM: Import updated for Tooltip migration.

The import correctly replaces HoverCard with the new Tooltip components.


58-80: LGTM: Tooltip implementation correctly replaces HoverCard.

The new Tooltip structure properly maintains:

  • Open state management via open and onOpenChange
  • Delay behavior with delayDuration={650}
  • Proper prop forwarding to trigger and content
  • Event handling (stopPropagation)
  • Existing styling and layout
src/components/ui/ResourceCountBadge.tsx (1)

13-33: LGTM: Component implementation is solid.

The ResourceCountBadge correctly:

  • Conditionally enables navigation only when the group has an id
  • Stops event propagation to prevent parent handlers from firing
  • Uses the singularize helper with showZero=true for proper pluralization
  • Applies conditional cursor styling and hover effects

The navigation URL structure (/group?id=${group?.id}&tab=resources) is appropriate for Next.js App Router.

src/modules/groups/details/GroupPeersSection.tsx (2)

106-109: LGTM: Props type definition is clean.

The Props type correctly defines optional peers and isLoading parameters, aligning with the loading state propagation pattern introduced across group detail sections.


111-120: LGTM: Loading state properly propagated.

The component correctly:

  • Destructures the new Props type
  • Provides a sensible default (isLoading = true) to show loading state when not explicitly controlled
  • Forwards isLoading to GroupPeersTable for consistent loading behavior

This aligns with the broader pattern of loading state propagation across group detail sections.

src/modules/access-control/ssh/SSHAccessType.tsx (2)

1-16: LGTM: Imports and Props type are well-structured.

The component correctly:

  • Imports necessary Select components and icons
  • Uses usePermissions hook for permission checks
  • Defines a precise Props type with union type "full" | "limited" for type safety
  • Uses proper React types for the onChange handler

18-50: LGTM: Component implementation is solid.

The SSHAccessType component correctly:

  • Uses permissions to control editability
  • Renders appropriate icons for each access type
  • Safely casts the select value (since it's controlled with only two options)
  • Includes data-cy attributes for testing
  • Provides clear visual feedback for the selected state

The permission check requiring both update and create permissions is strict but appears intentional for policy modifications.

src/modules/groups/details/GroupResourcesSection.tsx (2)

103-106: LGTM: Props type follows the established pattern.

The Props type correctly defines optional resources and isLoading parameters, consistent with other group detail sections.


108-111: LGTM: Loading state properly integrated.

The component correctly:

  • Provides a safe default value (isLoading = true)
  • Forwards isLoading to the DataTable component
  • Maintains consistency with the loading state propagation pattern

Also applies to: 125-125

src/modules/users/UserPeersSection.tsx (2)

1-17: LGTM: Imports and Props type are appropriate.

The component correctly imports necessary dependencies and defines a clean Props type with a required user parameter.


34-72: LGTM: Component rendering is well-structured.

The component correctly:

  • Uses Suspense with an appropriate skeleton fallback
  • Passes filtered peers to MinimalPeersTable
  • Implements portal-based heading rendering
  • Provides a clear NoResults card for empty states
  • Properly handles loading states
src/modules/networks/NetworkProvider.tsx (4)

15-16: LGTM: Context and state additions follow established patterns.

The new policy editing capability correctly:

  • Imports necessary types and providers
  • Adds openEditPolicyModal to the context API
  • Introduces currentPolicy state following the same pattern as other modal state management

Also applies to: 36-36, 63-63


126-129: LGTM: openEditPolicyModal implementation is straightforward.

The function correctly:

  • Stores the policy to be edited in state
  • Opens the policy modal
  • Follows the same pattern as other modal opening functions

258-258: LGTM: Provider value correctly exports openEditPolicyModal.

The context provider properly includes openEditPolicyModal in its value, making it available to consumers.


280-310: LGTM: Modal lifecycle correctly handles policy editing.

The refactored policy modal:

  • Wraps content in PoliciesProvider for policy management context
  • Properly clears currentPolicy state in onOpenChange
  • Passes currentPolicy to AccessControlModalContent for editing
  • Clears all related state in the onSuccess callback
  • Maintains existing mutation and update flows

The integration ensures policy state is properly managed throughout the modal lifecycle.

src/modules/groups/details/GroupPoliciesSection.tsx (2)

9-12: LGTM: Props type follows the established pattern.

The Props type correctly defines optional policies and isLoading parameters, consistent with other group detail sections in this PR.


14-14: LGTM: Loading state properly integrated.

The component correctly:

  • Provides a safe default value (isLoading = true)
  • Forwards isLoading to AccessControlTable
  • Maintains consistency with the loading state propagation pattern across all group detail sections

Also applies to: 19-19

src/modules/groups/details/GroupSetupKeysSection.tsx (1)

10-18: LGTM!

The Props type and loading state propagation are well-structured. The default isLoading = true ensures the table shows a loading state until the parent explicitly indicates data is ready, which is a safe default.

src/modules/access-control/table/AccessControlSourcesCell.tsx (1)

29-36: LGTM!

The permission-gated edit button and SSH-specific showUsers prop are well-implemented. The group class conditional enables the hover reveal pattern for TransparentEditIconButton, which is consistent with other components in the codebase.

src/components/ui/PolicyDirection.tsx (1)

73-84: LGTM!

The button correctly applies disabled styling for both the disabled prop and netbird-ssh protocol. The visual feedback is consistent with the interaction constraints.

src/app/(dashboard)/group/page.tsx (2)

157-165: LGTM!

The destructured { groupDetails, isLoading } pattern cleanly separates data from loading state. Count derivations with fallback to 0 handle the loading/empty cases well.


268-312: LGTM!

The loading state is correctly propagated to all group detail sections. The pattern is consistent and ensures each section can render appropriate loading UI.

src/modules/groups/details/GroupNetworkRoutesSection.tsx (1)

8-16: LGTM!

The Props type and loading state handling follow the same consistent pattern as other group detail sections.

src/components/HoverCard.tsx (1)

26-40: Verify asChild={true} behavior with wrapper div.

The asChild prop is set to true, which typically means the component merges its props onto its single child element. Here, the child is a plain <div>, which should work, but it's worth verifying this is the intended behavior rather than passing asChild={false} (or omitting it) to have the Content render its own element.

If the intent is simply to wrap children in a styled container, consider removing asChild:

🔎 Alternative if asChild is not needed
      <HoverCardPrimitive.Content
        ref={ref}
-       asChild={true}
        side={side}
        sideOffset={sideOffset}
        className={cn(tooltipVariants({ variant }), className)}
        {...props}
-     >
-       <div>{props.children}</div>
-     </HoverCardPrimitive.Content>
+     />
src/components/ui/GroupBadge.tsx (2)

43-50: LGTM!

The redirect helper is clean with proper URL encoding for the tab parameter. The early return for missing group.id prevents navigation to invalid URLs for unsaved groups.


55-63: LGTM!

The useHover condition correctly enables hover styling when either onClick or redirectToGroupPage is set. The click handler composes both behaviors (custom onClick + redirect), allowing flexible usage.

src/components/ui/PeerCountBadge.tsx (1)

44-63: LGTM!

The conditional rendering logic for switching between ResourceCountBadge and peer count Badge is clear and well-structured. The redirect behavior and styling are consistent with the ResourceCountBadge pattern.

src/modules/dns-nameservers/table/NameserverDistributionGroupsCell.tsx (1)

26-31: LGTM!

The permission-driven conditional rendering for the edit icon follows established patterns. The group class correctly enables the CSS group-hover behavior required by TransparentEditIconButton.

src/modules/groups/details/useGroupDetails.ts (1)

151-154: LGTM on the return structure refactor.

The new return structure { groupDetails, isLoading } provides a cleaner API for consumers to handle loading states. The memoization of groupDetails is well-implemented with correct dependencies.

src/modules/peer/PeerSSHToggle.tsx (3)

50-72: LGTM on policy filtering logic.

The assignedPolicies memo correctly filters SSH policies by checking both destinationResource matches and destination group membership, with proper ordering by enabled status.


76-102: LGTM on the disable confirmation flow.

The confirmation dialog clearly communicates the irreversibility of disabling SSH access from NetBird v0.61.0+ and provides a documentation link for users to learn more before proceeding.


149-200: LGTM on the conditional callouts.

The three callout conditions comprehensively cover the SSH configuration edge cases: version incompatibility, server disabled with policies, and server enabled without policies. This provides good guidance to users.

src/app/(dashboard)/team/user/page.tsx (2)

134-143: LGTM on tab visibility logic.

The visibility conditions correctly handle the different user types and permissions. Service users appropriately default to the access-tokens tab, while regular users default to peers.


274-330: LGTM on Tabs implementation.

The Tabs UI is well-structured with conditional rendering for tabs based on user type and permissions. The hidden prop on TabsList elegantly handles single-tab scenarios.

src/components/ui/MultipleGroups.tsx (1)

109-123: LGTM on conditional badge rendering.

The ternary chain for rendering ResourceCountBadge, UserCountStack, or PeerCountBadge based on the props is clear and provides good flexibility for different use cases.

src/modules/access-control/useAccessControl.ts (2)

444-457: LGTM on parsePortsToStrings helper.

The helper cleanly converts ports and port ranges into a sorted string array, correctly handling single-port ranges as simple port numbers.


151-164: LGTM on SSH state initialization.

The conditional initialization correctly determines sshAccessType based on whether an existing SSH policy has authorized_groups defined, providing proper defaults for both new and existing policies.

src/modules/access-control/ssh/SSHAuthorizedGroups.tsx (1)

100-138: LGTM on AuthorizedUserRow component.

The component correctly resolves the group, filters users by group membership, and renders the appropriate UI components. The memoization is properly applied.

src/modules/access-control/ssh/SSHUsernameSelector.tsx (1)

29-57: LGTM!

Component setup, state management, and the toggle/notFound logic are well-implemented. The toggle correctly handles add/remove and respects the disabled state.

src/modules/access-control/AccessControlModal.tsx (6)

54-56: LGTM!

SSH-related imports are correctly added to support the new SSH protocol functionality.


179-182: LGTM!

SSH-related state is properly destructured from useAccessControl and wired into the SSH UI components.


291-298: LGTM!

NetBird SSH protocol option is correctly added to the protocol selector.


307-326: Verify source selector configuration for SSH.

Line 313 sets showPeers={protocol !== "netbird-ssh"}, hiding peers in the source selector when SSH is selected. Given that SSH connections originate from peers, verify this is the intended behavior. Perhaps the source should still show peers since SSH clients are peers?


381-423: LGTM!

SSH-specific UI block is well-structured with appropriate warnings for destination resource scenarios and proper integration of SSHAccessType and SSHAuthorizedGroups components.


425-453: LGTM!

Ports UI is correctly preserved for non-SSH protocols while being hidden when SSH is selected.

Comment thread src/app/(dashboard)/peer/page.tsx
Comment thread src/components/ui/ResourceCountBadge.tsx
Comment thread src/contexts/PoliciesProvider.tsx
Comment thread src/modules/access-control/ssh/SSHAuthorizedGroups.tsx
Comment thread src/modules/access-control/ssh/SSHUsernameSelector.tsx
Comment thread src/modules/networks/resources/ResourcePolicyCell.tsx
Comment thread src/modules/peers/PeerActionCell.tsx
@heisbrot heisbrot merged commit bf81aeb into main Dec 30, 2025
4 checks passed
@heisbrot heisbrot deleted the feature/ssh-authorized-users branch December 30, 2025 08:27
@coderabbitai coderabbitai Bot mentioned this pull request Jan 16, 2026
This was referenced Jan 25, 2026
@coderabbitai coderabbitai Bot mentioned this pull request Feb 2, 2026
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