mirror of
https://github.com/netbirdio/dashboard.git
synced 2026-09-23 00:30:43 -07:00
Compare commits
29
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
14d9b80029 | ||
|
|
dedbe55308 | ||
|
|
796a06cf27 | ||
|
|
2443c6332d | ||
|
|
3b5193ae4e | ||
|
|
cf42dd52fc | ||
|
|
bc6842e5b5 | ||
|
|
a8ed755dda | ||
|
|
a87c06ef52 | ||
|
|
c0130d265c | ||
|
|
63ced3088a | ||
|
|
42b7a15466 | ||
|
|
c88bfa6476 | ||
|
|
c4138a8c45 | ||
|
|
3f5418bebc | ||
|
|
f60605e5e3 | ||
|
|
c1b2ededa7 | ||
|
|
31eaa4a241 | ||
|
|
ba365336ff | ||
|
|
06c238b013 | ||
|
|
ed56c240f3 | ||
|
|
1f3c7d87d7 | ||
|
|
9de2906fb2 | ||
|
|
359b443326 | ||
|
|
ecae39d94b | ||
|
|
30b858c1bc | ||
|
|
e82b269ff5 | ||
|
|
23a4b79f01 | ||
|
|
cc5a9b1033 |
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
|
||||
@@ -22,6 +22,10 @@ if [[ -z "${AUTH_AUDIENCE}" ]]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "${AUTH_AUDIENCE}" == "none" ]]; then
|
||||
unset AUTH_AUDIENCE
|
||||
fi
|
||||
|
||||
if [[ -z "${AUTH_SUPPORTED_SCOPES}" ]]; then
|
||||
if [[ -z "${AUTH0_DOMAIN}" ]]; then
|
||||
echo "AUTH_SUPPORTED_SCOPES environment variable must be set"
|
||||
@@ -54,11 +58,12 @@ export NETBIRD_MGMT_API_ENDPOINT=$(echo $NETBIRD_MGMT_API_ENDPOINT | sed -E 's/(
|
||||
export NETBIRD_MGMT_GRPC_API_ENDPOINT=${NETBIRD_MGMT_GRPC_API_ENDPOINT}
|
||||
export NETBIRD_HOTJAR_TRACK_ID=${NETBIRD_HOTJAR_TRACK_ID}
|
||||
export NETBIRD_TOKEN_SOURCE=${NETBIRD_TOKEN_SOURCE:-accessToken}
|
||||
export NETBIRD_DRAG_QUERY_PARAMS=${NETBIRD_DRAG_QUERY_PARAMS:-false}
|
||||
|
||||
echo "NetBird latest version: ${NETBIRD_LATEST_VERSION}"
|
||||
|
||||
# replace ENVs in the config
|
||||
ENV_STR="\$\$USE_AUTH0 \$\$AUTH_AUDIENCE \$\$AUTH_AUTHORITY \$\$AUTH_CLIENT_ID \$\$AUTH_CLIENT_SECRET \$\$AUTH_SUPPORTED_SCOPES \$\$NETBIRD_MGMT_API_ENDPOINT \$\$NETBIRD_MGMT_GRPC_API_ENDPOINT \$\$NETBIRD_HOTJAR_TRACK_ID \$\$AUTH_REDIRECT_URI \$\$AUTH_SILENT_REDIRECT_URI \$\$NETBIRD_TOKEN_SOURCE"
|
||||
ENV_STR="\$\$USE_AUTH0 \$\$AUTH_AUDIENCE \$\$AUTH_AUTHORITY \$\$AUTH_CLIENT_ID \$\$AUTH_CLIENT_SECRET \$\$AUTH_SUPPORTED_SCOPES \$\$NETBIRD_MGMT_API_ENDPOINT \$\$NETBIRD_MGMT_GRPC_API_ENDPOINT \$\$NETBIRD_HOTJAR_TRACK_ID \$\$AUTH_REDIRECT_URI \$\$AUTH_SILENT_REDIRECT_URI \$\$NETBIRD_TOKEN_SOURCE \$\$NETBIRD_DRAG_QUERY_PARAMS"
|
||||
|
||||
MAIN_JS=$(find /usr/share/nginx/html/static/js/main.*js)
|
||||
OIDC_TRUSTED_DOMAINS="/usr/share/nginx/html/OidcTrustedDomains.js"
|
||||
|
||||
@@ -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,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, expect } 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,33 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import {AddPeerModal} from '../pages/modals/add-peer-modal'
|
||||
import {TopMenu} from '../pages/top-menu';
|
||||
|
||||
const URL = 'https://app.netbird.io/'
|
||||
const localUrl = 'http://localhost:3000/'
|
||||
let addPeerModal: AddPeerModal;
|
||||
let topMenu: TopMenu;
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
@@ -0,0 +1,56 @@
|
||||
import { test } from '@playwright/test';
|
||||
import {AddPeerModal} from '../pages/modals/add-peer-modal'
|
||||
import {PeersPage} from '../pages/peers-page'
|
||||
|
||||
const URL = 'https://app.netbird.io/'
|
||||
const localUrl = 'http://localhost:3000/'
|
||||
let addPeerModal: AddPeerModal;
|
||||
let peersPage: PeersPage;
|
||||
|
||||
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();
|
||||
});
|
||||
|
||||
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
+133
-84
@@ -58,6 +58,7 @@
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.36.2",
|
||||
"@types/react-syntax-highlighter": "^15.5.3"
|
||||
}
|
||||
},
|
||||
@@ -204,9 +205,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/core/node_modules/semver": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
@@ -237,9 +238,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/eslint-parser/node_modules/semver": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
@@ -311,9 +312,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-compilation-targets/node_modules/semver": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
@@ -370,9 +371,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-define-polyfill-provider/node_modules/semver": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
@@ -1706,9 +1707,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/plugin-transform-runtime/node_modules/semver": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
@@ -1918,9 +1919,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/preset-env/node_modules/semver": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
@@ -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",
|
||||
@@ -5219,9 +5239,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
@@ -7467,9 +7487,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-jsx-a11y/node_modules/semver": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
@@ -7540,9 +7560,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-react/node_modules/semver": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
@@ -9512,9 +9532,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/istanbul-lib-instrument/node_modules/semver": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
@@ -10800,9 +10820,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/make-dir/node_modules/semver": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
@@ -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",
|
||||
@@ -14712,9 +14744,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.3.7",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
|
||||
"integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
|
||||
"version": "7.5.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
|
||||
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
|
||||
"dependencies": {
|
||||
"lru-cache": "^6.0.0"
|
||||
},
|
||||
@@ -15831,9 +15863,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tough-cookie": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz",
|
||||
"integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==",
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz",
|
||||
"integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==",
|
||||
"dependencies": {
|
||||
"psl": "^1.1.33",
|
||||
"punycode": "^2.1.1",
|
||||
@@ -16710,9 +16742,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/word-wrap": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
|
||||
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz",
|
||||
"integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -17249,9 +17281,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -17271,9 +17303,9 @@
|
||||
"integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw=="
|
||||
},
|
||||
"semver": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -17328,9 +17360,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -17371,9 +17403,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -18237,9 +18269,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -18394,9 +18426,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -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",
|
||||
@@ -20854,9 +20897,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -22543,9 +22586,9 @@
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -22589,9 +22632,9 @@
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -23955,9 +23998,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -24942,9 +24985,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
|
||||
"version": "6.3.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
|
||||
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -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",
|
||||
@@ -27566,9 +27615,9 @@
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
"version": "7.3.7",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
|
||||
"integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
|
||||
"version": "7.5.4",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
|
||||
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
|
||||
"requires": {
|
||||
"lru-cache": "^6.0.0"
|
||||
}
|
||||
@@ -28430,9 +28479,9 @@
|
||||
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="
|
||||
},
|
||||
"tough-cookie": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz",
|
||||
"integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==",
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz",
|
||||
"integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==",
|
||||
"requires": {
|
||||
"psl": "^1.1.33",
|
||||
"punycode": "^2.1.1",
|
||||
@@ -29069,9 +29118,9 @@
|
||||
}
|
||||
},
|
||||
"word-wrap": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
|
||||
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ=="
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.4.tgz",
|
||||
"integrity": "sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA=="
|
||||
},
|
||||
"workbox-background-sync": {
|
||||
"version": "6.5.4",
|
||||
|
||||
+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
-12
@@ -70,16 +70,7 @@ function App() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<ConfigProvider
|
||||
theme={{
|
||||
token: {
|
||||
borderRadius: 4,
|
||||
colorPrimary: "#1890ff",
|
||||
fontFamily: "Arial"
|
||||
},
|
||||
components: {Badge: {fontSizeSM: 20}},
|
||||
}}
|
||||
>
|
||||
|
||||
<Provider store={store}>
|
||||
{!show && <SecureLoading padding="3em" width={50} height={50}/>}
|
||||
{show &&
|
||||
@@ -124,8 +115,6 @@ function App() {
|
||||
</Layout>
|
||||
}
|
||||
</Provider>
|
||||
|
||||
</ConfigProvider>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ interface FormPolicy {
|
||||
}
|
||||
|
||||
const AccessControlEdit = () => {
|
||||
const { optionRender, blueTagRender, grayTagRender } =
|
||||
const { optionRender, blueTagRender, tagGroups, grayTagRender } =
|
||||
useGetGroupTagHelpers();
|
||||
|
||||
const { getTokenSilently } = useGetTokenSilently();
|
||||
@@ -64,7 +64,7 @@ const AccessControlEdit = () => {
|
||||
{ label: "Drop", value: "drop" },
|
||||
];
|
||||
const protocols: SelectProps["options"] = [
|
||||
{ label: "All", value: "all" },
|
||||
{ label: "ALL", value: "all" },
|
||||
{ label: "TCP", value: "tcp" },
|
||||
{ label: "UDP", value: "udp" },
|
||||
{ label: "ICMP", value: "icmp" },
|
||||
@@ -81,7 +81,7 @@ const AccessControlEdit = () => {
|
||||
biDirectional: false,
|
||||
reverseDirectional: false,
|
||||
});
|
||||
const [tagGroups, setTagGroups] = useState([] as string[]);
|
||||
// const [tagGroups, setTagGroups] = useState([] as string[]);
|
||||
const [formPolicy, setFormPolicy] = useState({} as FormPolicy);
|
||||
const [form] = Form.useForm();
|
||||
const inputNameRef = useRef<any>(null);
|
||||
@@ -99,9 +99,9 @@ const AccessControlEdit = () => {
|
||||
useEffect(() => {
|
||||
if (editDescription) inputDescriptionRef.current!.focus({ cursor: "end" });
|
||||
}, [editDescription]);
|
||||
useEffect(() => {
|
||||
setTagGroups(groups?.map((g) => g.name) || []);
|
||||
}, [groups]);
|
||||
// useEffect(() => {
|
||||
// setTagGroups(groups?.map((g) => g.name) || []);
|
||||
// }, [groups]);
|
||||
useEffect(() => {
|
||||
if (!policy) return;
|
||||
const fPolicy = {
|
||||
@@ -115,10 +115,10 @@ const AccessControlEdit = () => {
|
||||
ports: policy.rules[0].ports,
|
||||
action: policy.rules[0].action,
|
||||
tagSourceGroups: policy.rules[0].sources
|
||||
? policy.rules[0].sources?.map((t) => t.name)
|
||||
? policy.rules[0].sources?.map((t) => t.id || "")
|
||||
: [],
|
||||
tagDestinationGroups: policy.rules[0].destinations
|
||||
? policy.rules[0].destinations?.map((t) => t.name)
|
||||
? policy.rules[0].destinations?.map((t) => t.id || "")
|
||||
: [],
|
||||
} as FormPolicy;
|
||||
setFormPolicy(fPolicy);
|
||||
@@ -139,17 +139,21 @@ const AccessControlEdit = () => {
|
||||
const createPolicyToSave = (): PolicyToSave => {
|
||||
const sources =
|
||||
groups
|
||||
?.filter((g) => formPolicy.tagSourceGroups.includes(g.name))
|
||||
?.filter((g) => formPolicy.tagSourceGroups.includes(g.id || ""))
|
||||
.map((g) => g.id || "") || [];
|
||||
|
||||
const destinations =
|
||||
groups
|
||||
?.filter((g) => formPolicy.tagDestinationGroups.includes(g.name))
|
||||
?.filter((g) => formPolicy.tagDestinationGroups.includes(g.id || ""))
|
||||
.map((g) => g.id || "") || [];
|
||||
|
||||
const existingGroupsNames: any[] = groups?.map((g) => g.id);
|
||||
const sourcesNoId = formPolicy.tagSourceGroups.filter(
|
||||
(s) => !tagGroups.includes(s)
|
||||
(s) => !existingGroupsNames.includes(s)
|
||||
);
|
||||
|
||||
const destinationsNoId = formPolicy.tagDestinationGroups.filter(
|
||||
(s) => !tagGroups.includes(s)
|
||||
(s) => !existingGroupsNames.includes(s)
|
||||
);
|
||||
const groupsToSave = uniq([...sourcesNoId, ...destinationsNoId]);
|
||||
return {
|
||||
@@ -457,11 +461,8 @@ const AccessControlEdit = () => {
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Card
|
||||
bordered={true}
|
||||
// title={setupKey.name}
|
||||
style={{ marginBottom: "7px" }}
|
||||
>
|
||||
|
||||
<Card bordered={true} style={{ marginBottom: "7px" }}>
|
||||
<div style={{ maxWidth: "550px" }}>
|
||||
<Form
|
||||
layout="vertical"
|
||||
@@ -508,7 +509,6 @@ const AccessControlEdit = () => {
|
||||
<Form.Item
|
||||
name="name"
|
||||
label=""
|
||||
style={{ margin: "0" }}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
@@ -532,7 +532,7 @@ const AccessControlEdit = () => {
|
||||
{!editDescription ? (
|
||||
<div
|
||||
style={{
|
||||
margin: "12px 0 30px",
|
||||
margin: "0 0 39px",
|
||||
lineHeight: "22px",
|
||||
cursor: "pointer",
|
||||
}}
|
||||
@@ -551,7 +551,7 @@ const AccessControlEdit = () => {
|
||||
<Form.Item
|
||||
name="description"
|
||||
label="Description"
|
||||
style={{ marginTop: 24, fontWeight: "500" }}
|
||||
style={{ marginTop: 10, fontWeight: "500" }}
|
||||
>
|
||||
<Input
|
||||
placeholder="Add description..."
|
||||
@@ -568,7 +568,7 @@ const AccessControlEdit = () => {
|
||||
</Row>
|
||||
</Header>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Col span={24} style={{ marginBottom: "15px" }}>
|
||||
<Form.Item name="enabled" label="">
|
||||
<div
|
||||
style={{
|
||||
@@ -579,6 +579,7 @@ const AccessControlEdit = () => {
|
||||
<Switch
|
||||
onChange={handleChangeDisabled}
|
||||
defaultChecked={policy.enabled}
|
||||
size="small"
|
||||
/>
|
||||
<div>
|
||||
<label
|
||||
@@ -606,6 +607,7 @@ const AccessControlEdit = () => {
|
||||
</div>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
|
||||
<Col span={24}>
|
||||
<Row gutter={15}>
|
||||
<Col span={10}>
|
||||
@@ -622,9 +624,16 @@ const AccessControlEdit = () => {
|
||||
tagRender={blueTagRender}
|
||||
onChange={handleChangeSource}
|
||||
dropdownRender={dropDownRenderGroups}
|
||||
optionFilterProp="serchValue"
|
||||
>
|
||||
{tagGroups.map((m) => (
|
||||
<Option key={m}>{optionRender(m)}</Option>
|
||||
{tagGroups.map((m, index) => (
|
||||
<Option
|
||||
key={index}
|
||||
value={m.id}
|
||||
serchValue={m.name}
|
||||
>
|
||||
{optionRender(m.name, m.id)}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
@@ -775,16 +784,23 @@ const AccessControlEdit = () => {
|
||||
tagRender={blueTagRender}
|
||||
onChange={handleChangeDestination}
|
||||
dropdownRender={dropDownRenderGroups}
|
||||
optionFilterProp="serchValue"
|
||||
>
|
||||
{tagGroups.map((m) => (
|
||||
<Option key={m}>{optionRender(m)}</Option>
|
||||
{tagGroups.map((m, index) => (
|
||||
<Option
|
||||
key={index}
|
||||
value={m.id}
|
||||
serchValue={m.name}
|
||||
>
|
||||
{optionRender(m.name, m.id)}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Col span={24} style={{ marginBottom: "15px" }}>
|
||||
<Paragraph
|
||||
type={"secondary"}
|
||||
style={{ marginTop: "-15px", marginBottom: "30px" }}
|
||||
@@ -793,7 +809,7 @@ const AccessControlEdit = () => {
|
||||
protocol below
|
||||
</Paragraph>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Col span={24} style={{ marginBottom: "15px" }}>
|
||||
<Row>
|
||||
<Col span={10}>
|
||||
<Form.Item
|
||||
@@ -830,7 +846,14 @@ const AccessControlEdit = () => {
|
||||
maxWidth: "260px",
|
||||
fontWeight: "500",
|
||||
}}
|
||||
placeholder={<div color={"rgba(0,0,0,0.25)"} className="arimo-font">Select ports</div>}
|
||||
placeholder={
|
||||
<div
|
||||
color={"rgba(0,0,0,0.25)"}
|
||||
className="arimo-font"
|
||||
>
|
||||
Select ports
|
||||
</div>
|
||||
}
|
||||
className="menlo-font"
|
||||
value={formPolicyCopy}
|
||||
disabled={true}
|
||||
@@ -862,7 +885,14 @@ const AccessControlEdit = () => {
|
||||
maxWidth: "260px",
|
||||
fontWeight: "500",
|
||||
}}
|
||||
placeholder={<div color={"rgba(0,0,0,0.25)"} className="arimo-font">Select ports</div>}
|
||||
placeholder={
|
||||
<div
|
||||
color={"rgba(0,0,0,0.25)"}
|
||||
className="arimo-font"
|
||||
>
|
||||
Select ports
|
||||
</div>
|
||||
}
|
||||
tagRender={grayTagRender}
|
||||
onChange={handleChangePorts}
|
||||
className="menlo-font"
|
||||
@@ -890,7 +920,7 @@ const AccessControlEdit = () => {
|
||||
justifyContent: "start",
|
||||
padding: 0,
|
||||
gap: "10px",
|
||||
marginTop: "30px",
|
||||
marginTop: "12px",
|
||||
}}
|
||||
key={0}
|
||||
>
|
||||
|
||||
@@ -50,8 +50,12 @@ interface FormPolicy {
|
||||
}
|
||||
|
||||
const AccessControlNew = () => {
|
||||
const { optionRender, grayTagRender, blueTagRender } =
|
||||
useGetGroupTagHelpers();
|
||||
const {
|
||||
optionRender,
|
||||
blueTagRender,
|
||||
tagGroups,
|
||||
grayTagRender,
|
||||
} = useGetGroupTagHelpers();
|
||||
const { getTokenSilently } = useGetTokenSilently();
|
||||
const dispatch = useDispatch();
|
||||
const setupNewPolicyVisible = useSelector(
|
||||
@@ -63,7 +67,7 @@ const AccessControlNew = () => {
|
||||
{ label: "Drop", value: "drop" },
|
||||
];
|
||||
const protocols: SelectProps["options"] = [
|
||||
{ label: "All", value: "all" },
|
||||
{ label: "ALL", value: "all" },
|
||||
{ label: "TCP", value: "tcp" },
|
||||
{ label: "UDP", value: "udp" },
|
||||
{ label: "ICMP", value: "icmp" },
|
||||
@@ -81,7 +85,7 @@ const AccessControlNew = () => {
|
||||
biDirectional: true,
|
||||
reverseDirectional: true,
|
||||
});
|
||||
const [tagGroups, setTagGroups] = useState([] as string[]);
|
||||
// const [tagGroups, setTagGroups] = useState([] as string[]);
|
||||
const [formPolicy, setFormPolicy] = useState({} as FormPolicy);
|
||||
const [form] = Form.useForm();
|
||||
const inputNameRef = useRef<any>(null);
|
||||
@@ -93,9 +97,9 @@ const AccessControlNew = () => {
|
||||
useEffect(() => {
|
||||
if (editDescription) inputDescriptionRef.current!.focus({ cursor: "end" });
|
||||
}, [editDescription]);
|
||||
useEffect(() => {
|
||||
setTagGroups(groups?.map((g) => g.name) || []);
|
||||
}, [groups]);
|
||||
// useEffect(() => {
|
||||
// setTagGroups(groups?.map((g) => g.name) || []);
|
||||
// }, [groups]);
|
||||
useEffect(() => {
|
||||
if (!policy) return;
|
||||
const fPolicy = {
|
||||
@@ -122,19 +126,26 @@ const AccessControlNew = () => {
|
||||
const createPolicyToSave = (): PolicyToSave => {
|
||||
const sources =
|
||||
groups
|
||||
?.filter((g) => formPolicy.tagSourceGroups.includes(g.name))
|
||||
?.filter((g) => formPolicy.tagSourceGroups.includes(g.id || ""))
|
||||
.map((g) => g.id || "") || [];
|
||||
|
||||
const destinations =
|
||||
groups
|
||||
?.filter((g) => formPolicy.tagDestinationGroups.includes(g.name))
|
||||
?.filter((g) => formPolicy.tagDestinationGroups.includes(g.id || ""))
|
||||
.map((g) => g.id || "") || [];
|
||||
|
||||
|
||||
const existingGroupsNames: any[] = groups?.map((g) => g.id);
|
||||
const sourcesNoId = formPolicy.tagSourceGroups.filter(
|
||||
(s) => !tagGroups.includes(s)
|
||||
(s) => !existingGroupsNames.includes(s)
|
||||
);
|
||||
|
||||
const destinationsNoId = formPolicy.tagDestinationGroups.filter(
|
||||
(s) => !tagGroups.includes(s)
|
||||
(s) => !existingGroupsNames.includes(s)
|
||||
);
|
||||
|
||||
const groupsToSave = uniq([...sourcesNoId, ...destinationsNoId]);
|
||||
|
||||
return {
|
||||
id: formPolicy.id,
|
||||
name: formPolicy.name,
|
||||
@@ -456,18 +467,20 @@ const AccessControlNew = () => {
|
||||
textAlign: "start",
|
||||
whiteSpace: "pre-line",
|
||||
fontSize: "18px",
|
||||
margin: "0px",
|
||||
fontWeight: "500",
|
||||
}}
|
||||
>
|
||||
Create Rule
|
||||
Create rule
|
||||
</Paragraph>
|
||||
<Paragraph
|
||||
type={"secondary"}
|
||||
style={{
|
||||
textAlign: "start",
|
||||
whiteSpace: "pre-line",
|
||||
paddingBottom: "15px",
|
||||
marginTop: "-20px",
|
||||
fontSize: "14px",
|
||||
paddingBottom: "25px",
|
||||
marginBottom: "4px",
|
||||
}}
|
||||
>
|
||||
Use this rule to restrict access to groups of resources
|
||||
@@ -560,9 +573,12 @@ const AccessControlNew = () => {
|
||||
tagRender={blueTagRender}
|
||||
onChange={handleChangeSource}
|
||||
dropdownRender={dropDownRenderGroups}
|
||||
optionFilterProp="serchValue"
|
||||
>
|
||||
{tagGroups.map((m) => (
|
||||
<Option key={m}>{optionRender(m)}</Option>
|
||||
{tagGroups.map((m, index) => (
|
||||
<Option key={index} value={m.id} serchValue={m.name}>
|
||||
{optionRender(m.name, m.id)}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
@@ -713,9 +729,12 @@ const AccessControlNew = () => {
|
||||
tagRender={blueTagRender}
|
||||
onChange={handleChangeDestination}
|
||||
dropdownRender={dropDownRenderGroups}
|
||||
optionFilterProp="serchValue"
|
||||
>
|
||||
{tagGroups.map((m) => (
|
||||
<Option key={m}>{optionRender(m)}</Option>
|
||||
{tagGroups.map((m, index) => (
|
||||
<Option key={index} value={m.id} serchValue={m.name}>
|
||||
{optionRender(m.name, m.id)}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
@@ -767,6 +786,8 @@ const AccessControlNew = () => {
|
||||
color: "rgba(0, 0, 0, 0.88)",
|
||||
fontSize: "14px",
|
||||
fontWeight: "500",
|
||||
padding: "0 0 2px",
|
||||
display: "inline-block",
|
||||
}}
|
||||
>
|
||||
Ports
|
||||
@@ -828,9 +849,16 @@ const AccessControlNew = () => {
|
||||
className="menlo-font"
|
||||
placeholder={
|
||||
formPolicy.protocol === "all" ||
|
||||
formPolicy.protocol === "icmp"
|
||||
? "All"
|
||||
: (<div color={"rgba(0,0,0,0.25)"} className="arimo-font">Select ports</div>)
|
||||
formPolicy.protocol === "icmp" ? (
|
||||
"All"
|
||||
) : (
|
||||
<div
|
||||
color={"rgba(0,0,0,0.25)"}
|
||||
className="arimo-font"
|
||||
>
|
||||
Select ports
|
||||
</div>
|
||||
)
|
||||
}
|
||||
tagRender={grayTagRender}
|
||||
onChange={handleChangePorts}
|
||||
@@ -859,6 +887,7 @@ const AccessControlNew = () => {
|
||||
}}
|
||||
>
|
||||
<Switch
|
||||
size="small"
|
||||
onChange={handleChangeDisabled}
|
||||
defaultChecked={true}
|
||||
/>
|
||||
@@ -890,7 +919,7 @@ const AccessControlNew = () => {
|
||||
</Col>
|
||||
<Col
|
||||
span={24}
|
||||
style={{ marginTop: "20px", marginBottom: "25px" }}
|
||||
style={{ marginTop: "10px", marginBottom: "24px" }}
|
||||
>
|
||||
<Text type={"secondary"}>
|
||||
Learn more about
|
||||
@@ -900,7 +929,7 @@ const AccessControlNew = () => {
|
||||
href="https://docs.netbird.io/how-to/manage-network-access"
|
||||
>
|
||||
{" "}
|
||||
Access Controls
|
||||
access controls
|
||||
</a>
|
||||
</Text>
|
||||
</Col>
|
||||
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
} from "../store/nameservers/types";
|
||||
import { useGetGroupTagHelpers } from "../utils/groups";
|
||||
import { useGetTokenSilently } from "../utils/token";
|
||||
import { domainValidator } from "../utils/common";
|
||||
|
||||
const { Paragraph, Text } = Typography;
|
||||
|
||||
@@ -272,21 +273,6 @@ const NameServerGroupAdd = () => {
|
||||
setEditDescription(status);
|
||||
};
|
||||
|
||||
const domainRegex =
|
||||
/(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]/;
|
||||
|
||||
const domainValidator = (_: RuleObject, domain: string) => {
|
||||
if (domainRegex.test(domain)) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
setIsPrimary(false);
|
||||
return Promise.reject(
|
||||
new Error(
|
||||
"Please enter a valid domain, e.g. example.com or intra.example.com"
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const nameValidator = (_: RuleObject, value: string) => {
|
||||
const found = nsGroupData.find(
|
||||
(u) => u.name == value && u.id !== formNSGroup.id
|
||||
@@ -331,121 +317,117 @@ const NameServerGroupAdd = () => {
|
||||
{ add, remove }: any,
|
||||
{ errors }: any
|
||||
) => (
|
||||
<Col>
|
||||
<div style={{ width: "100%", maxWidth: "360px" }}>
|
||||
<label
|
||||
style={{
|
||||
color: "rgba(0, 0, 0, 0.88)",
|
||||
fontSize: "14px",
|
||||
fontWeight: "500",
|
||||
marginBottom: "10px",
|
||||
display: "block",
|
||||
}}
|
||||
>
|
||||
Nameservers
|
||||
</label>
|
||||
<div style={{ width: "100%", maxWidth: "360px" }}>
|
||||
<label
|
||||
style={{
|
||||
color: "rgba(0, 0, 0, 0.88)",
|
||||
fontSize: "14px",
|
||||
fontWeight: "500",
|
||||
marginBottom: "10px",
|
||||
display: "block",
|
||||
}}
|
||||
>
|
||||
Nameservers
|
||||
</label>
|
||||
|
||||
{!!fields.length && (
|
||||
<Row align="middle">
|
||||
<Col span={4} style={{ textAlign: "left" }}>
|
||||
<Typography.Text
|
||||
style={{ color: "#818183", paddingLeft: "5px" }}
|
||||
></Typography.Text>
|
||||
</Col>
|
||||
<Col span={10} style={{ textAlign: "left" }}>
|
||||
<Typography.Text style={{ color: "#818183", paddingLeft: "5px" }}>
|
||||
Nameserver IP
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
<Col span={4} style={{ textAlign: "left" }}>
|
||||
<Typography.Text style={{ color: "#818183", paddingLeft: "5px" }}>
|
||||
Port
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
<Col span={4} />
|
||||
</Row>
|
||||
)}
|
||||
{fields.map((field, index) => {
|
||||
return (
|
||||
<Row key={index}>
|
||||
<Col span={4} style={{ textAlign: "left" }}>
|
||||
<Form.Item
|
||||
style={{ margin: "3px" }}
|
||||
name={[field.name, "ns_type"]}
|
||||
rules={[
|
||||
{ required: true, message: "Missing first protocol" },
|
||||
]}
|
||||
initialValue={"udp"}
|
||||
>
|
||||
<Select
|
||||
disabled
|
||||
style={{ width: "100%" }}
|
||||
className="style-like-text"
|
||||
>
|
||||
<Option value="udp">UDP</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={10} style={{ margin: "1px" }}>
|
||||
<Form.Item
|
||||
style={{ margin: "1px" }}
|
||||
name={[field.name, "ip"]}
|
||||
rules={[{ validator: ipValidator }]}
|
||||
>
|
||||
<Input
|
||||
placeholder="e.g. X.X.X.X"
|
||||
style={{ width: "100%" }}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={4} style={{ textAlign: "center" }}>
|
||||
<Form.Item
|
||||
style={{ margin: "1px" }}
|
||||
name={[field.name, "port"]}
|
||||
rules={[{ required: true, message: "Missing port" }]}
|
||||
initialValue={53}
|
||||
>
|
||||
<InputNumber placeholder="Port" style={{ width: "100%" }} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col
|
||||
span={2}
|
||||
style={{
|
||||
textAlign: "center",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<MinusCircleOutlined onClick={() => remove(field.name)} />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
})}
|
||||
|
||||
<Row>
|
||||
<Col span={20}>
|
||||
<Form.Item>
|
||||
<Button
|
||||
type="dashed"
|
||||
onClick={() => add()}
|
||||
block
|
||||
style={{
|
||||
maxWidth: "270px",
|
||||
marginTop: "5px",
|
||||
}}
|
||||
disabled={fields.length > 1}
|
||||
icon={<PlusOutlined />}
|
||||
>
|
||||
Add Nameserver
|
||||
</Button>
|
||||
<Form.ErrorList errors={errors} />
|
||||
</Form.Item>
|
||||
{!!fields.length && (
|
||||
<Row align="middle">
|
||||
<Col span={4} style={{ textAlign: "left" }}>
|
||||
<Typography.Text
|
||||
style={{ color: "#818183", paddingLeft: "5px" }}
|
||||
></Typography.Text>
|
||||
</Col>
|
||||
<Col span={10} style={{ textAlign: "left" }}>
|
||||
<Typography.Text style={{ color: "#818183", paddingLeft: "5px" }}>
|
||||
Nameserver IP
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
<Col span={4} style={{ textAlign: "left" }}>
|
||||
<Typography.Text style={{ color: "#818183", paddingLeft: "5px" }}>
|
||||
Port
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
<Col span={4} />
|
||||
</Row>
|
||||
</div>
|
||||
</Col>
|
||||
)}
|
||||
{fields.map((field, index) => {
|
||||
return (
|
||||
<Row key={index}>
|
||||
<Col span={4} style={{ textAlign: "left" }}>
|
||||
<Form.Item
|
||||
style={{ margin: "3px" }}
|
||||
name={[field.name, "ns_type"]}
|
||||
rules={[{ required: true, message: "Missing first protocol" }]}
|
||||
initialValue={"udp"}
|
||||
>
|
||||
<Select
|
||||
disabled
|
||||
style={{ width: "100%" }}
|
||||
className="style-like-text"
|
||||
>
|
||||
<Option value="udp">UDP</Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={10} style={{ margin: "1px" }}>
|
||||
<Form.Item
|
||||
style={{ margin: "1px" }}
|
||||
name={[field.name, "ip"]}
|
||||
rules={[{ validator: ipValidator }]}
|
||||
>
|
||||
<Input
|
||||
placeholder="e.g. X.X.X.X"
|
||||
style={{ width: "100%" }}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={4} style={{ textAlign: "center" }}>
|
||||
<Form.Item
|
||||
style={{ margin: "1px" }}
|
||||
name={[field.name, "port"]}
|
||||
rules={[{ required: true, message: "Missing port" }]}
|
||||
initialValue={53}
|
||||
>
|
||||
<InputNumber placeholder="Port" style={{ width: "100%" }} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col
|
||||
span={2}
|
||||
style={{
|
||||
textAlign: "center",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
}}
|
||||
>
|
||||
<MinusCircleOutlined onClick={() => remove(field.name)} />
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
})}
|
||||
|
||||
<Row>
|
||||
<Col span={20}>
|
||||
<Form.Item>
|
||||
<Button
|
||||
type="dashed"
|
||||
onClick={() => add()}
|
||||
block
|
||||
style={{
|
||||
maxWidth: "270px",
|
||||
marginTop: "5px",
|
||||
}}
|
||||
disabled={fields.length > 1}
|
||||
icon={<PlusOutlined />}
|
||||
>
|
||||
Add nameserver
|
||||
</Button>
|
||||
<Form.ErrorList errors={errors} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
|
||||
// @ts-ignore
|
||||
@@ -470,9 +452,9 @@ const NameServerGroupAdd = () => {
|
||||
<Paragraph
|
||||
type={"secondary"}
|
||||
style={{
|
||||
marginTop: "-2",
|
||||
marginTop: "-4px",
|
||||
fontWeight: "400",
|
||||
marginBottom: "0",
|
||||
marginBottom: "4px",
|
||||
}}
|
||||
>
|
||||
Add domain if you want to have a specific one
|
||||
@@ -556,18 +538,20 @@ const NameServerGroupAdd = () => {
|
||||
textAlign: "start",
|
||||
whiteSpace: "pre-line",
|
||||
fontSize: "18px",
|
||||
margin: "0px",
|
||||
fontWeight: "500",
|
||||
}}
|
||||
>
|
||||
Add Nameserver
|
||||
Add nameserver
|
||||
</Paragraph>
|
||||
<Paragraph
|
||||
type={"secondary"}
|
||||
style={{
|
||||
textAlign: "start",
|
||||
whiteSpace: "pre-line",
|
||||
paddingBottom: "0",
|
||||
marginTop: "-20px",
|
||||
fontSize: "14px",
|
||||
paddingBottom: "25px",
|
||||
marginBottom: "4px",
|
||||
}}
|
||||
>
|
||||
Use this nameserver to resolve domains in your network
|
||||
@@ -706,7 +690,7 @@ const NameServerGroupAdd = () => {
|
||||
</Row>
|
||||
</Header>
|
||||
</Col>
|
||||
<Col span={24} flex="auto">
|
||||
<Col span={24}>
|
||||
<Form.List
|
||||
name="nameservers"
|
||||
rules={[{ validator: formListValidator }]}
|
||||
@@ -714,7 +698,7 @@ const NameServerGroupAdd = () => {
|
||||
{renderNSList}
|
||||
</Form.List>
|
||||
</Col>
|
||||
<Col span={24} flex="auto">
|
||||
<Col span={24}>
|
||||
<Form.List name="domains">{renderDomains}</Form.List>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
@@ -730,9 +714,9 @@ const NameServerGroupAdd = () => {
|
||||
<Paragraph
|
||||
type={"secondary"}
|
||||
style={{
|
||||
marginTop: "-2",
|
||||
marginTop: "-4px",
|
||||
fontWeight: "400",
|
||||
marginBottom: "0",
|
||||
marginBottom: "4px",
|
||||
}}
|
||||
>
|
||||
Advertise this route to peers that belong to the following
|
||||
@@ -749,9 +733,12 @@ const NameServerGroupAdd = () => {
|
||||
tagRender={blueTagRender}
|
||||
onChange={handleChangeTags}
|
||||
dropdownRender={dropDownRender}
|
||||
optionFilterProp="serchValue"
|
||||
>
|
||||
{tagGroups.map((m) => (
|
||||
<Option key={m}>{optionRender(m)}</Option>
|
||||
{tagGroups.map((m, index) => (
|
||||
<Option key={index} value={m.id} serchValue={m.name}>
|
||||
{optionRender(m.name, m.id)}
|
||||
</Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
@@ -787,8 +774,9 @@ const NameServerGroupAdd = () => {
|
||||
marginBottom: "0",
|
||||
}}
|
||||
>
|
||||
Disable this server if you don't want it to apply
|
||||
immediately
|
||||
{formNSGroup.enabled
|
||||
? "Disable this server if you don't want it to apply immediately"
|
||||
: " Enable this server if you want it to apply immediately"}
|
||||
</Paragraph>
|
||||
</div>
|
||||
</div>
|
||||
@@ -796,7 +784,7 @@ const NameServerGroupAdd = () => {
|
||||
</Col>
|
||||
<Col
|
||||
span={24}
|
||||
style={{ marginTop: "10px", marginBottom: "20px" }}
|
||||
style={{ marginTop: "10px", marginBottom: "24px" }}
|
||||
>
|
||||
<Text type={"secondary"}>
|
||||
Learn more about
|
||||
@@ -830,7 +818,7 @@ const NameServerGroupAdd = () => {
|
||||
onClick={handleFormSubmit}
|
||||
disabled={savedNSGroup.loading}
|
||||
>
|
||||
Create Nameserver
|
||||
Create nameserver
|
||||
</Button>
|
||||
</Space>
|
||||
</Col>
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
+226
-167
@@ -1,185 +1,244 @@
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {Link, useLocation} from 'react-router-dom';
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Link, useLocation } from "react-router-dom";
|
||||
import logo from "../assets/logo.png";
|
||||
import {Avatar, Button, Col, Dropdown, Grid, Menu, Row} from 'antd'
|
||||
import {ItemType} from "antd/lib/menu/hooks/useItems";
|
||||
import {AvatarSize} from "antd/es/avatar/SizeContext";
|
||||
import {UserOutlined} from '@ant-design/icons';
|
||||
import {useOidc, useOidcIdToken, useOidcUser} from '@axa-fr/react-oidc';
|
||||
import {getConfig} from "../config";
|
||||
import {User} from "../store/user/types";
|
||||
import {useDispatch, useSelector} from "react-redux";
|
||||
import {RootState} from "typesafe-actions";
|
||||
import {actions as userActions} from "../store/user";
|
||||
import {useGetTokenSilently} from "../utils/token";
|
||||
import {actions as personalAccessTokenActions} from "../store/personal-access-token";
|
||||
import { Avatar, Button, Col, Dropdown, Grid, Menu, Row } from "antd";
|
||||
import { ItemType } from "antd/lib/menu/hooks/useItems";
|
||||
import { AvatarSize } from "antd/es/avatar/SizeContext";
|
||||
import { UserOutlined } from "@ant-design/icons";
|
||||
import { useOidc, useOidcIdToken, useOidcUser } from "@axa-fr/react-oidc";
|
||||
import { getConfig } from "../config";
|
||||
import { User } from "../store/user/types";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import { RootState } from "typesafe-actions";
|
||||
import { actions as userActions } from "../store/user";
|
||||
import { useGetTokenSilently } from "../utils/token";
|
||||
import { actions as personalAccessTokenActions } from "../store/personal-access-token";
|
||||
|
||||
const {useBreakpoint} = Grid;
|
||||
const { useBreakpoint } = Grid;
|
||||
|
||||
const Navbar = () => {
|
||||
let location = useLocation();
|
||||
const config = getConfig();
|
||||
const { logout } = useOidc();
|
||||
const {getTokenSilently} = useGetTokenSilently()
|
||||
const dispatch = useDispatch()
|
||||
let location = useLocation();
|
||||
const config = getConfig();
|
||||
const { logout } = useOidc();
|
||||
const { getTokenSilently } = useGetTokenSilently();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const {oidcUser} = useOidcUser();
|
||||
const {idTokenPayload} = useOidcIdToken()
|
||||
const user = oidcUser;
|
||||
const [currentUser, setCurrentUser] = useState({} as User)
|
||||
const { oidcUser } = useOidcUser();
|
||||
const { idTokenPayload } = useOidcIdToken();
|
||||
const user = oidcUser;
|
||||
const [currentUser, setCurrentUser] = useState({} as User);
|
||||
|
||||
const screens = useBreakpoint();
|
||||
const screens = useBreakpoint();
|
||||
|
||||
const [hideMenuUser, setHideMenuUser] = useState(false)
|
||||
const users = useSelector((state: RootState) => state.user.data)
|
||||
const [isRefreshingUserState, setIsRefreshingUserState] = useState(false)
|
||||
const [hideMenuUser, setHideMenuUser] = useState(false);
|
||||
const users = useSelector((state: RootState) => state.user.data);
|
||||
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 to="/activity">Activity</Link>), key: '/activity'},
|
||||
{label: (<Link to="/settings">Settings</Link>), key: '/settings'}
|
||||
] as ItemType[]
|
||||
const items = [
|
||||
{ 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[];
|
||||
|
||||
const userEmailKey = 'user-email'
|
||||
const userLogoutKey = 'user-logout'
|
||||
const userDividerKey = 'user-divider'
|
||||
const adminOnlyTabs = ["/setup-keys", "/acls", "/routes", "/dns", "/activity", "/settings"]
|
||||
const [menuItems, setMenuItems] = useState(items)
|
||||
const logoutWithRedirect = () =>
|
||||
logout("/", {client_id: config.clientId});
|
||||
const userEmailKey = "user-email";
|
||||
const userLogoutKey = "user-logout";
|
||||
const userDividerKey = "user-divider";
|
||||
const adminOnlyTabs = [
|
||||
"/setup-keys",
|
||||
"/acls",
|
||||
"/routes",
|
||||
"/dns",
|
||||
"/users",
|
||||
"/activity",
|
||||
"/settings",
|
||||
];
|
||||
const [menuItems, setMenuItems] = useState(items);
|
||||
const logoutWithRedirect = () => {
|
||||
let lRemove = [
|
||||
"peerFilter",
|
||||
"setupKeysFilter",
|
||||
"accessControlFilter",
|
||||
"routesFilter",
|
||||
"nameServerFilter",
|
||||
"userFilter",
|
||||
"serviceUserFilter",
|
||||
"activityFilter",
|
||||
];
|
||||
lRemove.forEach((element) => {
|
||||
localStorage.removeItem(element);
|
||||
});
|
||||
logout("/", { client_id: config.clientId });
|
||||
};
|
||||
|
||||
const openPersonalUserPage = () => {
|
||||
dispatch(userActions.setUser({
|
||||
id: currentUser.id,
|
||||
email: currentUser.email,
|
||||
role: currentUser.role,
|
||||
auto_groups: currentUser.auto_groups ? currentUser.auto_groups : [],
|
||||
name: currentUser.name,
|
||||
is_current: currentUser.is_current,
|
||||
is_service_user: currentUser.is_service_user,
|
||||
} as User));
|
||||
dispatch(userActions.setUserTabOpen("Users"));
|
||||
dispatch(userActions.setEditUserPopupVisible(true));
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
const fs = items.filter(m => showTab(m?.key?.toString(), currentUser) && m?.key !== userEmailKey && m?.key !== userLogoutKey && m?.key !== userDividerKey)
|
||||
if (screens.xs === true) {
|
||||
setHideMenuUser(false)
|
||||
fs.push({type: 'divider', key: userDividerKey})
|
||||
fs.push({
|
||||
label: (
|
||||
<Link to="#">{user?.name}</Link>
|
||||
),
|
||||
icon: createAvatar("small"),
|
||||
key: userEmailKey
|
||||
})
|
||||
fs.push({
|
||||
label: (<Button type="link" block onClick={logoutWithRedirect}>Logout</Button>),
|
||||
key: userLogoutKey
|
||||
})
|
||||
setMenuItems([...fs])
|
||||
return
|
||||
}
|
||||
setMenuItems([...fs])
|
||||
setHideMenuUser(true)
|
||||
}, [screens, currentUser])
|
||||
|
||||
useEffect(() => {
|
||||
if (users.length === 0 && !isRefreshingUserState &&
|
||||
window.location.pathname !== '/peers' &&
|
||||
window.location.pathname !== '/users') {
|
||||
setIsRefreshingUserState(true)
|
||||
dispatch(userActions.getUsers.request({getAccessTokenSilently: getTokenSilently, payload: null}))
|
||||
return
|
||||
}
|
||||
if (users.length === 0 && isRefreshingUserState) {
|
||||
return
|
||||
}
|
||||
let runUser = oidcUser
|
||||
if (!oidcUser) {
|
||||
runUser = idTokenPayload
|
||||
}
|
||||
setIsRefreshingUserState(false)
|
||||
if (runUser) {
|
||||
const found = users.find(u => u.is_current ? u.is_current : runUser.sub ? u.id == runUser.sub : false)
|
||||
if (found) {
|
||||
setCurrentUser(found)
|
||||
}
|
||||
}
|
||||
}, [users, oidcUser])
|
||||
|
||||
const showTab = (key: string | undefined, user: User | undefined) => {
|
||||
if (!user) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (user.role?.toLowerCase() === "admin") {
|
||||
return true
|
||||
}
|
||||
return !adminOnlyTabs.find(t => t === key)
|
||||
}
|
||||
|
||||
const menuUser = (
|
||||
<Menu
|
||||
items={[
|
||||
{
|
||||
label: (<Link to="/users" onClick={openPersonalUserPage}>{user?.email}</Link>),
|
||||
key: '0',
|
||||
},
|
||||
{
|
||||
label: (<Link to="/logout" onClick={logoutWithRedirect}>Logout</Link>),
|
||||
key: '1',
|
||||
}
|
||||
]}
|
||||
/>
|
||||
const openPersonalUserPage = () => {
|
||||
dispatch(
|
||||
userActions.setUser({
|
||||
id: currentUser.id,
|
||||
email: currentUser.email,
|
||||
role: currentUser.role,
|
||||
auto_groups: currentUser.auto_groups ? currentUser.auto_groups : [],
|
||||
name: currentUser.name,
|
||||
is_current: currentUser.is_current,
|
||||
is_service_user: currentUser.is_service_user,
|
||||
} as User)
|
||||
);
|
||||
dispatch(userActions.setUserTabOpen("Users"));
|
||||
dispatch(userActions.setEditUserPopupVisible(true));
|
||||
};
|
||||
|
||||
const createAvatar = (size: AvatarSize) => {
|
||||
return user?.picture ? (
|
||||
<Avatar size={size} src={user?.picture} icon={<UserOutlined/>}/>
|
||||
) : (
|
||||
<Avatar size={size}>{(user?.name || '').slice(0, 1).toUpperCase()}</Avatar>
|
||||
)
|
||||
useEffect(() => {
|
||||
const fs = items.filter(
|
||||
(m) =>
|
||||
showTab(m?.key?.toString(), currentUser) &&
|
||||
m?.key !== userEmailKey &&
|
||||
m?.key !== userLogoutKey &&
|
||||
m?.key !== userDividerKey
|
||||
);
|
||||
if (screens.xs === true) {
|
||||
setHideMenuUser(false);
|
||||
fs.push({ type: "divider", key: userDividerKey });
|
||||
fs.push({
|
||||
label: <Link to="#">{user?.name}</Link>,
|
||||
icon: createAvatar("small"),
|
||||
key: userEmailKey,
|
||||
});
|
||||
fs.push({
|
||||
label: (
|
||||
<Button type="link" block onClick={logoutWithRedirect}>
|
||||
Logout
|
||||
</Button>
|
||||
),
|
||||
key: userLogoutKey,
|
||||
});
|
||||
setMenuItems([...fs]);
|
||||
return;
|
||||
}
|
||||
setMenuItems([...fs]);
|
||||
setHideMenuUser(true);
|
||||
}, [screens, currentUser]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
users.length === 0 &&
|
||||
!isRefreshingUserState &&
|
||||
window.location.pathname !== "/peers" &&
|
||||
window.location.pathname !== "/users"
|
||||
) {
|
||||
setIsRefreshingUserState(true);
|
||||
dispatch(
|
||||
userActions.getUsers.request({
|
||||
getAccessTokenSilently: getTokenSilently,
|
||||
payload: null,
|
||||
})
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (users.length === 0 && isRefreshingUserState) {
|
||||
return;
|
||||
}
|
||||
let runUser = oidcUser;
|
||||
if (!oidcUser) {
|
||||
runUser = idTokenPayload;
|
||||
}
|
||||
setIsRefreshingUserState(false);
|
||||
if (runUser) {
|
||||
const found = users.find((u) =>
|
||||
u.is_current ? u.is_current : runUser.sub ? u.id == runUser.sub : false
|
||||
);
|
||||
if (found) {
|
||||
setCurrentUser(found);
|
||||
}
|
||||
}
|
||||
}, [users, oidcUser]);
|
||||
|
||||
const showTab = (key: string | undefined, user: User | undefined) => {
|
||||
if (!user) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Row justify="space-evenly" align="middle">
|
||||
<Col flex="0 1 60px">
|
||||
<Link id="logo" to="/">
|
||||
<img
|
||||
alt="logo"
|
||||
style={{width: "55px"}}
|
||||
src={logo}
|
||||
/>
|
||||
</Link>
|
||||
</Col>
|
||||
<Col flex="1 1 auto">
|
||||
<div>
|
||||
<Menu mode="horizontal" selectable={true} selectedKeys={[location.pathname]}
|
||||
onSelect={(e) => {
|
||||
dispatch(userActions.setUser(null as unknown as User));
|
||||
dispatch(personalAccessTokenActions.resetPersonalAccessTokens(null))
|
||||
}}
|
||||
defaultSelectedKeys={[location.pathname]} items={menuItems}/>
|
||||
</div>
|
||||
</Col>
|
||||
{hideMenuUser &&
|
||||
<Col>
|
||||
<Dropdown overlay={menuUser} placement="bottomRight" trigger={['click']}>
|
||||
{createAvatar("large")}
|
||||
</Dropdown>
|
||||
</Col>
|
||||
}
|
||||
</Row>
|
||||
</>
|
||||
if (user.role?.toLowerCase() === "admin") {
|
||||
return true;
|
||||
}
|
||||
return !adminOnlyTabs.find((t) => t === key);
|
||||
};
|
||||
|
||||
const menuUser = (
|
||||
<Menu
|
||||
items={[
|
||||
{
|
||||
label: (
|
||||
<Link to="/users" onClick={openPersonalUserPage}>
|
||||
{user?.email}
|
||||
</Link>
|
||||
),
|
||||
key: "0",
|
||||
},
|
||||
{
|
||||
label: (
|
||||
<Link to="/logout" onClick={logoutWithRedirect}>
|
||||
Logout
|
||||
</Link>
|
||||
),
|
||||
key: "1",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
|
||||
const createAvatar = (size: AvatarSize) => {
|
||||
return user?.picture ? (
|
||||
<Avatar size={size} src={user?.picture} icon={<UserOutlined />} />
|
||||
) : (
|
||||
<Avatar size={size}>
|
||||
{(user?.name || "").slice(0, 1).toUpperCase()}
|
||||
</Avatar>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Row justify="space-evenly" align="middle">
|
||||
<Col flex="0 1 60px">
|
||||
<Link id="logo" to="/">
|
||||
<img alt="logo" style={{ width: "55px" }} src={logo} />
|
||||
</Link>
|
||||
</Col>
|
||||
<Col flex="1 1 auto">
|
||||
<div>
|
||||
<Menu
|
||||
mode="horizontal"
|
||||
selectable={true}
|
||||
selectedKeys={[location.pathname]}
|
||||
onSelect={(e) => {
|
||||
dispatch(userActions.setUser(null as unknown as User));
|
||||
dispatch(
|
||||
personalAccessTokenActions.resetPersonalAccessTokens(null)
|
||||
);
|
||||
}}
|
||||
defaultSelectedKeys={[location.pathname]}
|
||||
items={menuItems}
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
{hideMenuUser && (
|
||||
<Col>
|
||||
<Dropdown
|
||||
overlay={menuUser}
|
||||
placement="bottomRight"
|
||||
trigger={["click"]}
|
||||
>
|
||||
{createAvatar("large")}
|
||||
</Dropdown>
|
||||
</Col>
|
||||
)}
|
||||
</Row>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Navbar;
|
||||
Loaded 20 of 60 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user