Compare commits

..
12 Commits
Author SHA1 Message Date
Maycon Santos dd67ab6dcb Fix Windows server version (#298) 2023-11-24 17:42:31 +01:00
pascal-fischer 2613948cdf Fix OS display format for iOS (#297)
* fix OS format function to properly show iOS

* bring back windows check
2023-11-13 22:30:50 +08:00
pascal-fischer dc95d8bfd1 disallow routing through ios devices (#295) 2023-11-11 13:20:39 +01:00
Bethuel Mmbaga f49c28f550 Add integration activity events target to Activity view as system setting (#293) 2023-11-08 13:51:57 +03:00
Maycon Santos 2fc7b73ea0 Fix/blank page on empty policy groups (#292)
validate null sources and destinations fields
2023-11-01 11:56:12 +01:00
pascal-fischer a9354d3c87 removed disabled option from setup key filter switch (#291) 2023-10-27 20:06:42 +02:00
Sarooj bukhariandbraginini e0ae7d068a add search domains enabled to namespace (#288)
---------

Co-authored-by: braginini <bangvalo@gmail.com>
2023-10-20 09:33:44 +02:00
Sarooj bukhariandbraginini ddd812e9a0 Add a refresh button to the table views (#287)
Co-authored-by: braginini <bangvalo@gmail.com>
2023-10-15 19:04:05 +02:00
Sarooj bukhariandbraginini 2d55d0736f Add referesh button to the peers table (#286)
Co-authored-by: braginini <bangvalo@gmail.com>
2023-10-12 18:38:59 +02:00
Sarooj bukhari 8febc26f1f fix update distribution groups bug (#284) 2023-10-04 20:07:02 +02:00
Sarooj bukhari 3f854b01a0 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
2023-10-04 11:37:23 +02:00
Misha Bragin 303d51eff8 Display service username in the activity (#282) 2023-10-03 16:06:50 +02:00
23 changed files with 1914 additions and 962 deletions

No files matched your search

+55 -1
View File
@@ -73,6 +73,7 @@ const NameServerGroupAdd = () => {
const inputNameRef = useRef<any>(null);
const inputDescriptionRef = useRef<any>(null);
const [selectCustom, setSelectCustom] = useState(false);
const [matchDomains, setMatchDomains] = useState(0);
useEffect(() => {
if (editName)
@@ -87,7 +88,6 @@ const NameServerGroupAdd = () => {
cursor: "end",
});
}, [editDescription]);
useEffect(() => {
if (!nsGroup) return;
@@ -117,6 +117,7 @@ const NameServerGroupAdd = () => {
nameservers: [] as NameServer[],
groups: [],
enabled: false,
search_domains_enabled: false,
} as NameServerGroup)
);
setEditName(false);
@@ -156,6 +157,7 @@ const NameServerGroupAdd = () => {
],
groups: [],
enabled: true,
search_domains_enabled: false,
},
{
name: cloudflareChoice,
@@ -176,6 +178,7 @@ const NameServerGroupAdd = () => {
],
groups: [],
enabled: true,
search_domains_enabled: false,
},
{
name: quad9Choice,
@@ -196,6 +199,7 @@ const NameServerGroupAdd = () => {
],
groups: [],
enabled: true,
search_domains_enabled: false,
},
];
@@ -262,6 +266,8 @@ const NameServerGroupAdd = () => {
groups: existingGroups,
groupsToCreate: newGroups,
enabled: values.enabled,
search_domains_enabled:
matchDomains > 0 ? formNSGroup.search_domains_enabled : false,
} as NameServerGroupToSave;
};
@@ -462,6 +468,7 @@ const NameServerGroupAdd = () => {
</Col>
</Space>
</Row>
{setMatchDomains(fields.length)}
{fields.map((field, index) => {
return (
<Row key={index} style={{ marginBottom: "5px" }}>
@@ -522,6 +529,13 @@ const NameServerGroupAdd = () => {
});
};
const handleChangeMarkDomain = (checked: boolean) => {
setFormNSGroup({
...formNSGroup,
search_domains_enabled: checked,
});
};
return (
<>
{nsGroup && (
@@ -701,6 +715,46 @@ const NameServerGroupAdd = () => {
<Col span={24}>
<Form.List name="domains">{renderDomains}</Form.List>
</Col>
{matchDomains > 0 && (
<Col span={24}>
<Form.Item name="search_domains_enabled" label="">
<div
style={{
display: "flex",
gap: "15px",
}}
>
<Switch
onChange={handleChangeMarkDomain}
size="small"
/>
<div>
<label
style={{
color: "rgba(0, 0, 0, 0.88)",
fontSize: "14px",
fontWeight: "500",
}}
>
Mark match domains as search domains
</label>
<Paragraph
type={"secondary"}
style={{
marginTop: "-2",
fontWeight: "400",
marginBottom: "0",
}}
>
E.g., "peer.example.com" will be accessible with
"peer"
</Paragraph>
</div>
</div>
</Form.Item>
</Col>
)}
<Col span={24}>
<label
style={{
File diff suppressed because it is too large. Load diff
+207 -131
View File
@@ -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,14 @@ 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("ios") &&
!os.toLowerCase().startsWith("android")
);
};
const toggleEditName = (status: boolean, value?: string) => {
setEditName(status);
@@ -396,13 +412,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 +431,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 +675,48 @@ const PeerUpdate = (props: any) => {
}
}, [deletedRoute]);
const renderGroupRouting = (rowGroups: string[] | null) => {
let groupsMap = new Map<string, Group>();
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 (
<div className="gp-main-wrapper">
{displayGroups &&
displayGroups.length > 0 &&
displayGroups.map((group) => {
if (group.id && !groupToCompare?.includes(group?.id)) {
return null;
}
return (
<div className="g-r-wrapper">
<span className="f-r-name">
<Tag color={"blue"} style={{ marginRight: 3 }}>
{group.name}
</Tag>
</span>{" "}
</div>
);
})}
</div>
);
};
return (
<>
{peer && (
@@ -972,133 +1030,151 @@ const PeerUpdate = (props: any) => {
{/* --- */}
{!isGroupUpdateView && (
<>
{routeAddAllowed(peer.os) &&
<Card
bordered={true}
// loading={loading}Ć’
style={{ marginBottom: "7px" }}
>
<div style={{ maxWidth: "800px" }}>
<Paragraph
style={{
textAlign: "left",
whiteSpace: "pre-line",
fontSize: "16px",
fontWeight: "500",
}}
>
Network routes
</Paragraph>
<Row
gutter={21}
style={{ marginTop: "-16px", marginBottom: "10px" }}
>
<Col
xs={24}
sm={24}
md={20}
lg={20}
xl={20}
xxl={20}
span={20}
{routeAddAllowed(peer.os) && (
<Card
bordered={true}
// loading={loading}Ć’
style={{ marginBottom: "7px" }}
>
<div style={{ maxWidth: "800px" }}>
<Paragraph
style={{
textAlign: "left",
whiteSpace: "pre-line",
fontSize: "16px",
fontWeight: "500",
}}
>
<Paragraph
type={"secondary"}
style={{ textAlign: "left", whiteSpace: "pre-line" }}
Network routes
</Paragraph>
<Row
gutter={21}
style={{ marginTop: "-16px", marginBottom: "10px" }}
>
<Col
xs={24}
sm={24}
md={20}
lg={20}
xl={20}
xxl={20}
span={20}
>
Access other networks without installing NetBird on
every resource.
</Paragraph>
</Col>
<Col
xs={24}
sm={24}
md={1}
lg={1}
xl={1}
xxl={1}
span={1}
style={{ marginTop: "-16px" }}
>
{peerRoutes && peerRoutes.length > 0 && (
<Paragraph
type={"secondary"}
style={{ textAlign: "left", whiteSpace: "pre-line" }}
>
Access other networks without installing NetBird on
every resource.
</Paragraph>
</Col>
<Col
xs={24}
sm={24}
md={1}
lg={1}
xl={1}
xxl={1}
span={1}
style={{ marginTop: "-16px" }}
>
{peerRoutes && peerRoutes.length > 0 && (
<Button type="primary" onClick={onClickAddNewRoute}>
Add route
</Button>
)}
</Col>
</Row>
{peerRoutes && peerRoutes.length > 0 && (
<Table
size={"small"}
style={{ marginTop: "-10px" }}
showHeader={false}
scroll={{ x: 800 }}
pagination={false}
dataSource={peerRoutes}
>
<Column title="Name" dataIndex="network_id" />
<Column title="Name" dataIndex="network" />
<Column
title="enabled"
dataIndex="network"
render={(e, record: any, index) => {
return record.peer_groups ? (
renderGroupRouting(record.peer_groups)
) : (
<>
<Switch
defaultChecked={record.enabled}
size="small"
onChange={(checked) =>
onRouteEnableChange(checked, record)
}
/>
</>
);
}}
/>
<Column
align="right"
render={(text, record: any, index) => {
return record.peer_groups ? (
<Tooltip
color="#fff"
overlayClassName="peer-avail-tooltip"
title={`Peer "${formPeer.name}" is a part of a group used in a network route.
To remove this peer from the network route, you need to disassociate
this peer from the groups used in this route.`}
>
<Button type={"text"} disabled={true}>
Delete
</Button>
</Tooltip>
) : (
<Button
danger={true}
type={"text"}
onClick={() => {
showConfirmDelete(
record.id,
record.network_id
);
}}
>
Delete
</Button>
);
}}
/>
</Table>
)}
<Divider style={{ marginTop: "-12px" }}></Divider>
{(peerRoutes === null || peerRoutes.length === 0) && (
<Space
direction="vertical"
size="small"
align="start"
style={{
display: "flex",
padding: "35px 0px",
marginTop: "-40px",
justifyContent: "center",
}}
>
<Paragraph
style={{ textAlign: "start", whiteSpace: "pre-line" }}
>
You don't have any routes yet
</Paragraph>
<Button type="primary" onClick={onClickAddNewRoute}>
Add route
</Button>
)}
</Col>
</Row>
{peerRoutes && peerRoutes.length > 0 && (
<Table
size={"small"}
style={{ marginTop: "-10px" }}
showHeader={false}
scroll={{ x: 800 }}
pagination={false}
dataSource={peerRoutes}
>
<Column title="Name" dataIndex="network_id" />
<Column title="Name" dataIndex="network" />
<Column
title="enabled"
dataIndex="network"
render={(e, record: any, index) => {
return (
<>
<Switch
defaultChecked={record.enabled}
size="small"
onChange={(checked) =>
onRouteEnableChange(checked, record)
}
/>
</>
);
}}
/>
<Column
align="right"
render={(text, record: any, index) => {
return (
<Button
danger={true}
type={"text"}
onClick={() => {
showConfirmDelete(record.id, record.network_id);
}}
>
Delete
</Button>
);
}}
/>
</Table>
)}
<Divider style={{ marginTop: "-12px" }}></Divider>
{(peerRoutes === null || peerRoutes.length === 0) && (
<Space
direction="vertical"
size="small"
align="start"
style={{
display: "flex",
padding: "35px 0px",
marginTop: "-40px",
justifyContent: "center",
}}
>
<Paragraph
style={{ textAlign: "start", whiteSpace: "pre-line" }}
>
You don't have any routes yet
</Paragraph>
<Button type="primary" onClick={onClickAddNewRoute}>
Add route
</Button>
</Space>
)}
</div>
</Card>}
</Space>
)}
</div>
</Card>
)}
<Card bordered={true} style={{ marginBottom: "50px" }}>
<Col span={24}>
+164 -77
View File
@@ -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<string | null>(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({
@@ -144,6 +151,7 @@ const RouteAddNew = (selectedPeer: any) => {
!os.toLowerCase().startsWith("darwin") &&
!os.toLowerCase().startsWith("windows") &&
!os.toLowerCase().startsWith("android") &&
!os.toLowerCase().startsWith("ios") &&
route &&
!routes
.filter((r) => r.network_id === route.network_id)
@@ -159,6 +167,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 +189,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 +303,7 @@ const RouteAddNew = (selectedPeer: any) => {
const onCancel = () => {
if (savedRoute.loading) return;
setActiveTab(null);
setEditName(false);
dispatch(
routeActions.setRoute({
@@ -254,6 +315,7 @@ const RouteAddNew = (selectedPeer: any) => {
masquerade: false,
enabled: true,
groups: [],
peer_groups: [],
} as Route)
);
setVisibleNewRoute(false);
@@ -300,8 +362,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 +430,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,
const handleSingleChangeTags = (values: any) => {
const lastValue = values[values.length - 1];
if (values.length > 0) {
form.setFieldsValue({
peer_groups: [lastValue],
});
} else if (savedRoute.success) {
message.success({
content: "Route has been successfully added.",
key: saveKey,
duration: 2,
style: styleNotification,
});
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: (
<>
<Paragraph
type={"secondary"}
style={{
marginTop: "-2",
fontWeight: "400",
marginBottom: "5px",
}}
>
Assign a peer as a routing peer for the Network CIDR
</Paragraph>
{activeTab === "routingPeer" && (
<Form.Item name="peer" rules={[{ validator: peerValidator }]}>
<Select
showSearch
style={{ width: "100%" }}
placeholder="Select Peer"
dropdownRender={peerDropDownRender}
options={options}
allowClear={true}
disabled={!!selectedPeer.selectedPeer}
/>
</Form.Item>
)}{" "}
</>
),
},
{
key: "groupOfPeers",
label: "Peer group",
children: (
<>
<Paragraph
type={"secondary"}
style={{
marginTop: "-2",
fontWeight: "400",
marginBottom: "5px",
}}
>
Assign peer group with Linux machines to be used as routing peers
</Paragraph>
{activeTab === "groupOfPeers" && (
<Form.Item
name="peer_groups"
rules={[{ validator: peerGroupsValidaton }]}
>
<Select
mode="tags"
style={{ width: "100%" }}
tagRender={blueTagRender}
onChange={handleSingleChangeTags}
dropdownRender={dropDownRender}
optionFilterProp="serchValue"
>
{tagGroups.map((m, index) => (
<Option key={index} value={m.id} serchValue={m.name}>
{optionRender(m.name, m.id)}
</Option>
))}
</Select>
</Form.Item>
)}
</>
),
},
];
return (
<>
{route && (
{route && setupNewRouteVisible && (
<Modal
open={setupNewRouteVisible}
onCancel={onCancel}
@@ -558,7 +666,7 @@ const RouteAddNew = (selectedPeer: any) => {
marginBottom: "5px",
}}
>
Add a unique cryptographic key that is assigned
Add a unique network identifier that is assigned
to each device
</Paragraph>
<Form.Item
@@ -667,36 +775,15 @@ const RouteAddNew = (selectedPeer: any) => {
{!!!selectedPeer.selectedPeer && (
<Col span={24}>
<label
style={{
color: "rgba(0, 0, 0, 0.88)",
fontSize: "14px",
fontWeight: "500",
}}
>
Routing Peer
</label>
<Paragraph
type={"secondary"}
style={{
marginTop: "-2",
fontWeight: "400",
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.selectedPeer}
{activeTab ? (
<Tabs
defaultActiveKey={activeTab}
items={items}
onChange={onTabChange}
/>
</Form.Item>
) : (
""
)}
</Col>
)}
<Col span={24}>
+150 -34
View File
@@ -17,10 +17,13 @@ import {
Switch,
Card,
Typography,
Tabs,
} from "antd";
import type { TabsProps } from "antd";
import { Route, RouteToSave } from "../store/route/types";
import { Header } from "antd/es/layout/layout";
import { RuleObject } from "antd/lib/form";
import { isEmpty } from "lodash";
import {
initPeerMaps,
peerToPeerIP,
@@ -61,6 +64,16 @@ const RoutePeerUpdate = () => {
const [form] = Form.useForm();
const inputDescriptionRef = useRef<any>(null);
const [peerNameToIP, peerIPToName, peerIPToID] = initPeerMaps(peers);
const [activeTab, setActiveTab] = useState<string | null>(null);
useEffect(() => {
if (!isEmpty(formRoute))
if (formRoute.peer_groups) {
setActiveTab("groupOfPeers");
} else {
setActiveTab("routingPeer");
}
}, [formRoute]);
useEffect(() => {
if (editDescription)
@@ -87,6 +100,7 @@ const RoutePeerUpdate = () => {
!os.toLowerCase().startsWith("darwin") &&
!os.toLowerCase().startsWith("windows") &&
!os.toLowerCase().startsWith("android") &&
!os.toLowerCase().startsWith("ios") &&
route &&
!routes
.filter((r) => r.network_id === route.network_id)
@@ -100,7 +114,25 @@ const RoutePeerUpdate = () => {
}
});
const handleSingleChangeTags = (values: any) => {
const lastValue = values[values.length - 1];
if (values.length > 0) {
form.setFieldsValue({
peer_groups: [lastValue],
});
}
};
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) {
@@ -116,18 +148,38 @@ const RoutePeerUpdate = () => {
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 = () => {
@@ -165,6 +217,7 @@ const RoutePeerUpdate = () => {
masquerade: false,
enabled: true,
groups: [],
peer_groups: [],
} as Route)
);
setVisibleNewRoute(false);
@@ -269,6 +322,95 @@ const RoutePeerUpdate = () => {
}
}, [savedRoute]);
const onTabChange = (key: string) => {
console.log(key);
// setActiveTab(key);
};
const items: TabsProps["items"] = [
{
key: "routingPeer",
label: "Routing Peer",
disabled: true,
children: (
<>
<Paragraph
type={"secondary"}
style={{
marginTop: "-2",
fontWeight: "400",
marginBottom: "5px",
}}
>
Assign a peer as a routing peer for the Network CIDR
</Paragraph>
<Form.Item
name="peer"
rules={[
{
required: true,
message: "Please select routing one peer",
},
]}
style={{ maxWidth: "400px" }}
>
<Select
showSearch
style={{ width: "100%" }}
placeholder="Select Peer"
dropdownRender={peerDropDownRender}
options={options}
allowClear={true}
/>
</Form.Item>
</>
),
},
{
key: "groupOfPeers",
label: "Peer group",
disabled: true,
children: (
<>
<Paragraph
type={"secondary"}
style={{
marginTop: "-2",
fontWeight: "400",
marginBottom: "5px",
}}
>
Assign peer group with Linux machines to be used as routing peers
</Paragraph>
<Form.Item
name="peer_groups"
rules={[
{
required: true,
message: "Please select peer group",
},
]}
>
<Select
mode="tags"
style={{ maxWidth: "400px" }}
tagRender={blueTagRender}
onChange={handleSingleChangeTags}
dropdownRender={dropDownRender}
optionFilterProp="serchValue"
>
{tagGroups.map((m, index) => (
<Option key={index} value={m.id} serchValue={m.name}>
{optionRender(m.name, m.id)}
</Option>
))}
</Select>
</Form.Item>
</>
),
},
];
return (
<>
<Container style={{ paddingTop: "40px", paddingBottom: "50px" }}>
@@ -409,39 +551,13 @@ const RoutePeerUpdate = () => {
</Col>
<Col span={24}>
<label
style={{
color: "rgba(0, 0, 0, 0.88)",
fontSize: "14px",
fontWeight: "500",
}}
>
Routing Peer
</label>
<Paragraph
type={"secondary"}
style={{
marginTop: "-2",
fontWeight: "400",
marginBottom: "5px",
}}
>
Assign a peer as a routing peer for the Network CIDR
</Paragraph>
<Form.Item
name="peer"
rules={[{ validator: peerValidator }]}
style={{ maxWidth: "400px" }}
>
<Select
showSearch
style={{ width: "100%" }}
placeholder="Select Peer"
dropdownRender={peerDropDownRender}
options={options}
allowClear={true}
{activeTab && (
<Tabs
defaultActiveKey={activeTab}
items={items}
onChange={onTabChange}
/>
</Form.Item>
)}
</Col>
<Col span={24}>
<label
+135 -85
View File
@@ -96,6 +96,7 @@ const RouteAddNew = () => {
!os.toLowerCase().startsWith("darwin") &&
!os.toLowerCase().startsWith("windows") &&
!os.toLowerCase().startsWith("android") &&
!os.toLowerCase().startsWith("ios") &&
route &&
!routes
.filter((r) => r.network_id === route.network_id)
@@ -110,6 +111,14 @@ const RouteAddNew = () => {
});
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) {
@@ -126,20 +135,47 @@ const RouteAddNew = () => {
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 (inputRoute.peer_groups) {
let [currentPeersGroup, peerGroupsToCreate] =
getExistingAndToCreateGroupsLists(inputRoute.peer_groups);
let pay = {
...payload,
peer_groups: currentPeersGroup,
peerGroupsToCreate: peerGroupsToCreate,
};
return pay;
}
if (inputRoute.peer) {
let pay = { ...payload, peer: peerID };
return pay;
}
return payload;
};
const handleSingleChangeTags = (values: any) => {
const lastValue = values[values.length - 1];
if (values.length > 0) {
form.setFieldsValue({
peer_groups: [lastValue],
});
}
};
const handleFormSubmit = () => {
form
.validateFields()
@@ -232,7 +268,7 @@ const RouteAddNew = () => {
};
const selectPreValidator = (obj: RuleObject, value: string[]) => {
if (setupNewRouteHA && formRoute.peer == "") {
if (setupNewRouteHA && formRoute.peer == "" && !formRoute.peer_groups) {
let [, newGroups] = getExistingAndToCreateGroupsLists(value);
if (newGroups.length > 0) {
return Promise.reject(
@@ -247,53 +283,6 @@ const RouteAddNew = () => {
return selectValidator(obj, value);
};
const styleNotification = { marginTop: 85 };
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,
});
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]);
return (
<>
{route && (
@@ -310,7 +299,7 @@ const RouteAddNew = () => {
disabled={savedRoute.loading}
onClick={handleFormSubmit}
>
Add route
{formRoute.peer_groups ? "Save" : "Add route"}
</Button>
</Space>
}
@@ -337,7 +326,9 @@ const RouteAddNew = () => {
fontWeight: 500,
}}
>
Add new routing peer
{formRoute.peer_groups
? "Configure peer group"
: "Add new routing peer"}
</Paragraph>
<Paragraph
type={"secondary"}
@@ -350,8 +341,9 @@ const RouteAddNew = () => {
marginBottom: "4px",
}}
>
When you add multiple routing peers, NetBird enables high
availability
{formRoute.peer_groups
? "Add peer group with multiple peers to enable high availability"
: "When you add multiple routing peers, NetBird enables high availability"}
</Paragraph>
<Row align="top">
@@ -377,7 +369,6 @@ const RouteAddNew = () => {
Network name and CIDR that you are adding the route to
</Paragraph>
<Form.Item
// name="network_id"
label=""
rules={[
{
@@ -411,36 +402,95 @@ const RouteAddNew = () => {
</Col>
<Col span={24}>
<label
style={{
color: "rgba(0, 0, 0, 0.88)",
fontSize: "14px",
fontWeight: "500",
}}
>
Routing Peer
</label>
<Paragraph
type={"secondary"}
style={{
marginTop: "-2",
fontWeight: "400",
marginBottom: "5px",
}}
>
Assign a routing peer to the network. This peer has to reside
in the network
</Paragraph>
<Form.Item name="peer" rules={[{ validator: peerValidator }]}>
<Select
showSearch
style={{ width: "100%" }}
placeholder="Select Peer"
dropdownRender={peerDropDownRender}
options={options}
allowClear={true}
/>
</Form.Item>
{formRoute.peer_groups ? (
<>
<label
style={{
color: "rgba(0, 0, 0, 0.88)",
fontSize: "14px",
fontWeight: "500",
}}
>
Peer group
</label>
<Paragraph
type={"secondary"}
style={{
marginTop: "-2",
fontWeight: "400",
marginBottom: "5px",
}}
>
Assign peer group with Linux machines to be used as
routing peers
</Paragraph>
<Form.Item
name="peer_groups"
rules={[
{
required: true,
message: "Please select peer group",
},
]}
>
<Select
mode="tags"
style={{ maxWidth: "100%" }}
tagRender={blueTagRender}
onChange={handleSingleChangeTags}
dropdownRender={dropDownRender}
optionFilterProp="serchValue"
>
{tagGroups.map((m, index) => (
<Option key={index} value={m.id} serchValue={m.name}>
{optionRender(m.name, m.id)}
</Option>
))}
</Select>
</Form.Item>
</>
) : (
<>
<label
style={{
color: "rgba(0, 0, 0, 0.88)",
fontSize: "14px",
fontWeight: "500",
}}
>
Routing Peer
</label>
<Paragraph
type={"secondary"}
style={{
marginTop: "-2",
fontWeight: "400",
marginBottom: "5px",
}}
>
Assign a routing peer to the network. This peer has to
reside in the network
</Paragraph>
<Form.Item
name="peer"
rules={[
{
required: true,
message: "Please select routing one peer",
},
]}
>
<Select
showSearch
style={{ width: "100%" }}
placeholder="Select Peer"
dropdownRender={peerDropDownRender}
options={options}
allowClear={true}
/>
</Form.Item>
</>
)}
</Col>
<Col span={24}>
<label
+1 -1
View File
@@ -364,7 +364,7 @@ const SetupKeyNew = (props: any) => {
<Tooltip title="Peers that are offline for over 10 minutes will be removed automatically">
<Tag>
<Text type="secondary" style={{ fontSize: 10 }}>
Ephemeral
ephemeral
</Text>
</Tag>
</Tooltip>
+67 -4
View File
@@ -513,13 +513,15 @@ ul.ant-list-items {
box-shadow: none !important;
}
.d-none{
display: none!important;
.d-none {
display: none !important;
}
.nohover {
background: transparent!important;
cursor: text;
background: transparent !important;
cursor: text;
}
.emp-wrapper {
display: flex;
align-items: center;
@@ -528,4 +530,65 @@ ul.ant-list-items {
.emp-wrapper p {
margin: 0;
}
.g-r-wrapper {
display: flex;
gap: 8px;
margin-bottom: 10px;
}
.gp-main-wrapper .g-r-wrapper {
margin: 0;
}
.gp-main-wrapper {
display: flex;
gap: 5px;
align-items: center;
}
.availtooltip a {
padding: 5px 0 0;
display: inline-block;
font-size: 14px;
}
.availtooltip {
padding: 5px;
}
.availtooltip p {
color: #000;
font-size: 13.5px;
margin: 0;
}
.avail-inner {
display: flex;
align-items: flex-start;
gap: 10px;
}
.avail-inner span {
color: #1890FF;
font-size: 24px;
}
.avail-icon {
font-size: 24px;
}
.peer-avail-tooltip .ant-tooltip-inner {
color: #000;
font-size: 14px;
line-height: 24px;
padding: 25px;
}
.peer-avail-tooltip {
min-width: 400px;
}
.info-icon {
font-size: 22px;
color: #FD9349;
}
+3 -2
View File
@@ -69,8 +69,9 @@ export function* saveNameServerGroup(action: ReturnType<typeof actions.saveNameS
nameservers: nameserverGroupToSave.nameservers,
groups: newGroups,
enabled: nameserverGroupToSave.enabled,
} as NameServerGroup
}
search_domains_enabled: nameserverGroupToSave.search_domains_enabled,
} as NameServerGroup,
};
let effect
if (!nameserverGroupToSave.id) {
+1
View File
@@ -7,6 +7,7 @@ export interface NameServerGroup {
nameservers: NameServer[];
groups: string[];
enabled: boolean;
search_domains_enabled: boolean;
}
export interface NameServer {
+22 -1
View File
@@ -56,12 +56,16 @@ export function* saveRoute(
);
const routeToSave = action.payload.payload;
let groupsToCreate = routeToSave.groupsToCreate;
if (!groupsToCreate) {
groupsToCreate = [];
}
let peerGroupsToCreate = routeToSave.peerGroupsToCreate;
if (!peerGroupsToCreate) {
peerGroupsToCreate = [];
}
// first, create groups that were newly added by user
const responsesGroup = yield all(
groupsToCreate.map((g) =>
@@ -72,12 +76,28 @@ export function* saveRoute(
)
);
const responsesPeerGroup = yield all(
peerGroupsToCreate.map((g) =>
call(serviceGroup.createGroup, {
getAccessTokenSilently: action.payload.getAccessTokenSilently,
payload: { name: g },
})
)
);
const resGroups = (responsesGroup as ApiResponse<Group>[])
.filter((r) => r.statusCode === 200)
.map((g) => g.body as Group)
.map((g) => g.id);
const newGroups = [...routeToSave.groups, ...resGroups];
const resPeersGroups = (responsesPeerGroup as ApiResponse<Group>[])
.filter((r) => r.statusCode === 200)
.map((g) => g.body as Group)
.map((g) => g.id);
const newPeerGroups = [...routeToSave?.peer_groups || [], ...resPeersGroups];
const payloadToSave = {
getAccessTokenSilently: action.payload.getAccessTokenSilently,
payload: {
@@ -89,6 +109,7 @@ export function* saveRoute(
network: routeToSave.network,
network_id: routeToSave.network_id,
peer: routeToSave.peer,
peer_groups: newPeerGroups.length ? newPeerGroups : null,
groups: newGroups,
} as Route,
};
+14 -10
View File
@@ -1,18 +1,22 @@
export interface Route {
id?: string | null
description: string
enabled: boolean
peer: string
network: string
network_id: string
network_type?: string
metric?: number
masquerade: boolean
groups: string[]
id?: string | null;
description: string;
enabled: boolean;
peer: string;
network: string;
network_id: string;
network_type?: string;
metric?: number;
masquerade: boolean;
groups: string[];
peer_groups?: string[];
routesGroups?: string[];
groupedRoutes?: Array<any>;
}
export interface RouteToSave extends Route
{
groupsToCreate: string[]
peerGroupsToCreate: string[]
}
+11 -3
View File
@@ -1,15 +1,23 @@
export const formatOS = (os) => {
if (os.startsWith("windows 10")) {
if (os.lowerCase.startsWith("windows 10")) {
return "Windows 10";
}
if (os.lowerCase.startsWith("windows server 10")) {
return "Windows Server";
}
if (os.startsWith("Darwin")) {
return os.replace("Darwin", "MacOS");
}
// capitalize first letter
os = os.charAt(0).toUpperCase() + os.slice(1);
if (!os.startsWith("iOS")) {
// capitalize first letter
os = os.charAt(0).toUpperCase() + os.slice(1);
}
return os;
};
+102 -78
View File
@@ -1,95 +1,119 @@
import {Peer, PeerIPToID, PeerIPToName, PeerNameToIP} from "../store/peer/types";
import {Route} from "../store/route/types";
import {
Peer,
PeerIPToID,
PeerIPToName,
PeerNameToIP,
} from "../store/peer/types";
import { Route } from "../store/route/types";
export const routePeerSeparator = " - "
export const routePeerSeparator = " - ";
export const masqueradeDisabledMSG = "Enabling this option hides other NetBird network IPs behind the routing peer local address when accessing the target Network CIDR. This option allows access to your private networks without configuring routes on your local routers or other devices."
export const masqueradeDisabledMSG =
"Enabling this option hides other NetBird network IPs behind the routing peer local address when accessing the target Network CIDR. This option allows access to your private networks without configuring routes on your local routers or other devices.";
export const masqueradeEnabledMSG = "Disabling this option stops hiding all traffic coming from other NetBird peers behind the routing peer local address when accessing the target Network CIDR. You will need to configure routes for your NetBird network pointing to your routing peer on your local routers or other devices."
export const masqueradeEnabledMSG =
"Disabling this option stops hiding all traffic coming from other NetBird peers behind the routing peer local address when accessing the target Network CIDR. You will need to configure routes for your NetBird network pointing to your routing peer on your local routers or other devices.";
export const peerToPeerIP = (name: string, ip: string): string => {
return name + routePeerSeparator + ip
}
return name + routePeerSeparator + ip;
};
export const initPeerMaps = (peers: Peer[]): [PeerNameToIP, PeerIPToName, PeerIPToID] => {
let peerNameToIP = {} as PeerNameToIP
let peerIPToName = {} as PeerIPToName
let peerIPToID = {} as PeerIPToID
peers.forEach((p) => {
peerNameToIP[p.name] = p.ip
peerIPToName[p.ip] = p.name
peerIPToID[p.ip] = p.id ? p.id : ""
})
return [peerNameToIP, peerIPToName, peerIPToID]
}
export const initPeerMaps = (
peers: Peer[]
): [PeerNameToIP, PeerIPToName, PeerIPToID] => {
let peerNameToIP = {} as PeerNameToIP;
let peerIPToName = {} as PeerIPToName;
let peerIPToID = {} as PeerIPToID;
peers.forEach((p) => {
peerNameToIP[p.name] = p.ip;
peerIPToName[p.ip] = p.name;
peerIPToID[p.ip] = p.id ? p.id : "";
});
return [peerNameToIP, peerIPToName, peerIPToID];
};
export interface RouteDataTable extends Route {
key: string;
peer_ip: string;
peer_name: string;
key: string;
peer_ip: string;
peer_name: string;
peer_groups: Array<string>;
}
export interface GroupedDataTable {
key: string
network_id: any
network: string
enabled: boolean
masquerade: boolean
description: string
routesCount: number
groupedRoutes: RouteDataTable[]
routesGroups: string[]
key: string;
network_id: any;
network: string;
enabled: boolean;
masquerade: boolean;
description: string;
routesCount: number;
groupedRoutes: RouteDataTable[];
routesGroups: string[];
peer_groups?: Array<string>;
}
export const transformDataTable = (routes: Route[], peers: Peer[]): RouteDataTable[] => {
export const transformDataTable = (
routes: Route[],
peers: Peer[]
): RouteDataTable[] => {
let peerMap = Object.fromEntries(peers.map((p) => [p.id, p]));
return routes.map((route) => {
return {
key: route.id,
...route,
peer: route.peer,
peer_ip: peerMap[route.peer] ? peerMap[route.peer].ip : route.peer,
peer_name: peerMap[route.peer] ? peerMap[route.peer].name : route.peer,
} as RouteDataTable;
});
};
let peerMap = Object.fromEntries(peers.map(p => [p.id, p]));
return routes.map(route => {
return {
key: route.id,
...route,
peer: route.peer,
peer_ip: peerMap[route.peer] ? peerMap[route.peer].ip : route.peer,
peer_name: peerMap[route.peer] ? peerMap[route.peer].name : route.peer,
} as RouteDataTable
export const transformGroupedDataTable = (
routes: Route[],
peers: Peer[]
): GroupedDataTable[] => {
let keySet = new Set(
routes.map((r) => {
return r.network_id + r.network;
})
}
);
export const transformGroupedDataTable = (routes: Route[], peers: Peer[]): GroupedDataTable[] => {
let keySet = new Set(routes.map(r => {
return r.network_id + r.network
}))
let groupedRoutes: GroupedDataTable[] = [];
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
if (r.enabled) {
hasEnabled = true
}
listedRoutes.push(r)
groupList = groupList.concat(r.groups)
}
})
groupList = groupList.filter((value, index, arrary) => arrary.indexOf(value) === index)
let groupDataTableRoutes = transformDataTable(listedRoutes, peers)
groupedRoutes.push({
key: p.toString(),
network_id: lastRoute!.network_id,
network: lastRoute!.network,
masquerade: lastRoute!.masquerade,
description: lastRoute!.description,
enabled: hasEnabled,
routesCount: groupDataTableRoutes.length,
groupedRoutes: groupDataTableRoutes,
routesGroups: groupList,
})
})
return groupedRoutes
}
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;
if (r.enabled) {
hasEnabled = true;
}
listedRoutes.push(r);
groupList = groupList.concat(r.groups);
}
});
groupList = groupList.filter(
(value, index, arrary) => arrary.indexOf(value) === index
);
let groupDataTableRoutes = transformDataTable(listedRoutes, peers);
const filterEnabledRoutes = groupDataTableRoutes.filter(
(route) => route.enabled
);
groupedRoutes.push({
key: p.toString(),
network_id: lastRoute!.network_id,
network: lastRoute!.network,
masquerade: lastRoute!.masquerade,
description: lastRoute!.description,
enabled: hasEnabled,
routesCount: filterEnabledRoutes.length,
groupedRoutes: groupDataTableRoutes,
routesGroups: groupList,
peer_groups: lastRoute!.peer_groups,
});
});
return groupedRoutes;
};
+45 -3
View File
@@ -21,6 +21,7 @@ import {
Tooltip,
Typography,
} from "antd";
import { ReloadOutlined } from "@ant-design/icons";
import { Container } from "../components/Container";
import { useDispatch, useSelector } from "react-redux";
import { storeFilterState, getFilterState } from "../utils/filterState";
@@ -72,7 +73,7 @@ export const AccessControl = () => {
const { onChangePageSize, pageSizeOptions, pageSize } = usePageSizeHelpers();
const { getTokenSilently } = useGetTokenSilently();
const dispatch = useDispatch();
const policies = useSelector((state: RootState) => state.policy.data);
const failed = useSelector((state: RootState) => state.policy.failed);
const loading = useSelector((state: RootState) => state.policy.loading);
@@ -84,6 +85,7 @@ export const AccessControl = () => {
);
const [showTutorial, setShowTutorial] = useState(true);
const [isRefreshButtonDisabled, setIsRefreshButtonDisabled] = useState(false);
const [textToSearch, setTextToSearch] = useState("");
const [optionAllEnable, setOptionAllEnable] = useState("all");
const [currentPage, setCurrentPage] = useState(1);
@@ -184,6 +186,26 @@ export const AccessControl = () => {
);
}, []);
const fetchData = async () => {
setIsRefreshButtonDisabled(true);
dispatch(
policyActions.getPolicies.request({
getAccessTokenSilently: getTokenSilently,
payload: null,
})
);
dispatch(
groupActions.getGroups.request({
getAccessTokenSilently: getTokenSilently,
payload: null,
})
);
await new Promise((resolve) => setTimeout(resolve, 5000)).then(() =>
setIsRefreshButtonDisabled(false)
);
};
useEffect(() => {
if (failed) {
setShowTutorial(false);
@@ -298,7 +320,11 @@ export const AccessControl = () => {
setPolicyToAction(record as PolicyDataTable);
confirm({
icon: <ExclamationCircleOutlined />,
title: <span data-testid="confirm-delete-modal-title" className="font-500">Delete rule {record.name}</span>,
title: (
<span data-testid="confirm-delete-modal-title" className="font-500">
Delete rule {record.name}
</span>
),
width: 500,
content: (
<Space direction="vertical" size="small">
@@ -420,7 +446,6 @@ export const AccessControl = () => {
);
};
const toggleModalGroups = (
title: string,
groups: Group[] | string[] | null,
@@ -643,6 +668,7 @@ export const AccessControl = () => {
disabled={showTutorial}
/>
<Select
style={{ marginRight: "10px" }}
value={pageSize.toString()}
options={pageSizeOptions}
onChange={(value) => {
@@ -652,6 +678,22 @@ export const AccessControl = () => {
disabled={showTutorial}
/>
</Space>
<Tooltip
title={
isRefreshButtonDisabled
? "You can refresh it again in 5 seconds"
: "Refresh"
}
>
<Button
onClick={fetchData}
disabled={isRefreshButtonDisabled}
style={{ marginLeft: "5px", color: "#1890ff" }}
>
<ReloadOutlined />
</Button>
</Tooltip>
</Col>
{!showTutorial && (
<Col
+39 -2
View File
@@ -14,6 +14,7 @@ import {
Space,
Table,
Typography,
Tooltip,
} from "antd";
import { Event } from "../store/event/types";
import { filter } from "lodash";
@@ -23,7 +24,7 @@ import { useOidcUser } from "@axa-fr/react-oidc";
import { capitalize, formatDateTime } from "../utils/common";
import { User } from "../store/user/types";
import { usePageSizeHelpers } from "../utils/pageSize";
import { QuestionCircleFilled } from "@ant-design/icons";
import { ReloadOutlined } from "@ant-design/icons";
import { storeFilterState, getFilterState } from "../utils/filterState";
const { Title, Paragraph, Text } = Typography;
@@ -44,6 +45,7 @@ export const Activity = () => {
const setupKeys = useSelector((state: RootState) => state.setupKey.data);
const [textToSearch, setTextToSearch] = useState("");
const [isRefreshButtonDisabled, setIsRefreshButtonDisabled] = useState(false);
const [dataTable, setDataTable] = useState([] as EventDataTable[]);
const transformDataTable = (d: Event[]): EventDataTable[] => {
@@ -58,6 +60,21 @@ export const Activity = () => {
})
);
}, []);
const fetchData = async() => {
setIsRefreshButtonDisabled(true);
dispatch(
eventActions.getEvents.request({
getAccessTokenSilently: getTokenSilently,
payload: null,
})
);
await new Promise((resolve) => setTimeout(resolve, 5000)).then(() =>
setIsRefreshButtonDisabled(false)
);
};
// useEffect(() => {
// setDataTable(transformDataTable(events));
// }, [events]);
@@ -356,6 +373,9 @@ export const Activity = () => {
case "account.setting.peer.login.expiration.enable":
case "account.setting.peer.login.expiration.disable":
case "account.setting.peer.login.expiration.update":
case "integration.create":
case "integration.update":
case "integration.delete":
return renderMultiRowSpan("", "System setting");
case "personal.access.token.create":
case "personal.access.token.delete":
@@ -374,7 +394,7 @@ export const Activity = () => {
return "-";
case "service.user.create":
case "service.user.delete":
return renderMultiRowSpan(event.meta.username, "Service User");
return renderMultiRowSpan(event.meta.name, "Service User");
case "user.invite":
case "user.block":
case "user.delete":
@@ -427,6 +447,7 @@ export const Activity = () => {
<Col xs={24} sm={24} md={11} lg={11} xl={11} xxl={11} span={11}>
<Space size="middle">
<Select
style={{ marginRight: "10px" }}
value={pageSize.toString()}
options={pageSizeOptions}
onChange={(value) => {
@@ -435,6 +456,22 @@ export const Activity = () => {
className="select-rows-per-page-en"
/>
</Space>
<Tooltip
title={
isRefreshButtonDisabled
? "You can refresh it again in 5 seconds"
: "Refresh"
}
>
<Button
onClick={fetchData}
disabled={isRefreshButtonDisabled}
style={{ marginLeft: "5px", color: "#1890ff" }}
>
<ReloadOutlined />
</Button>
</Tooltip>
</Col>
</Row>
{failed && (
+41 -1
View File
@@ -2,7 +2,6 @@ import React, { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { RootState } from "typesafe-actions";
import { actions as nsGroupActions } from "../store/nameservers";
import { Container } from "../components/Container";
import {
Alert,
Button,
@@ -21,8 +20,10 @@ import {
Table,
Tag,
Typography,
Tooltip,
} from "antd";
import { filter } from "lodash";
import { ReloadOutlined } from "@ant-design/icons";
import tableSpin from "../components/Spin";
import { storeFilterState, getFilterState } from "../utils/filterState";
import { useGetTokenSilently } from "../utils/token";
@@ -77,6 +78,7 @@ export const Nameservers = () => {
);
const [textToSearch, setTextToSearch] = useState("");
const [isRefreshButtonDisabled, setIsRefreshButtonDisabled] = useState(false);
const [optionAllEnable, setOptionAllEnable] = useState("all");
const [dataTable, setDataTable] = useState([] as NameserverGroupDataTable[]);
const [showTutorial, setShowTutorial] = useState(false);
@@ -151,6 +153,7 @@ export const Nameservers = () => {
nameservers: nsGroup.nameservers,
groups: nsGroup.groups,
enabled: nsGroup.enabled,
search_domains_enabled: nsGroup.search_domains_enabled,
} as NameServerGroup)
);
};
@@ -166,6 +169,7 @@ export const Nameservers = () => {
nameservers: nsGroup.nameservers,
groups: nsGroup.groups,
enabled: nsGroup.enabled,
search_domains_enabled: nsGroup.search_domains_enabled,
} as NameServerGroup)
);
setShowGroupModal(true);
@@ -192,6 +196,26 @@ export const Nameservers = () => {
);
}, []);
const fetchData = async () => {
setIsRefreshButtonDisabled(true);
dispatch(
nsGroupActions.getNameServerGroups.request({
getAccessTokenSilently: getTokenSilently,
payload: null,
})
);
dispatch(
groupActions.getGroups.request({
getAccessTokenSilently: getTokenSilently,
payload: null,
})
);
await new Promise((resolve) => setTimeout(resolve, 5000)).then(() =>
setIsRefreshButtonDisabled(false)
);
};
const onChangeAllEnabled = ({ target: { value } }: RadioChangeEvent) => {
setOptionAllEnable(value);
storeFilterState("nameServerFilter", "quickFilter", value);
@@ -554,6 +578,22 @@ export const Nameservers = () => {
className="select-rows-per-page-en"
disabled={showTutorial}
/>
<Tooltip
title={
isRefreshButtonDisabled
? "You can refresh it again in 5 seconds"
: "Refresh"
}
>
<Button
onClick={fetchData}
disabled={isRefreshButtonDisabled}
style={{ color: "#1890ff" }}
>
<ReloadOutlined />
</Button>
</Tooltip>
</Space>
</Col>
<Col xs={24} sm={24} md={5} lg={5} xl={5} xxl={5} span={5}>
+62 -18
View File
@@ -94,6 +94,7 @@ export const Peers = () => {
const [groupPopupVisible, setGroupPopupVisible] = useState("");
const [showTutorial, setShowTutorial] = useState(false);
const [hadFirstRun, setHadFirstRun] = useState(true);
const [isRefreshButtonDisabled, setIsRefreshButtonDisabled] = useState(false);
const [confirmModal, confirmModalContextHolder] = Modal.useModal();
const optionsOnOff = [
@@ -182,6 +183,33 @@ export const Peers = () => {
);
};
const fetchData = async () => {
setIsRefreshButtonDisabled(true);
dispatch(
userActions.getUsers.request({
getAccessTokenSilently: getTokenSilently,
payload: null,
})
);
dispatch(
peerActions.getPeers.request({
getAccessTokenSilently: getTokenSilently,
payload: null,
})
);
dispatch(
groupActions.getGroups.request({
getAccessTokenSilently: getTokenSilently,
payload: null,
})
);
await new Promise((resolve) => setTimeout(resolve, 5000)).then(() =>
setIsRefreshButtonDisabled(false)
);
};
useEffect(() => {
getLocalItem<boolean>(StorageKey.hadFirstRun).then((f) =>
setHadFirstRun(f === null ? false : f)
@@ -248,7 +276,7 @@ export const Peers = () => {
const t = searchText
? searchText.toLowerCase().trim()
: textToSearch.toLowerCase().trim();
let f: Peer[] = filter(peers, (f: Peer) => {
let userEmail: string | null;
const u = users?.find((u) => u.id === f.user_id)?.email;
@@ -399,11 +427,7 @@ export const Peers = () => {
};
function handleSwitchSSH(record: PeerDataTable, checked: boolean) {
const peer = {
id: record.id,
ssh_enabled: checked,
name: record.name,
} as Peer;
const peer = { ...record, ssh_enabled: checked };
dispatch(
peerActions.updatePeer.request({
getAccessTokenSilently: getTokenSilently,
@@ -635,16 +659,20 @@ export const Peers = () => {
<Container style={{ paddingTop: "40px" }}>
<Row>
<Col span={24}>
<Title className="page-heading">{isAdmin ? "Peers" : "My peers"}</Title>
<Title className="page-heading">
{isAdmin ? "Peers" : "My peers"}
</Title>
{peers.length ? (
<Paragraph style={{ marginTop: "5px" }}>
{isAdmin ? "A list of all machines and devices connected to your private network. Use this view to manage peers" :
"A list of all your machines and devices that you connected to NetBird."}
{isAdmin
? "A list of all machines and devices connected to your private network. Use this view to manage peers"
: "A list of all your machines and devices that you connected to NetBird."}
</Paragraph>
) : (
<Paragraph style={{ marginTop: "5px" }} type={"secondary"}>
{isAdmin ? "A list of all machines and devices connected to your private network. Use this view to manage peers" :
"A list of all your machines and devices that you connected to NetBird."}
{isAdmin
? "A list of all machines and devices connected to your private network. Use this view to manage peers"
: "A list of all your machines and devices that you connected to NetBird."}
</Paragraph>
)}
@@ -666,11 +694,11 @@ export const Peers = () => {
<Col
xs={24}
sm={24}
md={11}
lg={11}
xl={11}
xxl={11}
span={11}
md={12}
lg={12}
xl={12}
xxl={12}
span={12}
>
<Space size="middle" style={{ marginRight: "15px" }}>
<Radio.Group
@@ -691,10 +719,10 @@ export const Peers = () => {
className="select-rows-per-page-en"
/>
</Space>
{isAdmin && (
<Select
mode="tags"
style={{ marginRight: "10px" }}
placeholder="Filter by groups"
tagRender={blueTagRender}
// dropdownRender={dropDownRender}
@@ -714,8 +742,24 @@ export const Peers = () => {
))}
</Select>
)}
<Tooltip
title={
isRefreshButtonDisabled
? "You can refresh it again in 5 seconds"
: "Refresh"
}
>
<Button
onClick={fetchData}
disabled={isRefreshButtonDisabled}
style={{ marginLeft: "5px", color: "#1890ff" }}
>
<ReloadOutlined />
</Button>
</Tooltip>
</Col>
<Col xs={24} sm={24} md={5} lg={5} xl={5} xxl={5} span={5}>
<Col xs={24} sm={24} md={4} lg={4} xl={4} xxl={4} span={4}>
<Row justify="end">
<Col>
{!showTutorial && (
+121 -33
View File
@@ -41,7 +41,7 @@ import { usePageSizeHelpers } from "../utils/pageSize";
import AddServiceUserPopup from "../components/popups/AddServiceUserPopup";
import InviteUserPopup from "../components/popups/InviteUserPopup";
import { Peer, PeerDataTable } from "../store/peer/types";
import { ExclamationCircleOutlined, MinusOutlined } from "@ant-design/icons";
import { ExclamationCircleOutlined, ReloadOutlined } from "@ant-design/icons";
import { actions as peerActions } from "../store/peer";
import { useOidcUser } from "@axa-fr/react-oidc";
import { storeFilterState, getFilterState } from "../utils/filterState";
@@ -79,6 +79,7 @@ export const RegularUsers = () => {
null as UserDataTable | null
);
const [textToSearch, setTextToSearch] = useState("");
const [isRefreshButtonDisabled, setIsRefreshButtonDisabled] = useState(false);
const [dataTable, setDataTable] = useState([] as UserDataTable[]);
const [confirmModal, confirmModalContextHolder] = Modal.useModal();
@@ -166,6 +167,31 @@ export const RegularUsers = () => {
);
}, [savedUser]);
const fetchData = async () => {
setIsRefreshButtonDisabled(true);
dispatch(
userActions.getUsers.request({
getAccessTokenSilently: getTokenSilently,
payload: null,
})
);
dispatch(
userActions.getRegularUsers.request({
getAccessTokenSilently: getTokenSilently,
payload: null,
})
);
dispatch(
groupActions.getGroups.request({
getAccessTokenSilently: getTokenSilently,
payload: null,
})
);
await new Promise((resolve) => setTimeout(resolve, 5000)).then(() =>
setIsRefreshButtonDisabled(false)
);
};
useEffect(() => {
if (!loading && groups.length && users) {
const searchText = getFilterState("userFilter", "search");
@@ -396,7 +422,7 @@ export const RegularUsers = () => {
icon: <ExclamationCircleOutlined />,
title: (
<span className="font-500">
Are you sure you want to block {user.name? user.name : user.id}?
Are you sure you want to block {user.name ? user.name : user.id}?
</span>
),
width: 600,
@@ -450,12 +476,18 @@ export const RegularUsers = () => {
const handleDeleteUser = (user: UserDataTable) => {
confirmModal.confirm({
icon: <ExclamationCircleOutlined />,
title: <span className="font-500">Are you sure you want to delete {user.name? user.name : user.id}?</span>,
title: (
<span className="font-500">
Are you sure you want to delete {user.name ? user.name : user.id}?
</span>
),
width: 500,
content: (
<Space direction="vertical" size="small">
<Paragraph>
Deleting this user will remove their devices and remove dashboard access.</Paragraph>
Deleting this user will remove their devices and remove dashboard
access.
</Paragraph>
</Space>
),
onOk() {
@@ -510,6 +542,7 @@ export const RegularUsers = () => {
<Col xs={24} sm={24} md={11} lg={11} xl={11} xxl={11} span={11}>
<Space size="middle">
<Select
style={{ marginRight: "10px" }}
value={pageSize.toString()}
options={pageSizeOptions}
onChange={(value) => {
@@ -518,6 +551,21 @@ export const RegularUsers = () => {
className="select-rows-per-page-en"
/>
</Space>
<Tooltip
title={
isRefreshButtonDisabled
? "You can refresh it again in 5 seconds"
: "Refresh"
}
>
<Button
onClick={fetchData}
disabled={isRefreshButtonDisabled}
style={{ marginLeft: "5px", color: "#1890ff" }}
>
<ReloadOutlined />
</Button>
</Tooltip>
</Col>
<Col xs={24} sm={24} md={5} lg={5} xl={5} xxl={5} span={5}>
{(isNetBirdHosted() || isLocalDev()) && (
@@ -583,47 +631,87 @@ export const RegularUsers = () => {
if ((record as User).is_current) {
return (
<>
<div>
{btn}
<Tag color="blue">me</Tag>
</div>
{(isNetBirdHosted() || isLocalDev()) && ((record as User).last_login && <Text type={"secondary"} style={{paddingLeft: "15px"}}>Last login: {String(timeAgo((record as User).last_login))}</Text>)}
</>
<>
<div>
{btn}
<Tag color="blue">me</Tag>
</div>
{(isNetBirdHosted() || isLocalDev()) &&
(record as User).last_login && (
<Text
type={"secondary"}
style={{ paddingLeft: "15px" }}
>
Last login:{" "}
{String(timeAgo((record as User).last_login))}
</Text>
)}
</>
);
}
if ((record as User).status === "invited") {
return (
<>
<div>
{btn}
<Tag color="gold">invited</Tag>
</div>
{(isNetBirdHosted() || isLocalDev()) && ((record as User).last_login && <Text type={"secondary"} style={{paddingLeft: "15px", paddingTop: "0px"}}>Last login: {String(timeAgo((record as User).last_login))}</Text>)}
</>
<>
<div>
{btn}
<Tag color="gold">invited</Tag>
</div>
{(isNetBirdHosted() || isLocalDev()) &&
(record as User).last_login && (
<Text
type={"secondary"}
style={{
paddingLeft: "15px",
paddingTop: "0px",
}}
>
Last login:{" "}
{String(timeAgo((record as User).last_login))}
</Text>
)}
</>
);
}
if ((record as User).status === "blocked") {
return (
<>
<div>
{btn}
<Tag color="red">blocked</Tag>
</div>
{(isNetBirdHosted() || isLocalDev()) && ((record as User).last_login && <Text type={"secondary"} style={{paddingLeft: "15px"}}>Last login: {String(timeAgo((record as User).last_login))}</Text>)}
</>
<>
<div>
{btn}
<Tag color="red">blocked</Tag>
</div>
{(isNetBirdHosted() || isLocalDev()) &&
(record as User).last_login && (
<Text
type={"secondary"}
style={{ paddingLeft: "15px" }}
>
Last login:{" "}
{String(timeAgo((record as User).last_login))}
</Text>
)}
</>
);
}
return (
<>
<div>
{btn}
</div>
{(isNetBirdHosted() || isLocalDev()) && ((record as User).last_login && <Text type={"secondary"} style={{paddingLeft: "15px", paddingTop: "0px"}}>Last login: {String(timeAgo((record as User).last_login))}</Text>)}
</>
<>
<div>{btn}</div>
{(isNetBirdHosted() || isLocalDev()) &&
(record as User).last_login && (
<Text
type={"secondary"}
style={{
paddingLeft: "15px",
paddingTop: "0px",
}}
>
Last login:{" "}
{String(timeAgo((record as User).last_login))}
</Text>
)}
</>
);
}}
/>
@@ -694,7 +782,7 @@ export const RegularUsers = () => {
/>
)}
{/*{isAdmin && (
{isAdmin && (
<Column
title="Delete user"
align="center"
@@ -730,7 +818,7 @@ export const RegularUsers = () => {
return witch;
}}
/>
)}*/}
)}
</Table>
</Card>
</Space>
+301 -171
View File
File diff suppressed because it is too large. Load diff
Loaded 20 of 23 files, more files were not shown because too many files have changed in this diff. Show more