Conversation
📝 WalkthroughWalkthroughuseGroupDetails 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
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
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)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
📜 Recent review detailsConfiguration used: defaults Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
⏰ 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)
🔇 Additional comments (2)
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: 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: MissingisNetworksLoadingin loading state computation.The
isLoadingflag combines multiple loading states but omitsisNetworksLoading(from line 46-47), even thoughnetworksis used inlinkedNetworkResourcescomputation. 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
Calloutimport 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@componentsimports (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
eventis a required prop,event?.initiator_namecan be simplified toevent.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
showZeroparameter adds useful zero-state display control. However, the truthy checkif (!count)treats0,undefined, andnullidentically. This may be intentional, but consider whether an explicit check forcount === undefined || count === nullwould better express the intent and avoid potential confusion whencountis explicitly0.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/nullvs.0more 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-sshwhen supported (version >= 0.61.0). The implementation is consistent in bothhandleReconnectand the initialconnecteffect.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
/peersand filters client-side byuser_id. While this works, it could be inefficient with many peers. If an endpoint like/users/${user.id}/peersexists 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 guardingonChangeto avoid redundant calls.The
useEffectcallsonChange("in")wheneverprotocol === "netbird-ssh", even ifvalueis 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
valueto 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 usesgroupDetails === nullwhile sections useisLoading.The
animate-pulseclass is conditionally applied whengroupDetails === null, but the section components receiveisLoading. These might not always be in sync (e.g.,isLoadingcould befalsewhilegroupDetailsis stillnullon error, or vice versa during refetch).Consider using
isLoadingconsistently 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 explicitnullmay cause React warning.When
ruleis falsy,return;is equivalent toreturn undefined;. While React handles this, explicitly returningnullis 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
Propstype extendsReact.HTMLAttributes<HTMLDivElement>, but onlygroup,variant, andclassNameare 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
currentGroupby matchinggroup?.nameagainstdropdownOptions. If group names aren't unique or the provided group's name doesn't match any dropdown option,currentGroupwill beundefinedandpeerCountwill default to 0. Consider matching byidinstead for reliability, or add a fallback to use the passedgroupdirectly.src/modules/peer/PeerSSHToggle.tsx (1)
227-264: Early return without key could be improved.On line 229,
if (!rule) return;returnsundefinedinside a.map(). While React handles this gracefully, it's cleaner to filter out invalid entries or returnnullexplicitly.🔎 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 whengroup.idis undefined.If
group.idis undefined, the filteruser.auto_groups.includes(group.id as string)castsundefinedto"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 ofreduce- result ignored, side effects used instead.The
reduceon lines 273-282 mutates the externalauthorizedGroupsvariable 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: Returnnullinstead of implicitundefined.The early return
if (accessType === "full") return;returnsundefined. For React components, explicitly returningnullis 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.
PostureCheckis 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 != 0is true,p-2is already applied regardless ofsearch. 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
useris sourced from iteratingvalues,values?.includes(user)will always betrue. 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
📒 Files selected for processing (72)
src/app/(dashboard)/group/page.tsxsrc/app/(dashboard)/network-routes/page.tsxsrc/app/(dashboard)/peer/page.tsxsrc/app/(dashboard)/team/user/page.tsxsrc/app/(remote-access)/peer/rdp/page.tsxsrc/app/(remote-access)/peer/ssh/page.tsxsrc/assets/icons/JumpcloudIcon.tsxsrc/assets/icons/OIDCIcon.tsxsrc/components/Badge.tsxsrc/components/DropdownMenu.tsxsrc/components/HoverCard.tsxsrc/components/Label.tsxsrc/components/PeerGroupSelector.tsxsrc/components/PortSelector.tsxsrc/components/Tabs.tsxsrc/components/Tooltip.tsxsrc/components/ui/GroupBadge.tsxsrc/components/ui/GroupBadgeIcon.tsxsrc/components/ui/MultipleGroups.tsxsrc/components/ui/PeerCountBadge.tsxsrc/components/ui/PolicyDirection.tsxsrc/components/ui/ResourceCountBadge.tsxsrc/components/ui/TruncatedText.tsxsrc/contexts/PeerProvider.tsxsrc/contexts/PoliciesProvider.tsxsrc/interfaces/Peer.tssrc/interfaces/Policy.tssrc/modules/access-control/AccessControlModal.tsxsrc/modules/access-control/ssh/SSHAccessType.tsxsrc/modules/access-control/ssh/SSHAuthorizedGroups.tsxsrc/modules/access-control/ssh/SSHUsernameSelector.tsxsrc/modules/access-control/table/AccessControlDestinationsCell.tsxsrc/modules/access-control/table/AccessControlPortsCell.tsxsrc/modules/access-control/table/AccessControlSourcesCell.tsxsrc/modules/access-control/useAccessControl.tssrc/modules/activity/ActivityDescription.tsxsrc/modules/dns-nameservers/table/NameserverDistributionGroupsCell.tsxsrc/modules/dns-nameservers/table/NameserverGroupTable.tsxsrc/modules/groups/details/GroupNameserversSection.tsxsrc/modules/groups/details/GroupNetworkRoutesSection.tsxsrc/modules/groups/details/GroupPeersSection.tsxsrc/modules/groups/details/GroupPoliciesSection.tsxsrc/modules/groups/details/GroupResourcesSection.tsxsrc/modules/groups/details/GroupSetupKeysSection.tsxsrc/modules/groups/details/GroupUsersSection.tsxsrc/modules/groups/details/useGroupDetails.tssrc/modules/groups/table/GroupsTable.tsxsrc/modules/groups/useGroupIdentification.tssrc/modules/networks/NetworkProvider.tsxsrc/modules/networks/resources/ResourceGroupCell.tsxsrc/modules/networks/resources/ResourcePolicyCell.tsxsrc/modules/networks/routing-peers/NetworkRoutingPeerName.tsxsrc/modules/networks/table/NetworkActionCell.tsxsrc/modules/peer/PeerSSHInstructions.tsxsrc/modules/peer/PeerSSHPolicyInfo.tsxsrc/modules/peer/PeerSSHPolicyModal.tsxsrc/modules/peer/PeerSSHToggle.tsxsrc/modules/peers/PeerActionCell.tsxsrc/modules/peers/PeerConnectButton.tsxsrc/modules/peers/PeerGroupCell.tsxsrc/modules/posture-checks/table/cells/PostureCheckPolicyUsageCell.tsxsrc/modules/remote-access/rdp/RDPCredentialsModal.tsxsrc/modules/remote-access/ssh/SSHButton.tsxsrc/modules/remote-access/ssh/SSHTooltip.tsxsrc/modules/remote-access/useNetBirdClient.tssrc/modules/route-group/NetworkRoutesTable.tsxsrc/modules/routes/RoutePeerCell.tsxsrc/modules/setup-keys/SetupKeysTable.tsxsrc/modules/users/HorizontalUsersStack.tsxsrc/modules/users/UserPeersSection.tsxsrc/utils/helpers.tssrc/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-1tomb-1.5is 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
PeerCounterbehavior.src/contexts/PeerProvider.tsx (1)
142-145: The implementation is correct as-is.The
PeerSSHInstructionscomponent 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-400todark:text-nb-gray-300aligns 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
iconPropertieshelper, and maintains type safety withReadonly<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
SegmentedTabsandNetBirdIconto 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
isAllGroupprop 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
useMemohook correctly derives the display text based onisAllGroupflag and user count, with appropriate dependencies to prevent unnecessary recalculations.
95-95: LGTM! Defensive fallback for user name.The fallback to
user?.idwhenuser?.nameis unavailable prevents potential runtime issues with missing user names.
108-108: LGTM! Dynamic user count display.Using the memoized
userCountTextproperly reflects the context (either "All Users" or the numeric count) based on theisAllGroupflag.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
AccessControlModalContenton each open via the key prop pattern. The cleanup logic appropriately resetscurrentPolicyon 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
defaultandxspresets are well-defined and follow consistent styling patterns.
60-60: LGTM! Size prop integration is correct.The new
sizeprop is properly defaulted and integrated into the className generation via thevariants()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
showSSHButtonis 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
insetfrom conditional to constantfalseunifies 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 theinitialPortsparameter 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
falseregardless 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
isLoadingprop enables external control of the loading state, aligning with the broader pattern introduced by the updateduseGroupDetailshook. The default value oftrueis 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()andstopPropagation()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}andredirectGroupTab={"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
isNativeSSHSupportedpattern.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-sshfor peers running v0.61.0+ and falls back totcpfor 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) andpeer?.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
hiddenprop 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 offalsemaintains backward compatibility.Also applies to: 49-78
src/modules/routes/RoutePeerCell.tsx (1)
47-54: LGTM!The refactor to use
PeerCountBadgeand the newGroupBadgenavigation 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
AuthorizedGroupstype andauthorized_groupsproperty 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 theProtocolunion type correctly extends the supported protocols for the new SSH feature.src/modules/groups/useGroupIdentification.ts (1)
12-17: LGTM!The new
isJumpcloudGroupandisOIDCGroupchecks follow the established pattern. The refactoredisRegularGrouplogic (now including JWT groups) and simplifiedisIntegrationGroupcheck 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
canUpdatecheck ensures the edit affordance only appears for authorized users.src/modules/groups/details/GroupNameserversSection.tsx (1)
11-19: LGTM!The
isLoadingprop propagation with a sensible default oftruefollows 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 oflocal_flagsthroughout 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
openandonOpenChange- 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
singularizehelper withshowZero=truefor 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
peersandisLoadingparameters, 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
isLoadingtoGroupPeersTablefor consistent loading behaviorThis 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
usePermissionshook 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
updateandcreatepermissions 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
resourcesandisLoadingparameters, consistent with other group detail sections.
108-111: LGTM: Loading state properly integrated.The component correctly:
- Provides a safe default value (
isLoading = true)- Forwards
isLoadingto theDataTablecomponent- 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
userparameter.
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
openEditPolicyModalto the context API- Introduces
currentPolicystate following the same pattern as other modal state managementAlso 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
openEditPolicyModalin its value, making it available to consumers.
280-310: LGTM: Modal lifecycle correctly handles policy editing.The refactored policy modal:
- Wraps content in
PoliciesProviderfor policy management context- Properly clears
currentPolicystate inonOpenChange- Passes
currentPolicytoAccessControlModalContentfor editing- Clears all related state in the
onSuccesscallback- 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
policiesandisLoadingparameters, 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
isLoadingtoAccessControlTable- 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 = trueensures 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
showUsersprop are well-implemented. Thegroupclass conditional enables the hover reveal pattern forTransparentEditIconButton, 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
disabledprop andnetbird-sshprotocol. 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: VerifyasChild={true}behavior with wrapper div.The
asChildprop is set totrue, 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 passingasChild={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.idprevents navigation to invalid URLs for unsaved groups.
55-63: LGTM!The
useHovercondition correctly enables hover styling when eitheronClickorredirectToGroupPageis 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
ResourceCountBadgeand peer countBadgeis clear and well-structured. The redirect behavior and styling are consistent with theResourceCountBadgepattern.src/modules/dns-nameservers/table/NameserverDistributionGroupsCell.tsx (1)
26-31: LGTM!The permission-driven conditional rendering for the edit icon follows established patterns. The
groupclass correctly enables the CSSgroup-hoverbehavior required byTransparentEditIconButton.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 ofgroupDetailsis well-implemented with correct dependencies.src/modules/peer/PeerSSHToggle.tsx (3)
50-72: LGTM on policy filtering logic.The
assignedPoliciesmemo correctly filters SSH policies by checking bothdestinationResourcematches 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
hiddenprop onTabsListelegantly handles single-tab scenarios.src/components/ui/MultipleGroups.tsx (1)
109-123: LGTM on conditional badge rendering.The ternary chain for rendering
ResourceCountBadge,UserCountStack, orPeerCountBadgebased on the props is clear and provides good flexibility for different use cases.src/modules/access-control/useAccessControl.ts (2)
444-457: LGTM onparsePortsToStringshelper.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
sshAccessTypebased on whether an existing SSH policy hasauthorized_groupsdefined, providing proper defaults for both new and existing policies.src/modules/access-control/ssh/SSHAuthorizedGroups.tsx (1)
100-138: LGTM onAuthorizedUserRowcomponent.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/notFoundlogic 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
useAccessControland 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
SSHAccessTypeandSSHAuthorizedGroupscomponents.
425-453: LGTM!Ports UI is correctly preserved for non-SSH protocols while being hidden when SSH is selected.
Summary by CodeRabbit
New Features
UI/UX Improvements
Bug Fixes
✏️ Tip: You can customize this high-level summary in your review settings.