mirror of
https://github.com/netbirdio/dashboard.git
synced 2026-09-23 00:30:43 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
748596f710 | ||
|
|
b06cb0ec3d | ||
|
|
0c924f7ded | ||
|
|
f29c915fc2 | ||
|
|
5f8579bfda | ||
|
|
a71389aa29 | ||
|
|
c3ba026452 | ||
|
|
193f8a7bdf | ||
|
|
f9814e1169 | ||
|
|
2f800bf912 | ||
|
|
0199ea81f3 | ||
|
|
a20894092b | ||
|
|
f4d6ce5770 | ||
|
|
dd67ab6dcb | ||
|
|
2613948cdf | ||
|
|
dc95d8bfd1 | ||
|
|
f49c28f550 | ||
|
|
2fc7b73ea0 | ||
|
|
a9354d3c87 | ||
|
|
e0ae7d068a | ||
|
|
ddd812e9a0 | ||
|
|
2d55d0736f | ||
|
|
8febc26f1f | ||
|
|
3f854b01a0 | ||
|
|
303d51eff8 |
No files matched your search
@@ -0,0 +1,15 @@
|
||||
name: Codespell
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
check:
|
||||
name: Check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
- name: codespell
|
||||
uses: codespell-project/actions-codespell@v2
|
||||
with:
|
||||
only_warn: 1
|
||||
skip: package-lock.json,*.svg
|
||||
+3
-3
@@ -1,15 +1,15 @@
|
||||
# Wiretrustee Dashboard
|
||||
Wiretrustee Dashboard is a the Wiretrustee Managemenet server UI. It allow users to signin, view setup keys and manage peers. This image is **not ready** for production use.
|
||||
Wiretrustee Dashboard is a the Wiretrustee Management server UI. It allow users to signin, view setup keys and manage peers. This image is **not ready** for production use.
|
||||
## Tags
|
||||
```latest``` ```vX.X.X``` not available yet.
|
||||
|
||||
```main``` builded on every PR being merged to the repository
|
||||
```main``` built on every PR being merged to the repository
|
||||
## How to use this image
|
||||
HTTP run:
|
||||
```shell
|
||||
docker run -d --rm -p 80:80 wiretrustee/dashboard:main
|
||||
```
|
||||
Using SSL certificate from Let's Encript®:
|
||||
Using SSL certificate from Let's Encrypt®:
|
||||
```shell
|
||||
docker run -d --rm -p 80:80 -p 443:443 \
|
||||
-e LETSENCRYPT_DOMAIN=app.mydomain.com \
|
||||
|
||||
@@ -387,7 +387,7 @@ check_nb_domain() {
|
||||
|
||||
if [ "$DOMAIN" == "netbird.example.com" ]; then
|
||||
echo "The NETBIRD_DOMAIN cannot be netbird.example.com" > /dev/stderr
|
||||
retrun 1
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ export class AccessControlPage {
|
||||
}
|
||||
|
||||
async assertDefaultAccessCotrolIsCreated() {
|
||||
await test.step('Assert that default cotrol access is created', async () => {
|
||||
await test.step('Assert that default control access is created', async () => {
|
||||
await expect(this.defaulAccessControl).toBeVisible();
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
@@ -125,6 +126,7 @@ const NameServerGroupAdd = () => {
|
||||
};
|
||||
|
||||
const onChange = (changedValues: any) => {
|
||||
|
||||
if (changedValues.primary !== undefined) {
|
||||
setIsPrimary(changedValues.primary);
|
||||
}
|
||||
@@ -156,6 +158,7 @@ const NameServerGroupAdd = () => {
|
||||
],
|
||||
groups: [],
|
||||
enabled: true,
|
||||
search_domains_enabled: false,
|
||||
},
|
||||
{
|
||||
name: cloudflareChoice,
|
||||
@@ -176,6 +179,7 @@ const NameServerGroupAdd = () => {
|
||||
],
|
||||
groups: [],
|
||||
enabled: true,
|
||||
search_domains_enabled: false,
|
||||
},
|
||||
{
|
||||
name: quad9Choice,
|
||||
@@ -196,6 +200,7 @@ const NameServerGroupAdd = () => {
|
||||
],
|
||||
groups: [],
|
||||
enabled: true,
|
||||
search_domains_enabled: false,
|
||||
},
|
||||
];
|
||||
|
||||
@@ -203,6 +208,7 @@ const NameServerGroupAdd = () => {
|
||||
let nsGroupLocal = {} as NameServerGroup;
|
||||
if (value === customChoice) {
|
||||
nsGroupLocal = nsGroup;
|
||||
nsGroupLocal.domains = [];
|
||||
} else {
|
||||
defaultDNSOptions.forEach((nsg) => {
|
||||
if (value === nsg.name) {
|
||||
@@ -262,6 +268,8 @@ const NameServerGroupAdd = () => {
|
||||
groups: existingGroups,
|
||||
groupsToCreate: newGroups,
|
||||
enabled: values.enabled,
|
||||
search_domains_enabled:
|
||||
matchDomains > 0 ? formNSGroup.search_domains_enabled : false,
|
||||
} as NameServerGroupToSave;
|
||||
};
|
||||
|
||||
@@ -462,6 +470,7 @@ const NameServerGroupAdd = () => {
|
||||
</Col>
|
||||
</Space>
|
||||
</Row>
|
||||
{setMatchDomains(fields.length)}
|
||||
{fields.map((field, index) => {
|
||||
return (
|
||||
<Row key={index} style={{ marginBottom: "5px" }}>
|
||||
@@ -522,6 +531,13 @@ const NameServerGroupAdd = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const handleChangeMarkDomain = (checked: boolean) => {
|
||||
setFormNSGroup({
|
||||
...formNSGroup,
|
||||
search_domains_enabled: checked,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{nsGroup && (
|
||||
@@ -701,6 +717,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
@@ -161,7 +161,7 @@ const Navbar = () => {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (user.role?.toLowerCase() === "admin") {
|
||||
if (user.role?.toLowerCase() === "admin" || user.role?.toLowerCase() === "owner") {
|
||||
return true;
|
||||
}
|
||||
return !adminOnlyTabs.find((t) => t === key);
|
||||
|
||||
+207
-131
@@ -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
@@ -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}>
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
+23
-10
@@ -79,6 +79,7 @@ const UserEdit = (props: any) => {
|
||||
const [formUser, setFormUser] = useState({} as FormUser);
|
||||
const [form] = Form.useForm();
|
||||
const [isAdmin, setIsAdmin] = useState(false);
|
||||
const [isOwner, setIsOwner] = useState(false);
|
||||
|
||||
const [confirmModal, confirmModalContextHolder] = Modal.useModal();
|
||||
|
||||
@@ -94,15 +95,21 @@ const UserEdit = (props: any) => {
|
||||
};
|
||||
|
||||
const createUserToSave = (values: any): UserToSave => {
|
||||
const autoGroups =
|
||||
groups
|
||||
?.filter((g) => values.autoGroupsNames.includes(g.id))
|
||||
.map((g) => g.id || "") || [];
|
||||
let autoGroups:string[] = []
|
||||
if (values.autoGroupsNames) {
|
||||
autoGroups =
|
||||
groups
|
||||
?.filter((g) => values.autoGroupsNames.includes(g.id))
|
||||
.map((g) => g.id || "") || [];
|
||||
}
|
||||
// find groups that do not yet exist (newly added by the user)
|
||||
const allGroupsNames: string[] = groups?.map((g) => g.id || "");
|
||||
const groupsToCreate = values.autoGroupsNames.filter(
|
||||
(s: string) => !allGroupsNames.includes(s)
|
||||
);
|
||||
let groupsToCreate:string[] = []
|
||||
if (values.autoGroupsNames) {
|
||||
groupsToCreate = values.autoGroupsNames.filter(
|
||||
(s: string) => !allGroupsNames.includes(s)
|
||||
);
|
||||
}
|
||||
let userID = user ? user.id : "";
|
||||
let isServiceUser = user ? user?.is_service_user : false;
|
||||
return {
|
||||
@@ -120,7 +127,8 @@ const UserEdit = (props: any) => {
|
||||
if (users) {
|
||||
let currentUser = users.find((user) => user?.is_current);
|
||||
if (currentUser) {
|
||||
setIsAdmin(currentUser.role === "admin");
|
||||
setIsAdmin(currentUser.role === "admin" || currentUser.role === "owner");
|
||||
setIsOwner(currentUser.role === "owner");
|
||||
}
|
||||
}
|
||||
}, [users]);
|
||||
@@ -403,7 +411,7 @@ const UserEdit = (props: any) => {
|
||||
>
|
||||
<Select
|
||||
style={{ width: "100%" }}
|
||||
disabled={user?.is_current}
|
||||
disabled={user?.is_current || user?.role === "owner"}
|
||||
>
|
||||
<Option value="admin">
|
||||
<Text type={"secondary"}>admin</Text>
|
||||
@@ -411,6 +419,11 @@ const UserEdit = (props: any) => {
|
||||
<Option value="user">
|
||||
<Text type={"secondary"}>user</Text>
|
||||
</Option>
|
||||
{!user?.is_service_user && isOwner && (
|
||||
<Option value="owner">
|
||||
<Text type={"secondary"}>owner</Text>
|
||||
</Option>
|
||||
)}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
@@ -473,7 +486,7 @@ const UserEdit = (props: any) => {
|
||||
label="Block user"
|
||||
style={{ marginRight: "50px", fontWeight: "500" }}
|
||||
>
|
||||
<Switch />
|
||||
<Switch disabled={user?.role == "owner"} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
)}
|
||||
|
||||
+67
-4
@@ -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;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import {ActionType, createAction, createAsyncAction} from 'typesafe-actions';
|
||||
import {Account} from './types';
|
||||
import {ApiError, ChangeResponse, RequestPayload} from '../../services/api-client/types';
|
||||
import {ApiError, ChangeResponse, DeleteResponse, RequestPayload} from '../../services/api-client/types';
|
||||
|
||||
const actions = {
|
||||
getAccounts: createAsyncAction(
|
||||
@@ -16,6 +16,13 @@ const actions = {
|
||||
)<RequestPayload<Account>, ChangeResponse<Account | null>, ChangeResponse<Account | null>>(),
|
||||
setUpdateAccount: createAction('SET_UPDATED_ACCOUNT')<ChangeResponse<Account | null>>(),
|
||||
resetUpdateAccount: createAction('RESET_UPDATED_ACCOUNT')<null>(),
|
||||
deleteAccount: createAsyncAction(
|
||||
'DELETE_ACCOUNT_REQUEST',
|
||||
'DELETE_ACCOUNT_SUCCESS',
|
||||
'DELETE_ACCOUNT_FAILURE'
|
||||
)<RequestPayload<string>, DeleteResponse<string | null>, DeleteResponse<string | null>>(),
|
||||
resetDeletedAccount: createAction('RESET_DELETED_ACCOUNT')<null>(),
|
||||
setDeleteAccount: createAction('SET_DELETE_ACCOUNT')<DeleteResponse<string | null>>(),
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -2,13 +2,14 @@ import { createReducer } from 'typesafe-actions';
|
||||
import { combineReducers } from 'redux';
|
||||
import { Account } from './types';
|
||||
import actions, { ActionTypes } from './actions';
|
||||
import {ApiError, ChangeResponse} from "../../services/api-client/types";
|
||||
import {ApiError, ChangeResponse, DeleteResponse} from "../../services/api-client/types";
|
||||
|
||||
type StateType = Readonly<{
|
||||
data: Account[] | null;
|
||||
loading: boolean;
|
||||
failed: ApiError | null;
|
||||
savedAccount: ChangeResponse<Account | null>;
|
||||
deletedAccount: DeleteResponse<string | null>;
|
||||
}>;
|
||||
|
||||
const initialState: StateType = {
|
||||
@@ -22,6 +23,13 @@ const initialState: StateType = {
|
||||
error: null,
|
||||
data : null
|
||||
},
|
||||
deletedAccount: <DeleteResponse<string | null>>{
|
||||
loading: false,
|
||||
success: false,
|
||||
failure: false,
|
||||
error: null,
|
||||
data : null
|
||||
},
|
||||
};
|
||||
|
||||
const data = createReducer<Account[], ActionTypes>(initialState.data as Account[])
|
||||
@@ -45,10 +53,18 @@ const updatedAccount = createReducer<ChangeResponse<Account | null>, ActionTypes
|
||||
.handleAction(actions.setUpdateAccount, (store, action) => action.payload)
|
||||
.handleAction(actions.resetUpdateAccount, () => initialState.savedAccount)
|
||||
|
||||
const deleteAccount = createReducer<DeleteResponse<string | null>, ActionTypes>(initialState.deletedAccount)
|
||||
.handleAction(actions.deleteAccount.request, () => initialState.deletedAccount)
|
||||
.handleAction(actions.deleteAccount.success, (store, action) => action.payload)
|
||||
.handleAction(actions.deleteAccount.failure, (store, action) => action.payload)
|
||||
.handleAction(actions.setDeleteAccount, (store, action) => action.payload)
|
||||
.handleAction(actions.resetDeletedAccount, () => initialState.deletedAccount)
|
||||
|
||||
|
||||
export default combineReducers({
|
||||
data,
|
||||
loading,
|
||||
failed,
|
||||
updatedAccount
|
||||
updatedAccount,
|
||||
deleteAccount
|
||||
});
|
||||
@@ -1,8 +1,9 @@
|
||||
import {all, call, put, takeLatest} from 'redux-saga/effects';
|
||||
import {ApiError, ApiResponse, ChangeResponse} from '../../services/api-client/types';
|
||||
import {all, call, put, select, takeLatest} from 'redux-saga/effects';
|
||||
import {ApiError, ApiResponse, ChangeResponse, DeleteResponse} from '../../services/api-client/types';
|
||||
import {Account} from './types'
|
||||
import service from './service';
|
||||
import actions from './actions';
|
||||
import {deletePeer} from "../peer/sagas";
|
||||
|
||||
export function* getAccounts(action: ReturnType<typeof actions.getAccounts.request>): Generator {
|
||||
try {
|
||||
@@ -55,10 +56,55 @@ export function* updateAccount(action: ReturnType<typeof actions.updateAccount.r
|
||||
}
|
||||
}
|
||||
|
||||
export function* deleteAccount(
|
||||
action: ReturnType<typeof actions.deleteAccount.request>
|
||||
): Generator {
|
||||
try {
|
||||
yield call(actions.setDeleteAccount, {
|
||||
loading: true,
|
||||
success: false,
|
||||
failure: false,
|
||||
error: null,
|
||||
data: null,
|
||||
} as DeleteResponse<string | null>);
|
||||
|
||||
const effect = yield call(service.deleteAccount, action.payload);
|
||||
const response = effect as ApiResponse<any>;
|
||||
|
||||
yield put(
|
||||
actions.deleteAccount.success({
|
||||
loading: false,
|
||||
success: true,
|
||||
failure: false,
|
||||
error: null,
|
||||
data: response.body,
|
||||
} as DeleteResponse<string | null>)
|
||||
);
|
||||
|
||||
const accounts = (yield select((state) => state.peer.data)) as Account[];
|
||||
yield put(
|
||||
actions.getAccounts.success(
|
||||
accounts.filter((p: Account) => p.id !== action.payload.payload)
|
||||
)
|
||||
);
|
||||
} catch (err) {
|
||||
yield put(
|
||||
actions.deleteAccount.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.getAccounts.request, getAccounts),
|
||||
takeLatest(actions.updateAccount.request, updateAccount),
|
||||
takeLatest(actions.deleteAccount.request, deleteAccount),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -15,5 +15,11 @@ export default {
|
||||
`/api/accounts/${id}`,
|
||||
payload
|
||||
);
|
||||
}
|
||||
},
|
||||
async deleteAccount(payload:RequestPayload<string>): Promise<ApiResponse<any>> {
|
||||
return apiClient.delete<any>(
|
||||
`/api/accounts/` + payload.payload,
|
||||
payload
|
||||
);
|
||||
},
|
||||
};
|
||||
@@ -8,6 +8,10 @@ export interface Account {
|
||||
jwt_groups_enabled: boolean;
|
||||
groups_propagation_enabled: boolean;
|
||||
jwt_groups_claim_name: string;
|
||||
jwt_allow_groups: string[];
|
||||
extra: {
|
||||
peer_approval_enabled: boolean;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -16,5 +20,7 @@ export interface FormAccount extends Account {
|
||||
jwt_groups_enabled: boolean;
|
||||
groups_propagation_enabled: boolean;
|
||||
jwt_groups_claim_name: string;
|
||||
jwt_allow_groups: string[];
|
||||
peer_login_expiration_formatted: ExpiresInValue;
|
||||
peer_approval_enabled: boolean;
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
Loaded 20 of 36 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user