Compare commits

..
31 Commits
Author SHA1 Message Date
Maycon Santos c3ba026452 Init domains array for custom nameserver group (#304) 2023-12-08 11:07:10 +01:00
Maycon Santos 193f8a7bdf Fix missing text update from #302 (#303) 2023-12-07 18:05:44 +01:00
Fabio Fantoni f9814e1169 add codespell github workflow and fix some typo (#296)
* add codespell github workflow

* fix some typo
2023-12-07 18:05:31 +01:00
Maycon Santos 2f800bf912 Add delete account support (#302)
* Add delete account support

* add peer approval form item

* block menu for non owners
2023-12-07 15:28:07 +01:00
pascal-fischerandMaycon Santos 0199ea81f3 Add support for peer approval (#299)
* add support for peer approval

* use gold color for tag

---------

Co-authored-by: Maycon Santos <mlsmaycon@gmail.com>
2023-12-05 13:17:40 +01:00
Maycon Santos a20894092b Add owner role support (#300)
Update checks for admin to include owner role

Updated role list to include role for users

Updated activity with new event type handler
2023-12-01 16:57:18 +01:00
Maycon Santos f4d6ce5770 Fix Windows server version lowercase 2023-11-24 18:29:43 +01:00
Maycon Santos dd67ab6dcb Fix Windows server version (#298) 2023-11-24 17:42:31 +01:00
pascal-fischer 2613948cdf Fix OS display format for iOS (#297)
* fix OS format function to properly show iOS

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

Co-authored-by: braginini <bangvalo@gmail.com>
2023-10-20 09:33:44 +02:00
Sarooj bukhariandbraginini ddd812e9a0 Add a refresh button to the table views (#287)
Co-authored-by: braginini <bangvalo@gmail.com>
2023-10-15 19:04:05 +02:00
Sarooj bukhariandbraginini 2d55d0736f Add referesh button to the peers table (#286)
Co-authored-by: braginini <bangvalo@gmail.com>
2023-10-12 18:38:59 +02:00
Sarooj bukhari 8febc26f1f fix update distribution groups bug (#284) 2023-10-04 20:07:02 +02:00
Sarooj bukhari 3f854b01a0 Add support to network routes with peer group (#275)
support to routes with peer group
updated the view logic to support the new type
updated peer view as well
for now, we are supporting a single group, but that can be extended
2023-10-04 11:37:23 +02:00
Misha Bragin 303d51eff8 Display service username in the activity (#282) 2023-10-03 16:06:50 +02:00
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
43 changed files with 3507 additions and 1767 deletions

No files matched your search

+15
View File
@@ -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
View File
@@ -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 \
+1 -1
View File
@@ -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
}
+1 -1
View File
@@ -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();
})
}
+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
+57 -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);
@@ -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
+1 -1
View File
@@ -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);
+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) => (
+23 -10
View File
@@ -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>
)}
@@ -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;
}
+8 -1
View File
@@ -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>>(),
};
+18 -2
View File
@@ -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
});
Loaded 20 of 43 files, more files were not shown because too many files have changed in this diff. Show more