mirror of
https://github.com/netbirdio/dashboard.git
synced 2026-09-23 00:30:43 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2d55d0736f | ||
|
|
8febc26f1f | ||
|
|
3f854b01a0 | ||
|
|
303d51eff8 | ||
|
|
21e69e642a | ||
|
|
835bb37ab9 | ||
|
|
a944dc8ab0 | ||
|
|
b2c51533fb | ||
|
|
fd24536926 | ||
|
|
8e8484cd45 | ||
|
|
6c87f53195 | ||
|
|
9bbbff7dc0 | ||
|
|
04a20fa31f | ||
|
|
3797db93f0 | ||
|
|
2e81765e85 | ||
|
|
cb9f76c0fc | ||
|
|
54accb665c | ||
|
|
cfea3bd489 | ||
|
|
a44a1c5424 | ||
|
|
c2c044421f | ||
|
|
e8d57c3445 | ||
|
|
0b892c0056 | ||
|
|
14d9b80029 | ||
|
|
dedbe55308 | ||
|
|
796a06cf27 |
No files matched your search
@@ -19,7 +19,7 @@ jobs:
|
||||
node-version: '16'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependecies
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
|
||||
- name: Build
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
name: run e2e tests
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
e2e_tests:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: setup-node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '16'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm install
|
||||
|
||||
- name: install playwright
|
||||
run: npx playwright install
|
||||
|
||||
- name: install playwright deps
|
||||
run: npx playwright install-deps
|
||||
|
||||
- name: create test environment
|
||||
run: bash ./e2e-tests/create-test-env.sh
|
||||
|
||||
- name: run e2e tests
|
||||
run: npx playwright test --workers 2
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report
|
||||
path: |
|
||||
playwright-report/
|
||||
test-results/
|
||||
retention-days: 3
|
||||
+12
@@ -29,3 +29,15 @@ src/auth_config.json
|
||||
src/.local-config*.json
|
||||
/public/OidcServiceWorker.js
|
||||
/public/OidcTrustedDomains.js
|
||||
/e2e-tests/node_modules/
|
||||
/e2e-tests/playwright-report/
|
||||
/e2e-tests/test-results/
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
.env
|
||||
Caddyfile
|
||||
docker-compose.yml
|
||||
machinekey/
|
||||
management.json
|
||||
turnserver.conf
|
||||
zitadel.env
|
||||
@@ -0,0 +1,3 @@
|
||||
#!/bin/bash
|
||||
docker-compose down --volumes
|
||||
rm -f docker-compose.yml Caddyfile zitadel.env dashboard.env machinekey/zitadel-admin-sa.token turnserver.conf management.json
|
||||
File diff suppressed because it is too large.
Load diff
@@ -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;
|
||||
@@ -0,0 +1,111 @@
|
||||
import { Page, test, expect } from "@playwright/test";
|
||||
|
||||
export class AddPeerModal {
|
||||
private readonly addPeerModal = this.page.getByTestId('add-peer-modal').locator('div').nth(2)
|
||||
private readonly linuxTab = this.page.getByTestId('add-peer-modal-linux-tab')
|
||||
private readonly windowsTab = this.page.getByTestId('add-peer-modal-windows-tab')
|
||||
private readonly macTab = this.page.getByTestId('add-peer-modal-mac-tab')
|
||||
private readonly androidTab = this.page.getByTestId('add-peer-modal-android-tab')
|
||||
private readonly dockerTab = this.page.getByTestId('add-peer-modal-docker-tab')
|
||||
private readonly linuxTabText = this.page.locator('pre').filter({ hasText: 'curl -fsSL https://pkgs.netbird.io/install.sh | sh' })
|
||||
private readonly windowsDownloadButton = this.page.getByTestId('download-windows-button')
|
||||
private readonly intelDownloadButton = this.page.getByTestId('download-intel-button')
|
||||
private readonly m1M2DownloadButton = this.page.getByTestId('download-m1-m2-button')
|
||||
private readonly androidDownloadButton = this.page.getByTestId('download-android-button')
|
||||
private readonly dockerDownloadButton = this.page.getByTestId('download-docker-button')
|
||||
private readonly closeButton = this.page.getByLabel('Close', { exact: true })
|
||||
|
||||
constructor(private readonly page: Page) {}
|
||||
|
||||
async assertPeerModalIsVisible() {
|
||||
await test.step('Assert that add peer modal is visible', async () => {
|
||||
await expect(this.addPeerModal).toBeVisible();
|
||||
})
|
||||
}
|
||||
|
||||
async assertPeerModalIsNotVisible() {
|
||||
await test.step('Assert that add peer modal is not visible', async () => {
|
||||
await expect(this.addPeerModal).not.toBeVisible();
|
||||
})
|
||||
}
|
||||
|
||||
async openLinuxTab() {
|
||||
await test.step('Open Linux tab on add peer modal', async () => {
|
||||
await this.linuxTab.click();
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
async openWindowsTab() {
|
||||
await test.step('Open Windows tab on add peer modal', async () => {
|
||||
await this.windowsTab.click();
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
async openMacTab() {
|
||||
await test.step('Open MacOS tab on add peer modal', async () => {
|
||||
await this.macTab.click();
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
async openAndroidTab() {
|
||||
await test.step('Open Android tab on add peer modal', async () => {
|
||||
await this.androidTab.click();
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
async openDockerTab() {
|
||||
await test.step('Open Docker tab on add peer modal', async () => {
|
||||
await this.dockerTab.click();
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
async assertLinuxTabHasCorrectText() {
|
||||
await test.step('Assert Linux tab has correct installation text', async () => {
|
||||
await expect(this.linuxTabText).toBeVisible();
|
||||
})
|
||||
}
|
||||
|
||||
async assertWindowsDownloadButtonHasCorrectLink() {
|
||||
await test.step('Assert Windows download button has a correct link', async () => {
|
||||
await expect(this.windowsDownloadButton).toHaveAttribute('href', 'https://pkgs.netbird.io/windows/x64');
|
||||
})
|
||||
}
|
||||
|
||||
async assertIntelDownloadButtonHasCorrectLink() {
|
||||
await test.step('Assert Intel download button has a correct link', async () => {
|
||||
await expect(this.intelDownloadButton).toHaveAttribute('href', 'https://pkgs.netbird.io/macos/amd64');
|
||||
})
|
||||
}
|
||||
|
||||
async assertM1M2DownloadButtonHasCorrectLink() {
|
||||
await test.step('Assert M1 & M2 download button has a correct link', async () => {
|
||||
await expect(this.m1M2DownloadButton).toHaveAttribute('href', 'https://pkgs.netbird.io/macos/arm64');
|
||||
})
|
||||
}
|
||||
|
||||
async assertAndroidDownloadButtonHasCorrectLink() {
|
||||
await test.step('Assert Android download button has a correct link', async () => {
|
||||
await expect(this.androidDownloadButton).toHaveAttribute('href', 'https://play.google.com/store/apps/details?id=io.netbird.client');
|
||||
})
|
||||
}
|
||||
|
||||
async assertDockerDownloadButtonHasCorrectLink() {
|
||||
await test.step('Assert Docker download button has a correct link', async () => {
|
||||
await expect(this.dockerDownloadButton).toHaveAttribute('href', 'https://docs.docker.com/engine/install/');
|
||||
})
|
||||
}
|
||||
|
||||
async closeAddPeerModal() {
|
||||
await test.step('Close Add peer modal', async () => {
|
||||
await this.closeButton.click();
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default AddPeerModal;
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Page, test } from "@playwright/test";
|
||||
|
||||
export class PeersPage {
|
||||
private readonly addNewPeerButton = this.page.getByTestId('add-new-peer-button')
|
||||
|
||||
constructor(private readonly page: Page) {}
|
||||
|
||||
async clickOnAddNewPeerButton() {
|
||||
await test.step('Click on Add new peer Button to open Add peer modal', async () => {
|
||||
await this.addNewPeerButton.click();
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default PeersPage;
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Page, test} from "@playwright/test";
|
||||
|
||||
export class TopMenu {
|
||||
private readonly accessControlButton = this.page.getByTestId('access-control-page')
|
||||
|
||||
constructor(private readonly page: Page) {}
|
||||
|
||||
async clickOnAccessControlOnTopMenu() {
|
||||
await test.step('Click on Access Control page on a top menu', async () => {
|
||||
await this.accessControlButton.click();
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default TopMenu;
|
||||
@@ -0,0 +1,22 @@
|
||||
import { test } from '@playwright/test'
|
||||
import {LoginPage} from '../pages/login-page'
|
||||
import {AccessControlPage} from '../pages/access-control-page'
|
||||
|
||||
let loginPage: LoginPage
|
||||
let accessControlPage: AccessControlPage
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
loginPage = new LoginPage(page);
|
||||
await loginPage.doLogin();
|
||||
});
|
||||
|
||||
test('Confirm that new user has Default access', async ({ page }) => {
|
||||
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();
|
||||
});
|
||||
@@ -0,0 +1,49 @@
|
||||
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'
|
||||
|
||||
let addPeerModal: AddPeerModal
|
||||
let peersPage: PeersPage
|
||||
let loginPage: LoginPage
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
addPeerModal = new AddPeerModal(page);
|
||||
loginPage = new LoginPage(page);
|
||||
await loginPage.doLogin();
|
||||
await addPeerModal.assertPeerModalIsVisible();
|
||||
});
|
||||
|
||||
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 () => {
|
||||
await addPeerModal.openWindowsTab();
|
||||
await addPeerModal.assertWindowsDownloadButtonHasCorrectLink();
|
||||
});
|
||||
|
||||
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 () => {
|
||||
await addPeerModal.openAndroidTab();
|
||||
await addPeerModal.assertAndroidDownloadButtonHasCorrectLink();
|
||||
});
|
||||
|
||||
test('Test Docker tab on a first access add peer modal', async () => {
|
||||
await addPeerModal.openDockerTab();
|
||||
await addPeerModal.assertDockerDownloadButtonHasCorrectLink();
|
||||
});
|
||||
|
||||
test('Close and open Add peer modal', async ({ page }) => {
|
||||
peersPage = new PeersPage(page);
|
||||
await addPeerModal.closeAddPeerModal();
|
||||
await addPeerModal.assertPeerModalIsNotVisible();
|
||||
await peersPage.clickOnAddNewPeerButton();
|
||||
await addPeerModal.assertPeerModalIsVisible();
|
||||
});
|
||||
Generated
+55
-6
@@ -58,13 +58,14 @@
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.36.2",
|
||||
"@types/react-syntax-highlighter": "^15.5.3"
|
||||
}
|
||||
},
|
||||
"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",
|
||||
@@ -3087,6 +3088,25 @@
|
||||
"opener": "^1.5.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test": {
|
||||
"version": "1.37.0",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.37.0.tgz",
|
||||
"integrity": "sha512-181WBLk4SRUyH1Q96VZl7BP6HcK0b7lbdeKisn3N/vnjitk+9HbdlFz/L5fey05vxaAhldIDnzo8KUoy8S3mmQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"playwright-core": "1.37.0"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@pmmmwh/react-refresh-webpack-plugin": {
|
||||
"version": "0.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.7.tgz",
|
||||
@@ -11712,6 +11732,18 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/playwright-core": {
|
||||
"version": "1.37.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.37.0.tgz",
|
||||
"integrity": "sha512-1c46jhTH/myQw6sesrcuHVtLoSNfJv8Pfy9t3rs6subY7kARv0HRw5PpyfPYPpPtQvBOmgbE6K+qgYUpj81LAA==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"playwright-core": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.16",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz",
|
||||
@@ -17141,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",
|
||||
@@ -19234,6 +19266,17 @@
|
||||
"opener": "^1.5.2"
|
||||
}
|
||||
},
|
||||
"@playwright/test": {
|
||||
"version": "1.37.0",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.37.0.tgz",
|
||||
"integrity": "sha512-181WBLk4SRUyH1Q96VZl7BP6HcK0b7lbdeKisn3N/vnjitk+9HbdlFz/L5fey05vxaAhldIDnzo8KUoy8S3mmQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*",
|
||||
"fsevents": "2.3.2",
|
||||
"playwright-core": "1.37.0"
|
||||
}
|
||||
},
|
||||
"@pmmmwh/react-refresh-webpack-plugin": {
|
||||
"version": "0.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.7.tgz",
|
||||
@@ -25594,6 +25637,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"playwright-core": {
|
||||
"version": "1.37.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.37.0.tgz",
|
||||
"integrity": "sha512-1c46jhTH/myQw6sesrcuHVtLoSNfJv8Pfy9t3rs6subY7kARv0HRw5PpyfPYPpPtQvBOmgbE6K+qgYUpj81LAA==",
|
||||
"dev": true
|
||||
},
|
||||
"postcss": {
|
||||
"version": "8.4.16",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz",
|
||||
|
||||
+2
-1
@@ -79,6 +79,7 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react-syntax-highlighter": "^15.5.3"
|
||||
"@types/react-syntax-highlighter": "^15.5.3",
|
||||
"@playwright/test": "^1.36.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import { defineConfig, devices } from '@playwright/test';
|
||||
|
||||
/**
|
||||
* Read environment variables from file.
|
||||
* https://github.com/motdotla/dotenv
|
||||
*/
|
||||
// require('dotenv').config();
|
||||
|
||||
/**
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
export default defineConfig({
|
||||
testDir: './e2e-tests/tests',
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: true,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: !!process.env.CI,
|
||||
/* Retry on CI only */
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: 'html',
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
// baseURL: 'http://127.0.0.1:3000',
|
||||
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: 'on-first-retry',
|
||||
screenshot: 'only-on-failure',
|
||||
video: 'retain-on-failure',
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: { channel: 'chrome', },
|
||||
},
|
||||
|
||||
// {
|
||||
// name: 'firefox',
|
||||
// use: { browserName: 'firefox', },
|
||||
// },
|
||||
//
|
||||
// {
|
||||
// name: 'webkit',
|
||||
// use: { browserName: 'webkit', },
|
||||
// },
|
||||
|
||||
/* Test against mobile viewports. */
|
||||
// {
|
||||
// name: 'Mobile Chrome',
|
||||
// use: { ...devices['Pixel 5'] },
|
||||
// },
|
||||
// {
|
||||
// name: 'Mobile Safari',
|
||||
// use: { ...devices['iPhone 12'] },
|
||||
// },
|
||||
|
||||
/* Test against branded browsers. */
|
||||
// {
|
||||
// name: 'Microsoft Edge',
|
||||
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
||||
// },
|
||||
// {
|
||||
// name: 'Google Chrome',
|
||||
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
|
||||
// },
|
||||
],
|
||||
|
||||
/* Run your local dev server before starting the tests */
|
||||
webServer: {
|
||||
command: 'npm run start',
|
||||
url: 'http://127.0.0.1:3000',
|
||||
reuseExistingServer: !process.env.CI,
|
||||
timeout: 180 * 1000,
|
||||
},
|
||||
});
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -35,12 +35,12 @@ const Navbar = () => {
|
||||
const [isRefreshingUserState, setIsRefreshingUserState] = useState(false);
|
||||
|
||||
const items = [
|
||||
{ label: <Link to="/peers">Peers</Link>, key: "/peers" },
|
||||
{ label: <Link to="/setup-keys">Setup Keys</Link>, key: "/setup-keys" },
|
||||
{ label: <Link to="/acls">Access Control</Link>, key: "/acls" },
|
||||
{ label: <Link to="/routes">Network Routes</Link>, key: "/routes" },
|
||||
{ label: <Link to="/dns">DNS</Link>, key: "/dns" },
|
||||
{ label: <Link to="/users">Users</Link>, key: "/users" },
|
||||
{ label: <Link data-testid="peers-page" to="/peers">Peers</Link>, key: "/peers" },
|
||||
{ label: <Link data-testid="setup-keys-page" to="/setup-keys">Setup Keys</Link>, key: "/setup-keys" },
|
||||
{ label: <Link data-testid="access-control-page" to="/acls">Access Control</Link>, key: "/acls" },
|
||||
{ label: <Link data-testid="network-routes-page" to="/routes">Network Routes</Link>, key: "/routes" },
|
||||
{ label: <Link data-testid="dns-page"to="/dns">DNS</Link>, key: "/dns" },
|
||||
{ label: <Link data-testid="usersf-page" to="/users">Users</Link>, key: "/users" },
|
||||
{ label: <Link to="/activity">Activity</Link>, key: "/activity" },
|
||||
{ label: <Link to="/settings">Settings</Link>, key: "/settings" },
|
||||
] as ItemType[];
|
||||
@@ -53,6 +53,7 @@ const Navbar = () => {
|
||||
"/acls",
|
||||
"/routes",
|
||||
"/dns",
|
||||
"/users",
|
||||
"/activity",
|
||||
"/settings",
|
||||
];
|
||||
|
||||
+208
-125
@@ -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,15 @@ 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);
|
||||
|
||||
@@ -389,13 +411,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 +430,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 +674,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 +1029,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}>
|
||||
|
||||
+163
-77
@@ -17,7 +17,9 @@ import {
|
||||
Switch,
|
||||
Modal,
|
||||
Typography,
|
||||
Tabs,
|
||||
} from "antd";
|
||||
import type { TabsProps } from "antd";
|
||||
import CreatableSelect from "react-select/creatable";
|
||||
import { Route, RouteToSave } from "../store/route/types";
|
||||
import { Header } from "antd/es/layout/layout";
|
||||
@@ -38,6 +40,7 @@ const { Panel } = Collapse;
|
||||
interface FormRoute extends Route {}
|
||||
|
||||
const RouteAddNew = (selectedPeer: any) => {
|
||||
const [activeTab, setActiveTab] = useState<string | null>(null);
|
||||
const {
|
||||
blueTagRender,
|
||||
handleChangeTags,
|
||||
@@ -75,6 +78,10 @@ const RouteAddNew = (selectedPeer: any) => {
|
||||
const [peerNameToIP, peerIPToName, peerIPToID] = initPeerMaps(peers);
|
||||
const [newRoute, setNewRoute] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (setupNewRouteVisible) setActiveTab("routingPeer");
|
||||
}, [setupNewRouteVisible]);
|
||||
|
||||
useEffect(() => {
|
||||
if (editName)
|
||||
inputNameRef.current!.focus({
|
||||
@@ -159,6 +166,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 +188,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 +302,7 @@ const RouteAddNew = (selectedPeer: any) => {
|
||||
|
||||
const onCancel = () => {
|
||||
if (savedRoute.loading) return;
|
||||
setActiveTab(null);
|
||||
setEditName(false);
|
||||
dispatch(
|
||||
routeActions.setRoute({
|
||||
@@ -254,6 +314,7 @@ const RouteAddNew = (selectedPeer: any) => {
|
||||
masquerade: false,
|
||||
enabled: true,
|
||||
groups: [],
|
||||
peer_groups: [],
|
||||
} as Route)
|
||||
);
|
||||
setVisibleNewRoute(false);
|
||||
@@ -300,8 +361,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 +429,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 +665,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 +774,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}>
|
||||
|
||||
Loaded 20 of 51 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user