Compare commits

...
23 Commits
Author SHA1 Message Date
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
braginini 21e69e642a Disable user deletion since the API isn't ready 2023-09-29 16:05:26 +02:00
pascal-fischer 835bb37ab9 Update jwt group sync visibility and update description (#281) 2023-09-29 13:58:46 +02:00
dependabot[bot] a944dc8ab0 Bump @adobe/css-tools from 4.0.1 to 4.3.1 (#265)
Bumps [@adobe/css-tools](https://github.com/adobe/css-tools) from 4.0.1 to 4.3.1.
- [Changelog](https://github.com/adobe/css-tools/blob/main/History.md)
- [Commits](https://github.com/adobe/css-tools/commits)

---
updated-dependencies:
- dependency-name: "@adobe/css-tools"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-29 00:39:19 +05:00
Sarooj bukhari b2c51533fb remove invalid key check (#280) 2023-09-28 21:26:11 +02:00
pascal-fischer fd24536926 add checks to hide last login for non netbird hosted deployments (#277) 2023-09-27 18:00:33 +02:00
Misha Bragin 8e8484cd45 Fix packages (#276) 2023-09-27 12:25:55 +02:00
Sarooj bukhari 6c87f53195 Update the /Activity view to use email addresses from the /api/events response (#273)
uses the events api response information to map user name and email
2023-09-23 10:45:27 +02:00
pascal-fischerandMaycon Santos 9bbbff7dc0 Fix last login for regular entries in users overview (#264)
* add last login to user overview for regular entries without tag

* add backward compatability to properly show with older version of management

* Merge main and update delete user popup messages

---------

Co-authored-by: Maycon Santos <mlsmaycon@gmail.com>
2023-09-23 10:40:22 +02:00
Sarooj bukhari 04a20fa31f add new fields on setting pag (#258) 2023-09-22 16:32:40 +02:00
Misha Bragin 3797db93f0 Hide network routes card for non-linux peers (#269) 2023-09-20 10:58:26 +02:00
Sarooj bukhari 2e81765e85 add delete user functionality (#272) 2023-09-19 15:13:31 +02:00
Yoann N cb9f76c0fc typo in ac creation (#266) 2023-09-18 23:22:52 +02:00
31 changed files with 3011 additions and 1726 deletions

No files matched your search

+6 -6
View File
@@ -63,9 +63,9 @@
}
},
"node_modules/@adobe/css-tools": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.0.1.tgz",
"integrity": "sha512-+u76oB43nOHrF4DDWRLWDCtci7f3QJoEBigemIdIeTi1ODqjx6Tad9NCVnPRwewWlKkVab5PlK8DCtPTyX7S8g=="
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.1.tgz",
"integrity": "sha512-/62yikz7NLScCGAAST5SHdnjaDJQBDq0M2muyRTpf2VQhw6StBg2ALiu73zSJQ4fMVLA+0uBhBHAle7Wg+2kSg=="
},
"node_modules/@ampproject/remapping": {
"version": "2.2.0",
@@ -17173,9 +17173,9 @@
},
"dependencies": {
"@adobe/css-tools": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.0.1.tgz",
"integrity": "sha512-+u76oB43nOHrF4DDWRLWDCtci7f3QJoEBigemIdIeTi1ODqjx6Tad9NCVnPRwewWlKkVab5PlK8DCtPTyX7S8g=="
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.1.tgz",
"integrity": "sha512-/62yikz7NLScCGAAST5SHdnjaDJQBDq0M2muyRTpf2VQhw6StBg2ALiu73zSJQ4fMVLA+0uBhBHAle7Wg+2kSg=="
},
"@ampproject/remapping": {
"version": "2.2.0",
-11
View File
@@ -1,11 +0,0 @@
{
"auth0Auth": "true",
"authAuthority": "https://netbird-localdev.eu.auth0.com",
"authClientId": "kBRMAOqIZ7hvpVCaypQLCJvTzkYYIXVt",
"authScopesSupported": "openid profile email api offline_access email_verified",
"authAudience": "http://localhost:3000/",
"apiOrigin": "http://0.0.0.0",
"grpcApiOrigin": "http://0.0.0.0:80",
"latestVersion": "v0.6.3"
}
+1 -1
View File
@@ -284,7 +284,7 @@ const AccessControlNew = () => {
<Divider style={{ margin: "8px 0" }} />
<Row style={{ padding: "0 8px 4px" }}>
<Col flex="auto">
<Text type={"secondary"}>Add new ports by pressing "Enter"</Text>
<Text type={"secondary"}>Add new groups by pressing "Enter"</Text>
</Col>
<Col flex="none">
<svg
+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
+209 -125
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
);
@@ -179,6 +192,16 @@ const PeerUpdate = (props: any) => {
useEffect(() => {}, [users]);
const routeAddAllowed = (os: string): boolean => {
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);
@@ -389,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,
@@ -408,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,
@@ -652,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 && (
@@ -965,132 +1030,151 @@ const PeerUpdate = (props: any) => {
{/* --- */}
{!isGroupUpdateView && (
<>
<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 -3
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>
@@ -472,8 +472,6 @@ const SetupKeyNew = (props: any) => {
placeholder="Associate groups with the key"
tagRender={blueTagRender}
dropdownRender={dropDownRender}
// enabled only when we have a new key !setupkey.id or when the key is valid
disabled={!(!setupKey.id || setupKey.valid)}
optionFilterProp="searchValue"
>
{tagGroups.map((m, index) => (
@@ -17,14 +17,16 @@ export const UbuntuTab = () => {
key: 1,
title: "Add repository",
commands: [
`sudo apt-get update`,
`sudo apt install ca-certificates curl gnupg -y`,
`curl -L https://pkgs.wiretrustee.com/debian/public.key | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/wiretrustee.gpg`,
`echo 'deb https://pkgs.wiretrustee.com/debian stable main' | sudo tee /etc/apt/sources.list.d/wiretrustee.list`,
`curl -sSL https://pkgs.netbird.io/debian/public.key | sudo gpg --dearmor --output /usr/share/keyrings/netbird-archive-keyring.gpg`,
`echo 'deb [signed-by=/usr/share/keyrings/netbird-archive-keyring.gpg] https://pkgs.netbird.io/debian stable main' | sudo tee /etc/apt/sources.list.d/netbird.list`,
].join("\n"),
commandsForCopy: [
`sudo apt install ca-certificates curl gnupg -y`,
`curl -L https://pkgs.wiretrustee.com/debian/public.key | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/wiretrustee.gpg`,
`echo 'deb https://pkgs.wiretrustee.com/debian stable main' | sudo tee /etc/apt/sources.list.d/wiretrustee.list`,
`sudo apt-get update`,
`sudo apt-get install ca-certificates curl gnupg -y`,
`curl -sSL https://pkgs.netbird.io/debian/public.key | sudo gpg --dearmor --output /usr/share/keyrings/netbird-archive-keyring.gpg`,
`echo 'deb [signed-by=/usr/share/keyrings/netbird-archive-keyring.gpg] https://pkgs.netbird.io/debian stable main' | sudo tee /etc/apt/sources.list.d/netbird.list`,
].join("\n"),
copied: false,
showCopyButton: false,
+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;
}
+13 -4
View File
@@ -1,11 +1,20 @@
import {ExpiresInValue} from "../../views/ExpiresInInput";
export interface Account {
id: string;
settings: { peer_login_expiration_enabled: boolean, peer_login_expiration: number}
id: string;
settings: {
peer_login_expiration_enabled: boolean;
peer_login_expiration: number;
jwt_groups_enabled: boolean;
groups_propagation_enabled: boolean;
jwt_groups_claim_name: string;
};
}
export interface FormAccount extends Account {
peer_login_expiration_enabled: boolean
peer_login_expiration_formatted : ExpiresInValue
peer_login_expiration_enabled: boolean;
jwt_groups_enabled: boolean;
groups_propagation_enabled: boolean;
jwt_groups_claim_name: string;
peer_login_expiration_formatted: ExpiresInValue;
}
+9 -7
View File
@@ -1,9 +1,11 @@
export interface Event {
id: string;
timestamp: string
activity: string
activity_code: string
initiator_id: string
target_id: string
meta: { [key: string]: string }
id: string;
timestamp: string;
activity: string;
activity_code: string;
initiator_id: string;
initiator_email: string;
initiator_name: string;
target_id: string;
meta: { [key: string]: string };
}
+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[]
}
+48 -30
View File
@@ -1,54 +1,72 @@
import {ActionType, createAction, createAsyncAction} from 'typesafe-actions';
import {User, UserToSave} from './types';
import {ApiError, CreateResponse, DeleteResponse, RequestPayload} from '../../services/api-client/types';
import { ActionType, createAction, createAsyncAction } from "typesafe-actions";
import { User, UserToSave } from "./types";
import {
ApiError,
CreateResponse,
DeleteResponse,
RequestPayload,
} from "../../services/api-client/types";
const actions = {
getUsers: createAsyncAction(
'GET_USERS_REQUEST',
'GET_USERS_SUCCESS',
'GET_USERS_FAILURE',
"GET_USERS_REQUEST",
"GET_USERS_SUCCESS",
"GET_USERS_FAILURE"
)<RequestPayload<null>, User[], ApiError>(),
getServiceUsers: createAsyncAction(
'GET_SERVICE_USERS_REQUEST',
'GET_SERVICE_USERS_SUCCESS',
'GET_SERVICE_USERS_FAILURE',
"GET_SERVICE_USERS_REQUEST",
"GET_SERVICE_USERS_SUCCESS",
"GET_SERVICE_USERS_FAILURE"
)<RequestPayload<null>, User[], ApiError>(),
getRegularUsers: createAsyncAction(
'GET_REGULAR_USERS_REQUEST',
'GET_REGULAR_USERS_SUCCESS',
'GET_REGULAR_USERS_FAILURE',
"GET_REGULAR_USERS_REQUEST",
"GET_REGULAR_USERS_SUCCESS",
"GET_REGULAR_USERS_FAILURE"
)<RequestPayload<null>, User[], ApiError>(),
deleteUser: createAsyncAction(
'DELETE_USER_REQUEST',
'DELETE_USER_SUCCESS',
'DELETE_USER_FAILURE',
)<RequestPayload<string>, DeleteResponse<string | null>, DeleteResponse<string | null>>(),
setDeletedUser: createAction('SET_DELETED_USER')<DeleteResponse<string | null>>(),
resetDeletedUser: createAction('RESET_DELETED_USER')<null>(),
"DELETE_USER_REQUEST",
"DELETE_USER_SUCCESS",
"DELETE_USER_FAILURE"
)<
RequestPayload<string>,
DeleteResponse<string | null>,
DeleteResponse<string | null>
>(),
setDeletedUser:
createAction("SET_DELETED_USER")<DeleteResponse<string | null>>(),
resetDeletedUser: createAction("RESET_DELETED_USER")<null>(),
// used to set a user object that was picked in the user table in the UserUpdate drawer (user update window on right-side).
setUser: createAction('SET_USER')<User>(),
setUser: createAction("SET_USER")<User>(),
// used to make the UserUpdate drawer visible in the UI.
setUpdateUserDrawerVisible: createAction('SET_UPDATE_USER_VISIBLE')<boolean>(),
setUpdateUserDrawerVisible: createAction(
"SET_UPDATE_USER_VISIBLE"
)<boolean>(),
// used to make the ViewUserPopup visible in the UI.
setInviteUserPopupVisible: createAction('SET_INVITE_USER_VISIBLE')<boolean>(),
setInviteUserPopupVisible: createAction("SET_INVITE_USER_VISIBLE")<boolean>(),
// used to make the EditUserPopup visible in the UI.
setEditUserPopupVisible: createAction('SET_EDIT_USER_VISIBLE')<boolean>(),
setEditUserPopupVisible: createAction("SET_EDIT_USER_VISIBLE")<boolean>(),
// used to make the AddServiceUserPopup visible in the UI.
setAddServiceUserPopupVisible: createAction('SET_ADD_SERVICE_USER_VISIBLE')<boolean>(),
setAddServiceUserPopupVisible: createAction(
"SET_ADD_SERVICE_USER_VISIBLE"
)<boolean>(),
// used to remember what tab was open on users page
setUserTabOpen: createAction('SET_USER_TAB_OPEN')<string>(),
setUserTabOpen: createAction("SET_USER_TAB_OPEN")<string>(),
saveUser: createAsyncAction(
'SAVE_USER_REQUEST',
'SAVE_USER_SUCCESS',
'SAVE_USER_FAILURE',
)<RequestPayload<UserToSave>, CreateResponse<User | null>, CreateResponse<User | null>>(),
setSavedUser: createAction('SET_SAVED_USER')<CreateResponse<User | null>>(),
resetSavedUser: createAction('RESET_SAVED_USER')<null>(),
"SAVE_USER_REQUEST",
"SAVE_USER_SUCCESS",
"SAVE_USER_FAILURE"
)<
RequestPayload<UserToSave>,
CreateResponse<User | null>,
CreateResponse<User | null>
>(),
setSavedUser: createAction("SET_SAVED_USER")<CreateResponse<User | null>>(),
resetSavedUser: createAction("RESET_SAVED_USER")<null>(),
};
export type ActionTypes = ActionType<typeof actions>;
+162 -138
View File
@@ -1,159 +1,183 @@
import {all, call, put, select, takeLatest} from 'redux-saga/effects';
import {ApiError, ApiResponse, CreateResponse, DeleteResponse} from '../../services/api-client/types';
import {User, UserToSave} from './types'
import service from './service';
import actions from './actions';
import { all, call, put, select, takeLatest } from "redux-saga/effects";
import {
ApiError,
ApiResponse,
CreateResponse,
DeleteResponse,
} from "../../services/api-client/types";
import { User, UserToSave } from "./types";
import service from "./service";
import actions from "./actions";
import serviceGroup from "../group/service";
import {Group} from "../group/types";
import { Group } from "../group/types";
export function* getUsers(action: ReturnType<typeof actions.getUsers.request>): Generator {
try {
const effect = yield call(service.getUsers, action.payload);
const response = effect as ApiResponse<User[]>;
export function* getUsers(
action: ReturnType<typeof actions.getUsers.request>
): Generator {
try {
const effect = yield call(service.getUsers, action.payload);
const response = effect as ApiResponse<User[]>;
yield put(actions.getUsers.success(response.body));
} catch (err) {
yield put(actions.getUsers.failure(err as ApiError));
}
yield put(actions.getUsers.success(response.body));
} catch (err) {
yield put(actions.getUsers.failure(err as ApiError));
}
}
export function* getServiceUsers(action: ReturnType<typeof actions.getServiceUsers.request>): Generator {
try {
action.payload.queryParams = {service_user: true}
const effect = yield call(service.getUsers, action.payload);
const response = effect as ApiResponse<User[]>;
export function* getServiceUsers(
action: ReturnType<typeof actions.getServiceUsers.request>
): Generator {
try {
action.payload.queryParams = { service_user: true };
const effect = yield call(service.getUsers, action.payload);
const response = effect as ApiResponse<User[]>;
yield put(actions.getServiceUsers.success(response.body));
} catch (err) {
yield put(actions.getServiceUsers.failure(err as ApiError));
}
yield put(actions.getServiceUsers.success(response.body));
} catch (err) {
yield put(actions.getServiceUsers.failure(err as ApiError));
}
}
export function* getRegularUsers(action: ReturnType<typeof actions.getRegularUsers.request>): Generator {
try {
action.payload.queryParams = {service_user: false}
const effect = yield call(service.getUsers, action.payload);
const response = effect as ApiResponse<User[]>;
export function* getRegularUsers(
action: ReturnType<typeof actions.getRegularUsers.request>
): Generator {
try {
action.payload.queryParams = { service_user: false };
const effect = yield call(service.getUsers, action.payload);
const response = effect as ApiResponse<User[]>;
yield put(actions.getRegularUsers.success(response.body));
} catch (err) {
yield put(actions.getRegularUsers.failure(err as ApiError));
}
yield put(actions.getRegularUsers.success(response.body));
} catch (err) {
yield put(actions.getRegularUsers.failure(err as ApiError));
}
}
export function* saveUser(action: ReturnType<typeof actions.saveUser.request>): Generator {
try {
yield put(actions.setSavedUser({
loading: true,
success: false,
failure: false,
error: null,
data: null
} as CreateResponse<User | null>))
export function* saveUser(
action: ReturnType<typeof actions.saveUser.request>
): Generator {
try {
yield put(
actions.setSavedUser({
loading: true,
success: false,
failure: false,
error: null,
data: null,
} as CreateResponse<User | null>)
);
const userToSave = action.payload.payload
const userToSave = action.payload.payload;
let groupsToCreate = userToSave.groupsToCreate
if (!groupsToCreate) {
groupsToCreate = []
}
// first, create groups that were newly added by user
const responsesGroup = yield all(groupsToCreate.map(g => call(serviceGroup.createGroup, {
getAccessTokenSilently: action.payload.getAccessTokenSilently,
payload: {name: g}
})
))
const resGroups = (responsesGroup as ApiResponse<Group>[]).filter(r => r.statusCode === 200).map(g => (g.body as Group)).map(g => g.id)
const newGroups = [...userToSave.auto_groups, ...resGroups]
let payload = {
name: userToSave.name,
email: userToSave.email,
role: userToSave.role,
auto_groups: newGroups,
is_service_user: userToSave.is_service_user,
is_blocked: userToSave.is_blocked
} as UserToSave
let effect
if (!userToSave.id) {
effect = yield call(service.createUser, {
getAccessTokenSilently: action.payload.getAccessTokenSilently,
payload: payload
});
} else {
payload.id = userToSave.id
effect = yield call(service.editUser, {
getAccessTokenSilently: action.payload.getAccessTokenSilently,
payload: payload
});
}
const response = effect as ApiResponse<User>;
yield put(actions.saveUser.success({
loading: false,
success: true,
failure: false,
error: null,
data: response.body
} as CreateResponse<User | null>));
} catch (err) {
yield put(actions.saveUser.failure({
loading: false,
success: false,
failure: false,
error: err as ApiError,
data: null
} as CreateResponse<User | null>));
let groupsToCreate = userToSave.groupsToCreate;
if (!groupsToCreate) {
groupsToCreate = [];
}
// first, create groups that were newly added by user
const responsesGroup = yield all(
groupsToCreate.map((g) =>
call(serviceGroup.createGroup, {
getAccessTokenSilently: action.payload.getAccessTokenSilently,
payload: { name: g },
})
)
);
const resGroups = (responsesGroup as ApiResponse<Group>[])
.filter((r) => r.statusCode === 200)
.map((g) => g.body as Group)
.map((g) => g.id);
const newGroups = [...userToSave.auto_groups, ...resGroups];
let payload = {
name: userToSave.name,
email: userToSave.email,
role: userToSave.role,
auto_groups: newGroups,
is_service_user: userToSave.is_service_user,
is_blocked: userToSave.is_blocked,
} as UserToSave;
let effect;
if (!userToSave.id) {
effect = yield call(service.createUser, {
getAccessTokenSilently: action.payload.getAccessTokenSilently,
payload: payload,
});
} else {
payload.id = userToSave.id;
effect = yield call(service.editUser, {
getAccessTokenSilently: action.payload.getAccessTokenSilently,
payload: payload,
});
}
const response = effect as ApiResponse<User>;
yield put(
actions.saveUser.success({
loading: false,
success: true,
failure: false,
error: null,
data: response.body,
} as CreateResponse<User | null>)
);
} catch (err) {
yield put(
actions.saveUser.failure({
loading: false,
success: false,
failure: false,
error: err as ApiError,
data: null,
} as CreateResponse<User | null>)
);
}
}
export function* deleteUser(action: ReturnType<typeof actions.deleteUser.request>): Generator {
try {
yield call(actions.setDeletedUser,{
loading: true,
success: false,
failure: false,
error: null,
data: null
} as DeleteResponse<string | null>)
export function* deleteUser(
action: ReturnType<typeof actions.deleteUser.request>
): Generator {
try {
yield put(
actions.setDeletedUser({
loading: true,
success: false,
failure: false,
error: null,
data: null,
} as DeleteResponse<string | null>)
);
const effect = yield call(service.deleteUser, action.payload);
const response = effect as ApiResponse<any>;
yield put(actions.deleteUser.success({
loading: false,
success: true,
failure: false,
error: null,
data: response.body
} as DeleteResponse<string | null>));
const users = (yield select(state => state.users.data)) as User[]
const regularUsers = (yield select(state => state.users.regularUsers)) as User[]
const serviceUsers = (yield select(state => state.users.serviceUsers)) as User[]
yield put(actions.getUsers.success(users.filter((p:User) => p.id !== action.payload.payload)))
yield put(actions.getRegularUsers.success(regularUsers.filter((p:User) => p.id !== action.payload.payload)))
yield put(actions.getServiceUsers.success(serviceUsers.filter((p:User) => p.id !== action.payload.payload)))
} catch (err) {
yield put(actions.deleteUser.failure({
loading: false,
success: false,
failure: false,
error: err as ApiError,
data: null
} as DeleteResponse<string | null>));
}
const effect = yield call(service.deleteUser, action.payload);
const response = effect as ApiResponse<any>;
yield put(
actions.deleteUser.success({
loading: false,
success: true,
failure: false,
error: null,
data: response.body,
} as DeleteResponse<string | null>)
);
} catch (err) {
yield put(
actions.deleteUser.failure({
loading: false,
success: false,
failure: false,
error: err as ApiError,
data: null,
} as DeleteResponse<string | null>)
);
}
}
export default function* sagas(): Generator {
yield all([
takeLatest(actions.getUsers.request, getUsers),
takeLatest(actions.getServiceUsers.request, getServiceUsers),
takeLatest(actions.getRegularUsers.request, getRegularUsers),
takeLatest(actions.saveUser.request, saveUser),
takeLatest(actions.deleteUser.request, deleteUser)
]);
yield all([
takeLatest(actions.getUsers.request, getUsers),
takeLatest(actions.getServiceUsers.request, getServiceUsers),
takeLatest(actions.getRegularUsers.request, getRegularUsers),
takeLatest(actions.saveUser.request, saveUser),
takeLatest(actions.deleteUser.request, deleteUser),
]);
}
Loaded 20 of 31 files, more files were not shown because too many files have changed in this diff. Show more