From 3f854b01a08fb4b1e01ca3fceda7c40b46279d4a Mon Sep 17 00:00:00 2001 From: Sarooj bukhari Date: Wed, 4 Oct 2023 14:37:23 +0500 Subject: [PATCH] Add support to network routes with peer group (#275) support to routes with peer group updated the view logic to support the new type updated peer view as well for now, we are supporting a single group, but that can be extended --- src/components/PeerUpdate.tsx | 337 +++++++++++++--------- src/components/RouteAddNew.tsx | 240 +++++++++++----- src/components/RoutePeerUpdate.tsx | 183 +++++++++--- src/components/RouteUpdate.tsx | 219 +++++++++------ src/components/SetupKeyEdit.tsx | 2 +- src/index.css | 71 ++++- src/store/route/sagas.ts | 23 +- src/store/route/types.ts | 24 +- src/utils/routes.ts | 180 ++++++------ src/views/Peers.tsx | 22 +- src/views/Routes.tsx | 433 +++++++++++++++++------------ src/views/SetupKeys.tsx | 2 +- 12 files changed, 1132 insertions(+), 604 deletions(-) diff --git a/src/components/PeerUpdate.tsx b/src/components/PeerUpdate.tsx index 107646c1..6e22d92b 100644 --- a/src/components/PeerUpdate.tsx +++ b/src/components/PeerUpdate.tsx @@ -35,6 +35,7 @@ import { LockOutlined, EditOutlined, ExclamationCircleOutlined, + ExclamationCircleFilled, } from "@ant-design/icons"; import { RuleObject } from "antd/lib/form"; import { useGetTokenSilently } from "../utils/token"; @@ -145,10 +146,22 @@ const PeerUpdate = (props: any) => { useEffect(() => { setPeerRoutes([]); + const temp: any[] = []; + if (peer && peer.groups) { + peer?.groups?.forEach((pg: any) => { + routes.forEach((route: any) => { + if (route.peer_groups?.includes(pg.id)) { + temp.push(route); + } + }); + }); + } + const filterPeerRoutes: any = routes.filter( (route) => route.peer === peer.id ); - setPeerRoutes(filterPeerRoutes); + let mergeArr: any = [...filterPeerRoutes, ...temp]; + setPeerRoutes(mergeArr); const filterNotPeerRoutes: any = routes.filter( (route) => route.peer !== peer.id ); @@ -180,11 +193,13 @@ const PeerUpdate = (props: any) => { useEffect(() => {}, [users]); const routeAddAllowed = (os: string): boolean => { - return os !== "" - && !os.toLowerCase().startsWith("darwin") - && !os.toLowerCase().startsWith("windows") - && !os.toLowerCase().startsWith("android") - } + return ( + os !== "" && + !os.toLowerCase().startsWith("darwin") && + !os.toLowerCase().startsWith("windows") && + !os.toLowerCase().startsWith("android") + ); + }; const toggleEditName = (status: boolean, value?: string) => { setEditName(status); @@ -396,13 +411,13 @@ const PeerUpdate = (props: any) => { const style = { marginTop: 85 }; if (savedGroups.loading) { message.loading({ - content: "Updating peer groups...", + content: "Updating peer group...", key: saveGroupsKey, style, }); } else if (savedGroups.success) { message.success({ - content: "Peer groups have been successfully updated.", + content: "Peer group have been successfully updated.", key: saveGroupsKey, duration: 2, style, @@ -415,7 +430,7 @@ const PeerUpdate = (props: any) => { } else if (savedGroups.error) { message.error({ content: - "Failed to update peer groups. You might not have enough permissions.", + "Failed to update peer group. You might not have enough permissions.", key: saveGroupsKey, duration: 2, style, @@ -659,6 +674,48 @@ const PeerUpdate = (props: any) => { } }, [deletedRoute]); + const renderGroupRouting = (rowGroups: string[] | null) => { + let groupsMap = new Map(); + groups.forEach((g) => { + groupsMap.set(g.id!, g); + }); + + let displayGroups: Group[] = []; + if (rowGroups) { + displayGroups = rowGroups + .filter((g) => groupsMap.get(g)) + .map((g) => groupsMap.get(g)!); + } + + const groupToCompare = + peer && + peer.groups && + peer?.groups.map((element1) => { + return element1.id; + }); + + return ( +
+ {displayGroups && + displayGroups.length > 0 && + displayGroups.map((group) => { + if (group.id && !groupToCompare?.includes(group?.id)) { + return null; + } + return ( +
+ + + {group.name} + + {" "} +
+ ); + })} +
+ ); + }; + return ( <> {peer && ( @@ -972,133 +1029,151 @@ const PeerUpdate = (props: any) => { {/* --- */} {!isGroupUpdateView && ( <> - {routeAddAllowed(peer.os) && - -
- - Network routes - - - - - Access other networks without installing NetBird on - every resource. - - - +
+ - {peerRoutes && peerRoutes.length > 0 && ( - - )} - - - {peerRoutes && peerRoutes.length > 0 && ( - + - - - { - return ( - <> - - onRouteEnableChange(checked, record) - } - /> - - ); - }} - /> + + + Access other networks without installing NetBird on + every resource. + + + + {peerRoutes && peerRoutes.length > 0 && ( + + )} + + + {peerRoutes && peerRoutes.length > 0 && ( +
+ + + { + return record.peer_groups ? ( + renderGroupRouting(record.peer_groups) + ) : ( + <> + + onRouteEnableChange(checked, record) + } + /> + + ); + }} + /> - { - return ( - - ); + { + return record.peer_groups ? ( + + + + ) : ( + + ); + }} + /> +
+ )} + + {(peerRoutes === null || peerRoutes.length === 0) && ( + - - )} - - {(peerRoutes === null || peerRoutes.length === 0) && ( - - - You don't have any routes yet - - - - )} -
- } + + You don't have any routes yet + + + + )} +
+
+ )} diff --git a/src/components/RouteAddNew.tsx b/src/components/RouteAddNew.tsx index 35e8589a..d10fa79e 100644 --- a/src/components/RouteAddNew.tsx +++ b/src/components/RouteAddNew.tsx @@ -17,7 +17,9 @@ import { Switch, Modal, Typography, + Tabs, } from "antd"; +import type { TabsProps } from "antd"; import CreatableSelect from "react-select/creatable"; import { Route, RouteToSave } from "../store/route/types"; import { Header } from "antd/es/layout/layout"; @@ -38,6 +40,7 @@ const { Panel } = Collapse; interface FormRoute extends Route {} const RouteAddNew = (selectedPeer: any) => { + const [activeTab, setActiveTab] = useState(null); const { blueTagRender, handleChangeTags, @@ -75,6 +78,10 @@ const RouteAddNew = (selectedPeer: any) => { const [peerNameToIP, peerIPToName, peerIPToID] = initPeerMaps(peers); const [newRoute, setNewRoute] = useState(false); + useEffect(() => { + if (setupNewRouteVisible) setActiveTab("routingPeer"); + }, [setupNewRouteVisible]); + useEffect(() => { if (editName) inputNameRef.current!.focus({ @@ -159,6 +166,12 @@ const RouteAddNew = (selectedPeer: any) => { } const createRouteToSave = (inputRoute: FormRoute): RouteToSave => { + if (inputRoute.peer_groups) { + inputRoute = { + ...inputRoute, + peer_groups: [inputRoute.peer_groups[inputRoute.peer_groups.length - 1]], + }; + } let peerIDList = inputRoute.peer.split(routePeerSeparator); let peerID: string; if (peerIDList.length === 1) { @@ -175,24 +188,70 @@ const RouteAddNew = (selectedPeer: any) => { inputRoute.groups ); - return { + const payload = { id: inputRoute.id, network: inputRoute.network, network_id: inputRoute.network_id, description: inputRoute.description, - peer: peerID, enabled: inputRoute.enabled, masquerade: inputRoute.masquerade, metric: inputRoute.metric, groups: existingGroups, groupsToCreate: groupsToCreate, } as RouteToSave; + + if (activeTab === "routingPeer") { + let pay = { ...payload, peer: peerID }; + return pay; + } + + if (activeTab === "groupOfPeers") { + if (inputRoute.peer_groups) { + let [currentPeersGroup, peerGroupsToCreate] = + getExistingAndToCreateGroupsLists(inputRoute.peer_groups); + + let pay = { + ...payload, + peer_groups: currentPeersGroup, + peerGroupsToCreate: peerGroupsToCreate, + }; + return pay; + } + } + + return payload; }; const handleFormSubmit = () => { form .validateFields() .then(() => { + const t = routes.filter((route) => { + if ( + route.network_id === formRoute.network_id && + route.network === formRoute.network + ) { + return route; + } + }); + + if ( + formRoute.peer_groups && + formRoute.peer_groups.length > 0 && + t && + t.length > 0 + ) { + const style = { marginTop: 85 }; + const duplicateNetworkIdKey = "duplicateKey"; + return message.error({ + content: + "A route with this network identifier and network range already exists. Please use a different network identifier or network range.", + key: duplicateNetworkIdKey, + duration: 5, + style, + }); + } + if (!setupNewRouteHA || formRoute.peer != "") { const routeToSave = createRouteToSave(formRoute); dispatch( @@ -243,6 +302,7 @@ const RouteAddNew = (selectedPeer: any) => { const onCancel = () => { if (savedRoute.loading) return; + setActiveTab(null); setEditName(false); dispatch( routeActions.setRoute({ @@ -254,6 +314,7 @@ const RouteAddNew = (selectedPeer: any) => { masquerade: false, enabled: true, groups: [], + peer_groups: [], } as Route) ); setVisibleNewRoute(false); @@ -300,8 +361,15 @@ const RouteAddNew = (selectedPeer: any) => { return Promise.resolve(); }; + const peerGroupsValidaton = (_: RuleObject, value: string) => { + if (value.length < 1) { + return Promise.reject(new Error("Please select a peer group")); + } + return Promise.resolve(); + }; + const selectPreValidator = (obj: RuleObject, value: string[]) => { - if (setupNewRouteHA && formRoute.peer == "") { + if (setupNewRouteHA && formRoute.peer === "") { let [, newGroups] = getExistingAndToCreateGroupsLists(value); if (newGroups.length > 0) { return Promise.reject( @@ -361,56 +429,95 @@ const RouteAddNew = (selectedPeer: any) => { } }; - const styleNotification = { marginTop: 85 }; + const onTabChange = (key: string) => { + setActiveTab(key); + }; - const saveKey = "saving"; - useEffect(() => { - if (savedRoute.loading) { - message.loading({ - content: "Saving...", - key: saveKey, - duration: 0, - style: styleNotification, - }); - } else if (savedRoute.success) { - message.success({ - content: "Route has been successfully added.", - key: saveKey, - duration: 2, - style: styleNotification, + const handleSingleChangeTags = (values: any) => { + const lastValue = values[values.length - 1]; + if (values.length > 0) { + form.setFieldsValue({ + peer_groups: [lastValue], }); - dispatch(routeActions.setSetupNewRouteVisible(false)); - dispatch(routeActions.setSetupEditRouteVisible(false)); - dispatch(routeActions.setSetupEditRoutePeerVisible(false)); - dispatch(routeActions.setSavedRoute({ ...savedRoute, success: false })); - dispatch(routeActions.resetSavedRoute(null)); - } else if (savedRoute.error) { - let errorMsg = "Failed to update network route"; - switch (savedRoute.error.statusCode) { - case 403: - errorMsg = - "Failed to update network route. You might not have enough permissions."; - break; - default: - errorMsg = savedRoute.error.data.message - ? savedRoute.error.data.message - : errorMsg; - break; - } - message.error({ - content: errorMsg, - key: saveKey, - duration: 5, - style: styleNotification, - }); - dispatch(routeActions.setSavedRoute({ ...savedRoute, error: null })); - dispatch(routeActions.resetSavedRoute(null)); } - }, [savedRoute]); + }; + + const items: TabsProps["items"] = [ + { + key: "routingPeer", + label: "Routing Peer", + children: ( + <> + + Assign a peer as a routing peer for the Network CIDR + + {activeTab === "routingPeer" && ( + + + {tagGroups.map((m, index) => ( + + ))} + + + )} + + ), + }, + ]; return ( <> - {route && ( + {route && setupNewRouteVisible && ( { marginBottom: "5px", }} > - Add a unique cryptographic key that is assigned + Add a unique network identifier that is assigned to each device { {!!!selectedPeer.selectedPeer && ( - - - Assign a peer as a routing peer for the Network CIDR - - - + + + ), + }, + { + key: "groupOfPeers", + label: "Peer group", + disabled: true, + children: ( + <> + + Assign peer group with Linux machines to be used as routing peers + + + + + + ), + }, + ]; + return ( <> @@ -409,39 +550,13 @@ const RoutePeerUpdate = () => { - - - Assign a peer as a routing peer for the Network CIDR - - - - + {formRoute.peer_groups ? ( + <> + + + Assign peer group with Linux machines to be used as + routing peers + + + + + + ) : ( + <> + + + Assign a routing peer to the network. This peer has to + reside in the network + + +