mirror of
https://github.com/netbirdio/dashboard.git
synced 2026-09-23 00:30:43 -07:00
Compare commits
7
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
54accb665c | ||
|
|
cfea3bd489 | ||
|
|
a44a1c5424 | ||
|
|
c2c044421f | ||
|
|
e8d57c3445 | ||
|
|
0b892c0056 | ||
|
|
14d9b80029 |
No files matched your search
@@ -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;
|
||||
@@ -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,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')
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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. */
|
||||
// {
|
||||
|
||||
@@ -53,6 +53,7 @@ const Navbar = () => {
|
||||
"/acls",
|
||||
"/routes",
|
||||
"/dns",
|
||||
"/users",
|
||||
"/activity",
|
||||
"/settings",
|
||||
];
|
||||
|
||||
@@ -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}
|
||||
@@ -413,7 +474,7 @@ const SetupKeyNew = (props: any) => {
|
||||
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 +485,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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
+204
-135
@@ -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),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -14,6 +14,7 @@ export interface SetupKey {
|
||||
auto_groups: string[]
|
||||
expires_in: number;
|
||||
usage_limit: any;
|
||||
ephemeral: boolean;
|
||||
}
|
||||
|
||||
export interface FormSetupKey extends SetupKey {
|
||||
|
||||
@@ -8,6 +8,7 @@ export interface User {
|
||||
is_current?: boolean;
|
||||
is_service_user?: boolean;
|
||||
is_blocked?: boolean;
|
||||
last_login?: string;
|
||||
}
|
||||
|
||||
export interface FormUser extends User {
|
||||
|
||||
+3
-1
@@ -100,7 +100,7 @@ export const timeAgo = (dateParam) => {
|
||||
const isToday = today.toDateString() === date.toDateString();
|
||||
const isYesterday = yesterday.toDateString() === date.toDateString();
|
||||
const isThisYear = today.getFullYear() === date.getFullYear();
|
||||
|
||||
const never = date.getFullYear() === 1;
|
||||
|
||||
if (seconds < -1) {
|
||||
return getFormattedDate(date, false, true);
|
||||
@@ -118,6 +118,8 @@ export const timeAgo = (dateParam) => {
|
||||
return getFormattedDate(date, 'yesterday'); // Yesterday at 10:20
|
||||
} else if (isThisYear) {
|
||||
return getFormattedDate(date, false, true); // 10. January at 10:20
|
||||
} else if (never) {
|
||||
return 'never';
|
||||
}
|
||||
|
||||
return getFormattedDate(date); // 10. January 2017. at 10:20
|
||||
|
||||
@@ -666,6 +666,7 @@ export const AccessControl = () => {
|
||||
<Row justify="end">
|
||||
<Col>
|
||||
<Button
|
||||
data-testid="add-rule-main-button"
|
||||
type="primary"
|
||||
disabled={savedPolicy.loading}
|
||||
onClick={onClickAddNewPolicy}
|
||||
@@ -719,6 +720,7 @@ export const AccessControl = () => {
|
||||
</a>
|
||||
</Paragraph>
|
||||
<Button
|
||||
data-testid="add-rule-empty-state-button"
|
||||
size={"middle"}
|
||||
type="primary"
|
||||
onClick={() => onClickAddNewPolicy()}
|
||||
|
||||
@@ -169,7 +169,37 @@ export const Activity = () => {
|
||||
const renderInitiator = (event: EventDataTable) => {
|
||||
let body = <></>;
|
||||
const user = users?.find((u) => u.id === event.initiator_id);
|
||||
if (event.initiator_id == "sys") {
|
||||
body = (
|
||||
<span
|
||||
style={{
|
||||
height: "auto",
|
||||
whiteSpace: "normal",
|
||||
textAlign: "left",
|
||||
}}
|
||||
>
|
||||
<Row>
|
||||
<Text type="secondary">System</Text>
|
||||
</Row>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
switch (event.activity_code) {
|
||||
case "peer.login.expire":
|
||||
body = (
|
||||
<span
|
||||
style={{
|
||||
height: "auto",
|
||||
whiteSpace: "normal",
|
||||
textAlign: "left",
|
||||
}}
|
||||
>
|
||||
<Row>
|
||||
<Text type="secondary">System</Text>
|
||||
</Row>
|
||||
</span>
|
||||
);
|
||||
break;
|
||||
case "setupkey.peer.add":
|
||||
const key = setupKeys?.find((k) => k.id === event.initiator_id);
|
||||
if (key) {
|
||||
@@ -255,6 +285,7 @@ export const Activity = () => {
|
||||
switch (event.activity_code) {
|
||||
case "account.create":
|
||||
case "user.join":
|
||||
case "dashboard.login":
|
||||
return "-";
|
||||
case "rule.add":
|
||||
case "rule.delete":
|
||||
@@ -290,6 +321,8 @@ export const Activity = () => {
|
||||
case "peer.rename":
|
||||
case "peer.login.expiration.disable":
|
||||
case "peer.login.expiration.enable":
|
||||
case "user.peer.login":
|
||||
case "peer.login.expire":
|
||||
return renderMultiRowSpan(event.meta.fqdn, event.meta.ip);
|
||||
case "route.add":
|
||||
case "route.delete":
|
||||
|
||||
+84
-73
@@ -248,6 +248,7 @@ export const Peers = () => {
|
||||
const t = searchText
|
||||
? searchText.toLowerCase().trim()
|
||||
: textToSearch.toLowerCase().trim();
|
||||
|
||||
let f: Peer[] = filter(peers, (f: Peer) => {
|
||||
let userEmail: string | null;
|
||||
const u = users?.find((u) => u.id === f.user_id)?.email;
|
||||
@@ -261,8 +262,9 @@ export const Peers = () => {
|
||||
(userEmail && userEmail.toLowerCase().includes(t))
|
||||
);
|
||||
}) as Peer[];
|
||||
|
||||
if (optionOnOff === "on") {
|
||||
f = filter(peers, (f: Peer) => f.connected);
|
||||
f = filter(f, (f: Peer) => f.connected);
|
||||
}
|
||||
|
||||
let filterByGroupArray: any = [];
|
||||
@@ -434,6 +436,7 @@ export const Peers = () => {
|
||||
};
|
||||
|
||||
const setUpdateGroupsVisible = (peerToAction: Peer, status: boolean) => {
|
||||
if (!isAdmin) return;
|
||||
if (status) {
|
||||
dispatch(peerActions.setPeer({ ...peerToAction }));
|
||||
dispatch(peerActions.setUpdateGroupsVisible(true));
|
||||
@@ -502,10 +505,7 @@ export const Peers = () => {
|
||||
open={groupPopupVisible === peerToAction.key}
|
||||
title={null}
|
||||
>
|
||||
<Button
|
||||
type="link"
|
||||
onClick={() => setGroupVisible(peerToAction, true)}
|
||||
>
|
||||
<Button type="link" onClick={() => setGroupVisible(peerToAction, true)}>
|
||||
{label}
|
||||
</Button>
|
||||
</Popover>
|
||||
@@ -592,6 +592,7 @@ export const Peers = () => {
|
||||
type="text"
|
||||
style={{ height: "auto", whiteSpace: "normal", textAlign: "left" }}
|
||||
onClick={() => setUpdateGroupsVisible(peer, true)}
|
||||
className={!isAdmin ? "nohover" : ""}
|
||||
>
|
||||
<span style={{ textAlign: "left" }}>
|
||||
<Row>
|
||||
@@ -607,6 +608,7 @@ export const Peers = () => {
|
||||
<div>
|
||||
<Button
|
||||
type="text"
|
||||
className={!isAdmin ? "nohover" : ""}
|
||||
style={{ height: "auto", textAlign: "left" }}
|
||||
onClick={() => setUpdateGroupsVisible(peer, true)}
|
||||
>
|
||||
@@ -633,16 +635,16 @@ export const Peers = () => {
|
||||
<Container style={{ paddingTop: "40px" }}>
|
||||
<Row>
|
||||
<Col span={24}>
|
||||
<Title className="page-heading">Peers</Title>
|
||||
<Title className="page-heading">{isAdmin ? "Peers" : "My peers"}</Title>
|
||||
{peers.length ? (
|
||||
<Paragraph style={{ marginTop: "5px" }}>
|
||||
A list of all machines and devices connected to your private
|
||||
network. Use this view to manage peers
|
||||
{isAdmin ? "A list of all machines and devices connected to your private network. Use this view to manage peers" :
|
||||
"A list of all your machines and devices that you connected to NetBird."}
|
||||
</Paragraph>
|
||||
) : (
|
||||
<Paragraph style={{ marginTop: "5px" }} type={"secondary"}>
|
||||
A list of all machines and devices connected to your private
|
||||
network. Use this view to manage peers
|
||||
{isAdmin ? "A list of all machines and devices connected to your private network. Use this view to manage peers" :
|
||||
"A list of all your machines and devices that you connected to NetBird."}
|
||||
</Paragraph>
|
||||
)}
|
||||
|
||||
@@ -690,22 +692,28 @@ export const Peers = () => {
|
||||
/>
|
||||
</Space>
|
||||
|
||||
<Select
|
||||
mode="tags"
|
||||
placeholder="Filter by groups"
|
||||
tagRender={blueTagRender}
|
||||
// dropdownRender={dropDownRender}
|
||||
optionFilterProp="serchValue"
|
||||
className="groupsSelect"
|
||||
onChange={handleGroupChange}
|
||||
value={groupsFilterArray}
|
||||
>
|
||||
{tagGroups.map((m, index) => (
|
||||
<Option key={index} value={m.id} serchValue={m.name}>
|
||||
{optionRender(m.name, m.id)}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
{isAdmin && (
|
||||
<Select
|
||||
mode="tags"
|
||||
placeholder="Filter by groups"
|
||||
tagRender={blueTagRender}
|
||||
// dropdownRender={dropDownRender}
|
||||
optionFilterProp="serchValue"
|
||||
className="groupsSelect"
|
||||
onChange={handleGroupChange}
|
||||
value={groupsFilterArray}
|
||||
>
|
||||
{tagGroups.map((m, index) => (
|
||||
<Option
|
||||
key={index}
|
||||
value={m.id}
|
||||
serchValue={m.name}
|
||||
>
|
||||
{optionRender(m.name, m.id)}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
)}
|
||||
</Col>
|
||||
<Col xs={24} sm={24} md={5} lg={5} xl={5} xxl={5} span={5}>
|
||||
<Row justify="end">
|
||||
@@ -757,7 +765,7 @@ export const Peers = () => {
|
||||
Get started by adding one to your network.
|
||||
</Paragraph>
|
||||
<Button
|
||||
data-testid="add-new-peer-button"
|
||||
data-testid="add-new-peer-button"
|
||||
size={"middle"}
|
||||
type="primary"
|
||||
onClick={() => setAddPeerModalOpen(true)}
|
||||
@@ -822,53 +830,56 @@ export const Peers = () => {
|
||||
return renderAddress(record);
|
||||
}}
|
||||
/>
|
||||
<Column
|
||||
title="Groups"
|
||||
dataIndex="groupsCount"
|
||||
align="center"
|
||||
render={(text, record: PeerDataTable, index) => {
|
||||
return renderPopoverGroups(
|
||||
text,
|
||||
record.groups,
|
||||
record
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Column
|
||||
title="SSH Server"
|
||||
dataIndex="ssh_enabled"
|
||||
align="center"
|
||||
render={(e, record: PeerDataTable, index) => {
|
||||
let isWindows = record.os
|
||||
.toLocaleLowerCase()
|
||||
.startsWith("windows");
|
||||
let toggle = (
|
||||
<Switch
|
||||
size={"small"}
|
||||
checked={e}
|
||||
disabled={isWindows}
|
||||
onClick={(checked: boolean) => {
|
||||
if (checked) {
|
||||
showConfirmEnableSSH(record);
|
||||
} else {
|
||||
handleSwitchSSH(record, checked);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
if (isWindows) {
|
||||
return (
|
||||
<Tooltip title="SSH server feature is not yet supported on Windows">
|
||||
{toggle}
|
||||
</Tooltip>
|
||||
);
|
||||
} else {
|
||||
return toggle;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{isAdmin && (
|
||||
<>
|
||||
<Column
|
||||
title="Groups"
|
||||
dataIndex="groupsCount"
|
||||
align="center"
|
||||
render={(text, record: PeerDataTable, index) => {
|
||||
return renderPopoverGroups(
|
||||
text,
|
||||
record.groups,
|
||||
record
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Column
|
||||
title="SSH Server"
|
||||
dataIndex="ssh_enabled"
|
||||
align="center"
|
||||
render={(e, record: PeerDataTable, index) => {
|
||||
let isWindows = record.os
|
||||
.toLocaleLowerCase()
|
||||
.startsWith("windows");
|
||||
let toggle = (
|
||||
<Switch
|
||||
size={"small"}
|
||||
checked={e}
|
||||
disabled={isWindows}
|
||||
onClick={(checked: boolean) => {
|
||||
if (checked) {
|
||||
showConfirmEnableSSH(record);
|
||||
} else {
|
||||
handleSwitchSSH(record, checked);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
if (isWindows) {
|
||||
return (
|
||||
<Tooltip title="SSH server feature is not yet supported on Windows">
|
||||
{toggle}
|
||||
</Tooltip>
|
||||
);
|
||||
} else {
|
||||
return toggle;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Column
|
||||
title="LastSeen"
|
||||
dataIndex="last_seen"
|
||||
|
||||
+35
-26
@@ -31,7 +31,7 @@ import { useGetTokenSilently } from "../utils/token";
|
||||
import { actions as groupActions } from "../store/group";
|
||||
import { Group } from "../store/group/types";
|
||||
import { TooltipPlacement } from "antd/es/tooltip";
|
||||
import { capitalize, isLocalDev, isNetBirdHosted } from "../utils/common";
|
||||
import {capitalize, isLocalDev, isNetBirdHosted, timeAgo} from "../utils/common";
|
||||
import { usePageSizeHelpers } from "../utils/pageSize";
|
||||
import AddServiceUserPopup from "../components/popups/AddServiceUserPopup";
|
||||
import InviteUserPopup from "../components/popups/InviteUserPopup";
|
||||
@@ -499,45 +499,54 @@ export const RegularUsers = () => {
|
||||
defaultSortOrder="ascend"
|
||||
render={(text, record, index) => {
|
||||
const btn = (
|
||||
<Button
|
||||
type="text"
|
||||
onClick={() =>
|
||||
handleEditUser(record as UserDataTable)
|
||||
}
|
||||
className="tooltip-label"
|
||||
>
|
||||
<Text className="font-500">
|
||||
{text && text.trim() !== ""
|
||||
? text
|
||||
: (record as User).id}
|
||||
</Text>
|
||||
</Button>
|
||||
<Button
|
||||
type="text"
|
||||
onClick={() =>
|
||||
handleEditUser(record as UserDataTable)
|
||||
}
|
||||
className="tooltip-label"
|
||||
>
|
||||
<Text className="font-500">
|
||||
{text && text.trim() !== ""
|
||||
? text
|
||||
: (record as User).id}
|
||||
</Text>
|
||||
</Button>
|
||||
);
|
||||
|
||||
if ((record as User).is_current) {
|
||||
return (
|
||||
<div>
|
||||
{btn}
|
||||
<Tag color="blue">me</Tag>
|
||||
</div>
|
||||
<>
|
||||
<div>
|
||||
{btn}
|
||||
<Tag color="blue">me</Tag>
|
||||
</div>
|
||||
<Text type={"secondary"} style={{paddingLeft: "15px"}}>Last login: {String(timeAgo((record as User).last_login))}</Text>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if ((record as User).status === "invited") {
|
||||
return (
|
||||
<div>
|
||||
{btn}
|
||||
<Tag color="gold">invited</Tag>
|
||||
</div>
|
||||
<>
|
||||
<div>
|
||||
{btn}
|
||||
<Tag color="gold">invited</Tag>
|
||||
</div>
|
||||
<Text type={"secondary"} style={{paddingLeft: "15px", paddingTop: "0px"}}>Last login: {String(timeAgo((record as User).last_login))}</Text>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if ((record as User).status === "blocked") {
|
||||
return (
|
||||
<div>
|
||||
{btn}
|
||||
<Tag color="red">blocked</Tag>
|
||||
</div>
|
||||
<>
|
||||
<div>
|
||||
{btn}
|
||||
<Tag color="red">blocked</Tag>
|
||||
</div>
|
||||
<Text type={"secondary"} style={{paddingLeft: "15px"}}>Last login: {String(timeAgo((record as User).last_login))}</Text>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Loaded 20 of 21 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user