Compare commits

...
18 Commits
Author SHA1 Message Date
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
Sarooj bukhariandMaycon Santos 54accb665c setup ui (#268)
* setup ui

* capital letter

---------

Co-authored-by: Maycon Santos <mlsmaycon@gmail.com>
2023-09-07 20:26:34 +02:00
Maycon Santos cfea3bd489 Add ephemeral peers views and subviews (#267) 2023-09-06 13:36:23 +02:00
Zoltan Papp a44a1c5424 Feature/ephemeral peers (#263)
* Add ephemeral peer switch for SetupKeyNew component

* Add "sys" handling in activity view
2023-09-04 13:49:11 +02:00
Sarooj bukhari c2c044421f Peer search bug (#262) 2023-08-25 12:25:58 +02:00
Yulia e8d57c3445 Fix flaky Access control test (#261)
Fixed flaky test by changing the way to access 
Access Control Page and add some test ids of Add Rules buttons.

Temporary removed tests for Firefox and webkit(safari)
2023-08-22 11:38:11 +02:00
pascal-fischer 0b892c0056 Add User last login and new events
Add last login to users overview list.
Additionally added new events for dashboard login, peer expiration and peer login.
2023-08-18 19:23:56 +02:00
Misha Bragin 14d9b80029 Limit UI view for the "user" role (#259) 2023-08-15 22:36:21 +02:00
31 changed files with 1755 additions and 1092 deletions

No files matched your search

+56
View File
@@ -0,0 +1,56 @@
import { Page, test, expect } from "@playwright/test";
export class AccessControlPage {
private readonly accessControlUrl = 'http://localhost:3000/acls'
private readonly defaulAccessControl = this.page.getByRole('cell', { name: 'Default' })
private readonly deleteButton = this.page.getByRole('button', { name: 'Delete' })
private readonly deleteModal = this.page.getByTestId('confirm-delete-modal-title')
private readonly confirmButton = this.page.getByRole('button', { name: 'OK' })
private readonly addRulesButton = this.page.getByTestId('add-rule-empty-state-button')
constructor(private readonly page: Page) {}
async openAccessControlPage() {
await test.step('Open Access Control page', async () => {
await this.page.goto(this.accessControlUrl);
})
}
async assertDefaultAccessCotrolIsCreated() {
await test.step('Assert that default cotrol access is created', async () => {
await expect(this.defaulAccessControl).toBeVisible();
})
}
async pressDeleteButton() {
await test.step('Press delete button', async () => {
await this.deleteButton.click();
})
}
async assertDeleteModalIsVisibile() {
await test.step('Assert access control deletion modal is visible', async () => {
await expect(this.deleteModal).toBeVisible();
})
}
async pressConfirmButton() {
await test.step('Press confirm button on access control deletion modal', async () => {
await this.confirmButton.click();
})
}
async assertDefaultAccessCotrolIsDeleted() {
await test.step('Assert default access control should be deleted', async () => {
await expect(this.defaulAccessControl).not.toBeVisible();
})
}
async assertAddRuleButtonIsVisile() {
await test.step('Assert Add Rules button is visible', async () => {
await expect(this.addRulesButton).toBeVisible();
})
}
}
export default AccessControlPage;
+34
View File
@@ -0,0 +1,34 @@
import { Page, test, expect} from "@playwright/test";
export class LoginPage {
private readonly localUrl = 'http://localhost:3000/'
private readonly usernameField = this.page.getByPlaceholder('username@domain')
private readonly nextButton = this.page.getByRole('button', { name: 'next' })
private readonly passwordField = this.page.getByLabel('Password')
private readonly skipButton = this.page.getByRole('button', { name: 'skip' });
private readonly netBirdLogo = this.page.getByRole('link', { name: 'logo' })
constructor(private readonly page: Page) {}
async doLogin() {
await test.step('Login to local enviroment', async () => {
await this.page.goto(this.localUrl);
await this.usernameField.fill('admin@localhost');
await this.pressNextButton();
await this.passwordField.fill('testMe123@');
await this.pressNextButton();
if (await this.skipButton.isVisible({ timeout: 300 })) {
await this.skipButton.click();
}
await expect(this.netBirdLogo).toBeVisible();
})
}
async pressNextButton() {
await test.step('Press next button', async () => {
await this.nextButton.click();
})
}
}
export default LoginPage;
+1 -1
View File
@@ -1,4 +1,4 @@
import { Page, test, expect } from "@playwright/test";
import { Page, test } from "@playwright/test";
export class PeersPage {
private readonly addNewPeerButton = this.page.getByTestId('add-new-peer-button')
+15 -26
View File
@@ -1,33 +1,22 @@
import { test, expect } from '@playwright/test';
import {AddPeerModal} from '../pages/modals/add-peer-modal'
import {TopMenu} from '../pages/top-menu';
import { test } from '@playwright/test'
import {LoginPage} from '../pages/login-page'
import {AccessControlPage} from '../pages/access-control-page'
const URL = 'https://app.netbird.io/'
const localUrl = 'http://localhost:3000/'
let addPeerModal: AddPeerModal;
let topMenu: TopMenu;
let loginPage: LoginPage
let accessControlPage: AccessControlPage
test.beforeEach(async ({ page }) => {
addPeerModal = new AddPeerModal(page);
await page.goto(localUrl);
await page.getByPlaceholder('username@domain').fill('admin@localhost');
await page.getByRole('button', { name: 'next' }).click();
await page.getByLabel('Password').fill('testMe123@');
await page.getByRole('button', { name: 'next' }).click();
const skipButton = page.getByRole('button', { name: 'skip' });
if (await skipButton.isVisible({ timeout: 300 })) {
await skipButton.click();
}
await addPeerModal.assertPeerModalIsVisible();
loginPage = new LoginPage(page);
await loginPage.doLogin();
});
test('Confirm that new user has Default access', async ({ page }) => {
topMenu = new TopMenu(page);
await addPeerModal.closeAddPeerModal();
await addPeerModal.assertPeerModalIsNotVisible();
await topMenu.clickOnAccessControlOnTopMenu();
await expect(page.getByRole('cell', { name: 'Default' })).toBeVisible();
await page.getByRole('button', { name: 'Delete' }).click();
await expect(page.getByTestId('confirm-delete-modal-title')).toBeVisible();
await page.getByRole('button', { name: 'Cancel' }).click();
accessControlPage = new AccessControlPage(page);
await accessControlPage.openAccessControlPage();
await accessControlPage.assertDefaultAccessCotrolIsCreated();
await accessControlPage.pressDeleteButton();
await accessControlPage.assertDeleteModalIsVisibile();
await accessControlPage.pressConfirmButton();
await accessControlPage.assertDefaultAccessCotrolIsDeleted();
await accessControlPage.assertAddRuleButtonIsVisile();
});
+12 -19
View File
@@ -1,48 +1,41 @@
import { test } from '@playwright/test';
import { test } from '@playwright/test'
import {AddPeerModal} from '../pages/modals/add-peer-modal'
import {PeersPage} from '../pages/peers-page'
import {LoginPage} from '../pages/login-page'
const URL = 'https://app.netbird.io/'
const localUrl = 'http://localhost:3000/'
let addPeerModal: AddPeerModal;
let peersPage: PeersPage;
let addPeerModal: AddPeerModal
let peersPage: PeersPage
let loginPage: LoginPage
test.beforeEach(async ({ page }) => {
addPeerModal = new AddPeerModal(page);
await page.goto(localUrl);
await page.getByPlaceholder('username@domain').fill('admin@localhost');
await page.getByRole('button', { name: 'next' }).click();
await page.getByLabel('Password').fill('testMe123@');
await page.getByRole('button', { name: 'next' }).click();
const skipButton = page.getByRole('button', { name: 'skip' });
if (await skipButton.isVisible({ timeout: 300 })) {
await skipButton.click();
}
loginPage = new LoginPage(page);
await loginPage.doLogin();
await addPeerModal.assertPeerModalIsVisible();
});
test('Test Linux tab on a first access add peer modal / @bc', async function ({ }) {
test('Test Linux tab on a first access add peer modal / @bc', async function () {
await addPeerModal.openLinuxTab();
await addPeerModal.assertLinuxTabHasCorrectText();
});
test('Test Windows tab on a first access add peer modal / @bc', async ({ }) => {
test('Test Windows tab on a first access add peer modal / @bc', async () => {
await addPeerModal.openWindowsTab();
await addPeerModal.assertWindowsDownloadButtonHasCorrectLink();
});
test('Test MacOS tab on a first access add peer modal / @bc', async ({ }) => {
test('Test MacOS tab on a first access add peer modal / @bc', async () => {
await addPeerModal.openMacTab();
await addPeerModal.assertIntelDownloadButtonHasCorrectLink();
await addPeerModal.assertM1M2DownloadButtonHasCorrectLink();
});
test('Test Android tab on a first access add peer modal', async ({ }) => {
test('Test Android tab on a first access add peer modal', async () => {
await addPeerModal.openAndroidTab();
await addPeerModal.assertAndroidDownloadButtonHasCorrectLink();
});
test('Test Docker tab on a first access add peer modal', async ({ }) => {
test('Test Docker tab on a first access add peer modal', async () => {
await addPeerModal.openDockerTab();
await addPeerModal.assertDockerDownloadButtonHasCorrectLink();
});
+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",
+9 -9
View File
@@ -39,15 +39,15 @@ export default defineConfig({
use: { channel: 'chrome', },
},
{
name: 'firefox',
use: { browserName: 'firefox', },
},
{
name: 'webkit',
use: { browserName: 'webkit', },
},
// {
// name: 'firefox',
// use: { browserName: 'firefox', },
// },
//
// {
// name: 'webkit',
// use: { browserName: 'webkit', },
// },
/* Test against mobile viewports. */
// {
-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
+1
View File
@@ -53,6 +53,7 @@ const Navbar = () => {
"/acls",
"/routes",
"/dns",
"/users",
"/activity",
"/settings",
];
+9 -1
View File
@@ -179,6 +179,13 @@ 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")
}
const toggleEditName = (status: boolean, value?: string) => {
setEditName(status);
@@ -965,6 +972,7 @@ const PeerUpdate = (props: any) => {
{/* --- */}
{!isGroupUpdateView && (
<>
{routeAddAllowed(peer.os) &&
<Card
bordered={true}
// loading={loading}Ć’
@@ -1090,7 +1098,7 @@ const PeerUpdate = (props: any) => {
</Space>
)}
</div>
</Card>
</Card>}
<Card bordered={true} style={{ marginBottom: "50px" }}>
<Col span={24}>
+78 -36
View File
@@ -13,6 +13,7 @@ import {
Tag,
Typography,
Card,
Tooltip,
} from "antd";
import { RootState } from "typesafe-actions";
import {
@@ -54,7 +55,7 @@ const SetupKeyNew = (props: any) => {
const savedSetupKey = useSelector(
(state: RootState) => state.setupKey.savedSetupKey
);
const groups = useSelector((state: RootState) => state.group.data);
const [form] = Form.useForm();
@@ -91,14 +92,13 @@ const SetupKeyNew = (props: any) => {
.filter((g) => allGroups.get(g))
.map((g) => allGroups.get(g)!.name);
}
const fSetupKey = {
...setupKey,
autoGroupNames: setupKey.auto_groups || [],
exp: moment(setupKey.expires),
last: moment(setupKey.last_used),
} as FormSetupKey;
form.setFieldsValue(fSetupKey);
form.setFieldsValue(fSetupKey);
setFormSetupKey(fSetupKey);
}, [setupKey]);
@@ -117,6 +117,7 @@ const SetupKeyNew = (props: any) => {
groupsToCreate: groupsToCreate,
expires_in: expiresIn,
usage_limit: formSetupKey.usage_limit,
ephemeral: formSetupKey.ephemeral,
} as SetupKeyToSave;
};
@@ -125,8 +126,7 @@ const SetupKeyNew = (props: any) => {
await form.validateFields();
} catch (e) {
const errorFields = (e as any).errorFields;
return console.log("errorInfo", errorFields);
}
}
const setupKeyToSave = createSetupKeyToSave();
dispatch(
@@ -257,8 +257,7 @@ const SetupKeyNew = (props: any) => {
dispatch(personalAccessTokenActions.resetPersonalAccessTokens(null));
setVisibleNewSetupKey(false);
};
return (
return (
<>
{!isGroupUpdateView && (
<Breadcrumb
@@ -302,13 +301,9 @@ const SetupKeyNew = (props: any) => {
{!isGroupUpdateView && (
<Row style={{ marginTop: "10px" }}>
<Col
xs={24}
sm={24}
md={11}
lg={11}
xl={11}
xxl={11}
span={11}
md={8}
lg={8}
style={{
paddingRight: "70px",
}}
@@ -342,7 +337,14 @@ const SetupKeyNew = (props: any) => {
/>
</Col>
<Col xs={24} sm={24} md={5} lg={5} xl={5} xxl={5} span={5}>
<Col
sm={24}
md={8}
lg={6}
style={{
paddingRight: "70px",
}}
>
<Paragraph
style={{
whiteSpace: "pre-line",
@@ -357,8 +359,48 @@ const SetupKeyNew = (props: any) => {
fontWeight: "500",
}}
></Paragraph>
{formSetupKey.type === "one-off" ? "One-off" : "Reusable"},
available uses
Type{" "}
{formSetupKey.ephemeral ? (
<Tooltip title="Peers that are offline for over 10 minutes will be removed automatically">
<Tag>
<Text type="secondary" style={{ fontSize: 10 }}>
Ephemeral
</Text>
</Tag>
</Tooltip>
) : (
" "
)}
</Paragraph>
<Col>
<Input
disabled
value={
formSetupKey.type === "one-off" ? "One-off" : "Reusable"
}
suffix={<LockOutlined style={{ color: "#BFBFBF" }} />}
style={{ marginTop: "8px" }}
/>
</Col>
</Col>
<Col sm={24} md={8} lg={3}>
<Paragraph
style={{
whiteSpace: "pre-line",
margin: 0,
fontWeight: "500",
}}
>
<Paragraph
style={{
whiteSpace: "pre-line",
margin: 0,
fontWeight: "500",
}}
></Paragraph>
{/* {formSetupKey.type === "one-off" ? "One-off" : "Reusable"}, */}
Available uses
</Paragraph>
<Col>
<Input
@@ -376,6 +418,25 @@ const SetupKeyNew = (props: any) => {
</Col>
</Row>
)}
<Row style={{ marginTop: `${isGroupUpdateView ? "0" : "39px"}` }}>
{!isGroupUpdateView && (
<Col xs={24} sm={24} md={5} lg={5} xl={5} xxl={5} span={5}>
<Paragraph style={{ margin: 0, fontWeight: "500" }}>
Expires
</Paragraph>
<Row>
<Input
style={{ marginTop: "8px" }}
disabled
suffix={<LockOutlined style={{ color: "#BFBFBF" }} />}
value={
customExpiresFormat(new Date(formSetupKey.expires))!
}
/>
</Row>
</Col>
)}
</Row>
<Row style={{ marginTop: `${isGroupUpdateView ? "0" : "39px"}` }}>
<Col
xs={24}
@@ -411,9 +472,7 @@ 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="serchValue"
optionFilterProp="searchValue"
>
{tagGroups.map((m, index) => (
<Option key={index} value={m.id} serchValue={m.name}>
@@ -424,23 +483,6 @@ const SetupKeyNew = (props: any) => {
</Form.Item>
</Col>
</Col>
{!isGroupUpdateView && (
<Col xs={24} sm={24} md={5} lg={5} xl={5} xxl={5} span={5}>
<Paragraph style={{ margin: 0, fontWeight: "500" }}>
Expires
</Paragraph>
<Row>
<Input
style={{ marginTop: "8px" }}
disabled
suffix={<LockOutlined style={{ color: "#BFBFBF" }} />}
value={
customExpiresFormat(new Date(formSetupKey.expires))!
}
/>
</Row>
</Col>
)}
</Row>
</Form>
</div>
+39
View File
@@ -75,6 +75,7 @@ const SetupKeyNew = () => {
const expiresIn = formSetupKey.expires_in
? formSetupKey.expires_in * 24 * 3600
: 7 * 24 * 3600; // the api expects seconds we have days
return {
id: formSetupKey.id,
name: formSetupKey.name,
@@ -84,6 +85,7 @@ const SetupKeyNew = () => {
groupsToCreate: groupsToCreate,
expires_in: expiresIn,
usage_limit: formSetupKey.usage_limit,
ephemeral: formSetupKey.ephemeral,
} as SetupKeyToSave;
};
@@ -155,6 +157,7 @@ const SetupKeyNew = () => {
usage_limit: 0,
used_times: 0,
expires_in: 7,
ephemeral: false,
} as SetupKey)
);
setFormSetupKey({} as FormSetupKey);
@@ -487,6 +490,42 @@ const SetupKeyNew = () => {
</Col>
</Row>
<Row style={{ marginTop: "24px" }} justify={"space-between"}>
<Col span={18}>
<Paragraph
style={{
whiteSpace: "pre-line",
margin: 0,
fontWeight: "500",
}}
>
Ephemeral peers
</Paragraph>
<Paragraph
type={"secondary"}
style={{ whiteSpace: "pre-line", margin: 0 }}
>
Peers that are offline for over 10 minutes will be removed
automatically
</Paragraph>
</Col>
<Col span={6}>
<Row justify={"end"}>
<Form.Item name="ephemeral" valuePropName="checked">
<Switch
size="small"
onChange={(checked) => {
setFormSetupKey({
...formSetupKey,
ephemeral: checked,
});
}}
/>
</Form.Item>
</Row>
</Col>
</Row>
<Row style={{ marginTop: "24px" }}>
<Col span={24}>
<Paragraph
+3 -2
View File
@@ -166,6 +166,7 @@ const UserEdit = (props: any) => {
dispatch(userActions.setUser(null as unknown as User));
dispatch(personalAccessTokenActions.resetPersonalAccessTokens(null));
dispatch(userActions.setUserTabOpen(key));
dispatch(userActions.setEditUserPopupVisible(false));
};
const selectValidator = (_: RuleObject, value: string[]) => {
@@ -331,7 +332,7 @@ const UserEdit = (props: any) => {
// menu: { items: menuItems },
},
{
title: user.name,
title: user?.name,
},
]}
/>
@@ -386,7 +387,7 @@ const UserEdit = (props: any) => {
style={{ marginRight: "70px", fontWeight: "500" }}
>
<Input
disabled={user.id}
disabled={user?.id}
value={formUser.email}
style={{ color: "#8c8c8c" }}
autoComplete="off"
@@ -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,
+13
View File
@@ -515,4 +515,17 @@ ul.ant-list-items {
.d-none{
display: none!important;
}
.nohover {
background: transparent!important;
cursor: text;
}
.emp-wrapper {
display: flex;
align-items: center;
gap: 8px;
}
.emp-wrapper p {
margin: 0;
}
+13 -4
View File
@@ -1,11 +1,20 @@
import {ExpiresInValue} from "../../views/ExpiresInInput";
export interface Account {
id: string;
settings: { peer_login_expiration_enabled: boolean, peer_login_expiration: number}
id: string;
settings: {
peer_login_expiration_enabled: boolean;
peer_login_expiration: number;
jwt_groups_enabled: boolean;
groups_propagation_enabled: boolean;
jwt_groups_claim_name: string;
};
}
export interface FormAccount extends Account {
peer_login_expiration_enabled: boolean
peer_login_expiration_formatted : ExpiresInValue
peer_login_expiration_enabled: boolean;
jwt_groups_enabled: boolean;
groups_propagation_enabled: boolean;
jwt_groups_claim_name: string;
peer_login_expiration_formatted: ExpiresInValue;
}
+9 -7
View File
@@ -1,9 +1,11 @@
export interface Event {
id: string;
timestamp: string
activity: string
activity_code: string
initiator_id: string
target_id: string
meta: { [key: string]: string }
id: string;
timestamp: string;
activity: string;
activity_code: string;
initiator_id: string;
initiator_email: string;
initiator_name: string;
target_id: string;
meta: { [key: string]: string };
}
+204 -135
View File
@@ -1,198 +1,268 @@
import {all, call, spawn, put, select, takeLatest} from 'redux-saga/effects';
import { all, call, spawn, put, select, takeLatest } from "redux-saga/effects";
import {
ApiError,
ApiResponse,
ChangeResponse,
CreateResponse,
DeleteResponse
} from '../../services/api-client/types';
import { Peer } from './types'
import service from './service';
import actions from './actions';
import {Group, GroupPeer} from "../group/types";
DeleteResponse,
} from "../../services/api-client/types";
import { Peer } from "./types";
import service from "./service";
import actions from "./actions";
import { Group, GroupPeer } from "../group/types";
import serviceGroup from "../group/service";
import {actions as groupActions} from "../group";
export function* getPeers(action: ReturnType<typeof actions.getPeers.request>): Generator {
import { actions as groupActions } from "../group";
import userService from "../user/service";
export function* getPeers(
action: ReturnType<typeof actions.getPeers.request>
): Generator {
try {
yield put(
actions.setDeletePeer({
loading: false,
success: false,
failure: false,
error: null,
data: null,
} as DeleteResponse<string | null>)
);
yield put(actions.setDeletePeer({
loading: false,
success: false,
failure: false,
error: null,
data: null
} as DeleteResponse<string | null>))
const users: any = yield call(userService.getUsers, action.payload);
let currentUser = users.body.find((user: any) => user.is_current);
const effect = yield call(service.getPeers, action.payload);
const response = effect as ApiResponse<Peer[]>;
yield put(actions.getPeers.success(response.body));
let peersBody: any;
if (currentUser.role === "user") {
peersBody = response.body.filter((pe) => {
return pe.user_id === currentUser.id;
});
} else {
peersBody = response.body;
}
yield put(actions.getPeers.success(peersBody));
} catch (err) {
yield put(actions.getPeers.failure(err as ApiError));
}
}
export function* setDeletePeer(action: ReturnType<typeof actions.setDeletePeer>): Generator {
yield put(actions.setDeletePeer(action.payload))
export function* setDeletePeer(
action: ReturnType<typeof actions.setDeletePeer>
): Generator {
yield put(actions.setDeletePeer(action.payload));
}
export function* deletePeer(action: ReturnType<typeof actions.deletedPeer.request>): Generator {
export function* deletePeer(
action: ReturnType<typeof actions.deletedPeer.request>
): Generator {
try {
yield call(actions.setDeletePeer,{
yield call(actions.setDeletePeer, {
loading: true,
success: false,
failure: false,
error: null,
data: null
} as DeleteResponse<string | null>)
data: null,
} as DeleteResponse<string | null>);
const effect = yield call(service.deletedPeer, action.payload);
const response = effect as ApiResponse<any>;
yield put(actions.deletedPeer.success({
loading: false,
success: true,
failure: false,
error: null,
data: response.body
} as DeleteResponse<string | null>));
yield put(
actions.deletedPeer.success({
loading: false,
success: true,
failure: false,
error: null,
data: response.body,
} as DeleteResponse<string | null>)
);
const peers = (yield select(state => state.peer.data)) as Peer[]
yield put(actions.getPeers.success(peers.filter((p:Peer) => p.id !== action.payload.payload)))
const peers = (yield select((state) => state.peer.data)) as Peer[];
yield put(
actions.getPeers.success(
peers.filter((p: Peer) => p.id !== action.payload.payload)
)
);
} catch (err) {
yield put(actions.deletedPeer.failure({
loading: false,
success: false,
failure: false,
error: err as ApiError,
data: null
} as DeleteResponse<string | null>));
yield put(
actions.deletedPeer.failure({
loading: false,
success: false,
failure: false,
error: err as ApiError,
data: null,
} as DeleteResponse<string | null>)
);
}
}
export function* saveGroups(action: ReturnType<typeof actions.saveGroups.request>): Generator {
export function* saveGroups(
action: ReturnType<typeof actions.saveGroups.request>
): Generator {
try {
yield put(actions.setSavedGroups({
loading: true,
success: false,
failure: false,
error: null,
data: null
}))
yield put(
actions.setSavedGroups({
loading: true,
success: false,
failure: false,
error: null,
data: null,
})
);
const currentGroups = [...(yield select(state => state.group.data)) as Group[]]
const currentGroups = [
...((yield select((state) => state.group.data)) as Group[]),
];
const peerGroupsToSave = action.payload.payload
const peerGroupsToSave = action.payload.payload;
let groupsToSave = [] as Group[]
let groupsNoId = [] as Group[]
let groupsToSave = [] as Group[];
let groupsNoId = [] as Group[];
groupsToSave = groupsToSave.concat(
currentGroups
.filter(g => peerGroupsToSave.groupsToRemove.includes(g.id || ''))
.map(g => ({
id: g.id,
name: g.name,
peers: (g.peers as GroupPeer[]).filter(p => p.id !== peerGroupsToSave.ID).map(p => p.id) as string[]
}))
)
currentGroups
.filter((g) => peerGroupsToSave.groupsToRemove.includes(g.id || ""))
.map((g) => ({
id: g.id,
name: g.name,
peers: (g.peers as GroupPeer[])
.filter((p) => p.id !== peerGroupsToSave.ID)
.map((p) => p.id) as string[],
}))
);
groupsToSave = groupsToSave.concat(
currentGroups
.filter(g => peerGroupsToSave.groupsToAdd.includes(g.id || ''))
.map(g => ({
id: g.id,
name: g.name,
Peers: g.peers ? [...(g.peers as GroupPeer[]).map((p:GroupPeer) => p.id), peerGroupsToSave.ID] : [peerGroupsToSave.ID]
}))
)
currentGroups
.filter((g) => peerGroupsToSave.groupsToAdd.includes(g.id || ""))
.map((g) => ({
id: g.id,
name: g.name,
Peers: g.peers
? [
...(g.peers as GroupPeer[]).map((p: GroupPeer) => p.id),
peerGroupsToSave.ID,
]
: [peerGroupsToSave.ID],
}))
);
groupsNoId = peerGroupsToSave.groupsNoId.map(g => ({
groupsNoId = peerGroupsToSave.groupsNoId.map((g) => ({
name: g,
peers: [peerGroupsToSave.ID]
}))
peers: [peerGroupsToSave.ID],
}));
if (!groupsNoId.length && !groupsToSave.length) {
return
return;
}
const responsesGroup = yield all(groupsToSave.map(g => call(serviceGroup.editGroup, {
getAccessTokenSilently: action.payload.getAccessTokenSilently,
payload: g
})
))
const responsesGroup = yield all(
groupsToSave.map((g) =>
call(serviceGroup.editGroup, {
getAccessTokenSilently: action.payload.getAccessTokenSilently,
payload: g,
})
)
);
const responsesGroupNoId = yield all(groupsNoId.map(g => call(serviceGroup.createGroup, {
getAccessTokenSilently: action.payload.getAccessTokenSilently,
payload: g
})
))
yield put(actions.saveGroups.success({
loading: false,
success: true,
failure: false,
error: null,
data: [...(responsesGroup as ApiResponse<Group>[]).map(r => r.body), ...(responsesGroupNoId as ApiResponse<Group>[]).map(r => r.body)]
} as CreateResponse<Group[] | null>))
const responsesGroupNoId = yield all(
groupsNoId.map((g) =>
call(serviceGroup.createGroup, {
getAccessTokenSilently: action.payload.getAccessTokenSilently,
payload: g,
})
)
);
yield put(groupActions.getGroups.request({ getAccessTokenSilently: action.payload.getAccessTokenSilently, payload: null }));
yield put(actions.getPeers.request({ getAccessTokenSilently: action.payload.getAccessTokenSilently, payload: null }));
yield put(
actions.saveGroups.success({
loading: false,
success: true,
failure: false,
error: null,
data: [
...(responsesGroup as ApiResponse<Group>[]).map((r) => r.body),
...(responsesGroupNoId as ApiResponse<Group>[]).map((r) => r.body),
],
} as CreateResponse<Group[] | null>)
);
yield put(
groupActions.getGroups.request({
getAccessTokenSilently: action.payload.getAccessTokenSilently,
payload: null,
})
);
yield put(
actions.getPeers.request({
getAccessTokenSilently: action.payload.getAccessTokenSilently,
payload: null,
})
);
} catch (err) {
console.log(err)
yield put(actions.saveGroups.failure({
loading: false,
success: false,
failure: true,
error: err as ApiError,
data: null
} as ChangeResponse<Group[] | null>));
console.log(err);
yield put(
actions.saveGroups.failure({
loading: false,
success: false,
failure: true,
error: err as ApiError,
data: null,
} as ChangeResponse<Group[] | null>)
);
}
}
export function* updatePeer(action: ReturnType<typeof actions.updatePeer.request>): Generator {
export function* updatePeer(
action: ReturnType<typeof actions.updatePeer.request>
): Generator {
try {
yield put(actions.setUpdatedPeer({
loading: true,
success: false,
failure: false,
error: null,
data: null
}))
yield put(
actions.setUpdatedPeer({
loading: true,
success: false,
failure: false,
error: null,
data: null,
})
);
const peer = action.payload.payload
const peerId = peer.id
const peer = action.payload.payload;
const peerId = peer.id;
const payloadToSave = {
getAccessTokenSilently: action.payload.getAccessTokenSilently,
payload: peer
}
payload: peer,
};
const effect = yield call(service.updatePeer, payloadToSave)
const effect = yield call(service.updatePeer, payloadToSave);
const response = effect as ApiResponse<Peer>;
yield put(actions.updatePeer.success({
loading: false,
success: true,
failure: false,
error: null,
data: response.body
} as ChangeResponse<Peer | null>));
const peers = (yield select(state => state.peer.data)) as Peer[]
yield put(actions.getPeers.success(peers.filter((p:Peer) => p.id !== peerId).concat(response.body)))
yield put(
actions.updatePeer.success({
loading: false,
success: true,
failure: false,
error: null,
data: response.body,
} as ChangeResponse<Peer | null>)
);
const peers = (yield select((state) => state.peer.data)) as Peer[];
yield put(
actions.getPeers.success(
peers.filter((p: Peer) => p.id !== peerId).concat(response.body)
)
);
} catch (err) {
console.log(err)
yield put(actions.updatePeer.failure({
loading: false,
success: false,
failure: true,
error: err as ApiError,
data: null
} as ChangeResponse<Peer | null>));
console.log(err);
yield put(
actions.updatePeer.failure({
loading: false,
success: false,
failure: true,
error: err as ApiError,
data: null,
} as ChangeResponse<Peer | null>)
);
}
}
@@ -201,7 +271,6 @@ export default function* sagas(): Generator {
takeLatest(actions.getPeers.request, getPeers),
takeLatest(actions.deletedPeer.request, deletePeer),
takeLatest(actions.saveGroups.request, saveGroups),
takeLatest(actions.updatePeer.request, updatePeer)
takeLatest(actions.updatePeer.request, updatePeer),
]);
}
+2 -1
View File
@@ -63,7 +63,8 @@ export function* saveSetupKey(action: ReturnType<typeof actions.saveSetupKey.req
auto_groups: newGroups,
type: keyToSave.type,
expires_in: keyToSave.expires_in,
usage_limit: keyToSave.usage_limit
usage_limit: keyToSave.usage_limit,
ephemeral: keyToSave.ephemeral
} as SetupKeyToSave
});
} else {
Loaded 20 of 31 files, more files were not shown because too many files have changed in this diff. Show more