diff --git a/src/components/PeerUpdate.tsx b/src/components/PeerUpdate.tsx
index 137ce60..ec76e90 100644
--- a/src/components/PeerUpdate.tsx
+++ b/src/components/PeerUpdate.tsx
@@ -353,7 +353,7 @@ const PeerUpdate = () => {
max={59}/>
diff --git a/src/components/RouteUpdate.tsx b/src/components/RouteUpdate.tsx
index 7429ffc..1709472 100644
--- a/src/components/RouteUpdate.tsx
+++ b/src/components/RouteUpdate.tsx
@@ -19,7 +19,7 @@ import {
Typography
} from "antd";
import {CloseOutlined, FlagFilled, QuestionCircleFilled} from "@ant-design/icons";
-import {Route} from "../store/route/types";
+import {Route, RouteToSave} from "../store/route/types";
import {Header} from "antd/es/layout/layout";
import {RuleObject} from "antd/lib/form";
import cidrRegex from 'cidr-regex';
@@ -31,6 +31,7 @@ import {
transformGroupedDataTable
} from '../utils/routes'
import {useGetAccessTokenSilently} from "../utils/token";
+import {useGetGroupTagHelpers} from "../utils/groups";
const {Paragraph} = Typography;
@@ -38,6 +39,17 @@ interface FormRoute extends Route {
}
const RouteUpdate = () => {
+ const {
+ tagRender,
+ handleChangeTags,
+ dropDownRender,
+ optionRender,
+ tagGroups,
+ getExistingAndToCreateGroupsLists,
+ getGroupNamesFromIDs,
+ selectValidator
+ } = useGetGroupTagHelpers()
+ const {Option} = Select;
const {getAccessTokenSilently} = useGetAccessTokenSilently()
const dispatch = useDispatch()
const setupNewRouteVisible = useSelector((state: RootState) => state.route.setupNewRouteVisible)
@@ -46,7 +58,6 @@ const RouteUpdate = () => {
const route = useSelector((state: RootState) => state.route.route)
const routes = useSelector((state: RootState) => state.route.data)
const savedRoute = useSelector((state: RootState) => state.route.savedRoute)
- // const [groupedDataTable, setGroupedDataTable] = useState([] as GroupedDataTable[]);
const [previousRouteKey, setPreviousRouteKey] = useState("")
const [editName, setEditName] = useState(false)
const [editDescription, setEditDescription] = useState(false)
@@ -63,11 +74,12 @@ const RouteUpdate = () => {
const defaultStatusMSG = "Status"
const [statusMSG, setStatusMSG] = useState(defaultStatusMSG)
const [peerNameToIP, peerIPToName] = initPeerMaps(peers);
+ const [newRoute, setNewRoute] = useState(false)
const optionsDisabledEnabled = [{label: 'Enabled', value: true}, {label: 'Disabled', value: false}]
useEffect(() => {
- if (setupNewRouteHA) {
+ if (!newRoute ) {
setRoutingPeerMSG("Add additional routing peer")
setMasqueradeMSG("Update Masquerade")
setStatusMSG("Update Status")
@@ -77,7 +89,7 @@ const RouteUpdate = () => {
setStatusMSG(defaultStatusMSG)
setPreviousRouteKey("")
}
- }, [setupNewRouteHA])
+ }, [newRoute])
useEffect(() => {
if (editName) inputNameRef.current!.focus({
@@ -96,9 +108,15 @@ const RouteUpdate = () => {
const fRoute = {
...route,
+ groups: getGroupNamesFromIDs(route.groups)
} as FormRoute
setFormRoute(fRoute)
setPreviousRouteKey(fRoute.network_id + fRoute.network)
+ if (!route.network_id) {
+ setNewRoute(true)
+ } else {
+ setNewRoute(false)
+ }
form.setFieldsValue(fRoute)
}, [route])
@@ -114,7 +132,7 @@ const RouteUpdate = () => {
}
})
- const createRouteToSave = (inputRoute: FormRoute): Route => {
+ const createRouteToSave = (inputRoute: FormRoute): RouteToSave => {
let peerIDList = inputRoute.peer.split(routePeerSeparator)
let peerID: string
if (peerIDList[1]) {
@@ -123,6 +141,8 @@ const RouteUpdate = () => {
peerID = peerNameToIP[inputRoute.peer]
}
+ let [ existingGroups, groupsToCreate ] = getExistingAndToCreateGroupsLists(inputRoute.groups)
+
return {
id: inputRoute.id,
network: inputRoute.network,
@@ -131,8 +151,10 @@ const RouteUpdate = () => {
peer: peerID,
enabled: inputRoute.enabled,
masquerade: inputRoute.masquerade,
- metric: inputRoute.metric
- } as Route
+ metric: inputRoute.metric,
+ groups: existingGroups,
+ groupsToCreate: groupsToCreate,
+ } as RouteToSave
}
const handleFormSubmit = () => {
@@ -195,13 +217,14 @@ const RouteUpdate = () => {
setVisibleNewRoute(false)
setSetupNewRouteHA(false)
setPreviousRouteKey("")
+ setNewRoute(false)
}
const onChange = (data: any) => {
setFormRoute({...formRoute, ...data})
}
- const dropDownRender = (menu: React.ReactElement) => (
+ const peerDropDownRender = (menu: React.ReactElement) => (
<>
{menu}
>
@@ -227,13 +250,32 @@ const RouteUpdate = () => {
return Promise.resolve()
}
+ const peerValidator = (_: RuleObject, value: string) => {
+
+ if (value == "" && newRoute) {
+ return Promise.reject(new Error("Please select routing one peer"))
+ }
+
+ return Promise.resolve()
+ }
+
+ const selectPreValidator = (obj: RuleObject, value: string[]) => {
+ if (setupNewRouteHA && formRoute.peer == '') {
+ let [, newGroups ] = getExistingAndToCreateGroupsLists(value)
+ if (newGroups.length > 0) {
+ return Promise.reject(new Error("You can't add new Groups from the group update view, please remove:\"" + newGroups +"\""))
+ }
+ }
+ return selectValidator(obj, value)
+ }
+
return (
<>
{route &&
{
+ onClick={handleFormSubmit}>{`${newRoute ? 'Create' : 'Save'}`}
}
>
-
@@ -343,12 +385,13 @@ const RouteUpdate = () => {
name="peer"
label={routingPeerMSG}
tooltip="Assign a peer as a routing peer for the Network CIDR"
+ rules={[{validator:peerValidator}]}
>
@@ -360,7 +403,7 @@ const RouteUpdate = () => {
label={masqueradeMSG}
tooltip={masqueradeDisabledMSG}
>
-
+
@@ -372,6 +415,28 @@ const RouteUpdate = () => {
+
+
+
+
+
diff --git a/src/store/route/actions.ts b/src/store/route/actions.ts
index 02c468c..82c987e 100644
--- a/src/store/route/actions.ts
+++ b/src/store/route/actions.ts
@@ -1,5 +1,5 @@
import { ActionType, createAction, createAsyncAction } from 'typesafe-actions';
-import {Route} from './types';
+import {Route, RouteToSave} from './types';
import {ApiError, CreateResponse, DeleteResponse, RequestPayload} from '../../services/api-client/types';
const actions = {
@@ -13,7 +13,7 @@ const actions = {
'SAVE_ROUTE_REQUEST',
'SAVE_ROUTE_SUCCESS',
'SAVE_ROUTE_FAILURE',
- ), CreateResponse, CreateResponse>(),
+ ), CreateResponse, CreateResponse>(),
setSavedRoute: createAction('SET_CREATE_ROUTE')>(),
resetSavedRoute: createAction('RESET_CREATE_ROUTE')(),
diff --git a/src/store/route/sagas.ts b/src/store/route/sagas.ts
index 9afd3d8..3715c9a 100644
--- a/src/store/route/sagas.ts
+++ b/src/store/route/sagas.ts
@@ -3,6 +3,9 @@ import {ApiError, ApiResponse, CreateResponse, DeleteResponse} from '../../servi
import {Route} from './types'
import service from './service';
import actions from './actions';
+import serviceGroup from "../group/service";
+import {Group} from "../group/types";
+import {actions as groupActions} from "../group";
export function* getRoutes(action: ReturnType): Generator {
try {
@@ -40,6 +43,21 @@ export function* saveRoute(action: ReturnType)
const routeToSave = action.payload.payload
+ let groupsToCreate = routeToSave.groupsToCreate
+ if (!groupsToCreate) {
+ groupsToCreate = []
+ }
+
+ // first, create groups that were newly added by user
+ const responsesGroup = yield all(groupsToCreate.map(g => call(serviceGroup.createGroup, {
+ getAccessTokenSilently: action.payload.getAccessTokenSilently,
+ payload: {name: g}
+ })
+ ))
+
+ const resGroups = (responsesGroup as ApiResponse[]).filter(r => r.statusCode === 200).map(g => (g.body as Group)).map(g => g.id)
+ const newGroups = [...routeToSave.groups, ...resGroups]
+
const payloadToSave = {
getAccessTokenSilently: action.payload.getAccessTokenSilently,
payload: {
@@ -50,7 +68,8 @@ export function* saveRoute(action: ReturnType)
metric: routeToSave.metric,
network: routeToSave.network,
network_id: routeToSave.network_id,
- peer: routeToSave.peer
+ peer: routeToSave.peer,
+ groups: newGroups
} as Route
}
@@ -72,8 +91,19 @@ export function* saveRoute(action: ReturnType)
data: response.body
} as CreateResponse));
+ yield put(groupActions.getGroups.request({
+ getAccessTokenSilently: action.payload.getAccessTokenSilently,
+ payload: null
+ }));
+
yield put(actions.getRoutes.request({ getAccessTokenSilently: action.payload.getAccessTokenSilently, payload: null }));
+
} catch (err) {
+ yield put(groupActions.getGroups.request({
+ getAccessTokenSilently: action.payload.getAccessTokenSilently,
+ payload: null
+ }));
+
yield put(actions.saveRoute.failure({
loading: false,
success: false,
diff --git a/src/store/route/types.ts b/src/store/route/types.ts
index c7ac3a8..cf7e2ea 100644
--- a/src/store/route/types.ts
+++ b/src/store/route/types.ts
@@ -1,3 +1,4 @@
+
export interface Route {
id?: string
description: string
@@ -8,4 +9,10 @@ export interface Route {
network_type?: string
metric?: number
masquerade: boolean
+ groups: string[]
+}
+
+export interface RouteToSave extends Route
+{
+ groupsToCreate: string[]
}
\ No newline at end of file
diff --git a/src/utils/routes.ts b/src/utils/routes.ts
index 6355b15..7f0fc4f 100644
--- a/src/utils/routes.ts
+++ b/src/utils/routes.ts
@@ -34,6 +34,7 @@ export interface GroupedDataTable {
description: string
routesCount: number
groupedRoutes: RouteDataTable[]
+ routesGroups: string[]
}
export const transformDataTable = (d:Route[],peerIPToName:PeerIPToName):RouteDataTable[] => {
@@ -52,10 +53,12 @@ export const transformGroupedDataTable = (routes:Route[],peerIPToName:PeerIPToNa
}))
let groupedRoutes:GroupedDataTable[] = []
+
keySet.forEach((p) => {
let hasEnabled = false
let lastRoute:Route
let listedRoutes:Route[] = []
+ let groupList:string[] = []
routes.forEach((r) => {
if ( p === r.network_id + r.network ) {
lastRoute = r
@@ -63,8 +66,10 @@ export const transformGroupedDataTable = (routes:Route[],peerIPToName:PeerIPToNa
hasEnabled = true
}
listedRoutes.push(r)
+ groupList = groupList.concat(r.groups)
}
})
+ groupList = groupList.filter((value,index,arrary) => arrary.indexOf(value) === index)
let groupDataTableRoutes = transformDataTable(listedRoutes,peerIPToName)
groupedRoutes.push({
key: p.toString(),
@@ -75,6 +80,7 @@ export const transformGroupedDataTable = (routes:Route[],peerIPToName:PeerIPToNa
enabled: hasEnabled,
routesCount: groupDataTableRoutes.length,
groupedRoutes: groupDataTableRoutes,
+ routesGroups: groupList,
})
})
return groupedRoutes
diff --git a/src/views/DNS.tsx b/src/views/DNS.tsx
index 92a32d8..c49b370 100644
--- a/src/views/DNS.tsx
+++ b/src/views/DNS.tsx
@@ -301,7 +301,7 @@ export const DNS = () => {
let errorMsg = "Failed to update nameserver group"
switch (savedNSGroup.error.statusCode) {
case 403:
- errorMsg = "Failed to update user. You might not have enough permissions."
+ errorMsg = "Failed to update nameserver group. You might not have enough permissions."
break
default:
errorMsg = savedNSGroup.error.data.message ? savedNSGroup.error.data.message : errorMsg
diff --git a/src/views/Routes.tsx b/src/views/Routes.tsx
index 67ec27c..82e97d9 100644
--- a/src/views/Routes.tsx
+++ b/src/views/Routes.tsx
@@ -9,7 +9,7 @@ import {
Input,
Menu,
message,
- Modal,
+ Modal, Popover,
Radio,
RadioChangeEvent,
Row,
@@ -24,7 +24,7 @@ import {
import {Container} from "../components/Container";
import {useDispatch, useSelector} from "react-redux";
import {RootState} from "typesafe-actions";
-import {Route} from "../store/route/types";
+import {Route, RouteToSave} from "../store/route/types";
import {actions as routeActions} from "../store/route";
import {actions as peerActions} from "../store/peer";
import {filter, sortBy} from "lodash";
@@ -42,6 +42,10 @@ import {
transformGroupedDataTable
} from '../utils/routes'
import {useGetAccessTokenSilently} from "../utils/token";
+import {Group} from "../store/group/types";
+import {TooltipPlacement} from "antd/es/tooltip";
+import {actions as groupActions} from "../store/group";
+import {useGetGroupTagHelpers} from "../utils/groups";
const {Title, Paragraph, Text} = Typography;
const {Column} = Table;
@@ -50,7 +54,11 @@ const {confirm} = Modal;
export const Routes = () => {
const {getAccessTokenSilently} = useGetAccessTokenSilently()
const dispatch = useDispatch()
+ const {
+ getGroupNamesFromIDs,
+ } = useGetGroupTagHelpers()
+ const groups = useSelector((state: RootState) => state.group.data)
const routes = useSelector((state: RootState) => state.route.data);
const failed = useSelector((state: RootState) => state.route.failed);
const loading = useSelector((state: RootState) => state.route.loading);
@@ -58,6 +66,7 @@ export const Routes = () => {
const savedRoute = useSelector((state: RootState) => state.route.savedRoute);
const peers = useSelector((state: RootState) => state.peer.data)
const loadingPeer = useSelector((state: RootState) => state.peer.loading);
+ const setupNewRouteVisible = useSelector((state: RootState) => state.route.setupNewRouteVisible)
const [showTutorial, setShowTutorial] = useState(true)
const [textToSearch, setTextToSearch] = useState('');
const [optionAllEnable, setOptionAllEnable] = useState('enabled');
@@ -67,6 +76,7 @@ export const Routes = () => {
const [routeToAction, setRouteToAction] = useState(null as RouteDataTable | null);
const [groupedDataTable, setGroupedDataTable] = useState([] as GroupedDataTable[]);
const [expandRowsOnClick, setExpandRowsOnClick] = useState(true)
+ const [groupPopupVisible, setGroupPopupVisible] = useState(false as boolean | undefined)
const [peerNameToIP, peerIPToName] = initPeerMaps(peers);
@@ -83,10 +93,6 @@ export const Routes = () => {
key: "view",
label: ()
},
- // {
- // key: "delete",
- // label: ()
- // },
{
key: "delete",
label: ()
@@ -104,12 +110,15 @@ export const Routes = () => {
useEffect(() => {
dispatch(peerActions.getPeers.request({getAccessTokenSilently: getAccessTokenSilently, payload: null}));
+ dispatch(groupActions.getGroups.request({getAccessTokenSilently: getAccessTokenSilently, payload: null}));
}, [])
const filterGroupedDataTable = (routes: GroupedDataTable[]): GroupedDataTable[] => {
const t = textToSearch.toLowerCase().trim()
let f: GroupedDataTable[] = filter(routes, (f) =>
- (f.network_id.toLowerCase().includes(t) || f.network.toLowerCase().includes(t) || f.description.toLowerCase().includes(t) || t === "")
+ (f.network_id.toLowerCase().includes(t) || f.network.toLowerCase().includes(t) ||
+ f.description.toLowerCase().includes(t) || t === "" ||
+ getGroupNamesFromIDs(f.routesGroups).find(u => u.toLowerCase().trim().includes(t)) )
) as GroupedDataTable[]
if (optionAllEnable !== "all") {
f = filter(f, (f) => f.enabled)
@@ -151,10 +160,19 @@ export const Routes = () => {
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: savedRoute.error.data ? savedRoute.error.data : savedRoute.error.message,
+ content: errorMsg,
key: saveKey,
- duration: 2,
+ duration: 5,
style: styleNotification
});
dispatch(routeActions.setSavedRoute({...savedRoute, error: null}))
@@ -224,7 +242,6 @@ export const Routes = () => {
const onClickAddNewRoute = () => {
- dispatch(routeActions.setSetupNewRouteHA(true));
dispatch(routeActions.setSetupNewRouteVisible(true));
dispatch(routeActions.setRoute({
network: '',
@@ -264,7 +281,8 @@ export const Routes = () => {
peer: route.peer ? peerToPeerIP(route.peer, peerNameToIP[route.peer]) : '',
metric: route.metric ? route.metric : 9999,
masquerade: route.masquerade,
- enabled: route.enabled
+ enabled: route.enabled,
+ groups: route.groups
} as Route))
dispatch(routeActions.setSetupNewRouteVisible(true));
}
@@ -293,17 +311,77 @@ export const Routes = () => {
});
}
+ const onPopoverVisibleChange = () => {
+ if (setupNewRouteVisible) {
+ setGroupPopupVisible(false)
+ } else {
+ setGroupPopupVisible(undefined)
+ }
+ }
+
function handleSwitchMasquerade(routeGroup: GroupedDataTable, checked: boolean) {
routeGroup.groupedRoutes.forEach((record) => {
const route = {
...record,
peer: peerNameToIP[record.peer],
masquerade: checked,
- } as Route
+ groupsToCreate: []
+ } as RouteToSave
dispatch(routeActions.saveRoute.request({getAccessTokenSilently: getAccessTokenSilently, payload: route}));
})
}
+ const renderPopoverGroups = (label: string, rowGroups: string[] | null, userToAction: RouteDataTable) => {
+
+ 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)!)
+ }
+
+ let btn =
+
+ if (!displayGroups || displayGroups!.length < 1) {
+ return btn
+ }
+
+ const content = displayGroups?.map((g, i) => {
+ const _g = g as Group
+ const peersCount = ` - ${_g.peers_count || 0} ${(!_g.peers_count || parseInt(_g.peers_count) !== 1) ? 'peers' : 'peer'} `
+ return (
+
+
+ {_g.name}
+
+ {peersCount}
+
+ )
+ })
+ const mainContent = ({content})
+ let popoverPlacement = "top"
+ if (content && content.length > 5) {
+ popoverPlacement = "rightTop"
+ }
+
+ return (
+
+ {btn}
+
+ )
+ }
+
const expandedRowRender = (record: GroupedDataTable) => {
return {
onFilter={(value: string | number | boolean, record) => (record as any).metric.includes(value)}
sorter={(a, b) => ((a as any).metric - ((b as any).metric))}
/>
- {
+ return renderPopoverGroups(text, record.groups, record)
+ }}
+ />
+ {
return text ? enabled : disabled
}}
@@ -336,7 +419,7 @@ export const Routes = () => {
render={(text, record) => {
if (deletedRoute.loading || savedRoute.loading) return <>>
return {
+ onOpenChange={visible => {
if (visible) setRouteToAction(record as RouteDataTable)
}}>
}}
@@ -438,7 +521,7 @@ export const Routes = () => {
sorter={(a, b) => ((a as any).network.localeCompare((b as any).network))}
// defaultSortOrder='ascend'
/>
- {
return text ? enabled :
disabled
@@ -463,8 +546,8 @@ export const Routes = () => {
if (count > 1) {
tag = on
}
- return {tag}
+ return
{tag}
+
}}
/>