Compare commits

..
Author SHA1 Message Date
Misha Bragin b06cb0ec3d Add a settings page for custom IdP and MFA (#308) 2023-12-12 13:42:14 +01:00
Misha Bragin 0c924f7ded Correct peer approval tooltip (#307) 2023-12-12 13:33:11 +01:00
Maycon Santos f29c915fc2 fix settings update blank page 2023-12-11 18:07:05 +01:00
Maycon Santos 5f8579bfda Fix peer expiration interval init on validation (#306) 2023-12-11 17:46:32 +01:00
pascal-fischer a71389aa29 not sending extra settings on selfhosted deployments (#305) 2023-12-11 16:52:29 +01:00
Maycon Santos c3ba026452 Init domains array for custom nameserver group (#304) 2023-12-08 11:07:10 +01:00
Maycon Santos 193f8a7bdf Fix missing text update from #302 (#303) 2023-12-07 18:05:44 +01:00
Fabio Fantoni f9814e1169 add codespell github workflow and fix some typo (#296)
* add codespell github workflow

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

* add peer approval form item

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

* use gold color for tag

---------

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

Updated role list to include role for users

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

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

Co-authored-by: braginini <bangvalo@gmail.com>
2023-10-20 09:33:44 +02:00
Sarooj bukhariandbraginini ddd812e9a0 Add a refresh button to the table views (#287)
Co-authored-by: braginini <bangvalo@gmail.com>
2023-10-15 19:04:05 +02:00
Sarooj bukhariandbraginini 2d55d0736f Add referesh button to the peers table (#286)
Co-authored-by: braginini <bangvalo@gmail.com>
2023-10-12 18:38:59 +02:00
Sarooj bukhari 8febc26f1f fix update distribution groups bug (#284) 2023-10-04 20:07:02 +02:00
Sarooj bukhari 3f854b01a0 Add support to network routes with peer group (#275)
support to routes with peer group
updated the view logic to support the new type
updated peer view as well
for now, we are supporting a single group, but that can be extended
2023-10-04 11:37:23 +02:00
Misha Bragin 303d51eff8 Display service username in the activity (#282) 2023-10-03 16:06:50 +02:00
braginini 21e69e642a Disable user deletion since the API isn't ready 2023-09-29 16:05:26 +02:00
pascal-fischer 835bb37ab9 Update jwt group sync visibility and update description (#281) 2023-09-29 13:58:46 +02:00
dependabot[bot] a944dc8ab0 Bump @adobe/css-tools from 4.0.1 to 4.3.1 (#265)
Bumps [@adobe/css-tools](https://github.com/adobe/css-tools) from 4.0.1 to 4.3.1.
- [Changelog](https://github.com/adobe/css-tools/blob/main/History.md)
- [Commits](https://github.com/adobe/css-tools/commits)

---
updated-dependencies:
- dependency-name: "@adobe/css-tools"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-09-29 00:39:19 +05:00
Sarooj bukhari b2c51533fb remove invalid key check (#280) 2023-09-28 21:26:11 +02:00
pascal-fischer fd24536926 add checks to hide last login for non netbird hosted deployments (#277) 2023-09-27 18:00:33 +02:00
Misha Bragin 8e8484cd45 Fix packages (#276) 2023-09-27 12:25:55 +02:00
Sarooj bukhari 6c87f53195 Update the /Activity view to use email addresses from the /api/events response (#273)
uses the events api response information to map user name and email
2023-09-23 10:45:27 +02:00
pascal-fischerandMaycon Santos 9bbbff7dc0 Fix last login for regular entries in users overview (#264)
* add last login to user overview for regular entries without tag

* add backward compatability to properly show with older version of management

* Merge main and update delete user popup messages

---------

Co-authored-by: Maycon Santos <mlsmaycon@gmail.com>
2023-09-23 10:40:22 +02:00
Sarooj bukhari 04a20fa31f add new fields on setting pag (#258) 2023-09-22 16:32:40 +02:00
Misha Bragin 3797db93f0 Hide network routes card for non-linux peers (#269) 2023-09-20 10:58:26 +02:00
Sarooj bukhari 2e81765e85 add delete user functionality (#272) 2023-09-19 15:13:31 +02:00
Yoann N cb9f76c0fc typo in ac creation (#266) 2023-09-18 23:22:52 +02:00
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
54 changed files with 3979 additions and 1955 deletions

No files matched your search

+15
View File
@@ -0,0 +1,15 @@
name: Codespell
on: [pull_request]
jobs:
check:
name: Check
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: codespell
uses: codespell-project/actions-codespell@v2
with:
only_warn: 1
skip: package-lock.json,*.svg
+3 -3
View File
@@ -1,15 +1,15 @@
# Wiretrustee Dashboard
Wiretrustee Dashboard is a the Wiretrustee Managemenet server UI. It allow users to signin, view setup keys and manage peers. This image is **not ready** for production use.
Wiretrustee Dashboard is a the Wiretrustee Management server UI. It allow users to signin, view setup keys and manage peers. This image is **not ready** for production use.
## Tags
```latest``` ```vX.X.X``` not available yet.
```main``` builded on every PR being merged to the repository
```main``` built on every PR being merged to the repository
## How to use this image
HTTP run:
```shell
docker run -d --rm -p 80:80 wiretrustee/dashboard:main
```
Using SSL certificate from Let's Encript®:
Using SSL certificate from Let's Encrypt®:
```shell
docker run -d --rm -p 80:80 -p 443:443 \
-e LETSENCRYPT_DOMAIN=app.mydomain.com \
+1 -1
View File
@@ -387,7 +387,7 @@ check_nb_domain() {
if [ "$DOMAIN" == "netbird.example.com" ]; then
echo "The NETBIRD_DOMAIN cannot be netbird.example.com" > /dev/stderr
retrun 1
return 1
fi
return 0
}
+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 control 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 -39
View File
@@ -12,8 +12,6 @@
"@axa-fr/react-oidc": "^5.14.0",
"@headlessui/react": "^1.5.0",
"@heroicons/react": "^1.0.4",
"@stripe/react-stripe-js": "^2.1.2",
"@stripe/stripe-js": "^2.1.0",
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
@@ -65,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",
@@ -3407,24 +3405,6 @@
"@sinonjs/commons": "^1.7.0"
}
},
"node_modules/@stripe/react-stripe-js": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-2.1.2.tgz",
"integrity": "sha512-2JcKRvOdMD698uGrY0cINLHb6XHnY24L+wlWlw2+TbI2aXlQZ9t8Mc+KssFES+EuNgISbrp7fJR3w/13epfiOw==",
"dependencies": {
"prop-types": "^15.7.2"
},
"peerDependencies": {
"@stripe/stripe-js": "^1.44.1 || ^2.0.0",
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/@stripe/stripe-js": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-2.1.0.tgz",
"integrity": "sha512-AdMHWqIq/s3TLmoyuzdb6l21STKDKNm82cjce1VelyQGinVNjVgSEPH5UDtxENEP7OzgMZH8RTphFMi0fMJGLQ=="
},
"node_modules/@surma/rollup-plugin-off-main-thread": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz",
@@ -17193,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",
@@ -19497,19 +19477,6 @@
"@sinonjs/commons": "^1.7.0"
}
},
"@stripe/react-stripe-js": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-2.1.2.tgz",
"integrity": "sha512-2JcKRvOdMD698uGrY0cINLHb6XHnY24L+wlWlw2+TbI2aXlQZ9t8Mc+KssFES+EuNgISbrp7fJR3w/13epfiOw==",
"requires": {
"prop-types": "^15.7.2"
}
},
"@stripe/stripe-js": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-2.1.0.tgz",
"integrity": "sha512-AdMHWqIq/s3TLmoyuzdb6l21STKDKNm82cjce1VelyQGinVNjVgSEPH5UDtxENEP7OzgMZH8RTphFMi0fMJGLQ=="
},
"@surma/rollup-plugin-off-main-thread": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz",
+2 -4
View File
@@ -7,8 +7,6 @@
"@axa-fr/react-oidc": "^5.14.0",
"@headlessui/react": "^1.5.0",
"@heroicons/react": "^1.0.4",
"@stripe/react-stripe-js": "^2.1.2",
"@stripe/stripe-js": "^2.1.0",
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
@@ -81,7 +79,7 @@
]
},
"devDependencies": {
"@playwright/test": "^1.36.2",
"@types/react-syntax-highlighter": "^15.5.3"
"@types/react-syntax-highlighter": "^15.5.3",
"@playwright/test": "^1.36.2"
}
}
+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
View File
Whitespace-only changes.
+57 -1
View File
@@ -73,6 +73,7 @@ const NameServerGroupAdd = () => {
const inputNameRef = useRef<any>(null);
const inputDescriptionRef = useRef<any>(null);
const [selectCustom, setSelectCustom] = useState(false);
const [matchDomains, setMatchDomains] = useState(0);
useEffect(() => {
if (editName)
@@ -87,7 +88,6 @@ const NameServerGroupAdd = () => {
cursor: "end",
});
}, [editDescription]);
useEffect(() => {
if (!nsGroup) return;
@@ -117,6 +117,7 @@ const NameServerGroupAdd = () => {
nameservers: [] as NameServer[],
groups: [],
enabled: false,
search_domains_enabled: false,
} as NameServerGroup)
);
setEditName(false);
@@ -125,6 +126,7 @@ const NameServerGroupAdd = () => {
};
const onChange = (changedValues: any) => {
if (changedValues.primary !== undefined) {
setIsPrimary(changedValues.primary);
}
@@ -156,6 +158,7 @@ const NameServerGroupAdd = () => {
],
groups: [],
enabled: true,
search_domains_enabled: false,
},
{
name: cloudflareChoice,
@@ -176,6 +179,7 @@ const NameServerGroupAdd = () => {
],
groups: [],
enabled: true,
search_domains_enabled: false,
},
{
name: quad9Choice,
@@ -196,6 +200,7 @@ const NameServerGroupAdd = () => {
],
groups: [],
enabled: true,
search_domains_enabled: false,
},
];
@@ -203,6 +208,7 @@ const NameServerGroupAdd = () => {
let nsGroupLocal = {} as NameServerGroup;
if (value === customChoice) {
nsGroupLocal = nsGroup;
nsGroupLocal.domains = [];
} else {
defaultDNSOptions.forEach((nsg) => {
if (value === nsg.name) {
@@ -262,6 +268,8 @@ const NameServerGroupAdd = () => {
groups: existingGroups,
groupsToCreate: newGroups,
enabled: values.enabled,
search_domains_enabled:
matchDomains > 0 ? formNSGroup.search_domains_enabled : false,
} as NameServerGroupToSave;
};
@@ -462,6 +470,7 @@ const NameServerGroupAdd = () => {
</Col>
</Space>
</Row>
{setMatchDomains(fields.length)}
{fields.map((field, index) => {
return (
<Row key={index} style={{ marginBottom: "5px" }}>
@@ -522,6 +531,13 @@ const NameServerGroupAdd = () => {
});
};
const handleChangeMarkDomain = (checked: boolean) => {
setFormNSGroup({
...formNSGroup,
search_domains_enabled: checked,
});
};
return (
<>
{nsGroup && (
@@ -701,6 +717,46 @@ const NameServerGroupAdd = () => {
<Col span={24}>
<Form.List name="domains">{renderDomains}</Form.List>
</Col>
{matchDomains > 0 && (
<Col span={24}>
<Form.Item name="search_domains_enabled" label="">
<div
style={{
display: "flex",
gap: "15px",
}}
>
<Switch
onChange={handleChangeMarkDomain}
size="small"
/>
<div>
<label
style={{
color: "rgba(0, 0, 0, 0.88)",
fontSize: "14px",
fontWeight: "500",
}}
>
Mark match domains as search domains
</label>
<Paragraph
type={"secondary"}
style={{
marginTop: "-2",
fontWeight: "400",
marginBottom: "0",
}}
>
E.g., "peer.example.com" will be accessible with
"peer"
</Paragraph>
</div>
</div>
</Form.Item>
</Col>
)}
<Col span={24}>
<label
style={{
File diff suppressed because it is too large. Load diff
+1 -1
View File
@@ -161,7 +161,7 @@ const Navbar = () => {
return false;
}
if (user.role?.toLowerCase() === "admin") {
if (user.role?.toLowerCase() === "admin" || user.role?.toLowerCase() === "owner") {
return true;
}
return !adminOnlyTabs.find((t) => t === key);
+209 -125
View File
@@ -35,6 +35,7 @@ import {
LockOutlined,
EditOutlined,
ExclamationCircleOutlined,
ExclamationCircleFilled,
} from "@ant-design/icons";
import { RuleObject } from "antd/lib/form";
import { useGetTokenSilently } from "../utils/token";
@@ -145,10 +146,22 @@ const PeerUpdate = (props: any) => {
useEffect(() => {
setPeerRoutes([]);
const temp: any[] = [];
if (peer && peer.groups) {
peer?.groups?.forEach((pg: any) => {
routes.forEach((route: any) => {
if (route.peer_groups?.includes(pg.id)) {
temp.push(route);
}
});
});
}
const filterPeerRoutes: any = routes.filter(
(route) => route.peer === peer.id
);
setPeerRoutes(filterPeerRoutes);
let mergeArr: any = [...filterPeerRoutes, ...temp];
setPeerRoutes(mergeArr);
const filterNotPeerRoutes: any = routes.filter(
(route) => route.peer !== peer.id
);
@@ -179,6 +192,16 @@ const PeerUpdate = (props: any) => {
useEffect(() => {}, [users]);
const routeAddAllowed = (os: string): boolean => {
return (
os !== "" &&
!os.toLowerCase().startsWith("darwin") &&
!os.toLowerCase().startsWith("windows") &&
!os.toLowerCase().startsWith("ios") &&
!os.toLowerCase().startsWith("android")
);
};
const toggleEditName = (status: boolean, value?: string) => {
setEditName(status);
@@ -389,13 +412,13 @@ const PeerUpdate = (props: any) => {
const style = { marginTop: 85 };
if (savedGroups.loading) {
message.loading({
content: "Updating peer groups...",
content: "Updating peer group...",
key: saveGroupsKey,
style,
});
} else if (savedGroups.success) {
message.success({
content: "Peer groups have been successfully updated.",
content: "Peer group have been successfully updated.",
key: saveGroupsKey,
duration: 2,
style,
@@ -408,7 +431,7 @@ const PeerUpdate = (props: any) => {
} else if (savedGroups.error) {
message.error({
content:
"Failed to update peer groups. You might not have enough permissions.",
"Failed to update peer group. You might not have enough permissions.",
key: saveGroupsKey,
duration: 2,
style,
@@ -652,6 +675,48 @@ const PeerUpdate = (props: any) => {
}
}, [deletedRoute]);
const renderGroupRouting = (rowGroups: string[] | null) => {
let groupsMap = new Map<string, Group>();
groups.forEach((g) => {
groupsMap.set(g.id!, g);
});
let displayGroups: Group[] = [];
if (rowGroups) {
displayGroups = rowGroups
.filter((g) => groupsMap.get(g))
.map((g) => groupsMap.get(g)!);
}
const groupToCompare =
peer &&
peer.groups &&
peer?.groups.map((element1) => {
return element1.id;
});
return (
<div className="gp-main-wrapper">
{displayGroups &&
displayGroups.length > 0 &&
displayGroups.map((group) => {
if (group.id && !groupToCompare?.includes(group?.id)) {
return null;
}
return (
<div className="g-r-wrapper">
<span className="f-r-name">
<Tag color={"blue"} style={{ marginRight: 3 }}>
{group.name}
</Tag>
</span>{" "}
</div>
);
})}
</div>
);
};
return (
<>
{peer && (
@@ -965,132 +1030,151 @@ const PeerUpdate = (props: any) => {
{/* --- */}
{!isGroupUpdateView && (
<>
<Card
bordered={true}
// loading={loading}Ć’
style={{ marginBottom: "7px" }}
>
<div style={{ maxWidth: "800px" }}>
<Paragraph
style={{
textAlign: "left",
whiteSpace: "pre-line",
fontSize: "16px",
fontWeight: "500",
}}
>
Network routes
</Paragraph>
<Row
gutter={21}
style={{ marginTop: "-16px", marginBottom: "10px" }}
>
<Col
xs={24}
sm={24}
md={20}
lg={20}
xl={20}
xxl={20}
span={20}
{routeAddAllowed(peer.os) && (
<Card
bordered={true}
// loading={loading}Ć’
style={{ marginBottom: "7px" }}
>
<div style={{ maxWidth: "800px" }}>
<Paragraph
style={{
textAlign: "left",
whiteSpace: "pre-line",
fontSize: "16px",
fontWeight: "500",
}}
>
<Paragraph
type={"secondary"}
style={{ textAlign: "left", whiteSpace: "pre-line" }}
Network routes
</Paragraph>
<Row
gutter={21}
style={{ marginTop: "-16px", marginBottom: "10px" }}
>
<Col
xs={24}
sm={24}
md={20}
lg={20}
xl={20}
xxl={20}
span={20}
>
Access other networks without installing NetBird on
every resource.
</Paragraph>
</Col>
<Col
xs={24}
sm={24}
md={1}
lg={1}
xl={1}
xxl={1}
span={1}
style={{ marginTop: "-16px" }}
>
{peerRoutes && peerRoutes.length > 0 && (
<Paragraph
type={"secondary"}
style={{ textAlign: "left", whiteSpace: "pre-line" }}
>
Access other networks without installing NetBird on
every resource.
</Paragraph>
</Col>
<Col
xs={24}
sm={24}
md={1}
lg={1}
xl={1}
xxl={1}
span={1}
style={{ marginTop: "-16px" }}
>
{peerRoutes && peerRoutes.length > 0 && (
<Button type="primary" onClick={onClickAddNewRoute}>
Add route
</Button>
)}
</Col>
</Row>
{peerRoutes && peerRoutes.length > 0 && (
<Table
size={"small"}
style={{ marginTop: "-10px" }}
showHeader={false}
scroll={{ x: 800 }}
pagination={false}
dataSource={peerRoutes}
>
<Column title="Name" dataIndex="network_id" />
<Column title="Name" dataIndex="network" />
<Column
title="enabled"
dataIndex="network"
render={(e, record: any, index) => {
return record.peer_groups ? (
renderGroupRouting(record.peer_groups)
) : (
<>
<Switch
defaultChecked={record.enabled}
size="small"
onChange={(checked) =>
onRouteEnableChange(checked, record)
}
/>
</>
);
}}
/>
<Column
align="right"
render={(text, record: any, index) => {
return record.peer_groups ? (
<Tooltip
color="#fff"
overlayClassName="peer-avail-tooltip"
title={`Peer "${formPeer.name}" is a part of a group used in a network route.
To remove this peer from the network route, you need to disassociate
this peer from the groups used in this route.`}
>
<Button type={"text"} disabled={true}>
Delete
</Button>
</Tooltip>
) : (
<Button
danger={true}
type={"text"}
onClick={() => {
showConfirmDelete(
record.id,
record.network_id
);
}}
>
Delete
</Button>
);
}}
/>
</Table>
)}
<Divider style={{ marginTop: "-12px" }}></Divider>
{(peerRoutes === null || peerRoutes.length === 0) && (
<Space
direction="vertical"
size="small"
align="start"
style={{
display: "flex",
padding: "35px 0px",
marginTop: "-40px",
justifyContent: "center",
}}
>
<Paragraph
style={{ textAlign: "start", whiteSpace: "pre-line" }}
>
You don't have any routes yet
</Paragraph>
<Button type="primary" onClick={onClickAddNewRoute}>
Add route
</Button>
)}
</Col>
</Row>
{peerRoutes && peerRoutes.length > 0 && (
<Table
size={"small"}
style={{ marginTop: "-10px" }}
showHeader={false}
scroll={{ x: 800 }}
pagination={false}
dataSource={peerRoutes}
>
<Column title="Name" dataIndex="network_id" />
<Column title="Name" dataIndex="network" />
<Column
title="enabled"
dataIndex="network"
render={(e, record: any, index) => {
return (
<>
<Switch
defaultChecked={record.enabled}
size="small"
onChange={(checked) =>
onRouteEnableChange(checked, record)
}
/>
</>
);
}}
/>
<Column
align="right"
render={(text, record: any, index) => {
return (
<Button
danger={true}
type={"text"}
onClick={() => {
showConfirmDelete(record.id, record.network_id);
}}
>
Delete
</Button>
);
}}
/>
</Table>
)}
<Divider style={{ marginTop: "-12px" }}></Divider>
{(peerRoutes === null || peerRoutes.length === 0) && (
<Space
direction="vertical"
size="small"
align="start"
style={{
display: "flex",
padding: "35px 0px",
marginTop: "-40px",
justifyContent: "center",
}}
>
<Paragraph
style={{ textAlign: "start", whiteSpace: "pre-line" }}
>
You don't have any routes yet
</Paragraph>
<Button type="primary" onClick={onClickAddNewRoute}>
Add route
</Button>
</Space>
)}
</div>
</Card>
</Space>
)}
</div>
</Card>
)}
<Card bordered={true} style={{ marginBottom: "50px" }}>
<Col span={24}>
+164 -77
View File
@@ -17,7 +17,9 @@ import {
Switch,
Modal,
Typography,
Tabs,
} from "antd";
import type { TabsProps } from "antd";
import CreatableSelect from "react-select/creatable";
import { Route, RouteToSave } from "../store/route/types";
import { Header } from "antd/es/layout/layout";
@@ -38,6 +40,7 @@ const { Panel } = Collapse;
interface FormRoute extends Route {}
const RouteAddNew = (selectedPeer: any) => {
const [activeTab, setActiveTab] = useState<string | null>(null);
const {
blueTagRender,
handleChangeTags,
@@ -75,6 +78,10 @@ const RouteAddNew = (selectedPeer: any) => {
const [peerNameToIP, peerIPToName, peerIPToID] = initPeerMaps(peers);
const [newRoute, setNewRoute] = useState(false);
useEffect(() => {
if (setupNewRouteVisible) setActiveTab("routingPeer");
}, [setupNewRouteVisible]);
useEffect(() => {
if (editName)
inputNameRef.current!.focus({
@@ -144,6 +151,7 @@ const RouteAddNew = (selectedPeer: any) => {
!os.toLowerCase().startsWith("darwin") &&
!os.toLowerCase().startsWith("windows") &&
!os.toLowerCase().startsWith("android") &&
!os.toLowerCase().startsWith("ios") &&
route &&
!routes
.filter((r) => r.network_id === route.network_id)
@@ -159,6 +167,12 @@ const RouteAddNew = (selectedPeer: any) => {
}
const createRouteToSave = (inputRoute: FormRoute): RouteToSave => {
if (inputRoute.peer_groups) {
inputRoute = {
...inputRoute,
peer_groups: [inputRoute.peer_groups[inputRoute.peer_groups.length - 1]],
};
}
let peerIDList = inputRoute.peer.split(routePeerSeparator);
let peerID: string;
if (peerIDList.length === 1) {
@@ -175,24 +189,70 @@ const RouteAddNew = (selectedPeer: any) => {
inputRoute.groups
);
return {
const payload = {
id: inputRoute.id,
network: inputRoute.network,
network_id: inputRoute.network_id,
description: inputRoute.description,
peer: peerID,
enabled: inputRoute.enabled,
masquerade: inputRoute.masquerade,
metric: inputRoute.metric,
groups: existingGroups,
groupsToCreate: groupsToCreate,
} as RouteToSave;
if (activeTab === "routingPeer") {
let pay = { ...payload, peer: peerID };
return pay;
}
if (activeTab === "groupOfPeers") {
if (inputRoute.peer_groups) {
let [currentPeersGroup, peerGroupsToCreate] =
getExistingAndToCreateGroupsLists(inputRoute.peer_groups);
let pay = {
...payload,
peer_groups: currentPeersGroup,
peerGroupsToCreate: peerGroupsToCreate,
};
return pay;
}
}
return payload;
};
const handleFormSubmit = () => {
form
.validateFields()
.then(() => {
const t = routes.filter((route) => {
if (
route.network_id === formRoute.network_id &&
route.network === formRoute.network
) {
return route;
}
});
if (
formRoute.peer_groups &&
formRoute.peer_groups.length > 0 &&
t &&
t.length > 0
) {
const style = { marginTop: 85 };
const duplicateNetworkIdKey = "duplicateKey";
return message.error({
content:
"A route with this network identifier and network range already exists. Please use a different network identifier or network range.",
key: duplicateNetworkIdKey,
duration: 5,
style,
});
}
if (!setupNewRouteHA || formRoute.peer != "") {
const routeToSave = createRouteToSave(formRoute);
dispatch(
@@ -243,6 +303,7 @@ const RouteAddNew = (selectedPeer: any) => {
const onCancel = () => {
if (savedRoute.loading) return;
setActiveTab(null);
setEditName(false);
dispatch(
routeActions.setRoute({
@@ -254,6 +315,7 @@ const RouteAddNew = (selectedPeer: any) => {
masquerade: false,
enabled: true,
groups: [],
peer_groups: [],
} as Route)
);
setVisibleNewRoute(false);
@@ -300,8 +362,15 @@ const RouteAddNew = (selectedPeer: any) => {
return Promise.resolve();
};
const peerGroupsValidaton = (_: RuleObject, value: string) => {
if (value.length < 1) {
return Promise.reject(new Error("Please select a peer group"));
}
return Promise.resolve();
};
const selectPreValidator = (obj: RuleObject, value: string[]) => {
if (setupNewRouteHA && formRoute.peer == "") {
if (setupNewRouteHA && formRoute.peer === "") {
let [, newGroups] = getExistingAndToCreateGroupsLists(value);
if (newGroups.length > 0) {
return Promise.reject(
@@ -361,56 +430,95 @@ const RouteAddNew = (selectedPeer: any) => {
}
};
const styleNotification = { marginTop: 85 };
const onTabChange = (key: string) => {
setActiveTab(key);
};
const saveKey = "saving";
useEffect(() => {
if (savedRoute.loading) {
message.loading({
content: "Saving...",
key: saveKey,
duration: 0,
style: styleNotification,
const handleSingleChangeTags = (values: any) => {
const lastValue = values[values.length - 1];
if (values.length > 0) {
form.setFieldsValue({
peer_groups: [lastValue],
});
} else if (savedRoute.success) {
message.success({
content: "Route has been successfully added.",
key: saveKey,
duration: 2,
style: styleNotification,
});
dispatch(routeActions.setSetupNewRouteVisible(false));
dispatch(routeActions.setSetupEditRouteVisible(false));
dispatch(routeActions.setSetupEditRoutePeerVisible(false));
dispatch(routeActions.setSavedRoute({ ...savedRoute, success: false }));
dispatch(routeActions.resetSavedRoute(null));
} else if (savedRoute.error) {
let errorMsg = "Failed to update network route";
switch (savedRoute.error.statusCode) {
case 403:
errorMsg =
"Failed to update network route. You might not have enough permissions.";
break;
default:
errorMsg = savedRoute.error.data.message
? savedRoute.error.data.message
: errorMsg;
break;
}
message.error({
content: errorMsg,
key: saveKey,
duration: 5,
style: styleNotification,
});
dispatch(routeActions.setSavedRoute({ ...savedRoute, error: null }));
dispatch(routeActions.resetSavedRoute(null));
}
}, [savedRoute]);
};
const items: TabsProps["items"] = [
{
key: "routingPeer",
label: "Routing Peer",
children: (
<>
<Paragraph
type={"secondary"}
style={{
marginTop: "-2",
fontWeight: "400",
marginBottom: "5px",
}}
>
Assign a peer as a routing peer for the Network CIDR
</Paragraph>
{activeTab === "routingPeer" && (
<Form.Item name="peer" rules={[{ validator: peerValidator }]}>
<Select
showSearch
style={{ width: "100%" }}
placeholder="Select Peer"
dropdownRender={peerDropDownRender}
options={options}
allowClear={true}
disabled={!!selectedPeer.selectedPeer}
/>
</Form.Item>
)}{" "}
</>
),
},
{
key: "groupOfPeers",
label: "Peer group",
children: (
<>
<Paragraph
type={"secondary"}
style={{
marginTop: "-2",
fontWeight: "400",
marginBottom: "5px",
}}
>
Assign peer group with Linux machines to be used as routing peers
</Paragraph>
{activeTab === "groupOfPeers" && (
<Form.Item
name="peer_groups"
rules={[{ validator: peerGroupsValidaton }]}
>
<Select
mode="tags"
style={{ width: "100%" }}
tagRender={blueTagRender}
onChange={handleSingleChangeTags}
dropdownRender={dropDownRender}
optionFilterProp="serchValue"
>
{tagGroups.map((m, index) => (
<Option key={index} value={m.id} serchValue={m.name}>
{optionRender(m.name, m.id)}
</Option>
))}
</Select>
</Form.Item>
)}
</>
),
},
];
return (
<>
{route && (
{route && setupNewRouteVisible && (
<Modal
open={setupNewRouteVisible}
onCancel={onCancel}
@@ -558,7 +666,7 @@ const RouteAddNew = (selectedPeer: any) => {
marginBottom: "5px",
}}
>
Add a unique cryptographic key that is assigned
Add a unique network identifier that is assigned
to each device
</Paragraph>
<Form.Item
@@ -667,36 +775,15 @@ const RouteAddNew = (selectedPeer: any) => {
{!!!selectedPeer.selectedPeer && (
<Col span={24}>
<label
style={{
color: "rgba(0, 0, 0, 0.88)",
fontSize: "14px",
fontWeight: "500",
}}
>
Routing Peer
</label>
<Paragraph
type={"secondary"}
style={{
marginTop: "-2",
fontWeight: "400",
marginBottom: "5px",
}}
>
Assign a peer as a routing peer for the Network CIDR
</Paragraph>
<Form.Item name="peer" rules={[{ validator: peerValidator }]}>
<Select
showSearch
style={{ width: "100%" }}
placeholder="Select Peer"
dropdownRender={peerDropDownRender}
options={options}
allowClear={true}
disabled={!!selectedPeer.selectedPeer}
{activeTab ? (
<Tabs
defaultActiveKey={activeTab}
items={items}
onChange={onTabChange}
/>
</Form.Item>
) : (
""
)}
</Col>
)}
<Col span={24}>
+150 -34
View File
@@ -17,10 +17,13 @@ import {
Switch,
Card,
Typography,
Tabs,
} from "antd";
import type { TabsProps } from "antd";
import { Route, RouteToSave } from "../store/route/types";
import { Header } from "antd/es/layout/layout";
import { RuleObject } from "antd/lib/form";
import { isEmpty } from "lodash";
import {
initPeerMaps,
peerToPeerIP,
@@ -61,6 +64,16 @@ const RoutePeerUpdate = () => {
const [form] = Form.useForm();
const inputDescriptionRef = useRef<any>(null);
const [peerNameToIP, peerIPToName, peerIPToID] = initPeerMaps(peers);
const [activeTab, setActiveTab] = useState<string | null>(null);
useEffect(() => {
if (!isEmpty(formRoute))
if (formRoute.peer_groups) {
setActiveTab("groupOfPeers");
} else {
setActiveTab("routingPeer");
}
}, [formRoute]);
useEffect(() => {
if (editDescription)
@@ -87,6 +100,7 @@ const RoutePeerUpdate = () => {
!os.toLowerCase().startsWith("darwin") &&
!os.toLowerCase().startsWith("windows") &&
!os.toLowerCase().startsWith("android") &&
!os.toLowerCase().startsWith("ios") &&
route &&
!routes
.filter((r) => r.network_id === route.network_id)
@@ -100,7 +114,25 @@ const RoutePeerUpdate = () => {
}
});
const handleSingleChangeTags = (values: any) => {
const lastValue = values[values.length - 1];
if (values.length > 0) {
form.setFieldsValue({
peer_groups: [lastValue],
});
}
};
const createRouteToSave = (inputRoute: FormRoute): RouteToSave => {
if (inputRoute.peer_groups) {
inputRoute = {
...inputRoute,
peer_groups: [
inputRoute.peer_groups[inputRoute.peer_groups.length - 1],
],
};
}
let peerIDList = inputRoute.peer.split(routePeerSeparator);
let peerID: string;
if (peerIDList.length === 1) {
@@ -116,18 +148,38 @@ const RoutePeerUpdate = () => {
inputRoute.groups
);
return {
const payload = {
id: inputRoute.id,
network: inputRoute.network,
network_id: inputRoute.network_id,
description: inputRoute.description,
peer: peerID,
enabled: inputRoute.enabled,
masquerade: inputRoute.masquerade,
metric: inputRoute.metric,
groups: existingGroups,
groupsToCreate: groupsToCreate,
} as RouteToSave;
if (activeTab === "routingPeer") {
let pay = { ...payload, peer: peerID };
return pay;
}
if (activeTab === "groupOfPeers") {
if (inputRoute.peer_groups) {
let [currentPeersGroup, peerGroupsToCreate] =
getExistingAndToCreateGroupsLists(inputRoute.peer_groups);
let pay = {
...payload,
peer_groups: currentPeersGroup,
peerGroupsToCreate: peerGroupsToCreate,
};
return pay;
}
}
return payload;
};
const handleFormSubmit = () => {
@@ -165,6 +217,7 @@ const RoutePeerUpdate = () => {
masquerade: false,
enabled: true,
groups: [],
peer_groups: [],
} as Route)
);
setVisibleNewRoute(false);
@@ -269,6 +322,95 @@ const RoutePeerUpdate = () => {
}
}, [savedRoute]);
const onTabChange = (key: string) => {
console.log(key);
// setActiveTab(key);
};
const items: TabsProps["items"] = [
{
key: "routingPeer",
label: "Routing Peer",
disabled: true,
children: (
<>
<Paragraph
type={"secondary"}
style={{
marginTop: "-2",
fontWeight: "400",
marginBottom: "5px",
}}
>
Assign a peer as a routing peer for the Network CIDR
</Paragraph>
<Form.Item
name="peer"
rules={[
{
required: true,
message: "Please select routing one peer",
},
]}
style={{ maxWidth: "400px" }}
>
<Select
showSearch
style={{ width: "100%" }}
placeholder="Select Peer"
dropdownRender={peerDropDownRender}
options={options}
allowClear={true}
/>
</Form.Item>
</>
),
},
{
key: "groupOfPeers",
label: "Peer group",
disabled: true,
children: (
<>
<Paragraph
type={"secondary"}
style={{
marginTop: "-2",
fontWeight: "400",
marginBottom: "5px",
}}
>
Assign peer group with Linux machines to be used as routing peers
</Paragraph>
<Form.Item
name="peer_groups"
rules={[
{
required: true,
message: "Please select peer group",
},
]}
>
<Select
mode="tags"
style={{ maxWidth: "400px" }}
tagRender={blueTagRender}
onChange={handleSingleChangeTags}
dropdownRender={dropDownRender}
optionFilterProp="serchValue"
>
{tagGroups.map((m, index) => (
<Option key={index} value={m.id} serchValue={m.name}>
{optionRender(m.name, m.id)}
</Option>
))}
</Select>
</Form.Item>
</>
),
},
];
return (
<>
<Container style={{ paddingTop: "40px", paddingBottom: "50px" }}>
@@ -409,39 +551,13 @@ const RoutePeerUpdate = () => {
</Col>
<Col span={24}>
<label
style={{
color: "rgba(0, 0, 0, 0.88)",
fontSize: "14px",
fontWeight: "500",
}}
>
Routing Peer
</label>
<Paragraph
type={"secondary"}
style={{
marginTop: "-2",
fontWeight: "400",
marginBottom: "5px",
}}
>
Assign a peer as a routing peer for the Network CIDR
</Paragraph>
<Form.Item
name="peer"
rules={[{ validator: peerValidator }]}
style={{ maxWidth: "400px" }}
>
<Select
showSearch
style={{ width: "100%" }}
placeholder="Select Peer"
dropdownRender={peerDropDownRender}
options={options}
allowClear={true}
{activeTab && (
<Tabs
defaultActiveKey={activeTab}
items={items}
onChange={onTabChange}
/>
</Form.Item>
)}
</Col>
<Col span={24}>
<label
Loaded 20 of 54 files, more files were not shown because too many files have changed in this diff. Show more