mirror of
https://github.com/netbirdio/dashboard.git
synced 2026-01-26 01:21:04 +00:00
Update route and add on update peer (#205)
This commit is contained in:
@@ -7,7 +7,7 @@ import {
|
||||
Col,
|
||||
Collapse,
|
||||
Divider,
|
||||
Drawer,
|
||||
message,
|
||||
Form,
|
||||
Input,
|
||||
Radio,
|
||||
@@ -36,6 +36,8 @@ import { RuleObject } from "antd/lib/form";
|
||||
import { useGetTokenSilently } from "../utils/token";
|
||||
import { timeAgo } from "../utils/common";
|
||||
import { actions as routeActions } from "../store/route";
|
||||
import RouteAddNew from "./RouteAddNew";
|
||||
import { Route } from "../store/route/types";
|
||||
import {useGetGroupTagHelpers} from "../utils/groups";
|
||||
|
||||
const { Paragraph } = Typography;
|
||||
@@ -65,7 +67,13 @@ const PeerUpdate = () => {
|
||||
const updatedPeers = useSelector(
|
||||
(state: RootState) => state.peer.updatedPeer
|
||||
);
|
||||
|
||||
const savedRoute = useSelector((state: RootState) => state.route.savedRoute);
|
||||
const deletedRoute = useSelector(
|
||||
(state: RootState) => state.route.deletedRoute
|
||||
);
|
||||
const setupNewRouteVisible = useSelector(
|
||||
(state: RootState) => state.route.setupNewRouteVisible
|
||||
);
|
||||
const [tagGroups, setTagGroups] = useState([] as string[]);
|
||||
const [selectedTagGroups, setSelectedTagGroups] = useState([] as string[]);
|
||||
const [peerGroups, setPeerGroups] = useState([] as GroupPeer[]);
|
||||
@@ -85,6 +93,8 @@ const PeerUpdate = () => {
|
||||
} as PeerGroupsToSave);
|
||||
const routes = useSelector((state: RootState) => state.route.data);
|
||||
const [form] = Form.useForm();
|
||||
const styleNotification = { marginTop: 85 };
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
//Unmounting component clean
|
||||
@@ -274,6 +284,20 @@ const PeerUpdate = () => {
|
||||
setCallingPeerAPI(false);
|
||||
setSubmitRunning(false);
|
||||
setEstimatedName("");
|
||||
|
||||
dispatch(routeActions.setSetupNewRouteVisible(false));
|
||||
dispatch(
|
||||
routeActions.setRoute({
|
||||
network: "",
|
||||
network_id: "",
|
||||
description: "",
|
||||
peer: "",
|
||||
masquerade: true,
|
||||
metric: 9999,
|
||||
enabled: true,
|
||||
groups: [],
|
||||
} as Route)
|
||||
);
|
||||
};
|
||||
|
||||
const noUpdateToGroups = (): Boolean => {
|
||||
@@ -432,6 +456,90 @@ const PeerUpdate = () => {
|
||||
setFormPeer({ ...formPeer, login_expiration_enabled: checked });
|
||||
};
|
||||
|
||||
const onClickAddNewRoute = () => {
|
||||
dispatch(routeActions.setSetupNewRouteVisible(true));
|
||||
dispatch(
|
||||
routeActions.setRoute({
|
||||
network: "",
|
||||
network_id: "",
|
||||
description: "",
|
||||
peer: "",
|
||||
masquerade: true,
|
||||
metric: 9999,
|
||||
enabled: true,
|
||||
groups: [],
|
||||
} as Route)
|
||||
);
|
||||
};
|
||||
|
||||
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 updated.",
|
||||
key: saveKey,
|
||||
duration: 2,
|
||||
style: styleNotification,
|
||||
});
|
||||
dispatch(routeActions.setSetupNewRouteVisible(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 deleteKey = "deleting";
|
||||
useEffect(() => {
|
||||
const style = { marginTop: 85 };
|
||||
if (deletedRoute.loading) {
|
||||
message.loading({ content: "Deleting...", key: deleteKey, style });
|
||||
} else if (deletedRoute.success) {
|
||||
message.success({
|
||||
content: "Route has been successfully deleted.",
|
||||
key: deleteKey,
|
||||
duration: 2,
|
||||
style,
|
||||
});
|
||||
dispatch(routeActions.resetDeletedRoute(null));
|
||||
} else if (deletedRoute.error) {
|
||||
message.error({
|
||||
content:
|
||||
"Failed to remove route. You might not have enough permissions.",
|
||||
key: deleteKey,
|
||||
duration: 2,
|
||||
style,
|
||||
});
|
||||
dispatch(routeActions.resetDeletedRoute(null));
|
||||
}
|
||||
}, [deletedRoute]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{peer && (
|
||||
@@ -460,71 +568,71 @@ const PeerUpdate = () => {
|
||||
>
|
||||
<Row gutter={16}>
|
||||
<Col span={24}>
|
||||
<Row align="top">
|
||||
<Col flex="auto">
|
||||
{!editName && peer.id && formPeer.name ? (
|
||||
<div
|
||||
style={{
|
||||
color: "rgba(0, 0, 0, 0.88)",
|
||||
fontWeight: "600",
|
||||
fontSize: "16px",
|
||||
}}
|
||||
onClick={() => toggleEditName(true, peer.name)}
|
||||
>
|
||||
{formPeer.name ? formPeer.name : peer.name}
|
||||
<EditOutlined style={{ marginLeft: "10px" }} />
|
||||
<Row align="top">
|
||||
<Col flex="auto">
|
||||
{!editName && peer.id && formPeer.name ? (
|
||||
<div
|
||||
style={{
|
||||
color: "rgba(0, 0, 0, 0.88)",
|
||||
fontWeight: "600",
|
||||
fontSize: "16px",
|
||||
}}
|
||||
onClick={() => toggleEditName(true, peer.name)}
|
||||
>
|
||||
{formPeer.name ? formPeer.name : peer.name}
|
||||
<EditOutlined style={{ marginLeft: "10px" }} />
|
||||
|
||||
<Paragraph
|
||||
type={"secondary"}
|
||||
style={{
|
||||
textAlign: "left",
|
||||
whiteSpace: "pre-line",
|
||||
fontWeight: "400",
|
||||
}}
|
||||
<Paragraph
|
||||
type={"secondary"}
|
||||
style={{
|
||||
textAlign: "left",
|
||||
whiteSpace: "pre-line",
|
||||
fontWeight: "400",
|
||||
}}
|
||||
>
|
||||
{formPeer.userEmail}{" "}
|
||||
</Paragraph>
|
||||
</div>
|
||||
) : (
|
||||
<Row>
|
||||
<Space direction={"vertical"} size="small">
|
||||
<Form.Item
|
||||
name="name"
|
||||
label="Name"
|
||||
style={{ margin: "1px" }}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message:
|
||||
"Please add a new name for this peer",
|
||||
whitespace: true,
|
||||
},
|
||||
{ validator: nameValidator },
|
||||
]}
|
||||
>
|
||||
{formPeer.userEmail}{" "}
|
||||
</Paragraph>
|
||||
</div>
|
||||
) : (
|
||||
<Row>
|
||||
<Space direction={"vertical"} size="small">
|
||||
<Form.Item
|
||||
name="name"
|
||||
label="Name"
|
||||
style={{ margin: "1px" }}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message:
|
||||
"Please add a new name for this peer",
|
||||
whitespace: true,
|
||||
},
|
||||
{ validator: nameValidator },
|
||||
]}
|
||||
>
|
||||
<Input
|
||||
placeholder={peer.name}
|
||||
ref={inputNameRef}
|
||||
onPressEnter={() => toggleEditName(false)}
|
||||
onBlur={() => toggleEditName(false)}
|
||||
autoComplete="off"
|
||||
max={59}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Domain name preview"
|
||||
tooltip="If the domain name already exists, we add an increment number suffix to it"
|
||||
style={{ margin: "1px" }}
|
||||
>
|
||||
<Paragraph>
|
||||
<Tag>{estimatedName}</Tag>
|
||||
</Paragraph>
|
||||
</Form.Item>
|
||||
</Space>
|
||||
</Row>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
<Input
|
||||
placeholder={peer.name}
|
||||
ref={inputNameRef}
|
||||
onPressEnter={() => toggleEditName(false)}
|
||||
onBlur={() => toggleEditName(false)}
|
||||
autoComplete="off"
|
||||
max={59}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="Domain name preview"
|
||||
tooltip="If the domain name already exists, we add an increment number suffix to it"
|
||||
style={{ margin: "1px" }}
|
||||
>
|
||||
<Paragraph>
|
||||
<Tag>{estimatedName}</Tag>
|
||||
</Paragraph>
|
||||
</Form.Item>
|
||||
</Space>
|
||||
</Row>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={30} style={{ marginTop: "25px" }}>
|
||||
@@ -558,16 +666,16 @@ const PeerUpdate = () => {
|
||||
</Col>
|
||||
<Col span={5}>
|
||||
<Form.Item
|
||||
name="dns_label"
|
||||
label="Domain name"
|
||||
style={{ fontWeight: "bold" }}
|
||||
name="dns_label"
|
||||
label="Domain name"
|
||||
style={{ fontWeight: "bold" }}
|
||||
>
|
||||
<Input
|
||||
disabled={true}
|
||||
value={formPeer.userEmail}
|
||||
style={{ color: "#8c8c8c" }}
|
||||
autoComplete="off"
|
||||
suffix={<LockOutlined style={{ color: "#BFBFBF" }} />}
|
||||
disabled={true}
|
||||
value={formPeer.userEmail}
|
||||
style={{ color: "#8c8c8c" }}
|
||||
autoComplete="off"
|
||||
suffix={<LockOutlined style={{ color: "#BFBFBF" }} />}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
@@ -714,7 +822,9 @@ const PeerUpdate = () => {
|
||||
style={{ marginTop: "-16px" }}
|
||||
>
|
||||
{peerRoutes && peerRoutes.length > 0 && (
|
||||
<Button type="primary">Add route</Button>
|
||||
<Button type="primary" onClick={onClickAddNewRoute}>
|
||||
Add route
|
||||
</Button>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
@@ -783,7 +893,9 @@ const PeerUpdate = () => {
|
||||
>
|
||||
You don't have any routes yet
|
||||
</Paragraph>
|
||||
<Button type="primary">Create route</Button>
|
||||
<Button type="primary" onClick={onClickAddNewRoute}>
|
||||
Add route
|
||||
</Button>
|
||||
</Space>
|
||||
)}
|
||||
</div>
|
||||
@@ -799,16 +911,19 @@ const PeerUpdate = () => {
|
||||
>
|
||||
<Panel
|
||||
key="0"
|
||||
header={<Paragraph
|
||||
header={
|
||||
<Paragraph
|
||||
style={{
|
||||
textAlign: "left",
|
||||
whiteSpace: "pre-line",
|
||||
fontSize: "16px",
|
||||
fontWeight: "bold",
|
||||
margin: "0",
|
||||
}}
|
||||
>
|
||||
System info
|
||||
</Paragraph>}
|
||||
>
|
||||
System info
|
||||
</Paragraph>
|
||||
}
|
||||
className="system-info-panel"
|
||||
>
|
||||
<Row gutter={16}>
|
||||
@@ -827,9 +942,7 @@ const PeerUpdate = () => {
|
||||
>
|
||||
Hostname:
|
||||
</Text>
|
||||
<Text type="secondary">
|
||||
{formPeer.hostname}
|
||||
</Text>
|
||||
<Text type="secondary">{formPeer.hostname}</Text>
|
||||
</Col>
|
||||
<Col
|
||||
span={24}
|
||||
@@ -855,17 +968,15 @@ const PeerUpdate = () => {
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
width: "100%",
|
||||
maxWidth: "130px",
|
||||
display: "inline-block",
|
||||
}}
|
||||
style={{
|
||||
width: "100%",
|
||||
maxWidth: "130px",
|
||||
display: "inline-block",
|
||||
}}
|
||||
>
|
||||
Agent version:
|
||||
</Text>
|
||||
<Text type="secondary">
|
||||
{formPeer.version}
|
||||
</Text>
|
||||
<Text type="secondary">{formPeer.version}</Text>
|
||||
</Col>
|
||||
{formPeer.ui_version && (
|
||||
<Col
|
||||
@@ -883,9 +994,7 @@ const PeerUpdate = () => {
|
||||
>
|
||||
UI version:
|
||||
</Text>
|
||||
<Text type={"secondary"}>
|
||||
{formPeer.ui_version}
|
||||
</Text>
|
||||
<Text type={"secondary"}>{formPeer.ui_version}</Text>
|
||||
</Col>
|
||||
)}
|
||||
</Row>
|
||||
@@ -895,6 +1004,7 @@ const PeerUpdate = () => {
|
||||
</Card>
|
||||
</Container>
|
||||
)}
|
||||
{setupNewRouteVisible && <RouteAddNew peer={peer} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
840
src/components/RouteAddNew.tsx
Normal file
840
src/components/RouteAddNew.tsx
Normal file
@@ -0,0 +1,840 @@
|
||||
import React, { useEffect, useRef, useState } from "react";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { RootState } from "typesafe-actions";
|
||||
import { actions as routeActions } from "../store/route";
|
||||
import {
|
||||
Button,
|
||||
Col,
|
||||
Divider,
|
||||
Collapse,
|
||||
Form,
|
||||
Input,
|
||||
InputNumber,
|
||||
Radio,
|
||||
Row,
|
||||
Select,
|
||||
SelectProps,
|
||||
Space,
|
||||
Switch,
|
||||
Modal,
|
||||
Typography,
|
||||
} from "antd";
|
||||
import {
|
||||
CloseOutlined,
|
||||
FlagFilled,
|
||||
QuestionCircleFilled,
|
||||
} from "@ant-design/icons";
|
||||
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";
|
||||
import {
|
||||
initPeerMaps,
|
||||
masqueradeDisabledMSG,
|
||||
peerToPeerIP,
|
||||
routePeerSeparator,
|
||||
transformGroupedDataTable,
|
||||
} from "../utils/routes";
|
||||
import { useGetTokenSilently } from "../utils/token";
|
||||
import { useGetGroupTagHelpers } from "../utils/groups";
|
||||
|
||||
const { Paragraph, Text } = Typography;
|
||||
const { Panel } = Collapse;
|
||||
|
||||
interface FormRoute extends Route {}
|
||||
|
||||
const RouteAddNew = (selectedPeer: any) => {
|
||||
const {
|
||||
blueTagRender,
|
||||
handleChangeTags,
|
||||
dropDownRender,
|
||||
optionRender,
|
||||
tagGroups,
|
||||
getExistingAndToCreateGroupsLists,
|
||||
getGroupNamesFromIDs,
|
||||
selectValidator,
|
||||
} = useGetGroupTagHelpers();
|
||||
// const { optionRender, blueTagRender, grayTagRender } =
|
||||
// useGetGroupTagHelpers();
|
||||
|
||||
const { Option } = Select;
|
||||
const { getTokenSilently } = useGetTokenSilently();
|
||||
const dispatch = useDispatch();
|
||||
const setupNewRouteVisible = useSelector(
|
||||
(state: RootState) => state.route.setupNewRouteVisible
|
||||
);
|
||||
const setupNewRouteHA = useSelector(
|
||||
(state: RootState) => state.route.setupNewRouteHA
|
||||
);
|
||||
const peers = useSelector((state: RootState) => state.peer.data);
|
||||
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 [previousRouteKey, setPreviousRouteKey] = useState("");
|
||||
const [editName, setEditName] = useState(false);
|
||||
const [editDescription, setEditDescription] = useState(false);
|
||||
const options: SelectProps["options"] = [];
|
||||
const [formRoute, setFormRoute] = useState({} as FormRoute);
|
||||
const [form] = Form.useForm();
|
||||
const inputNameRef = useRef<any>(null);
|
||||
const inputDescriptionRef = useRef<any>(null);
|
||||
const defaultRoutingPeerMSG = "Routing Peer";
|
||||
const [routingPeerMSG, setRoutingPeerMSG] = useState(defaultRoutingPeerMSG);
|
||||
const defaultMasqueradeMSG = "Masquerade";
|
||||
const [masqueradeMSG, setMasqueradeMSG] = useState(defaultMasqueradeMSG);
|
||||
const defaultStatusMSG = "Status";
|
||||
const [statusMSG, setStatusMSG] = useState(defaultStatusMSG);
|
||||
const [peerNameToIP, peerIPToName, peerIPToID] = initPeerMaps(peers);
|
||||
const [newRoute, setNewRoute] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!newRoute) {
|
||||
setRoutingPeerMSG(defaultRoutingPeerMSG);
|
||||
setMasqueradeMSG("Update Masquerade");
|
||||
setStatusMSG("Update Status");
|
||||
} else {
|
||||
setRoutingPeerMSG(defaultRoutingPeerMSG);
|
||||
setMasqueradeMSG(defaultMasqueradeMSG);
|
||||
setStatusMSG(defaultStatusMSG);
|
||||
setPreviousRouteKey("");
|
||||
}
|
||||
}, [newRoute]);
|
||||
|
||||
useEffect(() => {
|
||||
if (editName)
|
||||
inputNameRef.current!.focus({
|
||||
cursor: "end",
|
||||
});
|
||||
}, [editName]);
|
||||
|
||||
useEffect(() => {
|
||||
if (editDescription)
|
||||
inputDescriptionRef.current!.focus({
|
||||
cursor: "end",
|
||||
});
|
||||
}, [editDescription]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!route) return;
|
||||
|
||||
if (selectedPeer && selectedPeer.peer) {
|
||||
options?.push({
|
||||
label: peerToPeerIP(selectedPeer.peer.name, selectedPeer.peer.ip),
|
||||
value: peerToPeerIP(selectedPeer.peer.name, selectedPeer.peer.ip),
|
||||
disabled: false,
|
||||
});
|
||||
const udpateRoute = { ...route, peer: options[0].value } as FormRoute;
|
||||
setFormRoute(udpateRoute);
|
||||
form.setFieldsValue(udpateRoute);
|
||||
setPreviousRouteKey(udpateRoute.network_id + udpateRoute.network);
|
||||
} else {
|
||||
const fRoute = {
|
||||
...route,
|
||||
groups: getGroupNamesFromIDs(route.groups),
|
||||
} as FormRoute;
|
||||
setFormRoute(fRoute);
|
||||
setPreviousRouteKey(fRoute.network_id + fRoute.network);
|
||||
form.setFieldsValue(fRoute);
|
||||
}
|
||||
|
||||
if (!route.network_id) {
|
||||
setNewRoute(true);
|
||||
} else {
|
||||
setNewRoute(false);
|
||||
}
|
||||
}, [route]);
|
||||
|
||||
if (!selectedPeer.peer) {
|
||||
peers.forEach((p) => {
|
||||
let os: string;
|
||||
os = p.os;
|
||||
if (
|
||||
!os.toLowerCase().startsWith("darwin") &&
|
||||
!os.toLowerCase().startsWith("windows") &&
|
||||
!os.toLowerCase().startsWith("android") &&
|
||||
route &&
|
||||
!routes
|
||||
.filter((r) => r.network_id === route.network_id)
|
||||
.find((r) => r.peer === p.id)
|
||||
) {
|
||||
options?.push({
|
||||
label: peerToPeerIP(p.name, p.ip),
|
||||
value: peerToPeerIP(p.name, p.ip),
|
||||
disabled: false,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const createRouteToSave = (inputRoute: FormRoute): RouteToSave => {
|
||||
let peerIDList = inputRoute.peer.split(routePeerSeparator);
|
||||
let peerID: string;
|
||||
if (peerIDList.length === 1) {
|
||||
peerID = inputRoute.peer;
|
||||
} else {
|
||||
if (peerIDList[1]) {
|
||||
peerID = peerIPToID[peerIDList[1]];
|
||||
} else {
|
||||
peerID = peerIPToID[peerNameToIP[inputRoute.peer]];
|
||||
}
|
||||
}
|
||||
|
||||
let [existingGroups, groupsToCreate] = getExistingAndToCreateGroupsLists(
|
||||
inputRoute.groups
|
||||
);
|
||||
|
||||
return {
|
||||
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;
|
||||
};
|
||||
|
||||
const handleFormSubmit = () => {
|
||||
form
|
||||
.validateFields()
|
||||
.then(() => {
|
||||
if (!setupNewRouteHA || formRoute.peer != "") {
|
||||
const routeToSave = createRouteToSave(formRoute);
|
||||
dispatch(
|
||||
routeActions.saveRoute.request({
|
||||
getAccessTokenSilently: getTokenSilently,
|
||||
payload: routeToSave,
|
||||
})
|
||||
);
|
||||
} else {
|
||||
let groupedDataTable = transformGroupedDataTable(routes, peers);
|
||||
groupedDataTable.forEach((group) => {
|
||||
if (group.key == previousRouteKey) {
|
||||
group.groupedRoutes.forEach((route) => {
|
||||
let updateRoute: FormRoute = {
|
||||
...formRoute,
|
||||
id: route.id,
|
||||
peer: route.peer,
|
||||
metric: route.metric,
|
||||
enabled:
|
||||
formRoute.enabled != group.enabled
|
||||
? formRoute.enabled
|
||||
: route.enabled,
|
||||
};
|
||||
const routeToSave = createRouteToSave(updateRoute);
|
||||
dispatch(
|
||||
routeActions.saveRoute.request({
|
||||
getAccessTokenSilently: getTokenSilently,
|
||||
payload: routeToSave,
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((errorInfo) => {
|
||||
console.log("errorInfo", errorInfo);
|
||||
});
|
||||
};
|
||||
|
||||
const setVisibleNewRoute = (status: boolean) => {
|
||||
dispatch(routeActions.setSetupNewRouteVisible(status));
|
||||
};
|
||||
|
||||
const setSetupNewRouteHA = (status: boolean) => {
|
||||
dispatch(routeActions.setSetupNewRouteHA(status));
|
||||
};
|
||||
|
||||
const onCancel = () => {
|
||||
if (savedRoute.loading) return;
|
||||
setEditName(false);
|
||||
dispatch(
|
||||
routeActions.setRoute({
|
||||
network: "",
|
||||
network_id: "",
|
||||
description: "",
|
||||
peer: "",
|
||||
metric: 9999,
|
||||
masquerade: false,
|
||||
enabled: true,
|
||||
groups: [],
|
||||
} as Route)
|
||||
);
|
||||
setVisibleNewRoute(false);
|
||||
setSetupNewRouteHA(false);
|
||||
setPreviousRouteKey("");
|
||||
setNewRoute(false);
|
||||
};
|
||||
|
||||
const onChange = (data: any) => {
|
||||
setFormRoute({ ...formRoute, ...data });
|
||||
};
|
||||
|
||||
const peerDropDownRender = (menu: React.ReactElement) => <>{menu}</>;
|
||||
|
||||
const toggleEditName = (status: boolean) => {
|
||||
setEditName(status);
|
||||
};
|
||||
|
||||
const toggleEditDescription = (status: boolean) => {
|
||||
setEditDescription(status);
|
||||
};
|
||||
|
||||
const networkRangeValidator = (_: RuleObject, value: string) => {
|
||||
if (!cidrRegex().test(value)) {
|
||||
return Promise.reject(
|
||||
new Error("Please enter a valid CIDR, e.g. 192.168.1.0/24")
|
||||
);
|
||||
}
|
||||
|
||||
if (Number(value.split("/")[1]) < 7) {
|
||||
return Promise.reject(
|
||||
new Error("Please enter a network mask larger than /7")
|
||||
);
|
||||
}
|
||||
|
||||
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);
|
||||
};
|
||||
|
||||
const handleMasqueradeChange = (checked: boolean) => {
|
||||
setFormRoute({
|
||||
...formRoute,
|
||||
masquerade: checked,
|
||||
});
|
||||
};
|
||||
|
||||
const handleEnableChange = (checked: boolean) => {
|
||||
setFormRoute({
|
||||
...formRoute,
|
||||
enabled: checked,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{route && (
|
||||
<Modal
|
||||
open={setupNewRouteVisible}
|
||||
onCancel={onCancel}
|
||||
footer={
|
||||
<Space style={{ display: "flex", justifyContent: "end" }}>
|
||||
<Button onClick={onCancel} disabled={savedRoute.loading}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
disabled={savedRoute.loading}
|
||||
onClick={handleFormSubmit}
|
||||
>
|
||||
Add Route
|
||||
</Button>
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
<Form
|
||||
layout="vertical"
|
||||
form={form}
|
||||
requiredMark={false}
|
||||
onValuesChange={onChange}
|
||||
>
|
||||
<Row gutter={16}>
|
||||
<Col span={24}>
|
||||
<Header
|
||||
style={{
|
||||
border: "none",
|
||||
}}
|
||||
>
|
||||
<Paragraph
|
||||
style={{
|
||||
textAlign: "start",
|
||||
whiteSpace: "pre-line",
|
||||
fontSize: "18px",
|
||||
margin: "0px",
|
||||
marginBottom: "15px",
|
||||
}}
|
||||
>
|
||||
Add Route
|
||||
</Paragraph>
|
||||
|
||||
{!!selectedPeer.peer && (
|
||||
<div style={{ lineHeight: "20px" }}>
|
||||
<label
|
||||
style={{
|
||||
color: "rgba(0, 0, 0, 0.88)",
|
||||
fontSize: "14px",
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
>
|
||||
Routing Peer
|
||||
</label>
|
||||
<Paragraph
|
||||
type={"secondary"}
|
||||
style={{
|
||||
marginTop: "-2",
|
||||
fontWeight: "500",
|
||||
marginBottom: "5px",
|
||||
}}
|
||||
>
|
||||
Assign a peer as a routing peer for the Network CIDR
|
||||
</Paragraph>
|
||||
<Form.Item
|
||||
name="peer"
|
||||
rules={[{ validator: peerValidator }]}
|
||||
>
|
||||
<Select
|
||||
showSearch
|
||||
style={{ width: "100%" }}
|
||||
placeholder="Select Peer"
|
||||
dropdownRender={peerDropDownRender}
|
||||
options={options}
|
||||
allowClear={true}
|
||||
disabled={!!selectedPeer.peer}
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Row align="top">
|
||||
<Col span={24} style={{ lineHeight: "20px" }}>
|
||||
{!editName && formRoute.id ? (
|
||||
<div
|
||||
className={
|
||||
"access-control input-text ant-drawer-title"
|
||||
}
|
||||
onClick={() => toggleEditName(true)}
|
||||
>
|
||||
{formRoute.id ? formRoute.network_id : "New Route"}
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<label
|
||||
style={{
|
||||
color: "rgba(0, 0, 0, 0.88)",
|
||||
fontSize: "14px",
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
>
|
||||
Network Identifier
|
||||
</label>
|
||||
<Paragraph
|
||||
type={"secondary"}
|
||||
style={{
|
||||
marginTop: "-2",
|
||||
fontWeight: "500",
|
||||
marginBottom: "5px",
|
||||
}}
|
||||
>
|
||||
Add a unique cryptographic key that is assigned to
|
||||
each device
|
||||
</Paragraph>
|
||||
<Form.Item
|
||||
name="network_id"
|
||||
label=""
|
||||
|
||||
style={{marginBottom:"10px"}}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message:
|
||||
"Please add an identifier for this access route",
|
||||
whitespace: true,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input
|
||||
placeholder="for example “e.g. aws-eu-central-1-vpc”"
|
||||
ref={inputNameRef}
|
||||
disabled={!setupNewRouteHA && !newRoute}
|
||||
onPressEnter={() => toggleEditName(false)}
|
||||
onBlur={() => toggleEditName(false)}
|
||||
autoComplete="off"
|
||||
maxLength={40}
|
||||
/>
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
{!editDescription ? (
|
||||
<div
|
||||
onClick={() => toggleEditDescription(true)}
|
||||
style={{
|
||||
margin: "0 0 30px",
|
||||
lineHeight: "22px",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
>
|
||||
{formRoute.description &&
|
||||
formRoute.description.trim() !== "" ? (
|
||||
formRoute.description
|
||||
) : (
|
||||
<span style={{ textDecoration: "underline" }}>
|
||||
Add description
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<Form.Item
|
||||
name="description"
|
||||
label="Description"
|
||||
style={{ marginTop: 24 }}
|
||||
>
|
||||
<Input
|
||||
placeholder="Add description..."
|
||||
ref={inputDescriptionRef}
|
||||
disabled={!setupNewRouteHA && !newRoute}
|
||||
onPressEnter={() => toggleEditDescription(false)}
|
||||
onBlur={() => toggleEditDescription(false)}
|
||||
autoComplete="off"
|
||||
maxLength={200}
|
||||
/>
|
||||
</Form.Item>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
<Row align="top">
|
||||
<Col flex="auto"></Col>
|
||||
</Row>
|
||||
</Header>
|
||||
</Col>
|
||||
{/* {!!!selectedPeer.peer && (
|
||||
<Col span={24}>
|
||||
<Form.Item name="enabled" label="">
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
gap: "15px",
|
||||
}}
|
||||
>
|
||||
<Switch
|
||||
size={"small"}
|
||||
checked={formRoute.enabled}
|
||||
onChange={handleEnableChange}
|
||||
/>
|
||||
<div>
|
||||
<label
|
||||
style={{
|
||||
color: "rgba(0, 0, 0, 0.88)",
|
||||
fontSize: "14px",
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
>
|
||||
Enabled
|
||||
</label>
|
||||
<Paragraph
|
||||
type={"secondary"}
|
||||
style={{
|
||||
marginTop: "-2",
|
||||
fontWeight: "500",
|
||||
marginBottom: "0",
|
||||
}}
|
||||
>
|
||||
You can enable or disable the route
|
||||
</Paragraph>
|
||||
</div>
|
||||
</div>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
)} */}
|
||||
<Col span={24}>
|
||||
<label
|
||||
style={{
|
||||
color: "rgba(0, 0, 0, 0.88)",
|
||||
fontSize: "14px",
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
>
|
||||
Network Range
|
||||
</label>
|
||||
<Paragraph
|
||||
type={"secondary"}
|
||||
style={{
|
||||
marginTop: "-2",
|
||||
fontWeight: "500",
|
||||
marginBottom: "5px",
|
||||
}}
|
||||
>
|
||||
Add a private IP address range
|
||||
</Paragraph>
|
||||
<Form.Item
|
||||
name="network"
|
||||
label=""
|
||||
rules={[{ validator: networkRangeValidator }]}
|
||||
>
|
||||
<Input
|
||||
placeholder="for example “172.16.0.0/16”"
|
||||
disabled={!setupNewRouteHA && !newRoute}
|
||||
autoComplete="off"
|
||||
minLength={9}
|
||||
maxLength={43}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
{!!!selectedPeer.peer && (
|
||||
<Col span={24}>
|
||||
<label
|
||||
style={{
|
||||
color: "rgba(0, 0, 0, 0.88)",
|
||||
fontSize: "14px",
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
>
|
||||
Routing Peer
|
||||
</label>
|
||||
<Paragraph
|
||||
type={"secondary"}
|
||||
style={{
|
||||
marginTop: "-2",
|
||||
fontWeight: "500",
|
||||
marginBottom: "5px",
|
||||
}}
|
||||
>
|
||||
Assign a peer as a routing peer for the Network CIDR
|
||||
</Paragraph>
|
||||
<Form.Item name="peer" rules={[{ validator: peerValidator }]}>
|
||||
<Select
|
||||
showSearch
|
||||
style={{ width: "100%" }}
|
||||
placeholder="Select Peer"
|
||||
dropdownRender={peerDropDownRender}
|
||||
options={options}
|
||||
allowClear={true}
|
||||
disabled={!!selectedPeer.peer}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
)}
|
||||
<Col span={24}>
|
||||
<label
|
||||
style={{
|
||||
color: "rgba(0, 0, 0, 0.88)",
|
||||
fontSize: "14px",
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
>
|
||||
Distribution groups
|
||||
</label>
|
||||
<Paragraph
|
||||
type={"secondary"}
|
||||
style={{
|
||||
marginTop: "-2",
|
||||
fontWeight: "500",
|
||||
marginBottom: "5px",
|
||||
}}
|
||||
>
|
||||
Advertise this route to peers that belong to the following
|
||||
groups
|
||||
</Paragraph>
|
||||
<Form.Item
|
||||
name="groups"
|
||||
label=""
|
||||
rules={[{ validator: selectPreValidator }]}
|
||||
>
|
||||
<Select
|
||||
mode="tags"
|
||||
style={{ width: "100%" }}
|
||||
placeholder="Associate groups with the network route"
|
||||
tagRender={blueTagRender}
|
||||
onChange={handleChangeTags}
|
||||
dropdownRender={dropDownRender}
|
||||
>
|
||||
{tagGroups.map((m) => (
|
||||
<Option key={m}>{optionRender(m)}</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
|
||||
<Col span={24}>
|
||||
<Form.Item name="enabled" label="">
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
gap: "15px",
|
||||
}}
|
||||
>
|
||||
<Switch
|
||||
size={"small"}
|
||||
checked={formRoute.enabled}
|
||||
onChange={handleEnableChange}
|
||||
/>
|
||||
<div>
|
||||
<label
|
||||
style={{
|
||||
color: "rgba(0, 0, 0, 0.88)",
|
||||
fontSize: "14px",
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
>
|
||||
Enabled
|
||||
</label>
|
||||
<Paragraph
|
||||
type={"secondary"}
|
||||
style={{
|
||||
marginTop: "-2",
|
||||
fontWeight: "500",
|
||||
marginBottom: "0",
|
||||
}}
|
||||
>
|
||||
You can enable or disable the route
|
||||
</Paragraph>
|
||||
</div>
|
||||
</div>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
|
||||
<Col span={24}>
|
||||
<Collapse
|
||||
onChange={onChange}
|
||||
bordered={false}
|
||||
ghost={true}
|
||||
style={{ padding: "0" }}
|
||||
>
|
||||
<Panel
|
||||
key="0"
|
||||
header={
|
||||
<Paragraph
|
||||
style={{
|
||||
textAlign: "left",
|
||||
whiteSpace: "pre-line",
|
||||
fontSize: "14px",
|
||||
fontWeight: "400",
|
||||
margin: "0",
|
||||
textDecoration: "underline",
|
||||
}}
|
||||
>
|
||||
More settings
|
||||
</Paragraph>
|
||||
}
|
||||
className="system-info-panel"
|
||||
>
|
||||
<Row gutter={16} style={{padding:"15px 0 0"}}>
|
||||
<Col span={22}>
|
||||
<Form.Item name="masquerade" label="">
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
gap: "15px",
|
||||
}}
|
||||
>
|
||||
<Switch
|
||||
size={"small"}
|
||||
disabled={!setupNewRouteHA && !newRoute}
|
||||
checked={formRoute.masquerade}
|
||||
onChange={handleMasqueradeChange}
|
||||
/>
|
||||
<div>
|
||||
<label
|
||||
style={{
|
||||
color: "rgba(0, 0, 0, 0.88)",
|
||||
fontSize: "14px",
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
>
|
||||
Masquerade
|
||||
</label>
|
||||
<Paragraph
|
||||
type={"secondary"}
|
||||
style={{
|
||||
marginTop: "-2",
|
||||
fontWeight: "500",
|
||||
marginBottom: "0",
|
||||
}}
|
||||
>
|
||||
Allow access to your private networks without
|
||||
configuring routes on your local routers or
|
||||
other devices.
|
||||
</Paragraph>
|
||||
</div>
|
||||
</div>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
|
||||
<Col span={24}>
|
||||
<label
|
||||
style={{
|
||||
color: "rgba(0, 0, 0, 0.88)",
|
||||
fontSize: "14px",
|
||||
fontWeight: "bold",
|
||||
}}
|
||||
>
|
||||
Metric
|
||||
</label>
|
||||
<Paragraph
|
||||
type={"secondary"}
|
||||
style={{
|
||||
marginTop: "-2",
|
||||
fontWeight: "500",
|
||||
marginBottom: "5px",
|
||||
}}
|
||||
>
|
||||
Lower metrics indicating higher priority routes
|
||||
</Paragraph>
|
||||
<Row>
|
||||
<Col span={12}>
|
||||
<Form.Item name="metric" label="">
|
||||
<InputNumber
|
||||
min={1}
|
||||
max={9999}
|
||||
autoComplete="off"
|
||||
className="w-100"
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
</Row>
|
||||
</Panel>
|
||||
</Collapse>
|
||||
</Col>
|
||||
<Col
|
||||
span={24}
|
||||
style={{ marginTop: "20px", marginBottom: "25px" }}
|
||||
>
|
||||
<Text type={"secondary"}>
|
||||
Learn more about
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href="https://docs.netbird.io/how-to/routing-traffic-to-private-networks"
|
||||
>
|
||||
{" "}
|
||||
Network Routes
|
||||
</a>
|
||||
</Text>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form>
|
||||
</Modal>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default RouteAddNew;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -197,7 +197,7 @@ td.non-highlighted-table-column {
|
||||
}
|
||||
|
||||
.tag-box .ant-select-selector {
|
||||
padding: 0 5px!important;
|
||||
padding: 0 5px !important;
|
||||
}
|
||||
|
||||
.tag-box .ant-select-selection-item {
|
||||
@@ -219,4 +219,8 @@ td.non-highlighted-table-column {
|
||||
align-items: center;
|
||||
margin-top: 3px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.w-100 {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -28,6 +28,7 @@ const actions = {
|
||||
|
||||
setRoute: createAction('SET_ROUTE')<Route>(),
|
||||
setSetupNewRouteVisible: createAction('SET_SETUP_NEW_ROUTE_VISIBLE')<boolean>(),
|
||||
setSetupEditRouteVisible: createAction('SET_SETUP_EDIT_ROUTE_VISIBLE')<boolean>(),
|
||||
setSetupNewRouteHA: createAction('SET_SETUP_NEW_ROUTE_HA')<boolean>()
|
||||
};
|
||||
|
||||
|
||||
@@ -13,7 +13,8 @@ type StateType = Readonly<{
|
||||
deleteRoute: DeleteResponse<string | null>;
|
||||
savedRoute: CreateResponse<Route | null>;
|
||||
setupNewRouteVisible: boolean;
|
||||
setupNewRouteHA: boolean
|
||||
setupNewRouteHA: boolean;
|
||||
setupEditRouteVisible: boolean;
|
||||
}>;
|
||||
|
||||
const initialState: StateType = {
|
||||
@@ -27,17 +28,18 @@ const initialState: StateType = {
|
||||
success: false,
|
||||
failure: false,
|
||||
error: null,
|
||||
data : null
|
||||
data: null,
|
||||
},
|
||||
savedRoute: <CreateResponse<Route | null>>{
|
||||
loading: false,
|
||||
success: false,
|
||||
failure: false,
|
||||
error: null,
|
||||
data : null
|
||||
data: null,
|
||||
},
|
||||
setupNewRouteVisible: false,
|
||||
setupNewRouteHA: false
|
||||
setupNewRouteHA: false,
|
||||
setupEditRouteVisible: false,
|
||||
};
|
||||
|
||||
const data = createReducer<Route[], ActionTypes>(initialState.data as Route[])
|
||||
@@ -79,6 +81,13 @@ const savedRoute = createReducer<CreateResponse<Route | null>, ActionTypes>(init
|
||||
const setupNewRouteVisible = createReducer<boolean, ActionTypes>(initialState.setupNewRouteVisible)
|
||||
.handleAction(actions.setSetupNewRouteVisible, (store, action) => action.payload)
|
||||
|
||||
const setupEditRouteVisible = createReducer<boolean, ActionTypes>(
|
||||
initialState.setupEditRouteVisible
|
||||
).handleAction(
|
||||
actions.setSetupEditRouteVisible,
|
||||
(store, action) => action.payload
|
||||
);
|
||||
|
||||
const setupNewRouteHA = createReducer<boolean, ActionTypes>(initialState.setupNewRouteHA)
|
||||
.handleAction(actions.setSetupNewRouteHA, (store, action) => action.payload)
|
||||
|
||||
@@ -91,5 +100,6 @@ export default combineReducers({
|
||||
deletedRoute,
|
||||
savedRoute,
|
||||
setupNewRouteVisible,
|
||||
setupNewRouteHA
|
||||
setupNewRouteHA,
|
||||
setupEditRouteVisible,
|
||||
});
|
||||
|
||||
1320
src/views/Routes.tsx
1320
src/views/Routes.tsx
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user