diff --git a/.cargo/config.toml b/.cargo/config.toml deleted file mode 100644 index df4976b85..000000000 --- a/.cargo/config.toml +++ /dev/null @@ -1,3 +0,0 @@ -# Without running core on windows will result in stack overflow -[target.x86_64-pc-windows-msvc] -rustflags = ["-C", "link-args=/STACK:8388608"] diff --git a/.github/workflows/lint-web.yml b/.github/workflows/lint-web.yml index 12a74e2dd..b58b73c4c 100644 --- a/.github/workflows/lint-web.yml +++ b/.github/workflows/lint-web.yml @@ -33,6 +33,9 @@ jobs: run: | npm i -g npm pnpm pnpm i --frozen-lockfile + - name: Build + working-directory: ./web + run: pnpm build - name: Lint working-directory: ./web run: pnpm run lint diff --git a/.gitmodules b/.gitmodules index a5df1c851..561704131 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,4 +3,4 @@ url = ../proto.git [submodule "web/src/shared/defguard-ui"] path = web/src/shared/defguard-ui - url = ../ui.git + url = git@github.com:DefGuard/ui.git diff --git a/Dockerfile b/Dockerfile index 8939f2b52..100ea408a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,10 @@ FROM public.ecr.aws/docker/library/node:24 AS web WORKDIR /app -COPY web/package.json web/pnpm-lock.yaml web/.npmrc ./ +COPY web/package.json web/pnpm-lock.yaml ./ RUN npm i -g pnpm RUN pnpm install --ignore-scripts --frozen-lockfile COPY web/ . -RUN pnpm run generate-translation-types RUN pnpm build FROM public.ecr.aws/docker/library/rust:1 AS chef @@ -31,7 +30,6 @@ RUN cargo chef cook --release --recipe-path recipe.json # build project COPY --from=web /app/dist ./web/dist -COPY web/src/shared/images/svg ./web/src/shared/images/svg RUN apt-get update && apt-get -y install protobuf-compiler libprotobuf-dev COPY Cargo.toml Cargo.lock ./ # for vergen diff --git a/e2e/config.ts b/e2e/config.ts index a9dfbe521..bdca41622 100644 --- a/e2e/config.ts +++ b/e2e/config.ts @@ -1,3 +1,4 @@ +import { table } from 'console'; import { User } from './types'; import { mergeObjects } from './utils/utils'; @@ -29,6 +30,25 @@ export const testsConfig: TestsConfig = mergeObjects(envConfig, defaultConfig); export const routes = { base: testsConfig.BASE_URL, me: '/me', + profile: '/user/', + tab: { + details: '?tab=details', + devices: '?tab=devices', + authentication_keys: '?tab=auth-keys', + api_tokens: '?tab=api-tokens', + }, + firewall: { + rules: '/acl/rules', + aliases: '/acl/aliases', + }, + locations: '/locations', + network_devices: '/network-devices', + openid_apps: '/openid', + webhooks: '/webhooks', + identity: { + users: '/users', + groups: '/groups', + }, consent: '/consent', addDevice: '/add-device', auth: { @@ -37,6 +57,14 @@ export const routes = { recovery: '/auth/mfa/recovery', email: '/auth/mfa/email', }, + settings: { + base: '/settings', + smtp: '/settings/smtp', + openid: '/settings/openid', + tab: { + openid: '/settings?tab=openid', + }, + }, admin: { wizard: '/admin/wizard', users: '/admin/users', diff --git a/e2e/tests/acl.spec.ts b/e2e/tests/acl.spec.ts new file mode 100644 index 000000000..b0df9a21d --- /dev/null +++ b/e2e/tests/acl.spec.ts @@ -0,0 +1,32 @@ +import { expect, test } from '@playwright/test'; + +import { defaultUserAdmin, routes } from '../config'; +import { Protocols } from '../types'; +import { createAlias } from '../utils/acl'; +import { loginBasic } from '../utils/controllers/login'; +import { dockerRestart } from '../utils/docker'; + +test.describe('Test aliases', () => { + // let testUser: User; + + test.beforeEach(() => { + dockerRestart(); + // testUser = { ...testUserTemplate, username: 'test' }; + }); + + test('Create alias and check content', async ({ page, browser }) => { + const name = 'TestAlias'; + const addresses = ['1.2.3.4/24', '10.10.10.10/20', '1.2.4.2']; + const ports = ['80', '443']; + const protocols = [Protocols.UDP, Protocols.ICMP]; + await createAlias(browser, name, addresses, ports, protocols); + await loginBasic(page, defaultUserAdmin); + await page.goto(routes.base + routes.firewall.aliases); + const aliasRow = await page.locator('.virtual-row').filter({ hasText: name }); + await expect(aliasRow).toBeVisible(); + await expect(aliasRow).toContainText(addresses.join(', ')); + await expect(aliasRow).toContainText(ports.join(', ')); + await expect(aliasRow).toContainText(Protocols.UDP); + await expect(aliasRow).toContainText(Protocols.ICMP); + }); +}); diff --git a/e2e/tests/auth.spec.ts b/e2e/tests/auth.spec.ts index 59026a199..2405d34e8 100644 --- a/e2e/tests/auth.spec.ts +++ b/e2e/tests/auth.spec.ts @@ -3,16 +3,16 @@ import { TOTP } from 'totp-generator'; import { defaultUserAdmin, routes, testUserTemplate } from '../config'; import { User } from '../types'; -import { acceptRecovery } from '../utils/controllers/acceptRecovery'; import { createUser } from '../utils/controllers/createUser'; import { loginBasic, loginRecoveryCodes, loginTOTP } from '../utils/controllers/login'; import { logout } from '../utils/controllers/logout'; import { enableEmailMFA } from '../utils/controllers/mfa/enableEmail'; +import { enableSecurityKey } from '../utils/controllers/mfa/enableSecurityKey'; import { enableTOTP } from '../utils/controllers/mfa/enableTOTP'; -import { changePassword, changePasswordByAdmin } from '../utils/controllers/profile'; import { disableUser } from '../utils/controllers/toggleUserState'; import { dockerRestart } from '../utils/docker'; import { waitForBase } from '../utils/waitForBase'; +import { waitForPromise } from '../utils/waitForPromise'; import { waitForRoute } from '../utils/waitForRoute'; test.describe('Test user authentication', () => { @@ -25,36 +25,39 @@ test.describe('Test user authentication', () => { test('Basic auth with default admin', async ({ page }) => { await waitForBase(page); + const responsePromise = page.waitForResponse('**/auth'); await loginBasic(page, defaultUserAdmin); - await waitForRoute(page, routes.admin.wizard); - expect(page.url()).toBe(routes.base + routes.admin.wizard); + const response = await responsePromise; + expect(response.ok()).toBeTruthy(); }); test('Create user and login as him', async ({ page, browser }) => { await waitForBase(page); await createUser(browser, testUser); + const responsePromise = page.waitForResponse('**/auth'); await loginBasic(page, testUser); - await waitForRoute(page, routes.me); - expect(page.url()).toBe(routes.base + routes.me); + const response = await responsePromise; + expect(response.ok()).toBeTruthy(); }); - test('Login with admin user TOTP', async ({ page, browser }) => { + test('Login with admin user via TOTP', async ({ page, browser }) => { await waitForBase(page); await loginBasic(page, defaultUserAdmin); const { secret } = await enableTOTP(browser, defaultUserAdmin); - await acceptRecovery(page); + const responsePromise = page.waitForResponse('**/auth'); await loginTOTP(page, defaultUserAdmin, secret); - await page.waitForLoadState('networkidle'); - await waitForRoute(page, routes.admin.wizard); + const response = await responsePromise; + expect(response.ok()).toBeTruthy(); }); - test('Login with user TOTP', async ({ page, browser }) => { + test('Login with user via TOTP', async ({ page, browser }) => { await waitForBase(page); await createUser(browser, testUser); const { secret } = await enableTOTP(browser, testUser); + const responsePromise = page.waitForResponse('**/auth'); await loginTOTP(page, testUser, secret); - await waitForRoute(page, routes.me); - expect(page.url()).toBe(routes.base + routes.me); + const response = await responsePromise; + expect(response.ok()).toBeTruthy(); }); test('Recovery code login', async ({ page, browser }) => { @@ -64,24 +67,28 @@ test.describe('Test user authentication', () => { expect(recoveryCodes).toBeDefined(); if (!recoveryCodes) return; expect(recoveryCodes?.length > 0).toBeTruthy(); + const responsePromise = page.waitForResponse('**/auth'); await loginRecoveryCodes(page, testUser, recoveryCodes[0]); - await waitForRoute(page, routes.me); - expect(page.url()).toBe(routes.base + routes.me); + const response = await responsePromise; + expect(response.ok()).toBeTruthy(); }); test('Login with Email TOTP', async ({ page, browser }) => { await waitForBase(page); await createUser(browser, testUser); const { secret } = await enableEmailMFA(browser, testUser); + await loginBasic(page, testUser); await page.goto(routes.base + routes.auth.email); const { otp: code } = TOTP.generate(secret, { digits: 6, period: 60, }); + const responsePromise = page.waitForResponse('**/verify'); await page.getByTestId('field-code').fill(code); - await page.locator('button[type="submit"]').click(); - await waitForRoute(page, routes.me); + await page.locator('[type="submit"]').click(); + const response = await responsePromise; + expect(response.ok()).toBeTruthy(); }); test('Login as disabled user', async ({ page, browser }) => { @@ -90,30 +97,91 @@ test.describe('Test user authentication', () => { await disableUser(browser, testUser); await page.goto(routes.base); await waitForRoute(page, routes.auth.login); - await page.getByTestId('login-form-username').fill(testUser.username); - await page.getByTestId('login-form-password').fill(testUser.password); + await page.getByTestId('field-username').fill(testUser.username); + await page.getByTestId('field-password').fill(testUser.password); + await page.getByTestId('sign-in').click(); const responsePromise = page.waitForResponse('**/auth'); - await page.getByTestId('login-form-submit').click(); const response = await responsePromise; - expect(response.status()).toBe(401); - expect(page.url()).toBe(routes.base + routes.auth.login); + expect(response.ok()).toBeFalsy(); + }); + + test('Logout when enabled', async ({ page, browser }) => { + await waitForBase(page); + await createUser(browser, testUser); + await loginBasic(page, testUser); + const responsePromise = page.waitForResponse('**/logout'); + await page.getByTestId('avatar-icon').click(); + await page.getByTestId('logout').click(); + const response = await responsePromise; + expect(response.status()).toBe(200); + await waitForPromise(1000); + await expect(page.url()).toBe(routes.base + routes.auth.login); }); test('Logout when disabled', async ({ page, browser }) => { await waitForBase(page); await createUser(browser, testUser); await loginBasic(page, testUser); - await waitForRoute(page, routes.me); - expect(page.url()).toBe(routes.base + routes.me); await disableUser(browser, testUser); - const responsePromise = page.waitForResponse((resp) => resp.status() === 401); - await page.locator('a[href="/me"]').click(); - await responsePromise; + const responsePromise = page.waitForResponse('**/logout'); + await page.getByTestId('avatar-icon').click(); + await page.getByTestId('logout').click(); + const response = await responsePromise; + expect(response.status()).toBe(401); + }); + + test('Create user and log in with security key', async ({ page, browser, context }) => { + await waitForBase(page); + await createUser(browser, testUser); + const { credentialId, rpId, privateKey, userHandle } = await enableSecurityKey( + browser, + testUser, + 'key_name', + ); + await page.goto(routes.base); + await waitForRoute(page, routes.auth.login); + await page.getByTestId('field-username').fill(testUser.username); + await page.getByTestId('field-password').fill(testUser.password); + await page.getByTestId('sign-in').click(); + await page.waitForTimeout(1000); + + const authenticator = await context.newCDPSession(page); + await authenticator.send('WebAuthn.enable'); + const { authenticatorId: loginAuthenticatorId } = await authenticator.send( + 'WebAuthn.addVirtualAuthenticator', + { + options: { + protocol: 'ctap2', + transport: 'usb', + hasResidentKey: true, + hasUserVerification: true, + isUserVerified: true, + }, + }, + ); + + await authenticator.send('WebAuthn.addCredential', { + authenticatorId: loginAuthenticatorId, + credential: { + credentialId, + isResidentCredential: true, + rpId, + privateKey, + userHandle, + signCount: 1, + }, + }); + const responsePromise = page.waitForResponse('**/auth'); + await page.getByTestId('login-with-passkey').click(); + await page.waitForTimeout(2000); + const response = await responsePromise; + expect(response.ok()).toBeTruthy(); }); }); test.describe('Test password change', () => { let testUser: User; + const newPassword = 'MyNewPassword1!@#$'; test.beforeEach(() => { dockerRestart(); @@ -124,24 +192,86 @@ test.describe('Test password change', () => { await waitForBase(page); await createUser(browser, testUser); await loginBasic(page, testUser); - testUser.password = await changePassword(page, testUser.password); + await page.getByTestId('change-password').click(); + await page.getByTestId('field-current').fill(testUser.password); + await page.getByTestId('field-password').fill(newPassword); + await page.getByTestId('field-repeat').fill(newPassword); + await page.getByTestId('submit-password-change').click(); await logout(page); + testUser.password = newPassword; + const responsePromise = page.waitForResponse('**/auth'); await loginBasic(page, testUser); - await waitForRoute(page, routes.me); - expect(page.url()).toBe(routes.base + routes.me); + const response = await responsePromise; + expect(response.ok()).toBeTruthy(); }); test('Change user password by admin', async ({ page, browser }) => { await waitForBase(page); await createUser(browser, testUser); await loginBasic(page, defaultUserAdmin); - const profileURL = routes.base + routes.admin.users + '/' + testUser.username; - await page.goto(profileURL); - await waitForRoute(page, profileURL); - testUser.password = await changePasswordByAdmin(page); + await page.goto(routes.base + routes.identity.users); + const userRow = await page + .locator('.virtual-row') + .filter({ hasText: testUser.username }); + await userRow.locator('.icon-button').click(); + await page.getByTestId('change-password').click(); + await page.getByTestId('field-password').fill(newPassword); + await page.getByTestId('submit-password-change').click(); await logout(page); + testUser.password = newPassword; + const responsePromise = await page.waitForResponse('**/auth'); await loginBasic(page, testUser); - await waitForRoute(page, routes.me); - expect(page.url()).toBe(routes.base + routes.me); + const response = await responsePromise; + expect(response.ok()).toBeTruthy(); + }); +}); + +test.describe('API tokens management', () => { + let testUser: User; + const token_name = 'test token name'; + test.beforeEach(() => { + dockerRestart(); + testUser = { ...testUserTemplate, username: 'test' }; + }); + test('Add API token as default admin', async ({ page }) => { + await waitForBase(page); + await loginBasic(page, defaultUserAdmin); + await page.goto( + routes.base + routes.profile + defaultUserAdmin.username + routes.tab.api_tokens, + ); + await page.getByTestId('add-token').click(); + await page.getByTestId('field-name').fill(token_name); + await page.getByTestId('submit').click(); + const api_token = await page.getByTestId('copy-field').textContent(); + await page.getByTestId('close').click(); + + const row = await page + .locator('.table-row-container') + .filter({ hasText: token_name }); + await row.locator('.icon-button').click(); + await page.getByTestId('delete').click(); + await expect(row).not.toBeVisible(); + expect(api_token).toBeDefined(); + }); + test('Add API token as new user with admin privileges', async ({ page, browser }) => { + await waitForBase(page); + await createUser(browser, testUser, ['admin']); + await loginBasic(page, testUser); + await page.goto( + routes.base + routes.profile + testUser.username + routes.tab.api_tokens, + ); + await page.getByTestId('add-token').click(); + await page.getByTestId('field-name').fill(token_name); + await page.getByTestId('submit').click(); + const api_token = await page.getByTestId('copy-field').textContent(); + await page.getByTestId('close').click(); + + const row = await page + .locator('.table-row-container') + .filter({ hasText: token_name }); + await row.locator('.icon-button').click(); + await page.getByTestId('delete').click(); + await expect(row).not.toBeVisible(); + expect(api_token).toBeDefined(); }); }); diff --git a/e2e/tests/authenticationKeys.spec.ts b/e2e/tests/authenticationKeys.spec.ts index a9477f011..e4fa8829f 100644 --- a/e2e/tests/authenticationKeys.spec.ts +++ b/e2e/tests/authenticationKeys.spec.ts @@ -6,13 +6,12 @@ import { apiCreateUser, apiGetUserAuthKeys } from '../utils/api/users'; import { loginBasic } from '../utils/controllers/login'; import { dockerRestart } from '../utils/docker'; import { waitForBase } from '../utils/waitForBase'; -import { waitForPromise } from '../utils/waitForPromise'; import { waitForRoute } from '../utils/waitForRoute'; test.describe('Authentication keys', () => { const testUser: User = { ...testUserTemplate, username: 'test' }; const testSshKey = `ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCG+jXb1VHl8Xvxwz1fFFteFX6+4fdOWgWxEA3++p64/s6iHJ3p6jUBc+4cVJg+p7/YKlYGJfxLT3/nrDPTuJ7l/RzbFrqM424KuA+/ZXCa6pDRmB7K+kjSi1I28HokQEL972yhbrkmfqjfPyPHk8RQX3Uw2f6WsQBWvBPMA9pveN6bh6scC5z9VKIoTKHK76RJxkrN7x59EsF1NyJo6jQDOhiBrVS/z3nUhWm05J7AtJn/r0SCZi0K8bXR2zkArr+hodY2WFwYCvsEp+VFTL+O/16enCK8MMz8xbXYVDuiXo7U/cC7s1dmGWsmkmjTVSs2x0KirmgnVwrbdmi0BtEpK5hLLCRFze33kb1VkgFx2kKbEsMJw9qqC7X9t5xTFqR/WVAOBuwCMyUPxF9rqWx0KGy4Y5aqkNwgviOtAKLbNhHx2ToN2/UMaB8KYY+nlb9Y1aOWTebx3T84MrLwE1Vfbd5qJq99ZWFrcvN6I2xQCyHa1zGgMraeKCwqbu39H7BMKTNeTvfXCR/SoVhFpteT/kiX2Hmufufq4feNnlfcFnLVvFRzEQFxDi7+hMrm87dMci58HQu9QIij80qjZtQaXqgjuyxUCx0hCBM4oFUE3rTfJYX7HzTB83Ugqumu3qv8s0aToXQLXFGN6Hkw+IOBupWZFIfy33JAd5az0+r8rQ== `; - const testPgpKey = `-----BEGIN PGP PUBLIC KEY BLOCK----- + const testGPGKey = `-----BEGIN PGP PUBLIC KEY BLOCK----- Version: Keybase OpenPGP v1.0.0 xo0EZdcYugEEAO7b3DjbnGMVLHuAaYNuBnQ9ilfzWqidqLF3P+y7bpyQkFA8Rx3M @@ -44,48 +43,44 @@ ajY8ozCCcZ+QDGRFVB7sVl/39qsQDQgWTGCdwqwxEZeFskDhCfvtk3j7lW9NinaM QW+7CejaY/Essu7DN6HwqwXbipny63b8ct1UXjG02S+Q =VWAR -----END PGP PUBLIC KEY BLOCK-----`; + const key_name = 'test key name'; test.beforeEach(async ({ page }) => { dockerRestart(); await waitForBase(page); await loginBasic(page, defaultUserAdmin); await apiCreateUser(page, testUser); - const url = routes.base + routes.admin.users + '/' + testUser.username; + const url = routes.base + routes.profile + testUser.username + routes.tab.details; await page.goto(url); await waitForRoute(page, url); }); test('Add authentication key (SSH)', async ({ page }) => { - await page.getByTestId('add-authentication-key-button').click(); - await page.locator('#add-authentication-key-modal').waitFor({ - state: 'visible', - }); - const modal = page.locator('#add-authentication-key-modal'); - await modal.getByRole('button', { name: 'SSH', exact: true }).click(); - const form = modal.locator('form'); - await form.getByTestId('field-title').fill('test ssh'); - await form.getByTestId('field-keyValue').fill(testSshKey); + await page.goto( + routes.base + routes.profile + testUser.username + routes.tab.authentication_keys, + ); + await page.getByTestId('add-new-key').click(); + await page.locator('#add-auth-key-modal').waitFor({ state: 'visible' }); + await page.locator('.box-track').click(); + const options = await page.locator('.select-floating'); + await options.locator('.select-option').filter({ hasText: 'SSH' }).click(); + if (await options.isVisible()) { + await options.click(); + } + await page.getByTestId('field-name').fill(key_name); + await page.getByTestId('field-key').fill(testSshKey); + await page.getByTestId('add-key').click(); const responsePromise = page.waitForResponse('**/auth_key'); - await modal.locator('button[type="submit"]').click(); const response = await responsePromise; expect(response.status()).toBe(201); const profileKeys = await apiGetUserAuthKeys(page, testUser.username); expect(profileKeys.length).toBe(1); - expect(profileKeys[0].name).toBe('test ssh'); + expect(profileKeys[0].name).toBe(key_name); expect(profileKeys[0].key_type).toBe(AuthenticationKeyType.SSH); - // check if it can be deleted + const row = page.locator('.table-row-container').filter({ hasText: key_name }); + await row.locator('.icon-button').click(); + await page.getByTestId('delete-key').click(); const deletePromise = page.waitForResponse('**/auth_key'); - const card = page.locator('.authentication-key-item'); - card.waitFor({ - state: 'visible', - }); - await waitForPromise(1000); - await card.locator('.edit-button').click(); - await page.getByRole('button', { name: 'Delete Key', exact: true }).click(); - await page - .locator('.modal-content') - .getByRole('button', { name: 'Delete', exact: true }) - .click(); const deleteResponse = await deletePromise; expect(deleteResponse.status()).toBe(200); const afterDeleteKeys = await apiGetUserAuthKeys(page, testUser.username); @@ -93,34 +88,31 @@ QW+7CejaY/Essu7DN6HwqwXbipny63b8ct1UXjG02S+Q }); test('Add authentication key (GPG)', async ({ page }) => { - await page.getByTestId('add-authentication-key-button').click(); - await page.locator('#add-authentication-key-modal').waitFor({ - state: 'visible', - }); - const modal = page.locator('#add-authentication-key-modal'); - await modal.getByRole('button', { name: 'GPG', exact: true }).click(); + await page.goto( + routes.base + routes.profile + testUser.username + routes.tab.authentication_keys, + ); + await page.getByTestId('add-new-key').click(); + await page.locator('#add-auth-key-modal').waitFor({ state: 'visible' }); + await page.locator('.box-track').click(); + const options = await page.locator('.select-floating'); + await options.locator('.select-option').filter({ hasText: 'GPG' }).click(); + if (await options.isVisible()) { + await options.click(); + } + await page.getByTestId('field-name').fill(key_name); + await page.getByTestId('field-key').fill(testGPGKey); + await page.getByTestId('add-key').click(); const responsePromise = page.waitForResponse('**/auth_key'); - const form = modal.locator('form'); - await form.getByTestId('field-title').fill('test pgp'); - await form.getByTestId('field-keyValue').fill(testPgpKey); - await modal.locator('button[type="submit"]').click(); const response = await responsePromise; expect(response.status()).toBe(201); const profileKeys = await apiGetUserAuthKeys(page, testUser.username); expect(profileKeys.length).toBe(1); - expect(profileKeys[0].name).toBe('test pgp'); + expect(profileKeys[0].name).toBe(key_name); expect(profileKeys[0].key_type).toBe(AuthenticationKeyType.GPG); - // check if it can be deleted + const row = page.locator('.table-row-container').filter({ hasText: key_name }); + await row.locator('.icon-button').click(); + await page.getByTestId('delete-key').click(); const deletePromise = page.waitForResponse('**/auth_key'); - const card = page.locator('.authentication-key-item'); - await waitForPromise(200); - await card.locator('.edit-button').click(); - await waitForPromise(200); - await page.getByRole('button', { name: 'Delete Key', exact: true }).click(); - await page - .locator('.modal-content') - .getByRole('button', { name: 'Delete', exact: true }) - .click(); const deleteResponse = await deletePromise; expect(deleteResponse.status()).toBe(200); const afterDeleteKeys = await apiGetUserAuthKeys(page, testUser.username); diff --git a/e2e/tests/device.spec.ts b/e2e/tests/device.spec.ts new file mode 100644 index 000000000..41619d5d5 --- /dev/null +++ b/e2e/tests/device.spec.ts @@ -0,0 +1,71 @@ +import { expect, test } from '@playwright/test'; + +import { defaultUserAdmin, routes, testUserTemplate } from '../config'; +import { NetworkForm, User } from '../types'; +import { apiCreateUser, apiGetUserProfile } from '../utils/api/users'; +import { loginBasic } from '../utils/controllers/login'; +import { createDevice } from '../utils/controllers/vpn/createDevice'; +import { createRegularLocation } from '../utils/controllers/vpn/createNetwork'; +import { dockerRestart } from '../utils/docker'; +import { waitForBase } from '../utils/waitForBase'; + +const testKeys = { + private: '4K1BwtDCd0XUwq6WThkrQ4/DQ4vIpyEki5aIokqx21c=', + public: 'n4/imOLXU35yYkWNHvZk2RZfI3l1NwVd4bJsuhTuRzw=', +}; + +const testNetwork: NetworkForm = { + name: 'test devices', + address: '10.10.10.1/24', + endpoint: '127.0.0.1', + allowed_ips: ['1.4.2.5'], + port: '5055', +}; + +test.describe('Add user device', () => { + const testUser: User = { ...testUserTemplate, username: 'test' }; + const device_name = 'test'; + test.beforeEach(async ({ browser }) => { + dockerRestart(); + const context = await browser.newContext(); + const page = await context.newPage(); + // wait for fronted + await waitForBase(page); + // prepare test network + await createRegularLocation(browser, testNetwork); + // make test user + await loginBasic(page, defaultUserAdmin); + await apiCreateUser(page, testUser); + await context.close(); + }); + + test('Add test user device with automatically generated public key', async ({ + page, + browser, + }) => { + await waitForBase(page); + await createDevice(browser, testUser, { + name: device_name, + }); + await loginBasic(page, testUser); + await page.goto( + routes.base + routes.profile + testUser.username + routes.tab.devices, + ); + const testUserProfile = await apiGetUserProfile(page, testUser.username); + expect(testUserProfile.devices.length).toBe(1); + expect(testUserProfile.devices[0].name).toBe(device_name); + }); + + test('Add test user device with provided public key', async ({ page, browser }) => { + await waitForBase(page); + await createDevice(browser, testUser, { + name: 'test', + pubKey: testKeys.public, + }); + await loginBasic(page, testUser); + const testUserProfile = await apiGetUserProfile(page, testUser.username); + expect(testUserProfile.devices.length).toBe(1); + expect(testUserProfile.devices[0].name).toBe(device_name); + expect(testUserProfile.devices[0].wireguard_pubkey).toBe(testKeys.public); + }); +}); diff --git a/e2e/tests/enrollment.spec.ts b/e2e/tests/enrollment.spec.ts index 6d3ea8082..1a80d5098 100644 --- a/e2e/tests/enrollment.spec.ts +++ b/e2e/tests/enrollment.spec.ts @@ -1,32 +1,24 @@ import { expect, test } from '@playwright/test'; -import { testsConfig, testUserTemplate } from '../config'; +import { testUserTemplate } from '../config'; import { NetworkForm, User } from '../types'; -import { apiGetUserProfile } from '../utils/api/users'; -import { - createDevice, - createUserEnrollment, - password, - selectEnrollment, - setPassword, - setToken, - validateData, -} from '../utils/controllers/enrollment'; +import { apiEnrollmentActivateUser, apiEnrollmentStart } from '../utils/api/enrollment'; +import { createUserEnrollment, password } from '../utils/controllers/enrollment'; import { loginBasic } from '../utils/controllers/login'; -import { disableUser, enableUser } from '../utils/controllers/toggleUserState'; -import { createNetwork } from '../utils/controllers/vpn/createNetwork'; +import { disableUser } from '../utils/controllers/toggleUserState'; +import { createRegularLocation } from '../utils/controllers/vpn/createNetwork'; import { dockerRestart } from '../utils/docker'; import { waitForBase } from '../utils/waitForBase'; -import { waitForPromise } from '../utils/waitForPromise'; const testNetwork: NetworkForm = { name: 'test network', address: '10.10.10.1/24', endpoint: '127.0.0.1', + allowed_ips: ['127.1.5.1'], port: '5055', }; -test.describe('Create user with enrollment enabled', () => { +test.describe('Create user and enroll him', () => { let token: string; const user: User = { ...testUserTemplate, username: 'test' }; @@ -34,72 +26,34 @@ test.describe('Create user with enrollment enabled', () => { dockerRestart(); const response = await createUserEnrollment(browser, user); token = response.token; - await createNetwork(browser, testNetwork); + await createRegularLocation(browser, testNetwork); }); - test('Try to complete enrollment with disabled user', async ({ page, browser }) => { + test('Complete user enrollment via API', async ({ request, page }) => { expect(token).toBeDefined(); - await waitForBase(page); - await disableUser(browser, user); - await page.goto(testsConfig.ENROLLMENT_URL); - await waitForPromise(2000); - // Test if we can send the token - await selectEnrollment(page); - const startResponse = page.waitForResponse('**/start'); - await setToken(token, page); - expect((await startResponse).status()).toBe(403); - // Check if we are still on the token page - expect(page.url()).toBe(`${testsConfig.ENROLLMENT_URL}/token`); - - // Test other enrollment steps - await enableUser(browser, user); - await page.reload(); - await setToken(token, page); - // Welcome page - await page.getByTestId('enrollment-next').click(); - // Data validation - await validateData(user, page); - await page.getByTestId('enrollment-next').click(); - await disableUser(browser, user); - // Set password - await setPassword(page); - // VPN - await page.getByTestId('enrollment-next').click(); + await apiEnrollmentStart(request, token); + await apiEnrollmentActivateUser(request, password, '+48123456789'); - // Test if we can create a device configuration, if the admin has disabled us after the token validation - const deviceResponse = page.waitForResponse('**/create_device'); - await createDevice(page); - expect((await deviceResponse).status()).toBe(400); + await waitForBase(page); + const responsePromise = page.waitForResponse('**/auth'); + await loginBasic(page, { username: user.username, password }); + const response = await responsePromise; + expect(response.ok()).toBeTruthy(); }); - - test('Complete enrollment with created user', async ({ page }) => { + test('Try to complete disabled user enrollment via API', async ({ + page, + request, + browser, + }) => { expect(token).toBeDefined(); + await disableUser(browser, user); + await apiEnrollmentStart(request, token); + await apiEnrollmentActivateUser(request, password, '+48123456789'); + await waitForBase(page); - await page.goto(testsConfig.ENROLLMENT_URL); - await waitForPromise(2000); - await selectEnrollment(page); - await setToken(token, page); - // Welcome page - await page.getByTestId('enrollment-next').click(); - // Data validation - await validateData(user, page); - await page.getByTestId('enrollment-next').click(); - // Set password - await setPassword(page); - // VPN - await page.getByTestId('enrollment-next').click(); - await createDevice(page); - // Finish message - await page.getByTestId('enrollment-next').click({ timeout: 2000 }); - await page.locator('#enrollment-finish-card').waitFor({ state: 'visible' }); - await page.waitForLoadState('networkidle'); - waitForPromise(2000); - loginBasic(page, { username: user.username, password }); - await waitForPromise(2000); - const testUserProfile = await apiGetUserProfile(page, user.username); - expect(testUserProfile.devices.length).toBe(1); - const createdDevice = testUserProfile.devices[0]; - expect(createdDevice.networks[0].device_wireguard_ips).toStrictEqual(['10.10.10.2']); - expect(createdDevice.name).toBe('test'); + const responsePromise = page.waitForResponse('**/auth'); + await loginBasic(page, { username: user.username, password }); + const response = await responsePromise; + expect(response.ok()).toBeFalsy(); }); }); diff --git a/e2e/tests/externalopenid.spec.ts b/e2e/tests/externalopenid.spec.ts index 5d3d2a5fc..00d74fda6 100644 --- a/e2e/tests/externalopenid.spec.ts +++ b/e2e/tests/externalopenid.spec.ts @@ -8,7 +8,7 @@ import { logout } from '../utils/controllers/logout'; import { copyOpenIdClientIdAndSecret } from '../utils/controllers/openid/copyClientId'; import { createExternalProvider } from '../utils/controllers/openid/createExternalProvider'; import { CreateOpenIdClient } from '../utils/controllers/openid/createOpenIdClient'; -import { createNetwork } from '../utils/controllers/vpn/createNetwork'; +import { createRegularLocation } from '../utils/controllers/vpn/createNetwork'; import { dockerRestart } from '../utils/docker'; import { waitForBase } from '../utils/waitForBase'; import { waitForPromise } from '../utils/waitForPromise'; @@ -29,6 +29,7 @@ test.describe('External OIDC.', () => { const testNetwork: NetworkForm = { name: 'test network', address: '10.10.10.1/24', + allowed_ips: ['1.2.3.4'], endpoint: '127.0.0.1', port: '5055', }; @@ -46,7 +47,7 @@ test.describe('External OIDC.', () => { await loginBasic(page, defaultUserAdmin); await apiCreateUser(page, testUser); await logout(page); - await createNetwork(browser, testNetwork); + await createRegularLocation(browser, testNetwork); await context.close(); }); @@ -54,7 +55,7 @@ test.describe('External OIDC.', () => { expect(client.clientID).toBeDefined(); expect(client.clientSecret).toBeDefined(); await waitForBase(page); - const oidcLoginButton = await page.getByTestId('login-oidc'); + const oidcLoginButton = await page.locator('.oidc-button'); expect(oidcLoginButton).not.toBeNull(); expect(await oidcLoginButton.textContent()).toBe(`Sign in with ${client.name}`); await oidcLoginButton.click(); diff --git a/e2e/tests/externalopenidmfa.spec.ts b/e2e/tests/externalopenidmfa.spec.ts deleted file mode 100644 index 579fc2381..000000000 --- a/e2e/tests/externalopenidmfa.spec.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { expect, test } from '@playwright/test'; - -import { defaultUserAdmin, testsConfig, testUserTemplate } from '../config'; -import { NetworkForm, OpenIdClient, User } from '../types'; -import { apiCreateUser, apiGetUserProfile } from '../utils/api/users'; -import { loginBasic } from '../utils/controllers/login'; -import { logout } from '../utils/controllers/logout'; -import { copyOpenIdClientIdAndSecret } from '../utils/controllers/openid/copyClientId'; -import { createExternalProvider } from '../utils/controllers/openid/createExternalProvider'; -import { CreateOpenIdClient } from '../utils/controllers/openid/createOpenIdClient'; -import { createDevice } from '../utils/controllers/vpn/createDevice'; -import { createNetwork } from '../utils/controllers/vpn/createNetwork'; -import { dockerRestart } from '../utils/docker'; -import { waitForBase } from '../utils/waitForBase'; -import { waitForPromise } from '../utils/waitForPromise'; - -test.describe('External OIDC.', () => { - const testUser: User = { ...testUserTemplate, username: 'test' }; - - const client: OpenIdClient = { - name: 'test 01', - redirectURL: [`${testsConfig.ENROLLMENT_URL}/openid/mfa/callback`], - scopes: ['openid', 'profile', 'email'], - }; - - const testNetwork: NetworkForm = { - name: 'test network', - address: '10.10.10.1/24', - endpoint: '127.0.0.1', - port: '5055', - location_mfa_mode: 'external', - }; - - test.beforeEach(async ({ browser }) => { - dockerRestart(); - await CreateOpenIdClient(browser, client); - [client.clientID, client.clientSecret] = await copyOpenIdClientIdAndSecret( - browser, - client.name, - ); - const context = await browser.newContext(); - const page = await context.newPage(); - await createExternalProvider(browser, client); - await loginBasic(page, defaultUserAdmin); - await apiCreateUser(page, testUser); - await logout(page); - await createNetwork(browser, testNetwork); - await context.close(); - }); - - test('Complete client MFA through external OpenID', async ({ page, browser }) => { - await waitForBase(page); - const mfaStartUrl = `${testsConfig.ENROLLMENT_URL}/api/v1/client-mfa/start`; - await createDevice(browser, testUser, { - name: 'test', - }); - await loginBasic(page, testUser); - const testUserProfile = await apiGetUserProfile(page, testUser.username); - expect(testUserProfile.devices.length).toBe(1); - const createdDevice = testUserProfile.devices[0]; - const pubkey = createdDevice.wireguard_pubkey; - const data = { - method: 2, - pubkey: pubkey, - location_id: 1, - }; - const response = await page.request.post(mfaStartUrl, { - data: data, - }); - expect(response.ok()).toBeTruthy(); - const { token } = await response.json(); - expect(token).toBeDefined(); - expect(token.length).toBeGreaterThan(0); - - const preconditionResponse = await page.request.post( - testsConfig.ENROLLMENT_URL + '/api/v1/client-mfa/finish', - { - data: { - token: token, - }, - }, - ); - expect(preconditionResponse.status()).toBe(428); - - const url = testsConfig.ENROLLMENT_URL + '/openid/mfa' + `?token=${token}`; - await page.goto(url); - await waitForPromise(2000); - await page.getByTestId('openid-allow').click(); - await waitForPromise(2000); - - const finish = testsConfig.ENROLLMENT_URL + '/api/v1/client-mfa/finish'; - const finishResponse = await page.request.post(finish, { - data: { - token: token, - }, - }); - expect(finishResponse.ok()).toBeTruthy(); - const finishData = await finishResponse.json(); - expect(finishData.preshared_key).toBeDefined(); - expect(finishData.preshared_key.length).toBeGreaterThan(0); - }); -}); diff --git a/e2e/tests/groups.spec.ts b/e2e/tests/groups.spec.ts index 4cbb2acf5..46b503326 100644 --- a/e2e/tests/groups.spec.ts +++ b/e2e/tests/groups.spec.ts @@ -1,21 +1,84 @@ import { expect, test } from '@playwright/test'; -import { routes, testUserTemplate } from '../config'; +import { defaultUserAdmin, routes, testUserTemplate } from '../config'; +import { apiCreateUser } from '../utils/api/users'; import { createUser } from '../utils/controllers/createUser'; +import { createGroup } from '../utils/controllers/groups'; import { loginBasic } from '../utils/controllers/login'; import { dockerRestart } from '../utils/docker'; import { waitForBase } from '../utils/waitForBase'; -import { waitForRoute } from '../utils/waitForRoute'; +import { waitForPromise } from '../utils/waitForPromise'; test.describe('Test groups', () => { test.beforeEach(() => dockerRestart()); + test('Create group', async ({ page, browser }) => { + const groups = ['test_group1', 'test_group2', 'test_group3']; + + for (const group of groups) { + await createGroup(browser, group); + } + await loginBasic(page, defaultUserAdmin); + await waitForPromise(1000); + await page.goto(routes.base + routes.identity.groups); + for (const group of groups) { + await expect(page.locator('text=' + group + '')).toBeVisible(); + } + }); + test('Add user to admin group', async ({ page, browser }) => { const testUser = { ...testUserTemplate, username: 'test' }; await waitForBase(page); await createUser(browser, testUser, ['admin']); await loginBasic(page, testUser); - await waitForRoute(page, routes.admin.wizard); - expect(page.url()).toBe(routes.base + routes.admin.wizard); + await page.goto(routes.base + routes.openid_apps); + const addButton = page.getByTestId('add-new-app'); + await expect(addButton).toBeVisible(); + await expect(addButton).toBeEnabled(); + }); + test('Add user to new group', async ({ page, browser }) => { + const testUser = { ...testUserTemplate, username: 'test' }; + await waitForBase(page); + await createGroup(browser, 'test_group2'); + await createUser(browser, testUser, ['test_group2']); + await loginBasic(page, testUser); + await expect(page.url()).toBe( + routes.base + routes.profile + testUser.username + routes.tab.details, + ); + }); + test('Bulk assign users to new group', async ({ page, browser }) => { + const testUser = { ...testUserTemplate, username: 'testuserfirst' }; + const testUser2 = { ...testUserTemplate, username: 'testusersecond' }; + const group_name = 'test_group2'; + testUser2.mail = 'test2@test.com'; + testUser2.phone = '9087654321'; + + await waitForBase(page); + await createGroup(browser, group_name); + await loginBasic(page, defaultUserAdmin); + + await apiCreateUser(page, testUser); + await apiCreateUser(page, testUser2); + await page.goto(routes.base + routes.identity.users); + const firstUser = await page + .locator('.virtual-row') + .filter({ hasText: testUser.username }); + await firstUser.locator('.checkbox').click(); + const secondUser = await page + .locator('.virtual-row') + .filter({ hasText: testUser2.username }); + await secondUser.locator('.checkbox').click(); + await page.getByTestId('bulk-assign').click(); + await page + .locator('.modal') + .locator('.checkbox') + .filter({ hasText: group_name }) + .click(); + await page.getByTestId('submit').click(); + await waitForPromise(2000); + await page.goto(routes.base + routes.identity.users); + + await expect(firstUser).toContainText(group_name); + await expect(secondUser).toContainText(group_name); }); }); diff --git a/e2e/tests/networkdevice.spec.ts b/e2e/tests/networkdevice.spec.ts new file mode 100644 index 000000000..e5bf05813 --- /dev/null +++ b/e2e/tests/networkdevice.spec.ts @@ -0,0 +1,110 @@ +import { expect, test } from '@playwright/test'; + +import { defaultUserAdmin, routes, testUserTemplate } from '../config'; +import { NetworkForm, User } from '../types'; +import { createUser } from '../utils/controllers/createUser'; +import { loginBasic } from '../utils/controllers/login'; +import { createRegularLocation } from '../utils/controllers/vpn/createNetwork'; +import { + createNetworkCLIDevice, + startNetworkDeviceEnrollment, +} from '../utils/controllers/vpn/createNetworkDevice'; +import { dockerRestart } from '../utils/docker'; +import { waitForBase } from '../utils/waitForBase'; + +const testKeys = { + private: '4K1BwtDCd0XUwq6WThkrQ4/DQ4vIpyEki5aIokqx21c=', + public: 'n4/imOLXU35yYkWNHvZk2RZfI3l1NwVd4bJsuhTuRzw=', +}; + +const testNetwork: NetworkForm = { + name: 'test devices', + address: '10.10.10.1/24', + endpoint: '127.0.0.1', + allowed_ips: ['1.2.4.5'], + port: '5055', +}; + +test.describe('Network devices', () => { + const testUser: User = { ...testUserTemplate, username: 'test' }; + + test.beforeEach(async ({ browser }) => { + dockerRestart(); + const context = await browser.newContext(); + const page = await context.newPage(); + await waitForBase(page); + await createRegularLocation(browser, testNetwork); + await loginBasic(page, defaultUserAdmin); + await createUser(browser, testUser); + await context.close(); + }); + + test('Create and setup Defguard CLI network device', async ({ + page, + browser, + request, + }) => { + const deviceName = 'test'; + const deviceDesc = 'test device description'; + await waitForBase(page); + await createNetworkCLIDevice(browser, defaultUserAdmin, { + name: deviceName, + pubKey: testKeys.public, + description: deviceDesc, + }); + await loginBasic(page, defaultUserAdmin); + await page.goto(routes.base + routes.network_devices); + const deviceRow = page.locator('.virtual-row').filter({ hasText: deviceName }); + await expect(deviceRow).toContainText('Awaiting Setup'); + await expect(deviceRow).toContainText(deviceName); + await expect(deviceRow).toContainText(testNetwork.name); + await expect(deviceRow).toContainText(defaultUserAdmin.username); + await deviceRow.locator('.icon-button').click(); + await page.getByTestId('generate-auth-token').click(); + const command = await page.getByTestId('copy-field').locator('p').textContent(); + await page.getByTestId('close').click(); + const tokenMatch = command?.match(/-t\s+(\S+)/); + + const token = tokenMatch?.[1]; + const res = await request.post(`http://localhost:8080/api/v1/enrollment/start`, { + data: { + token, + }, + }); + expect(res.status()).toBe(200); + const responsePayload = await res.json(); + expect(responsePayload).toHaveProperty('instance'); + const createDeviceRes = await request.post( + `http://localhost:8080/api/v1/enrollment/create_device`, + { + data: { + name: 'dev', + pubkey: 'DwcCqbwTEvI4erU8RrTUg3fRILhBVzy3rrTqEPGYKIA=', + token: null, + }, + }, + ); + expect(createDeviceRes.status()).toBe(200); + const createDeviceResPayload = await createDeviceRes.json(); + expect(createDeviceResPayload).toHaveProperty('configs'); + const configs = createDeviceResPayload['configs']; + expect(configs.length).toEqual(1); + const config = configs.pop(); + expect(config['endpoint']).toEqual(`${testNetwork.endpoint}:${testNetwork.port}`); + }); + + test('Create Manual WireGuard Client network device', async ({ page, browser }) => { + const deviceName = 'test'; + const deviceDesc = 'test device description'; + await waitForBase(page); + await startNetworkDeviceEnrollment(browser, defaultUserAdmin, { + name: deviceName, + pubKey: testKeys.public, + description: deviceDesc, + }); + await startNetworkDeviceEnrollment(browser, defaultUserAdmin, { + name: deviceName + '2', + description: deviceDesc, + }); + }); +}); diff --git a/e2e/tests/openid.spec.ts b/e2e/tests/openid.spec.ts index 894323789..492538bc4 100644 --- a/e2e/tests/openid.spec.ts +++ b/e2e/tests/openid.spec.ts @@ -1,8 +1,8 @@ import { expect, Page, test } from '@playwright/test'; -import { defaultUserAdmin, routes, testUserTemplate } from '../config'; +import { routes, testUserTemplate } from '../config'; import { OpenIdClient, User } from '../types'; -import { apiCreateUser } from '../utils/api/users'; +import { createUser } from '../utils/controllers/createUser'; import { loginBasic, loginTOTP } from '../utils/controllers/login'; import { logout } from '../utils/controllers/logout'; import { enableTOTP } from '../utils/controllers/mfa/enableTOTP'; @@ -29,11 +29,7 @@ test.describe('Authorize OpenID client.', () => { dockerRestart(); await CreateOpenIdClient(browser, client); client.clientID = await copyOpenIdClientId(browser, 1); - const context = await browser.newContext(); - const page = await context.newPage(); - await loginBasic(page, defaultUserAdmin); - await apiCreateUser(page, testUser); - context.close(); + await createUser(browser, testUser); }); test('Authorize when session is active.', async ({ page }) => { @@ -42,7 +38,7 @@ test.describe('Authorize OpenID client.', () => { await loginBasic(page, testUser); await fillAndSubmitOpenIDDebugger(page, client); await page.waitForURL(routes.base + routes.consent + '**'); - await page.getByTestId('openid-allow').click(); + await page.getByTestId('accept-openid').click(); await page.waitForURL('https://oidcdebugger.com/**'); await waitForPromise(2000); const headerMessage = await page @@ -53,8 +49,8 @@ test.describe('Authorize OpenID client.', () => { await page.goto(routes.base + routes.me, { waitUntil: 'networkidle', }); - await waitForRoute(page, routes.me); - await page.getByTestId('authorized-apps').getByRole('button').click(); + const authorizedApps = page.locator('#authorized-apps-card').locator('.app'); + await expect(authorizedApps).toContainText(client.name); await logout(page); }); @@ -65,7 +61,7 @@ test.describe('Authorize OpenID client.', () => { await waitForRoute(page, routes.auth.login); await loginBasic(page, testUser); await page.waitForURL(routes.base + routes.consent + '**'); - await page.getByTestId('openid-allow').click(); + await page.getByTestId('accept-openid').click(); await page.waitForURL('https://oidcdebugger.com/**'); await waitForPromise(2000); const headerMessage = await page @@ -76,8 +72,8 @@ test.describe('Authorize OpenID client.', () => { await page.goto(routes.base + routes.me, { waitUntil: 'networkidle', }); - await waitForRoute(page, routes.me); - await page.getByTestId('authorized-apps').getByRole('button').click(); + const authorizedApps = page.locator('#authorized-apps-card').locator('.app'); + await expect(authorizedApps).toContainText(client.name); await logout(page); }); @@ -91,7 +87,7 @@ test.describe('Authorize OpenID client.', () => { await fillAndSubmitOpenIDDebugger(page, client); await loginTOTP(page, testUser, secret); await page.waitForURL(routes.base + routes.consent + '**'); - await page.getByTestId('openid-allow').click(); + await page.getByTestId('accept-openid').click(); await page.waitForURL('https://oidcdebugger.com/**'); await waitForPromise(2000); const headerMessage = await page @@ -102,8 +98,8 @@ test.describe('Authorize OpenID client.', () => { await page.goto(routes.base + routes.me, { waitUntil: 'networkidle', }); - await waitForRoute(page, routes.me); - await page.getByTestId('authorized-apps').getByRole('button').click(); + const authorizedApps = page.locator('#authorized-apps-card').locator('.app'); + await expect(authorizedApps).toContainText(client.name); await logout(page); }); }); diff --git a/e2e/tests/vpn/device.spec.ts b/e2e/tests/vpn/device.spec.ts deleted file mode 100644 index 82af8cf63..000000000 --- a/e2e/tests/vpn/device.spec.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { expect, test } from '@playwright/test'; - -import { defaultUserAdmin, testUserTemplate } from '../../config'; -import { NetworkForm, User } from '../../types'; -import { apiCreateUser, apiGetUserProfile } from '../../utils/api/users'; -import { loginBasic } from '../../utils/controllers/login'; -import { createDevice } from '../../utils/controllers/vpn/createDevice'; -import { createNetwork } from '../../utils/controllers/vpn/createNetwork'; -import { dockerRestart } from '../../utils/docker'; -import { waitForBase } from '../../utils/waitForBase'; - -const testKeys = { - private: '4K1BwtDCd0XUwq6WThkrQ4/DQ4vIpyEki5aIokqx21c=', - public: 'n4/imOLXU35yYkWNHvZk2RZfI3l1NwVd4bJsuhTuRzw=', -}; - -const testNetwork: NetworkForm = { - name: 'test devices', - address: '10.10.10.1/24', - endpoint: '127.0.0.1', - port: '5055', -}; - -test.describe('Add user device', () => { - const testUser: User = { ...testUserTemplate, username: 'test' }; - - test.beforeEach(async ({ browser }) => { - dockerRestart(); - const context = await browser.newContext(); - const page = await context.newPage(); - // wait for fronted - await waitForBase(page); - // prepare test network - await createNetwork(browser, testNetwork); - // make test user - await loginBasic(page, defaultUserAdmin); - await apiCreateUser(page, testUser); - await context.close(); - }); - - test('Add test user device with generate', async ({ page, browser }) => { - await waitForBase(page); - await createDevice(browser, testUser, { - name: 'test', - }); - await loginBasic(page, testUser); - const testUserProfile = await apiGetUserProfile(page, testUser.username); - expect(testUserProfile.devices.length).toBe(1); - const createdDevice = testUserProfile.devices[0]; - expect(createdDevice.networks[0].device_wireguard_ips).toStrictEqual(['10.10.10.2']); - }); - - test('Add test user device with manual', async ({ page, browser }) => { - await waitForBase(page); - await createDevice(browser, testUser, { - name: 'test', - pubKey: testKeys.public, - }); - await loginBasic(page, testUser); - const testUserProfile = await apiGetUserProfile(page, testUser.username); - expect(testUserProfile.devices.length).toBe(1); - const createdDevice = testUserProfile.devices[0]; - expect(createdDevice.networks[0].device_wireguard_ips).toStrictEqual(['10.10.10.2']); - }); -}); diff --git a/e2e/tests/vpn/networkdevice.spec.ts b/e2e/tests/vpn/networkdevice.spec.ts deleted file mode 100644 index 0b037fbf4..000000000 --- a/e2e/tests/vpn/networkdevice.spec.ts +++ /dev/null @@ -1,165 +0,0 @@ -import { expect, test } from '@playwright/test'; - -import { defaultUserAdmin, routes, testUserTemplate } from '../../config'; -import { NetworkForm, User } from '../../types'; -import { apiCreateUser } from '../../utils/api/users'; -import { loginBasic } from '../../utils/controllers/login'; -import { createNetwork } from '../../utils/controllers/vpn/createNetwork'; -import { - createNetworkDevice, - doAction, - editNetworkDevice, - getDeviceRow, - startNetworkDeviceEnrollment, -} from '../../utils/controllers/vpn/createNetworkDevice'; -import { dockerRestart } from '../../utils/docker'; -import { waitForBase } from '../../utils/waitForBase'; -import { waitForRoute } from '../../utils/waitForRoute'; - -const testKeys = { - private: '4K1BwtDCd0XUwq6WThkrQ4/DQ4vIpyEki5aIokqx21c=', - public: 'n4/imOLXU35yYkWNHvZk2RZfI3l1NwVd4bJsuhTuRzw=', -}; - -const testNetwork: NetworkForm = { - name: 'test devices', - address: '10.10.10.1/24', - endpoint: '127.0.0.1', - port: '5055', -}; - -test.describe('Network devices', () => { - const testUser: User = { ...testUserTemplate, username: 'test' }; - - test.beforeEach(async ({ browser }) => { - dockerRestart(); - const context = await browser.newContext(); - const page = await context.newPage(); - // wait for fronted - await waitForBase(page); - // prepare test network - await createNetwork(browser, testNetwork); - // make test user - await loginBasic(page, defaultUserAdmin); - await apiCreateUser(page, testUser); - await context.close(); - }); - - test('Network devices CRUD and actions', async ({ page, browser }) => { - const deviceName = 'test'; - const deviceDesc = 'test device description'; - await waitForBase(page); - await createNetworkDevice(browser, defaultUserAdmin, { - name: deviceName, - pubKey: testKeys.public, - description: deviceDesc, - }); - await loginBasic(page, defaultUserAdmin); - - // Check if the device is really there - await page.goto(routes.base + routes.admin.devices); - const deviceList = page.locator('#devices-page-devices-list').first(); - const deviceRows = deviceList.locator('.device-row'); - await expect(deviceRows).toHaveCount(1); - const deviceRow = await getDeviceRow({ page, deviceName }); - const name = await deviceRow.locator('.cell-1').first().innerText(); - expect(name).toBe(deviceName); - const location = await deviceRow.locator('.cell-2').first().innerText(); - expect(location).toBe(testNetwork.name); - const desc = await deviceRow.locator('.cell-4').first().innerText(); - expect(desc).toBe(deviceDesc); - const addedBy = await deviceRow.locator('.cell-5').first().innerText(); - expect(addedBy).toBe(defaultUserAdmin.username); - - // Make sure the device is not displayed on the user's page - await page.goto(routes.base + routes.me); - await waitForRoute(page, routes.me); - const devices = page.locator('.device-card'); - const networkDevice = (await devices.all()).find(async (val) => { - if ((await val.getByTestId('device-name').innerText()) === deviceName) { - return true; - } else { - return false; - } - }); - expect(networkDevice).toBeUndefined(); - await page.goto(routes.base + routes.admin.devices); - await waitForRoute(page, routes.admin.devices); - - await editNetworkDevice(browser, defaultUserAdmin, deviceName, { - name: deviceName + '-test', - description: 'new description', - }); - await page.reload(); - await waitForRoute(page, routes.admin.devices); - const newName = await deviceRow.locator('.cell-1').first().innerText(); - expect(newName).toBe(deviceName + '-test'); - const newDesc = await deviceRow.locator('.cell-4').first().innerText(); - expect(newDesc).toBe('new description'); - - // View the config - await doAction({ page, deviceRow, action: 'View config' }); - const configDisplayCard = page.locator('#standalone-device-config-modal'); - const config = await configDisplayCard.locator('.config').first().innerText(); - expect(config).toContain(`${testNetwork.endpoint}:${testNetwork.port}`); - await configDisplayCard.getByRole('button', { name: 'Close' }).click(); - - // Generate the token command - await doAction({ page, deviceRow, action: 'Generate auth token' }); - const tokenCard = page.locator('.modal-content'); - const command = await tokenCard.locator('.expanded-content').first().innerText(); - expect(command.length).toBeGreaterThan(0); - await tokenCard.getByRole('button', { name: 'Close' }).click(); - - // Delete device - await doAction({ page, deviceRow, action: 'Delete' }); - const deleteModal = page.locator('.modal'); - await deleteModal.getByRole('button', { name: 'Delete' }).click(); - await expect(deviceRows).toHaveCount(0); - }); - - test('Network devices enrollment', async ({ page, browser, request }) => { - const deviceName = 'test'; - const deviceDesc = 'test device description'; - await waitForBase(page); - const command = await startNetworkDeviceEnrollment(browser, defaultUserAdmin, { - name: deviceName, - pubKey: testKeys.public, - description: deviceDesc, - }); - const urlMatch = command.match(/-u\s+(\S+)/); - const tokenMatch = command.match(/-t\s+(\S+)/); - expect(urlMatch).not.toBeNull(); - expect(tokenMatch).not.toBeNull(); - expect(urlMatch?.length).toBeGreaterThan(0); - expect(tokenMatch?.length).toBeGreaterThan(0); - const url = urlMatch?.pop() as string; - const token = tokenMatch?.pop() as string; - console.log('URL:', url, 'Token:', token); - const res = await request.post(`http://localhost:8080/api/v1/enrollment/start`, { - data: { - token, - }, - }); - expect(res.status()).toBe(200); - const responsePayload = await res.json(); - expect(responsePayload).toHaveProperty('instance'); - const createDeviceRes = await request.post( - `http://localhost:8080/api/v1/enrollment/create_device`, - { - data: { - name: 'dev', - pubkey: 'DwcCqbwTEvI4erU8RrTUg3fRILhBVzy3rrTqEPGYKIA=', - token: null, - }, - }, - ); - expect(createDeviceRes.status()).toBe(200); - const createDeviceResPayload = await createDeviceRes.json(); - expect(createDeviceResPayload).toHaveProperty('configs'); - const configs = createDeviceResPayload['configs']; - expect(configs.length).toEqual(1); - const config = configs.pop(); - expect(config['endpoint']).toEqual(`${testNetwork.endpoint}:${testNetwork.port}`); - }); -}); diff --git a/e2e/tests/vpn/wizard.spec.ts b/e2e/tests/vpn/wizard.spec.ts deleted file mode 100644 index 1f9f8c48c..000000000 --- a/e2e/tests/vpn/wizard.spec.ts +++ /dev/null @@ -1,108 +0,0 @@ -import { expect, test } from '@playwright/test'; -import * as fs from 'fs'; -import lodash from 'lodash'; -import path from 'path'; - -import { defaultUserAdmin, routes, testUserTemplate } from '../../config'; -import { NetworkForm } from '../../types'; -import { - apiCreateUsersBulk, - apiGetUserProfile, - apiGetUsers, -} from '../../utils/api/users'; -import { loginBasic } from '../../utils/controllers/login'; -import { createNetwork } from '../../utils/controllers/vpn/createNetwork'; -import { dockerRestart } from '../../utils/docker'; -import { waitForBase } from '../../utils/waitForBase'; -import { waitForPromise } from '../../utils/waitForPromise'; -import { waitForRoute } from '../../utils/waitForRoute'; - -test.describe('Setup VPN (wizard) ', () => { - test.beforeAll(() => { - dockerRestart(); - }); - - test.afterEach(() => { - dockerRestart(); - }); - - test('Wizard Import', async ({ page }) => { - await waitForBase(page); - // create users to map devices to; - const users = lodash.range(50).map((id) => ({ - ...testUserTemplate, - firstName: `test${id}`, - username: `test${id}`, - mail: `test${id}@test.com`, - })); - await loginBasic(page, defaultUserAdmin); - await apiCreateUsersBulk(page, users); - await page.goto(routes.base + routes.admin.wizard); - await page.getByTestId('setup-network').click(); - const navNext = page.getByTestId('wizard-next'); - const navBack = page.getByTestId('wizard-back'); - await page.getByTestId('setup-option-import').click(); - await navNext.click(); - await page.getByTestId('field-name').fill('test network'); - await page.getByTestId('field-endpoint').fill('127.0.0.1:5051'); - const fileChooserPromise = page.waitForEvent('filechooser'); - await page.getByTestId('upload-config').click(); - const responseImportConfigPromise = page.waitForResponse('**/import'); - const fileChooser = await fileChooserPromise; - const filePath = path.resolve( - __dirname.split('e2e')[0], - 'e2e', - 'assets', - 'test.config', - ); - fs.accessSync(filePath, fs.constants.F_OK); - const configData = fs.readFileSync(filePath, null); - await fileChooser.setFiles([ - { - name: 'test.config', - buffer: configData, - mimeType: 'text/plain', - }, - ]); - await navNext.click(); - const response = await responseImportConfigPromise; - expect(response.status()).toBe(201); - const isNavDisabled = await navBack.isDisabled(); - expect(isNavDisabled).toBe(true); - let rowIndex = 0; - for (const user of users) { - const selectElement = page.getByTestId(`user-select-${rowIndex}`); - const selectFloatingExpand = page.locator('.select-floating-ui'); - await selectElement.click(); - await waitForPromise(200); - await selectFloatingExpand.waitFor({ state: 'visible' }); - await page - .locator('.select-floating-ui button > span') - .locator(`text='${user.firstName + ' ' + user.lastName}'`) - .click(); - await selectFloatingExpand.waitFor({ state: 'hidden' }); - rowIndex++; - } - const responseMapConfigPromise = page.waitForResponse('**/devices'); - await navNext.click(); - const responseMapConfig = await responseMapConfigPromise; - expect(responseMapConfig.status()).toBe(201); - await waitForRoute(page, routes.admin.overview); - const apiUsers = await apiGetUsers(page); - for (const user of apiUsers.filter((u) => u.username !== 'admin')) { - const userProfile = await apiGetUserProfile(page, user.username); - expect(userProfile.devices.length).toBe(1); - } - }); - - test('Wizard Manual', async ({ page, browser }) => { - await waitForBase(page); - const network: NetworkForm = { - name: 'test manual', - address: '10.10.10.1/24', - endpoint: '127.0.0.1', - port: '5055', - }; - await createNetwork(browser, network); - }); -}); diff --git a/e2e/tests/webhook.spec.ts b/e2e/tests/webhook.spec.ts new file mode 100644 index 000000000..d74c1b323 --- /dev/null +++ b/e2e/tests/webhook.spec.ts @@ -0,0 +1,112 @@ +import { expect, test } from '@playwright/test'; + +import { defaultUserAdmin, routes } from '../config'; +import { loginBasic } from '../utils/controllers/login'; +import { createWebhook } from '../utils/controllers/webhook'; +import { dockerRestart } from '../utils/docker'; +import { waitForPromise } from '../utils/waitForPromise'; + +test.describe('Test webhooks', () => { + test.beforeEach(() => { + dockerRestart(); + }); + const webhook_url = 'https://defguard.defguard/webhook'; + const webhook_description = 'example webhook'; + const webhook_secret = 'secret'; + + test('Create webhook and verify content', async ({ page, browser }) => { + await createWebhook(browser, webhook_url, webhook_description, webhook_secret); + await loginBasic(page, defaultUserAdmin); + await page.goto(routes.base + routes.webhooks, { + waitUntil: 'networkidle', + }); + const webhookRow = await page + .locator('.virtual-row') + .filter({ hasText: webhook_url }); + await expect(webhookRow).toContainText(webhook_url); + await expect(webhookRow).toContainText(webhook_description); + await expect(webhookRow).toContainText('Active'); + }); + + const new_webhook_url = 'https://changed.defguard/webhook'; + const new_webhook_description = 'changed webhook'; + + test('Create, modify webhook and verify content', async ({ page, browser }) => { + await createWebhook(browser, webhook_url, webhook_description, 'secret'); + await loginBasic(page, defaultUserAdmin); + await page.goto(routes.base + routes.webhooks, { + waitUntil: 'networkidle', + }); + const webhookRow = await page + .locator('.virtual-row') + .filter({ hasText: webhook_url }); + await expect(webhookRow).toContainText(webhook_url); + await expect(webhookRow).toContainText(webhook_description); + await expect(webhookRow).toContainText('Active'); + // check if webhook is OK + // then edit webhook + await webhookRow.locator('.icon-button').click(); + await page.getByTestId('edit').click(); + + await page.getByTestId('field-url').fill(new_webhook_url); + await page.getByTestId('field-description').fill(new_webhook_description); + await page.getByTestId('submit').click(); + await waitForPromise(2000); + await page.goto(routes.base + routes.webhooks, { + waitUntil: 'networkidle', + }); + + const new_webhookRow = await page + .locator('.virtual-row') + .filter({ hasText: new_webhook_url }); + await expect(new_webhookRow).toContainText(new_webhook_url); + await expect(new_webhookRow).toContainText(new_webhook_description); + await expect(new_webhookRow).toContainText('Active'); + }); + + test('Create webhook, change state and verify content', async ({ page, browser }) => { + await createWebhook(browser, webhook_url, webhook_description, webhook_secret); + await loginBasic(page, defaultUserAdmin); + await page.goto(routes.base + routes.webhooks, { + waitUntil: 'networkidle', + }); + const webhookRow = await page + .locator('.virtual-row') + .filter({ hasText: webhook_url }); + await expect(webhookRow).toContainText(webhook_url); + await expect(webhookRow).toContainText(webhook_description); + await expect(webhookRow).toContainText('Active'); + + // is everything ok after changing state to Disabled? + await webhookRow.locator('.icon-button').click(); + await page.getByTestId('change-state').click(); + await page.locator('.virtual-row').filter({ hasText: webhook_url }); + await expect(webhookRow).toContainText(webhook_url); + await expect(webhookRow).toContainText(webhook_description); + await expect(webhookRow).toContainText('Disabled'); + + // is everything ok after changing state to Enabled? + await webhookRow.locator('.icon-button').click(); + await page.getByTestId('change-state').click(); + await expect(webhookRow).toContainText(webhook_url); + await expect(webhookRow).toContainText(webhook_description); + await expect(webhookRow).toContainText('Active'); + }); + test('Create webhook and delete it', async ({ page, browser }) => { + await createWebhook(browser, webhook_url, webhook_description, webhook_secret); + await loginBasic(page, defaultUserAdmin); + await page.goto(routes.base + routes.webhooks, { + waitUntil: 'networkidle', + }); + const webhookRow = await page + .locator('.virtual-row') + .filter({ hasText: webhook_url }); + await expect(webhookRow).toContainText(webhook_url); + await expect(webhookRow).toContainText(webhook_description); + await expect(webhookRow).toContainText('Active'); + await webhookRow.locator('.icon-button').click(); + await page.getByTestId('delete').click(); + + await expect(webhookRow).not.toBeVisible(); + }); +}); diff --git a/e2e/tests/wizard.spec.ts b/e2e/tests/wizard.spec.ts new file mode 100644 index 000000000..a44d7dee6 --- /dev/null +++ b/e2e/tests/wizard.spec.ts @@ -0,0 +1,43 @@ +import { test } from '@playwright/test'; + +import { NetworkForm } from '../types'; +import { + createRegularLocation, + createServiceLocation, +} from '../utils/controllers/vpn/createNetwork'; +import { dockerRestart } from '../utils/docker'; +import { waitForBase } from '../utils/waitForBase'; + +test.describe('Setup VPN (wizard) ', () => { + test.beforeAll(() => { + dockerRestart(); + }); + + test.afterEach(() => { + dockerRestart(); + }); + + test('Wizard Regular Location', async ({ page, browser }) => { + await waitForBase(page); + const network: NetworkForm = { + name: 'test regular', + address: '10.10.10.1/24', + endpoint: '127.0.0.1', + port: '5055', + allowed_ips: ['127.1.5.1'], + }; + await createRegularLocation(browser, network); + }); + + test('Wizard Service Location', async ({ page, browser }) => { + await waitForBase(page); + const network: NetworkForm = { + name: 'test service', + address: '10.10.10.1/24', + endpoint: '127.0.0.1', + port: '5055', + allowed_ips: ['127.1.5.1'], + }; + await createServiceLocation(browser, network); + }); +}); diff --git a/e2e/types.ts b/e2e/types.ts index 966f45e8b..2d4be5841 100644 --- a/e2e/types.ts +++ b/e2e/types.ts @@ -67,7 +67,7 @@ export type NetworkForm = { address: string; endpoint: string; port: string; - allowed_ips?: string; + allowed_ips?: string[]; dns?: string; location_mfa_mode?: string; }; @@ -90,3 +90,9 @@ export type EditNetworkDeviceForm = { }; export type OpenIdScope = 'openid' | 'profile' | 'email' | 'phone'; + +export enum Protocols { + UDP = 'UDP', + TCP = 'TCP', + ICMP = 'ICMP', +} diff --git a/e2e/utils/acl.ts b/e2e/utils/acl.ts new file mode 100644 index 000000000..53fe9c9b8 --- /dev/null +++ b/e2e/utils/acl.ts @@ -0,0 +1,56 @@ +import { Browser } from 'playwright'; + +import { defaultUserAdmin, routes } from '../config'; +import { Protocols } from '../types'; +import { loginBasic } from './controllers/login'; +import { waitForBase } from './waitForBase'; + +export const createAlias = async ( + browser: Browser, + name: string, + addresses?: string[], + ports?: string[], + protocols?: Protocols[], +): Promise => { + const context = await browser.newContext(); + const page = await context.newPage(); + await waitForBase(page); + await loginBasic(page, defaultUserAdmin); + await page.goto(routes.base + routes.firewall.aliases); + await page.getByTestId('add-alias').click(); + const modal = await page.locator('.card'); + await modal.getByTestId('field-name').fill(name); + + if (addresses) { + await modal.getByTestId('radio-addresses').click(); + await modal.getByTestId('field-destination').fill(addresses.join(',')); + } + + if (ports) { + await modal.getByTestId('radio-ports').click(); + await modal.getByTestId('field-ports').fill(ports.join(',')); + } + + if (protocols) { + await modal.getByTestId('radio-protocols').click(); + for (const protocol of protocols) { + await modal.getByTestId('field-protocols').filter({ hasText: protocol }).click(); + } + } + await modal.locator('button[data-variant="primary"]').click(); + await context.close(); +}; + +// export const createRule = async ( +// browser: Browser, +// name: string, +// addresses?: string[], +// ports?: string[], +// protocols?: Protocols[], +// ): Promise => { +// const context = await browser.newContext(); +// const page = await context.newPage(); +// await waitForBase(page); +// //TODO +// await context.close(); +// }; diff --git a/e2e/utils/api/enrollment.ts b/e2e/utils/api/enrollment.ts new file mode 100644 index 000000000..c0c218aee --- /dev/null +++ b/e2e/utils/api/enrollment.ts @@ -0,0 +1,31 @@ +import { APIRequestContext } from '@playwright/test'; + +import { testsConfig } from '../../config'; + +export const apiEnrollmentStart = async (request: APIRequestContext, token: string) => { + const url = `${testsConfig.ENROLLMENT_URL}/api/v1/enrollment/start`; + const response = await request.post(url, { + data: { token }, + headers: { + 'Content-Type': 'application/json', + }, + }); + return response.json(); +}; + +export const apiEnrollmentActivateUser = async ( + request: APIRequestContext, + password: string, + phoneNumber?: string, +): Promise => { + const url = `${testsConfig.ENROLLMENT_URL}/api/v1/enrollment/activate_user`; + await request.post(url, { + data: { + password, + phone_number: phoneNumber, + }, + headers: { + 'Content-Type': 'application/json', + }, + }); +}; diff --git a/e2e/utils/api/users.ts b/e2e/utils/api/users.ts index 6b993adbc..a6e0e6633 100644 --- a/e2e/utils/api/users.ts +++ b/e2e/utils/api/users.ts @@ -18,11 +18,12 @@ export const apiGetUserProfile = async ( username: string, ): Promise => { const url = testsConfig.CORE_BASE_URL + '/user/' + username; - const userProfile = await page.evaluate(async (url) => { - return await fetch(url, { - method: 'GET', - }).then((res) => res.json()); - }, url); + const response = await page.request.get(url, { + headers: { + 'Content-Type': 'application/json', + }, + }); + const userProfile = await response.json(); return userProfile; }; @@ -41,11 +42,12 @@ export const apiGetUserAuthKeys = async ( username: string, ): Promise => { const url = testsConfig.CORE_BASE_URL + `/user/${username}/auth_key`; - const userData = await page.evaluate(async (url) => { - return await fetch(url, { - method: 'GET', - }).then((res) => res.json()); - }, url); + const response = await page.request.get(url, { + headers: { + 'Content-Type': 'application/json', + }, + }); + const userData = await response.json(); return userData; }; @@ -57,28 +59,17 @@ export const apiCreateUsersBulk = async (page: Page, users: User[]): Promise => { const url = testsConfig.CORE_BASE_URL + '/user'; - await page.evaluate( - async ({ user, url }) => { - const options = { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'access-control-allow-origin': '*', - }, - body: JSON.stringify({ - username: user.username, - first_name: user.firstName, - last_name: user.lastName, - email: user.mail, - phone: user.phone, - password: user.password, - }), - }; - await fetch(url, options); + await page.request.post(url, { + data: { + username: user.username, + first_name: user.firstName, + last_name: user.lastName, + email: user.mail, + phone: user.phone, + password: user.password, }, - { - user, - url, + headers: { + 'Content-Type': 'application/json', }, - ); + }); }; diff --git a/e2e/utils/controllers/acceptRecovery.ts b/e2e/utils/controllers/acceptRecovery.ts index 4f1ab7648..3a9ae7c00 100644 --- a/e2e/utils/controllers/acceptRecovery.ts +++ b/e2e/utils/controllers/acceptRecovery.ts @@ -1,20 +1,20 @@ import { Page } from 'playwright'; -import { routes } from '../../config'; import { getPageClipboard } from '../getPageClipboard'; +import { waitForPromise } from '../waitForPromise'; // accepts recovery and returns codes export const acceptRecovery = async (page: Page): Promise => { try { - // if modal won't show up it means another method was already active and new codes won't show up - const modalElement = page.locator('#view-recovery-codes'); - await modalElement.waitFor({ state: 'visible', timeout: 2000 }); - await page.getByTestId('copy-recovery').click(); - const codes = (await getPageClipboard(page)).split('\n'); - await page.getByTestId('accept-recovery').click(); - await modalElement.waitFor({ state: 'hidden' }); - await page.waitForURL(routes.base + routes.auth.login); - return codes; + await waitForPromise(2000); + + await page.getByTestId('copy-recovery-codes').click(); + const recoveryString = await getPageClipboard(page); + const recovery = recoveryString.split('\n').filter((line) => line.trim()); + + await page.getByTestId('confirm-code-save').click(); + await page.getByTestId('finish-recovery-codes').click(); + return recovery; } catch { return undefined; } diff --git a/e2e/utils/controllers/createUser.ts b/e2e/utils/controllers/createUser.ts index b84b1e5c4..67d1d27d5 100644 --- a/e2e/utils/controllers/createUser.ts +++ b/e2e/utils/controllers/createUser.ts @@ -1,9 +1,9 @@ import { Browser } from 'playwright'; +import { expect } from 'playwright/test'; import { defaultUserAdmin, routes } from '../../config'; import { User } from '../../types'; import { waitForBase } from '../waitForBase'; -import { waitForPromise } from '../waitForPromise'; import { loginBasic } from './login'; // create user via default admin on separate context @@ -16,9 +16,10 @@ export const createUser = async ( const page = await context.newPage(); await waitForBase(page); await loginBasic(page, defaultUserAdmin); - await page.goto(routes.base + routes.admin.users); + await page.goto(routes.base + routes.identity.users); await page.getByTestId('add-user').click(); - const formElement = page.getByTestId('add-user-form'); + await page.getByTestId('add-user-manually').click(); + const formElement = page.locator('[id="add-user-modal"]'); await formElement.waitFor({ state: 'visible' }); await formElement.getByTestId('field-username').fill(user.username); await formElement.getByTestId('field-password').fill(user.password); @@ -26,26 +27,21 @@ export const createUser = async ( await formElement.getByTestId('field-last_name').fill(user.lastName); await formElement.getByTestId('field-email').fill(user.mail); await formElement.getByTestId('field-phone').fill(user.phone); - await formElement.locator('button[type="submit"]').click(); + await formElement.getByTestId('add-user-submit').click(); await formElement.waitFor({ state: 'hidden', timeout: 2000 }); if (groups) { - groups = groups.map((g) => g.toLocaleLowerCase()); - await page.goto(routes.base + routes.admin.users + `/${user.username}`, { - waitUntil: 'networkidle', - }); - await page.getByTestId('edit-user').click(); - await page.waitForLoadState('networkidle'); - await waitForPromise(2000); - await page.getByTestId('groups-select').locator('.select-container').click(); - await waitForPromise(2000); + await page.goto(routes.base + routes.identity.users); + const userRow = page.locator('.virtual-row').filter({ hasText: user.username }); + await userRow.locator('.icon-button').click(); + await page.getByTestId('edit-groups').click(); for (const group of groups) { - await page - .locator('.select-floating-ui') - .locator('.options-container') - .locator(`button >> span:has-text("${group}")`) - .click(); + await page.locator('.item:has-text("' + group + '") .checkbox').click(); + } + await page.locator('button:has-text("Submit")').click(); + + for (const group of groups) { + await expect(userRow).toContainText(group); } - await page.getByTestId('user-edit-save').click(); } await context.close(); }; diff --git a/e2e/utils/controllers/enrollment.ts b/e2e/utils/controllers/enrollment.ts index 3ccacca15..7a2d16f87 100644 --- a/e2e/utils/controllers/enrollment.ts +++ b/e2e/utils/controllers/enrollment.ts @@ -3,9 +3,7 @@ import { Browser, expect, Page } from '@playwright/test'; import { defaultUserAdmin, routes } from '../../config'; import { User } from '../../types'; import { waitForBase } from '../waitForBase'; -import { waitForPromise } from '../waitForPromise'; import { loginBasic } from './login'; -import { logout } from './logout'; export const password = 'TestEnrollment1234!!'; @@ -17,41 +15,44 @@ type EnrollmentResponse = { export const createUserEnrollment = async ( browser: Browser, user: User, + groups?: string[], ): Promise => { const context = await browser.newContext(); const page = await context.newPage(); await waitForBase(page); - await page.goto(routes.base + routes.auth.login); await loginBasic(page, defaultUserAdmin); - await page.goto(routes.base + routes.admin.users); + + await page.goto(routes.base + routes.identity.users); await page.getByTestId('add-user').click(); - const formElement = page.getByTestId('add-user-form'); + await page.getByTestId('add-user-self-enrollment').click(); + const formElement = page.locator('[id="add-user-modal"]'); await formElement.waitFor({ state: 'visible' }); await formElement.getByTestId('field-username').fill(user.username); await formElement.getByTestId('field-first_name').fill(user.firstName); await formElement.getByTestId('field-last_name').fill(user.lastName); await formElement.getByTestId('field-email').fill(user.mail); await formElement.getByTestId('field-phone').fill(user.phone); - await formElement.getByTestId('field-enable_enrollment').click(); - await formElement.locator('button[type="submit"]').click(); - waitForPromise(2000); - const modalElement = page.locator('#add-user-modal'); - const enrollmentForm = modalElement.getByTestId('start-enrollment-form'); - await enrollmentForm.locator('.toggle-option').nth(1).click(); - await enrollmentForm.locator('button[type="submit"]').click(); - waitForPromise(2000); - // Copy to clipboard - const tokenStep = modalElement.locator('#enrollment-token-step'); - const tokenDiv = tokenStep.locator('.copy-field.spacer').nth(1); // field with token - const tokenP = tokenDiv.locator('p.display-element'); - const token = await tokenP.textContent(); - expect(token.length).toBeGreaterThan(0); - // close modal - await modalElement.locator('.controls button.cancel').click(); - await modalElement.waitFor({ state: 'hidden' }); - // logout - await logout(page); - await context.close(); + await formElement.getByTestId('add-user-submit').click(); + const token = await formElement.getByTestId('activation-token-field').textContent(); + await formElement.locator('button[data-variant="primary"]').click(); + if (groups) { + await page.goto(routes.base + routes.identity.users); + const userRow = page.locator('.virtual-row').filter({ hasText: user.username }); + await userRow.locator('.icon-button').click(); + await page.getByTestId('edit-groups').click(); + for (const group of groups) { + await page.locator('.item:has-text("' + group + '") .checkbox').click(); + } + await page.locator('button:has-text("Submit")').click(); + + for (const group of groups) { + await expect(userRow).toContainText(group); + } + } + if (!token) { + throw new Error('No token'); + } + return { user, token }; }; @@ -61,9 +62,9 @@ export const selectEnrollment = async (page: Page) => { }; export const setToken = async (token: string, page: Page) => { - const formElement = page.getByTestId('enrollment-token-form'); - await formElement.getByTestId('field-token').fill(token); - await page.getByTestId('enrollment-token-submit-button').click(); + await page.getByTestId('start-enrollment').click(); + await page.getByTestId('field-token').fill(token); + await page.getByTestId('page-nav-next').click(); }; export const validateData = async (user: User, page: Page) => { diff --git a/e2e/utils/controllers/groups.ts b/e2e/utils/controllers/groups.ts new file mode 100644 index 000000000..1dd1e37be --- /dev/null +++ b/e2e/utils/controllers/groups.ts @@ -0,0 +1,20 @@ +import { Browser, expect } from 'playwright/test'; + +import { defaultUserAdmin } from '../../config'; +import { waitForBase } from '../waitForBase'; +import { waitForPromise } from '../waitForPromise'; +import { loginBasic } from './login'; + +export const createGroup = async (browser: Browser, group_name: string) => { + const context = await browser.newContext(); + const page = await context.newPage(); + await waitForBase(page); + await loginBasic(page, defaultUserAdmin); + await page.getByTestId('groups').click(); + await page.getByTestId('add-new-group').click(); + await page.getByTestId('field-name').fill(group_name); + await page.getByTestId('next').click(); + await page.getByTestId('submit').click(); + await waitForPromise(1000); + expect(page.locator(':text("' + group_name + '")')).toBeVisible(); +}; diff --git a/e2e/utils/controllers/login.ts b/e2e/utils/controllers/login.ts index aa8c7d6f1..c1bbd0c12 100644 --- a/e2e/utils/controllers/login.ts +++ b/e2e/utils/controllers/login.ts @@ -1,4 +1,3 @@ -import { expect } from '@playwright/test'; import { Page } from 'playwright'; import { TOTP } from 'totp-generator'; @@ -15,26 +14,25 @@ type AuthInfo = User | Pick; export const loginBasic = async (page: Page, userInfo: AuthInfo) => { await page.goto(testsConfig.BASE_URL); await waitForRoute(page, routes.auth.login); - await page.getByTestId('login-form-username').fill(userInfo.username); - await page.getByTestId('login-form-password').fill(userInfo.password); - const responsePromise = page.waitForResponse('**/auth'); - await page.getByTestId('login-form-submit').click(); - const response = await responsePromise; - expect([200, 201].includes(response.status())).toBeTruthy(); - await waitForPromise(2000); + await page.getByTestId('field-username').fill(userInfo.username); + await page.getByTestId('field-password').fill(userInfo.password); + await page.getByTestId('sign-in').click(); + await waitForPromise(1000); }; export const loginTOTP = async (page: Page, userInfo: AuthInfo, totpSecret: string) => { - await loginBasic(page, userInfo); + await page.goto(testsConfig.BASE_URL); + await waitForRoute(page, routes.auth.login); + await page.getByTestId('field-username').fill(userInfo.username); + await page.getByTestId('field-password').fill(userInfo.password); + await page.getByTestId('sign-in').click(); await waitForRoute(page, routes.auth.totp); - const codeField = page.getByTestId('field-code'); + const codeField = await page.getByTestId('field-code'); await codeField.clear(); - const responsePromise = page.waitForResponse('**/verify'); const { otp: token } = TOTP.generate(totpSecret); - await codeField.type(token); - await page.locator('button[type="submit"]').click(); - const response = await responsePromise; - expect(response.status()).toBe(200); + await codeField.fill(token); + await page.getByTestId('submit-totp').click(); + await waitForPromise(1000); }; export const loginRecoveryCodes = async ( @@ -42,11 +40,15 @@ export const loginRecoveryCodes = async ( userInfo: AuthInfo, code: string, ): Promise => { - await loginBasic(page, userInfo); - await page.goto(routes.base + routes.auth.recovery, { - waitUntil: 'networkidle', - }); + await page.goto(testsConfig.BASE_URL); + await waitForRoute(page, routes.auth.login); + await page.getByTestId('field-username').fill(userInfo.username); + await page.getByTestId('field-password').fill(userInfo.password); + await page.getByTestId('sign-in').click(); + await page.locator('a:has-text("Use recovery codes instead")').click(); + await waitForPromise(1000); await page.getByTestId('field-code').clear(); - await page.getByTestId('field-code').fill(code.trim(), { delay: 100 }); - await page.locator('button[type="submit"]').click(); + await page.getByTestId('field-code').fill(code.trim()); + await page.getByTestId('submit-recovery-code').click(); + await waitForPromise(1000); }; diff --git a/e2e/utils/controllers/logout.ts b/e2e/utils/controllers/logout.ts index 6e78b105c..f709e9312 100644 --- a/e2e/utils/controllers/logout.ts +++ b/e2e/utils/controllers/logout.ts @@ -1,6 +1,7 @@ import { Page } from 'playwright'; export const logout = async (page: Page) => { + await page.getByTestId('avatar-icon').click(); await page.getByTestId('logout').click(); await page.waitForLoadState('load'); }; diff --git a/e2e/utils/controllers/mfa/enableEmail.ts b/e2e/utils/controllers/mfa/enableEmail.ts index 0faaeffeb..36a46676d 100644 --- a/e2e/utils/controllers/mfa/enableEmail.ts +++ b/e2e/utils/controllers/mfa/enableEmail.ts @@ -21,16 +21,16 @@ export const setupSMTP = async (browser: Browser) => { await waitForBase(page); await waitForPromise(5000); await loginBasic(page, defaultUserAdmin); - await page.goto(routes.base + routes.admin.settings); - await page.getByRole('button', { name: 'SMTP' }).click(); + await page.goto(routes.base + routes.settings.smtp); await page.getByTestId('field-smtp_server').fill('testServer.com'); await page.getByTestId('field-smtp_port').fill('543'); await page.getByTestId('field-smtp_user').fill('testuser'); await page.getByTestId('field-smtp_password').fill('test'); await page.getByTestId('field-smtp_sender').fill('test@test.com'); - const requestPromise = page.waitForRequest('**/settings'); - await page.getByRole('button', { name: 'Save changes' }).click(); - await requestPromise; + const saveButton = await page.getByTestId('save-changes'); + if (await saveButton.isEnabled()) { + await saveButton.click(); + } await waitForPromise(1000); await logout(page); await context.close(); @@ -44,17 +44,11 @@ export const enableEmailMFA = async ( const context = await browser.newContext(); const page = await context.newPage(); await waitForBase(page); - // make so app info will allow email mfa to be enabled for user await waitForPromise(5000); await loginBasic(page, user); - await page.goto(routes.base + routes.me); - await page.getByTestId('edit-user').click(); - await page.getByTestId('edit-email-mfa').scrollIntoViewIfNeeded(); - await page.getByTestId('edit-email-mfa').click(); - const requestPromise = page.waitForRequest('**/init'); - await page.getByTestId('enable-email-mfa-option').click(); - const formElement = page.locator('#register-mfa-email-form'); - await requestPromise; + await page.goto(routes.base + routes.profile); + await page.getByTestId('email-codes-row').locator('.icon-button').click(); + await page.getByTestId('enable-email').click(); await waitForPromise(2000); const secret = await extractEmailSecret(user.username); const { otp: code } = TOTP.generate(secret, { @@ -62,8 +56,7 @@ export const enableEmailMFA = async ( period: 60, }); await page.getByTestId('field-code').fill(code); - await formElement.locator('button[type="submit"]').click(); - await formElement.waitFor({ state: 'detached', timeout: 1000 }); + await page.getByTestId('submit').click(); const recovery = await acceptRecovery(page); await context.close(); return { diff --git a/e2e/utils/controllers/mfa/enableSecurityKey.ts b/e2e/utils/controllers/mfa/enableSecurityKey.ts new file mode 100644 index 000000000..046ce5ddc --- /dev/null +++ b/e2e/utils/controllers/mfa/enableSecurityKey.ts @@ -0,0 +1,59 @@ +import { Browser } from 'playwright'; + +import { routes } from '../../../config'; +import { User } from '../../../types'; +import { waitForBase } from '../../waitForBase'; +import { loginBasic } from '../login'; + +export type EnableSecurityKeyResult = { + credentialId: string; + rpId?: string; + privateKey: string; + userHandle?: string; +}; + +export const enableSecurityKey = async ( + browser: Browser, + user: User, + keyName: string, +): Promise => { + const context = await browser.newContext(); + const page = await context.newPage(); + await waitForBase(page); + await loginBasic(page, user); + await page.goto(routes.base + routes.profile); + await page.getByTestId('passkeys-row').locator('.icon-button').click(); + await page.getByTestId('add-passkey').click(); + await page.getByTestId('field-name').fill(keyName); + await page.getByTestId('submit').click(); + + const authenticator = await context.newCDPSession(page); + await authenticator.send('WebAuthn.enable'); + + const { authenticatorId } = await authenticator.send( + 'WebAuthn.addVirtualAuthenticator', + { + options: { + protocol: 'ctap2', + transport: 'usb', + hasResidentKey: true, + hasUserVerification: true, + isUserVerified: true, + }, + }, + ); + await page.waitForTimeout(2000); + await page.getByTestId('confirm-code-save').click(); + await page.getByTestId('finish-recovery-codes').click(); + const { credentials } = await authenticator.send('WebAuthn.getCredentials', { + authenticatorId, + }); + const credential = credentials[0]; + await context.close(); + return { + credentialId: credential.credentialId, + rpId: credential.rpId, + privateKey: credential.privateKey, + userHandle: credential.userHandle, + }; +}; diff --git a/e2e/utils/controllers/mfa/enableTOTP.ts b/e2e/utils/controllers/mfa/enableTOTP.ts index ba9a9d57c..0c84bcd61 100644 --- a/e2e/utils/controllers/mfa/enableTOTP.ts +++ b/e2e/utils/controllers/mfa/enableTOTP.ts @@ -6,7 +6,6 @@ import { User } from '../../../types'; import { getPageClipboard } from '../../getPageClipboard'; import { waitForBase } from '../../waitForBase'; import { waitForRoute } from '../../waitForRoute'; -import { acceptRecovery } from '../acceptRecovery'; import { loginBasic } from '../login'; export type EnableTOTPResult = { @@ -22,20 +21,26 @@ export const enableTOTP = async ( const page = await context.newPage(); await waitForBase(page); await loginBasic(page, user); - await page.goto(routes.base + routes.me); - await waitForRoute(page, routes.me); - await page.getByTestId('edit-user').click(); - await page.getByTestId('edit-totp').scrollIntoViewIfNeeded(); - await page.getByTestId('edit-totp').click(); - await page.getByTestId('enable-totp-option').click(); - await page.getByTestId('copy-totp').click(); - const totpSecret = await getPageClipboard(page); + await page.goto(routes.base + routes.profile + user.username + routes.tab.details); + await waitForRoute(page, routes.profile + user.username + routes.tab.details); + const totpContainer = await page.locator('[data-testid="totp-row"]'); + + await totpContainer.locator('.icon-button').click(); + await page.getByTestId('enable-totp').click(); + const totpSecret = + (await page.getByTestId('totp-code').locator('p').textContent()) ?? ''; + const { otp: token } = TOTP.generate(totpSecret); - const totpForm = page.getByTestId('register-totp-form'); - await totpForm.getByTestId('field-code').fill(token); - await totpForm.locator('button[type="submit"]').click(); - await totpForm.waitFor({ state: 'hidden' }); - const recovery = await acceptRecovery(page); + + await page.getByTestId('field-code').fill(token); + await page.getByTestId('submit-totp').click(); + await page.getByTestId('copy-recovery-codes').click(); + const recoveryString = await getPageClipboard(page); + const recovery = recoveryString.split('\n').filter((line) => line.trim()); + + await page.getByTestId('confirm-code-save').click(); + await page.getByTestId('finish-recovery-codes').click(); + await context.close(); return { secret: totpSecret, diff --git a/e2e/utils/controllers/openid/copyClientId.ts b/e2e/utils/controllers/openid/copyClientId.ts index e15a0a8de..50d314af2 100644 --- a/e2e/utils/controllers/openid/copyClientId.ts +++ b/e2e/utils/controllers/openid/copyClientId.ts @@ -10,9 +10,10 @@ export const copyOpenIdClientId = async (browser: Browser, clientId: number) => const page = await context.newPage(); await waitForBase(page); await loginBasic(page, defaultUserAdmin); - await page.goto(routes.base + routes.admin.openid, { waitUntil: 'networkidle' }); - await page.getByTestId(`edit-openid-client-${clientId}`).click(); - await page.getByTestId('copy-openid-client-id').click(); + await page.goto(routes.base + routes.openid_apps, { waitUntil: 'networkidle' }); + const deviceRow = page.locator('.virtual-row').nth(clientId - 1); + await deviceRow.locator('.icon-button').click(); + await page.getByTestId('copy-id').click(); const id = await getPageClipboard(page); return id; }; @@ -25,17 +26,17 @@ export const copyOpenIdClientIdAndSecret = async ( const page = await context.newPage(); await waitForBase(page); await loginBasic(page, defaultUserAdmin); - await page.goto(routes.base + routes.admin.openid, { waitUntil: 'networkidle' }); - await page - .locator('div') - .filter({ - hasText: new RegExp(`^${clientName}$`), - }) - .click(); - await page.getByTestId('copy-client-id').click(); + await page.goto(routes.base + routes.openid_apps, { waitUntil: 'networkidle' }); + const userRow = await page.locator('.virtual-row').filter({ hasText: clientName }); + await userRow.locator('.icon-button').click(); + await page.getByTestId('copy-id').click(); const id = await getPageClipboard(page); - await page.locator('.variant-copy').nth(1).click(); + + await userRow.locator('.icon-button').click(); + await page.getByTestId('copy-secret').click(); + const secret = await getPageClipboard(page); + await context.close(); return [id, secret]; }; diff --git a/e2e/utils/controllers/openid/createExternalProvider.ts b/e2e/utils/controllers/openid/createExternalProvider.ts index 277c7e5e5..c544343a2 100644 --- a/e2e/utils/controllers/openid/createExternalProvider.ts +++ b/e2e/utils/controllers/openid/createExternalProvider.ts @@ -10,14 +10,13 @@ export const createExternalProvider = async (browser: Browser, client: OpenIdCli const page = await context.newPage(); await waitForBase(page); await loginBasic(page, defaultUserAdmin); - await page.goto(routes.base + routes.admin.settings, { waitUntil: 'networkidle' }); - await page.getByRole('button', { name: 'OpenID' }).click(); - await page.locator('div#provider-settings .content-frame').click(); - await page.getByRole('button', { name: 'Custom' }).click(); - await page.getByTestId('field-base_url').fill('http://localhost:8000/'); + await page.goto(routes.base + routes.settings.tab.openid); + await page.getByTestId('connect-custom').click(); + + await page.getByTestId('field-base_url').fill(routes.base + '/'); await page.getByTestId('field-client_id').fill(client.clientID || ''); await page.getByTestId('field-client_secret').fill(client.clientSecret || ''); await page.getByTestId('field-display_name').fill(client.name); - await page.getByRole('button', { name: 'Save changes' }).click(); + await page.getByTestId('continue').click(); await context.close(); }; diff --git a/e2e/utils/controllers/openid/createOpenIdClient.ts b/e2e/utils/controllers/openid/createOpenIdClient.ts index d03c59871..34c320233 100644 --- a/e2e/utils/controllers/openid/createOpenIdClient.ts +++ b/e2e/utils/controllers/openid/createOpenIdClient.ts @@ -11,27 +11,23 @@ export const CreateOpenIdClient = async (browser: Browser, client: OpenIdClient) const page = await context.newPage(); await waitForBase(page); await loginBasic(page, defaultUserAdmin); - await page.goto(routes.base + routes.admin.openid, { waitUntil: 'networkidle' }); - await page.getByTestId('add-openid-client').click(); - const modalElement = page.locator('#openid-client-modal'); - await modalElement.waitFor({ state: 'visible' }); - const modalForm = modalElement.locator('form'); - await modalForm.getByTestId('field-name').fill(client.name); - const urls = client.redirectURL.length; - for (let i = 0; i < urls; i++) { - const isLast = i === urls - 1; - await modalForm - .getByTestId(`field-redirect_uri.${i}.url`) - .fill(client.redirectURL[i]); - if (!isLast) { - await modalForm.locator('button:has-text("Add URL")').click(); + await page.goto(routes.base + routes.openid_apps, { waitUntil: 'networkidle' }); + await page.getByTestId('add-new-app').click(); + await page.getByTestId('field-name').fill(client.name); + + for (const idx in client.redirectURL) { + page.getByTestId('field-redirect_uri[' + idx + ']').fill(client.redirectURL[idx]); + if (Number(idx) + 1 < client.redirectURL.length) { + await page.getByTestId('add-url').click(); } } + for (const scope of client.scopes) { - await modalForm.getByTestId(`field-scope-${scope}`).click(); + await page.getByTestId(`field-scope-${scope}`).click(); } + await page.getByTestId('save-settings').click(); const responsePromise = page.waitForResponse('**/oauth'); - await modalForm.locator('button[type="submit"]').click(); + const resp = await responsePromise; expect(resp.status()).toBe(201); await context.close(); diff --git a/e2e/utils/controllers/toggleUserState.ts b/e2e/utils/controllers/toggleUserState.ts index 50e2d085a..fd9deb29f 100644 --- a/e2e/utils/controllers/toggleUserState.ts +++ b/e2e/utils/controllers/toggleUserState.ts @@ -1,4 +1,5 @@ import { Browser } from 'playwright'; +import { expect } from 'playwright/test'; import { defaultUserAdmin, routes } from '../../config'; import { User } from '../../types'; @@ -10,11 +11,11 @@ export const enableUser = async (browser: Browser, user: User): Promise => const page = await context.newPage(); await waitForBase(page); await loginBasic(page, defaultUserAdmin); - await page.goto(routes.base + '/admin/users/' + user.username); - await page.getByTestId('edit-user').click(); - await page.getByTestId('status-select').locator('.select-container').click(); - await page.locator('.select-option:has-text("Active")').click(); - await page.getByTestId('user-edit-save').click(); + await page.goto(routes.base + routes.identity.users); + const userRow = page.locator('.virtual-row').filter({ hasText: user.username }); + await userRow.locator('.icon-button').click(); + await page.getByTestId('change-account-status').click(); + await expect(userRow).toContainText('Active'); await context.close(); }; @@ -23,10 +24,10 @@ export const disableUser = async (browser: Browser, user: User): Promise = const page = await context.newPage(); await waitForBase(page); await loginBasic(page, defaultUserAdmin); - await page.goto(routes.base + '/admin/users/' + user.username); - await page.getByTestId('edit-user').click(); - await page.getByTestId('status-select').locator('.select-container').click(); - await page.locator('.select-option:has-text("Disabled")').click(); - await page.getByTestId('user-edit-save').click(); + await page.goto(routes.base + routes.identity.users); + const userRow = page.locator('.virtual-row').filter({ hasText: user.username }); + await userRow.locator('.icon-button').click(); + await page.getByTestId('change-account-status').click(); + await expect(userRow).toContainText('Disabled'); await context.close(); }; diff --git a/e2e/utils/controllers/vpn/createDevice.ts b/e2e/utils/controllers/vpn/createDevice.ts index 8f0c712ae..bfb6e0a77 100644 --- a/e2e/utils/controllers/vpn/createDevice.ts +++ b/e2e/utils/controllers/vpn/createDevice.ts @@ -1,36 +1,26 @@ -import { Browser, expect } from '@playwright/test'; +import { Browser } from '@playwright/test'; import { routes } from '../../../config'; import { DeviceForm, User } from '../../../types'; -import { waitForRoute } from '../../waitForRoute'; import { loginBasic } from '../login'; export const createDevice = async (browser: Browser, user: User, device: DeviceForm) => { const context = await browser.newContext(); const page = await context.newPage(); await loginBasic(page, user); - await page.goto(routes.base + routes.me); + await page.goto(routes.base + routes.profile + user.username + routes.tab.devices); await page.getByTestId('add-device').click(); - await waitForRoute(page, routes.addDevice); - // chose manual - const choiceCard = page.locator('#setup-method-step'); - await choiceCard.waitFor({ state: 'visible' }); - await choiceCard.getByTestId('add-device-method-native-wg').click(); - await page.getByTestId('nav-next-step').click(); - const configStep = page.locator('#add-device-setup-step'); - await configStep.waitFor({ state: 'visible' }); - // fill form - await configStep.getByTestId('field-name').clear(); - await configStep.getByTestId('field-name').fill(device.name); + const modal = await page.locator('#add-user-device-modal'); + await modal.locator('.fold-button').click(); + await modal.getByTestId('client-manual').click(); + await modal.getByTestId('field-name').fill(device.name); if (device.pubKey && device.pubKey.length) { - await page.locator('.toggle-option').nth(1).click(); - await configStep.getByTestId('field-publicKey').clear(); - await configStep.getByTestId('field-publicKey').fill(device.pubKey); + await modal.getByTestId('field-genChoice-manual').click(); + await modal.getByTestId('field-publicKey').fill(device.pubKey); } - // await response - const responsePromise = page.waitForResponse('**/device/**'); - await page.getByTestId('nav-next-step').click(); - const response = await responsePromise; - expect(response.status()).toBe(201); + // const responsePromise = page.waitForResponse('**/device/**'); //TODO: broken for now. + await modal.getByTestId('continue').click(); + // const response = await responsePromise; + // expect(response.status()).toBe(201); await context.close(); }; diff --git a/e2e/utils/controllers/vpn/createNetwork.ts b/e2e/utils/controllers/vpn/createNetwork.ts index da0126919..024130a60 100644 --- a/e2e/utils/controllers/vpn/createNetwork.ts +++ b/e2e/utils/controllers/vpn/createNetwork.ts @@ -5,51 +5,91 @@ import { NetworkForm } from '../../../types'; import { waitForBase } from '../../waitForBase'; import { loginBasic } from '../login'; -export const createNetwork = async (browser: Browser, network: NetworkForm) => { +export const createRegularLocation = async (browser: Browser, network: NetworkForm) => { const context = await browser.newContext(); const page = await context.newPage(); await waitForBase(page); await loginBasic(page, defaultUserAdmin); - await page.goto(routes.base + routes.admin.wizard); - await page.getByTestId('setup-network').click(); - const navNext = page.getByTestId('wizard-next'); - await page.getByTestId('setup-option-manual').click(); - await navNext.click(); - - // fill form - for (const key of Object.keys(network).filter((key) => key !== 'location_mfa_mode')) { - const field = page.getByTestId(`field-${key}`); - await field.clear(); - await field.type(network[key]); + await page.goto(routes.base + routes.locations); + await page.getByTestId('add-location').click(); + await page.getByTestId('add-regular-location').click(); + + await page.getByTestId('field-name').fill(network.name); + await page.getByTestId('field-endpoint').fill(network.endpoint); + await page.getByTestId('field-port').fill(network.port); + await page.getByTestId('continue').click(); + + await page.getByTestId('field-address').fill(network.address); + + if (network.allowed_ips) { + let addresses = ''; + for (const ip of network.allowed_ips) { + addresses += ip + ','; + } + addresses = addresses.slice(0, -1); + await page.getByTestId('field-allowed_ips').fill(addresses); + await page.getByTestId('continue').click(); } - // select location MFA mode + + await page.getByTestId('continue').click(); + if (network.location_mfa_mode) { - const mfaModeSelect = page.locator('div.location-mfa-mode-select'); - let mode: number; // TODO: do it better switch (network.location_mfa_mode) { - case 'none': - mode = 0; - break; case 'internal': - mode = 1; + await page.getByTestId('enforce-internal-mfa').click(); break; case 'external': - mode = 2; + await page.getByTestId('enforce-external-mfa').click(); break; default: - mode = 0; + await page.getByTestId('do-not-enforce-mfa').click(); break; } - // 0 - do not enforce mfa - // 1 - internal mfa - // 2 - external mfa - const mfaMode = mfaModeSelect.locator(`div.location-mfa-mode`).nth(mode); - await mfaMode.click(); } + await page.getByTestId('finish').click(); + + await page.getByTestId('acl-continue').click(); + await page.getByTestId('create-location').click(); + + await page.waitForURL('**/locations'); + + await expect(page.url()).toBe(routes.base + routes.locations); + await context.close(); +}; + +export const createServiceLocation = async (browser: Browser, network: NetworkForm) => { + const context = await browser.newContext(); + const page = await context.newPage(); + await waitForBase(page); + await loginBasic(page, defaultUserAdmin); + await page.goto(routes.base + routes.locations); + await page.getByTestId('add-location').click(); + await page.getByTestId('add-service-location').click(); + + await page.getByTestId('field-name').fill(network.name); + await page.getByTestId('field-address').fill(network.endpoint); + await page.getByTestId('field-port').fill(network.port); + + await page.getByTestId('continue').click(); + + await page.getByTestId('field-endpoint').fill(network.address); + + if (network.allowed_ips) { + let addresses = ''; + for (const ip of network.allowed_ips) { + addresses += ip + ','; + } + addresses = addresses.slice(0, -1); + await page.getByTestId('field-allowed_ips').fill(addresses); + await page.getByTestId('continue').click(); + } + + await page.getByTestId('continue').click(); + await page.getByTestId('finish').click(); + await page.getByTestId('acl-continue').click(); + await page.getByTestId('create-location').click(); - const responseCreateNetworkPromise = page.waitForResponse('**/network'); - await navNext.click(); - const response = await responseCreateNetworkPromise; - expect(response.status()).toBe(201); + await page.waitForURL('**/locations'); + await expect(page.url()).toBe(routes.base + routes.locations); await context.close(); }; diff --git a/e2e/utils/controllers/vpn/createNetworkDevice.ts b/e2e/utils/controllers/vpn/createNetworkDevice.ts index 638dfad4f..a346e9b09 100644 --- a/e2e/utils/controllers/vpn/createNetworkDevice.ts +++ b/e2e/utils/controllers/vpn/createNetworkDevice.ts @@ -2,6 +2,8 @@ import { Browser, expect, Locator, Page } from '@playwright/test'; import { routes } from '../../../config'; import { EditNetworkDeviceForm, NetworkDeviceForm, User } from '../../../types'; +import { getPageClipboard } from '../../getPageClipboard'; +import { waitForPromise } from '../../waitForPromise'; import { waitForRoute } from '../../waitForRoute'; import { loginBasic } from '../login'; @@ -39,7 +41,7 @@ export const doAction = async ({ await editMenu.getByRole('button', { name: action }).click(); }; -export const createNetworkDevice = async ( +export const createNetworkCLIDevice = async ( browser: Browser, user: User, device: NetworkDeviceForm, @@ -47,30 +49,20 @@ export const createNetworkDevice = async ( const context = await browser.newContext(); const page = await context.newPage(); await loginBasic(page, user); - await page.goto(routes.base + routes.admin.devices, { + await page.goto(routes.base + routes.network_devices, { waitUntil: 'networkidle', }); - await page.getByRole('button', { name: 'Add new' }).click(); - const configCard = page.locator('#add-standalone-device-modal'); - await configCard.waitFor({ state: 'visible' }); - // select native-wg method - await page.getByTestId('standalone-device-choice-card-manual').click(); - await configCard.getByRole('button', { name: 'Next' }).click(); - const deviceNameInput = configCard.getByTestId('field-name'); - await deviceNameInput.fill(device.name); - if (device.description && device.description.length > 0) { - const deviceDescriptionInput = page.getByTestId('field-description'); - await deviceDescriptionInput.fill(device.description); - } - if (device.pubKey && device.pubKey.length) { - await configCard.locator('.toggle-option').nth(1).click(); - const devicePublicKeyInput = configCard.getByTestId('field-wireguard_pubkey'); - await devicePublicKeyInput.fill(device.pubKey); + await page.getByTestId('add-device').click(); + await page.getByTestId('defguard-cli').click(); + await page.getByTestId('field-name').fill(device.name); + if (device.description) { + await page.getByTestId('field-description').fill(device.description); } - const responsePromise = page.waitForResponse('**/device/network'); - await page.getByRole('button', { name: 'Add Device' }).click(); - const response = await responsePromise; - expect(response.status()).toBe(201); + await page.getByTestId('submit').click(); + await page.getByTestId('finish').click(); + await waitForPromise(1000); + const deviceRow = page.locator('.virtual-row').filter({ hasText: device.name }); + await expect(deviceRow).toContainText('Awaiting Setup'); await context.close(); }; @@ -82,21 +74,28 @@ export const startNetworkDeviceEnrollment = async ( const context = await browser.newContext(); const page = await context.newPage(); await loginBasic(page, user); - await page.goto(routes.base + routes.admin.devices); - await page.getByRole('button', { name: 'Add new' }).click(); - const configCard = page.locator('#add-standalone-device-modal'); - await configCard.getByRole('button', { name: 'Next' }).click(); - const deviceNameInput = configCard.getByTestId('field-name'); - await deviceNameInput.fill(device.name); - if (device.description && device.description.length > 0) { - const deviceDescriptionInput = page.getByTestId('field-description'); - await deviceDescriptionInput.fill(device.description); + await page.goto(routes.base + routes.network_devices); + await page.getByTestId('add-device').click(); + await page.getByTestId('wireguard-client').click(); + + await page.getByTestId('field-name').fill(device.name); + if (device.description) { + await page.getByTestId('field-description').fill(device.description); } - const responsePromise = page.waitForResponse('**/device/network'); - await page.getByRole('button', { name: 'Add Device' }).click(); - const response = await responsePromise; - expect(response.status()).toBe(200); - const tokenCommand = await page.locator('.expanded-content').innerText(); + if (device.pubKey) { + await page.getByTestId('field-generateKeys-false').click(); + await page.getByTestId('field-wireguard_pubkey').fill(device.pubKey); + } + await page.getByTestId('submit').click(); + await waitForPromise(2000); + await page.getByTestId('copy-config').click(); + + await page.getByTestId('finish').click(); + + const deviceRow = page.locator('.virtual-row').filter({ hasText: device.name }); + await expect(deviceRow).toContainText('Ready'); + + const tokenCommand = await getPageClipboard(page); await context.close(); return tokenCommand; }; diff --git a/e2e/utils/controllers/webhook.ts b/e2e/utils/controllers/webhook.ts new file mode 100644 index 000000000..54b618e0d --- /dev/null +++ b/e2e/utils/controllers/webhook.ts @@ -0,0 +1,30 @@ +import { Browser } from 'playwright'; + +import { defaultUserAdmin, routes } from '../../config'; +import { waitForBase } from '../waitForBase'; +import { loginBasic } from './login'; + +export const createWebhook = async ( + browser: Browser, + url: string, + description: string, + secret_token?: string, +): Promise => { + const context = await browser.newContext(); + const page = await context.newPage(); + await waitForBase(page); + await loginBasic(page, defaultUserAdmin); + await page.goto(routes.base + routes.webhooks); + await page.getByTestId('add-new-webhook').click(); + await page.getByTestId('field-url').fill(url); + await page.getByTestId('field-description').fill(description); + if (secret_token) { + await page.getByTestId('field-token').fill(secret_token); + } else { + await page.getByTestId('field-token').fill(' '); + } + await page.getByTestId('field-on_user_created').click(); + await page.getByTestId('submit').click(); + + await context.close(); +}; diff --git a/web/.biomeignore b/web/.biomeignore deleted file mode 100644 index 8260f01a7..000000000 --- a/web/.biomeignore +++ /dev/null @@ -1,2 +0,0 @@ -src/i18n/*.ts -src/i18n/*.tsx diff --git a/web/.browserslistrc b/web/.browserslistrc new file mode 100644 index 000000000..549f14c02 --- /dev/null +++ b/web/.browserslistrc @@ -0,0 +1 @@ +>0.3%, last 2 versions, since 2025, not op_mini all, not dead diff --git a/web/.editorconfig b/web/.editorconfig index 57516b7ac..73f410322 100644 --- a/web/.editorconfig +++ b/web/.editorconfig @@ -7,23 +7,5 @@ insert_final_newline = true indent_style = space indent_size = 2 -[*.{ts,tsx}] -indent_style = space -indent_size = 2 -rulers = 90 - -[*.{scss}] -indent_style = space -indent_size = 2 -rulers = 90 - -[*.{html}] -indent_style = space -indent_size = 2 +[*.{ts,tsx,js,jsx,scss,html,json,yaml}] rulers = 90 - -[*.{json,yaml}] -insert_final_newline = true -indent_style = space -indent_size = 2 -rulers = 80 diff --git a/web/.gitattributes b/web/.gitattributes new file mode 100644 index 000000000..ee7f45a36 --- /dev/null +++ b/web/.gitattributes @@ -0,0 +1,36 @@ +* text=auto eol=lf + +*.js text eol=lf +*.jsx text eol=lf +*.ts text eol=lf +*.tsx text eol=lf +*.css text eol=lf +*.scss text eol=lf +*.json text eol=lf +*.html text eol=lf +*.md text eol=lf +*.yml text eol=lf +*.yaml text eol=lf +*.lock text eol=lf + +*.woff binary +*.woff2 binary +*.ttf binary +*.otf binary +*.eot binary +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.webp binary +*.avif binary +*.ico binary +*.mp4 binary +*.webm binary +*.ogg binary +*.mp3 binary +*.wav binary +*.pdf binary +*.zip binary +*.tar binary +*.gz binary diff --git a/web/.gitignore b/web/.gitignore index 88e111584..175f4eee7 100644 --- a/web/.gitignore +++ b/web/.gitignore @@ -1,2 +1,29 @@ -*.local* -./*.env +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local +project.inlang/cache +.env.development + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? +.tanstack +.cfft* +blueprint-templates diff --git a/web/.npmrc b/web/.npmrc deleted file mode 100644 index 319e41e69..000000000 --- a/web/.npmrc +++ /dev/null @@ -1 +0,0 @@ -strict-peer-dependencies=false diff --git a/web/.nvmrc b/web/.nvmrc deleted file mode 100644 index 53a256a2e..000000000 --- a/web/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -v24.7 diff --git a/web/.prettierignore b/web/.prettierignore index 74dbecd59..402ea0086 100644 --- a/web/.prettierignore +++ b/web/.prettierignore @@ -1,4 +1,3 @@ -/src/i18n/*.ts -/src/i18n/*.tsx /src/**/*.tsx /src/**/*.ts +/src/**/*.js diff --git a/web/.stylelintrc.json b/web/.stylelintrc.json new file mode 100644 index 000000000..6a4e21333 --- /dev/null +++ b/web/.stylelintrc.json @@ -0,0 +1,10 @@ +{ + "extends": ["stylelint-config-standard-scss"], + "plugins": ["stylelint-scss"], + "rules": { + "at-rule-no-unknown": null, + "scss/at-rule-no-unknown": true, + "custom-property-empty-line-before": null, + "value-keyword-case": null + } +} diff --git a/web/.typesafe-i18n.json b/web/.typesafe-i18n.json deleted file mode 100644 index 28148cbaf..000000000 --- a/web/.typesafe-i18n.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "adapter": "react", - "$schema": "https://unpkg.com/typesafe-i18n@5.26.2/schema/typesafe-i18n.json", - "baseLocale": "en" -} diff --git a/web/README.md b/web/README.md index c88e39e5b..4dcad1f91 100644 --- a/web/README.md +++ b/web/README.md @@ -1,73 +1,73 @@ -# Defguard frontend +# React + TypeScript + Vite -React.js based, web user interface for Defguard project. +This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. -## Run development server +Currently, two official plugins are available: -Install dependencies +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) (or [oxc](https://oxc.rs) when used in [rolldown-vite](https://vite.dev/guide/rolldown)) for Fast Refresh +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh -```bash - pnpm install -``` - -Start the dev server - -```bash - pnpm run dev -``` - -You can configure API proxy by changing `target` key value under `/api` path within `vite.config.ts` file. - -## Build docker image +## React Compiler -```bash -docker build . -``` - -## Linting - -For linting this project uses [prettier](https://www.npmjs.com/package/prettier), [eslint](https://www.npmjs.com/package/eslint) and [stylelint](https://stylelint.io/) +The React Compiler is currently not compatible with SWC. See [this issue](https://github.com/vitejs/vite-plugin-react/issues/428) for tracking the progress. -#### Available commands +## Expanding the ESLint configuration -Check linting in all supported files ( both prettier and eslint ) +If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: -```bash -pnpm run lint -``` +```js +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... -Fix all autofixable problems with eslint + // Remove tseslint.configs.recommended and replace with this + tseslint.configs.recommendedTypeChecked, + // Alternatively, use this for stricter rules + tseslint.configs.strictTypeChecked, + // Optionally, add this for stylistic rules + tseslint.configs.stylisticTypeChecked, -```bash -pnpm run eslint-fix + // Other configs... + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) ``` -Fix all autofixable probles with prettier - -```bash -pnpm run prettier-fix +You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: + +```js +// eslint.config.js +import reactX from 'eslint-plugin-react-x' +import reactDom from 'eslint-plugin-react-dom' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + // Enable lint rules for React + reactX.configs['recommended-typescript'], + // Enable lint rules for React DOM + reactDom.configs.recommended, + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) ``` - -Fix all autofixable problems with pretter and eslint - -```bash -pnpm fix-lint -``` - -#### Adding new SVG components - -Move .svg files into `src/shared/images/svg` then use command: - -```bash -pnpm parse-svgs -``` - -This will generate new components within `src/shared/components/svg`, they can be used as a regular components. Also this command doesn't replace or modify already existing files. - -## Conventional Commits - -Using [commitlint](https://commitlint.js.org/#/) with this [config](https://github.com/conventional-changelog/commitlint/tree/master/%40commitlint/config-conventional). - -## Versioning - -Using [standard-version](https://github.com/conventional-changelog/standard-version) diff --git a/web/biome.json b/web/biome.json index 2bcbfd1d7..832d57347 100644 --- a/web/biome.json +++ b/web/biome.json @@ -1,21 +1,29 @@ { - "$schema": "https://biomejs.dev/schemas/2.2.2/schema.json", - "vcs": { "enabled": false, "clientKind": "git", "useIgnoreFile": false }, + "$schema": "https://biomejs.dev/schemas/2.3.11/schema.json", + "vcs": { + "enabled": false, + "clientKind": "git", + "useIgnoreFile": false + }, "files": { "ignoreUnknown": false, - "includes": ["src/**", "!src/i18n", "!**/svg"] + "includes": [ + "src/**", + "!src/messages", + "!src/paraglide/**/*.js", + "!src/routeTree.gen.ts" + ] }, "formatter": { "enabled": true, "formatWithErrors": false, - "indentStyle": "space", - "indentWidth": 2, - "lineEnding": "lf", - "lineWidth": 90, "attributePosition": "auto", "bracketSameLine": false, "bracketSpacing": true, "expand": "auto", + "lineEnding": "lf", + "lineWidth": 90, + "indentStyle": "space", "useEditorconfig": true }, "linter": { @@ -23,47 +31,26 @@ "rules": { "recommended": true, "a11y": "off", - "complexity": { - "noBannedTypes": "error", - "noUselessTypeConstraint": "error" - }, "correctness": { - "noChildrenProp": "error", - "noPrecisionLoss": "error", - "noUnusedVariables": "error", - "useExhaustiveDependencies": "error", - "useHookAtTopLevel": "error", - "useJsxKeyInIterable": "error", "useUniqueElementIds": "off" }, - "security": { "noDangerouslySetInnerHtmlWithChildren": "error" }, "style": { - "noNamespace": "error", - "noNonNullAssertion": "error", - "useArrayLiterals": "error", - "useAsConstAssertion": "error", - "useBlockStatements": "off", - "useLiteralEnumMembers": "off" + "useLiteralEnumMembers": "off", + "useBlockStatements": "off" }, "suspicious": { - "noCommentText": "error", - "noDuplicateJsxProps": "error", - "noExplicitAny": "error", - "noExtraNonNullAssertion": "error", - "noMisleadingInstantiator": "error", - "noUnsafeDeclarationMerging": "error", "noArrayIndexKey": "off" } } }, "javascript": { "formatter": { + "quoteStyle": "single", "jsxQuoteStyle": "double", "quoteProperties": "asNeeded", "trailingCommas": "all", "semicolons": "always", "arrowParentheses": "always", - "quoteStyle": "single", "attributePosition": "auto", "bracketSameLine": false, "bracketSpacing": true @@ -71,14 +58,10 @@ }, "assist": { "enabled": true, - "actions": { "source": { "organizeImports": "on" } } - }, - "overrides": [ - { - "includes": ["src/shared/links.ts"], - "formatter": { - "enabled": false + "actions": { + "source": { + "organizeImports": "on" } } - ] + } } diff --git a/web/index.html b/web/index.html index 33c26718f..5917d0880 100644 --- a/web/index.html +++ b/web/index.html @@ -4,36 +4,34 @@ - - - - + + + Defguard - - - - + + + + - - - - - - - - - - - - + + + + + + + + + + + +
-
-
+
- \ No newline at end of file + diff --git a/web/messages/en/activity.json b/web/messages/en/activity.json new file mode 100644 index 000000000..e13c9eefb --- /dev/null +++ b/web/messages/en/activity.json @@ -0,0 +1,78 @@ +{ + "$schema": "https://inlang.com/schema/inlang-message-format", + "activity_event_recovery_code_used": "Recovery code used", + "activity_event_user_login": "User login", + "activity_event_user_login_failed": "User login failed", + "activity_event_user_mfa_login": "User MFA login", + "activity_event_user_mfa_login_failed": "User MFA login failed", + "activity_event_user_logout": "User logout", + "activity_event_user_added": "User added", + "activity_event_user_modified": "User modified", + "activity_event_user_removed": "User removed", + "activity_event_user_groups_modified": "User groups modified", + "activity_event_mfa_disabled": "MFA disabled", + "activity_event_user_mfa_disabled": "User MFA disabled", + "activity_event_mfa_totp_enabled": "MFA TOTP enabled", + "activity_event_mfa_totp_disabled": "MFA TOTP disabled", + "activity_event_mfa_email_enabled": "MFA email enabled", + "activity_event_mfa_email_disabled": "MFA email disabled", + "activity_event_mfa_security_key_added": "MFA security key added", + "activity_event_mfa_security_key_removed": "MFA security key removed", + "activity_event_device_added": "Device added", + "activity_event_device_modified": "Device modified", + "activity_event_device_removed": "Device removed", + "activity_event_network_device_added": "Network device added", + "activity_event_network_device_modified": "Network device modified", + "activity_event_network_device_removed": "Network device removed", + "activity_event_activity_log_stream_created": "Activity log stream created", + "activity_event_activity_log_stream_modified": "Activity log stream modified", + "activity_event_activity_log_stream_removed": "Activity log stream removed", + "activity_event_vpn_client_connected": "VPN client connected", + "activity_event_vpn_client_disconnected": "VPN client disconnected", + "activity_event_vpn_client_connected_mfa": "VPN client connected with MFA", + "activity_event_vpn_client_disconnected_mfa": "VPN client disconnected with MFA", + "activity_event_vpn_client_mfa_failed": "VPN client MFA failed", + "activity_event_enrollment_token_added": "Enrollment token added", + "activity_event_enrollment_started": "Enrollment started", + "activity_event_enrollment_device_added": "Enrollment device added", + "activity_event_enrollment_completed": "Enrollment completed", + "activity_event_password_reset_requested": "Password reset requested", + "activity_event_password_reset_started": "Password reset started", + "activity_event_password_reset_completed": "Password reset completed", + "activity_event_vpn_location_added": "VPN location added", + "activity_event_vpn_location_removed": "VPN location removed", + "activity_event_vpn_location_modified": "VPN location modified", + "activity_event_api_token_added": "API token added", + "activity_event_api_token_removed": "API token removed", + "activity_event_api_token_renamed": "API token renamed", + "activity_event_open_id_app_added": "OpenID app added", + "activity_event_open_id_app_removed": "OpenID app removed", + "activity_event_open_id_app_modified": "OpenID app modified", + "activity_event_open_id_app_state_changed": "OpenID app state changed", + "activity_event_open_id_provider_removed": "OpenID provider removed", + "activity_event_open_id_provider_modified": "OpenID provider modified", + "activity_event_settings_updated": "Settings updated", + "activity_event_settings_updated_partial": "Settings partially updated", + "activity_event_settings_default_branding_restored": "Default branding restored", + "activity_event_groups_bulk_assigned": "Groups bulk assigned", + "activity_event_group_added": "Group added", + "activity_event_group_modified": "Group modified", + "activity_event_group_removed": "Group removed", + "activity_event_group_member_added": "Group member added", + "activity_event_group_member_removed": "Group member removed", + "activity_event_group_members_modified": "Group members modified", + "activity_event_web_hook_added": "Webhook added", + "activity_event_web_hook_modified": "Webhook modified", + "activity_event_web_hook_removed": "Webhook removed", + "activity_event_web_hook_state_changed": "Webhook state changed", + "activity_event_authentication_key_added": "Authentication key added", + "activity_event_authentication_key_removed": "Authentication key removed", + "activity_event_authentication_key_renamed": "Authentication key renamed", + "activity_event_password_changed": "Password changed", + "activity_event_password_changed_by_admin": "Password changed by admin", + "activity_event_password_reset": "Password reset", + "activity_event_client_configuration_token_added": "Client configuration token added", + "activity_event_user_snat_binding_added": "User SNAT binding added", + "activity_event_user_snat_binding_modified": "User SNAT binding modified", + "activity_event_user_snat_binding_removed": "User SNAT binding removed" +} diff --git a/web/messages/en/auth.json b/web/messages/en/auth.json new file mode 100644 index 000000000..e690a586c --- /dev/null +++ b/web/messages/en/auth.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://inlang.com/schema/inlang-message-format", + "login_main_title": "Welcome to Defguard", + "login_main_subtitle": "Please enter your credentials to enter your account", + "login_main_forgot": "If you forgot your password, contact your organization's administrator.", + "login_main_attempts_info": "Too many login attempts. Please try again later.", + "login_error_invalid": "username or password is incorrect.", + "login_mfa_title": "Two-factor authentication", + "login_totp_subtitle": "Use code from your authentication app to proceed.", + "login_totp_label": "Authentication Code", + "login_mfa_alternative_recovery": "recovery codes", + "login_mfa_alternative_email": "email", + "login_mfa_alternative_passkey": "Passkey", + "login_mfa_alternative_totp": "Authenticator app instead", + "login_mfa_alternative_back": "Back to login", + "login_mfa_use_instead": "Use {method} instead", + "login_mfa_recovery_subtitle": "Enter one of active recovery codes bellow.", + "login_mfa_recovery_label": "Recovery Code", + "login_mfa_passkey_subtitle": "Please use one of your Passkeys to log in.", + "login_mfa_passkey_button": "Login with Passkey", + "login_mfa_email_subtitle": "Use code sent to your email to proceed." +} diff --git a/web/messages/en/common.json b/web/messages/en/common.json new file mode 100644 index 000000000..299403d00 --- /dev/null +++ b/web/messages/en/common.json @@ -0,0 +1,53 @@ +{ + "$schema": "https://inlang.com/schema/inlang-message-format", + "test_placeholder": "Placeholder text", + "test_placeholder_long": "Placeholder text - Nam viverra, urna a rhoncus scelerisque, dolor nunc commodo ante, non ullamcorper quam elit a nunc. Aenean elementum sed lorem eu facilisis.", + "test_placeholder_extreme": "Placeholder text - Lorem ipsum dolor sit amet, consectetur adipiscing elit. In euismod pretium convallis. Integer commodo nunc et orci vulputate, vel euismod nunc faucibus. Morbi non lacinia tellus. Quisque egestas hendrerit rutrum. Cras vel ex at metus aliquet convallis. Pellentesque volutpat velit orci, in suscipit lectus ultricies eget. Mauris rhoncus tempor hendrerit. Nam viverra, urna a rhoncus scelerisque, dolor nunc commodo ante, non ullamcorper quam elit a nunc. Aenean elementum sed lorem eu facilisis.", + "misc_or": "or", + "misc_and": "and", + "misc_for": "for", + "misc_clipboard_copy": "Copied to clipboard!", + "misc_secret": "Secret", + "misc_active": "Active", + "misc_disabled": "Disabled", + "controls_connect": "Connect", + "controls_search": "Search", + "controls_accept": "Accept", + "controls_dont_allow": "Don't allow", + "controls_finish": "Finish", + "controls_send_email": "Send email", + "controls_copy": "Copy", + "controls_download": "Download", + "controls_rename": "Rename", + "controls_close": "Close", + "controls_back": "Back", + "controls_delete": "Delete", + "controls_edit": "Edit", + "controls_logout": "Logout", + "controls_keep": "Keep", + "controls_submit": "Submit", + "controls_cancel": "Cancel", + "controls_save": "Save", + "controls_save_changes": "Save changes", + "controls_sign_in": "Sign in", + "controls_enable": "Enable", + "controls_disable": "Disable", + "controls_next": "Next", + "controls_continue": "Continue", + "controls_verify_code": "Verify code", + "controls_complete": "Complete", + "controls_copy_clipboard": "Copy to clipboard", + "state_disabled": "Disabled", + "state_warning": "Warning", + "state_active": "Active", + "state_default": "Default", + "state_enabled": "Enabled", + "state_step": "Step {step}", + "state_pending": "Pending", + "state_enrolled": "Enrolled", + "col_created_at": "Created at", + "search_empty_common_title": "Nothing is found.", + "search_empty_common_subtitle": "Try adjusting your filters or search terms.", + "search_users": "Find users", + "select_users_all": "All users" +} diff --git a/web/messages/en/components.json b/web/messages/en/components.json new file mode 100644 index 000000000..1e418adcd --- /dev/null +++ b/web/messages/en/components.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://inlang.com/schema/inlang-message-format", + "cmp_selection_section_all": "Select all", + "cmp_selection_section_selected_filter": "Show selected only", + "cmp_selection_section_search_placeholder": "Filter/Search", + "cmp_selection_section_empty_title": "Nothing was found.", + "cmp_selection_section_empty_subtitle": "Please try another search request.", + "cmp_openid_scope_email": "Email", + "cmp_openid_scope_openid": "OpenID", + "cmp_openid_scope_phone": "Phone", + "cmp_openid_scope_profile": "Profile", + "cmp_openid_scope_groups": "Groups", + "cmp_nav_group_vpn": "VPN", + "cmp_nav_group_identity": "Identity & Access", + "cmp_nav_group_integrations": "Applications & Integration", + "cmp_nav_group_admin": "Administration", + "cmp_nav_group_firewall": "Firewall", + "cmp_nav_item_rules": "Rules", + "cmp_nav_item_destinations": "Destinations", + "cmp_nav_item_aliases": "Aliases", + "cmp_nav_item_users": "Users", + "cmp_nav_item_groups": "Groups", + "cmp_nav_item_network_devices": "Network Devices", + "cmp_nav_item_openid": "OpenID Apps", + "cmp_nav_item_webhooks": "Webhooks", + "cmp_nav_item_locations": "Locations", + "cmp_nav_item_overview": "VPN Overview", + "cmp_nav_item_settings": "Settings", + "cmp_nav_item_activity_log": "Activity log", + "cmp_webhook_event_user_delete": "User deleted", + "cmp_webhook_event_user_add": "New user created", + "cmp_webhook_event_user_edit": "User modified", + "cmp_webhook_event_user_hw": "User Yubikey provision", + "cmp_file_upload_button": "Upload file", + "cmp_file_upload_unknown_name": "Unknown filename", + "cmp_sync_behavior_target_all": "All", + "cmp_sync_behavior_target_users": "Users", + "cmp_sync_behavior_target_groups": "Groups", + "cmp_copy_button": "Copy to clipboard", + "cmp_copy_button_tooltip": "Content copied to clipboard." +} diff --git a/web/messages/en/form.json b/web/messages/en/form.json new file mode 100644 index 000000000..55776dd0e --- /dev/null +++ b/web/messages/en/form.json @@ -0,0 +1,55 @@ +{ + "$schema": "https://inlang.com/schema/inlang-message-format", + "form_min_value": "Minimal value of {value} required", + "form_error_license": "License is not valid", + "form_error_file_format": "Invalid file format", + "form_error_file_contents": "File content is not valid", + "form_error_ip_or_domain": "Only valid IP or domain is allowed", + "form_error_port_max": "Port exceeds maximum value", + "form_error_len": "Required length {length}", + "form_error_name_reserved": "Name already taken", + "form_error_email": "Enter valid email", + "form_error_required": "Field is required", + "form_error_token": "Token is not valid", + "form_error_invalid": "Field is invalid", + "form_error_forbidden_char": "Field contains forbidden characters", + "form_error_username_taken": "Username is already in use", + "form_error_email_reserved": "Email is already in use", + "form_error_max_len": "Maximum length of {length} exceeded", + "form_error_min_len": "Minimal length of {length} required", + "form_error_code": "Incorrect code. Please check and try again.", + "form_error_invalid_key": "Invalid key format", + "form_error_ip_invalid": "IP is invalid", + "form_error_ip_reserved": "IP is not available", + "password_form_check_title": "Your password must include:", + "password_form_check_number": "At least one number required", + "password_form_check_special": "At least one special character", + "password_form_check_lowercase": "At least one lowercase character", + "password_form_check_uppercase": "At least one uppercase character", + "password_form_check_minimum": "Minimum length of 8", + "password_form_check_repeat_match": "Password's doesn't match", + "password_form_special_error": "Password doesn't meet the requirements. Please check the list below.", + "form_label_auth_code": "Authentication Code", + "form_label_verification_code": "Verification Code", + "form_label_username": "Username", + "form_label_password": "Password", + "form_label_first_name": "First Name", + "form_label_last_name": "Last Name", + "form_label_phone": "Phone Number", + "form_label_email": "Email", + "form_label_user_groups": "User Groups", + "form_label_current_password": "Current Password", + "form_label_new_password": "New Password", + "form_label_confirm_new_password": "Confirm New Password", + "form_label_url": "URL", + "form_label_token": "Token", + "form_label_device_name": "Device name", + "form_label_key": "Key", + "form_label_name": "Name", + "form_label_public_key": "Public key", + "form_label_device_public_key": "Device public key (wireguard)", + "form_label_type": "Type", + "form_label_description": "Description", + "form_label_secret_token": "Secret Token", + "form_label_webhook_url": "Webhook URL" +} diff --git a/web/messages/en/groups.json b/web/messages/en/groups.json new file mode 100644 index 000000000..b89bf6757 --- /dev/null +++ b/web/messages/en/groups.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://inlang.com/schema/inlang-message-format", + "groups_title": "Groups", + "groups_table_title": "All groups", + "groups_add": "Add new group", + "groups_search_placeholder": "Find group", + "groups_col_name": "Group name", + "groups_col_users_count": "Added users", + "groups_col_type": "Type", + "groups_col_locations": "Used in locations", + "groups_type_admin": "Admin", + "groups_type_user": "User" +} diff --git a/web/messages/en/modal.json b/web/messages/en/modal.json new file mode 100644 index 000000000..21237ecf7 --- /dev/null +++ b/web/messages/en/modal.json @@ -0,0 +1,113 @@ +{ + "$schema": "https://inlang.com/schema/inlang-message-format", + "modal_change_password_title": "Change password", + "modal_change_password_submit": "Change password", + "modal_delete_authorized_app_title": "Delete authorized app", + "modal_delete_authorized_app_content_1": "Are you sure you want to remove this application? If you do, you won't be able to use it to log in with defguard anymore.", + "modal_delete_authorized_app_content_2": "Please note: until you log out from this application, it will remain signed in with DefGuard.", + "modal_mfa_enable_email_title": "Email MFA setup", + "modal_mfa_enable_email_verification": "Email verification", + "modal_mfa_enable_email_content": "To setup your MFA enter the code that was sent to your account email: {email}", + "modal_mfa_enable_email_resend": "Resend email", + "modal_mfa_enable_totp_title": "Authentication App Setup", + "modal_mfa_enable_totp_step_1_title": "Scan QR code", + "modal_mfa_enable_totp_step_1_content": "To set up Multi-Factor Authentication (MFA), simply scan this QR code using an authenticator app (like Google Authenticator, Microsoft Authenticator, or any other you prefer).", + "modal_mfa_enable_totp_qr_problem": "Can't scan QR code? Enter code manually in the app.", + "modal_mfa_enable_totp_step_2_title": "Enter 6-digit code from authentication app", + "modal_mfa_add_passkey_title": "Add passkey", + "modal_mfa_add_passkey_content": "Passkeys can be used as your second authentication factor instead of a verification code. Learn how to set them up in the documentation.", + "modal_mfa_add_passkey_label": "Passkey name", + "modal_recovery_codes_download_title": "Download recovery codes", + "modal_recovery_codes_download_cta_download": "Download codes", + "modal_recovery_codes_download_confirm": "I have saved my codes", + "modal_recovery_codes_explain": "Recovery codes are your backup access. Store them securely (e.g. in a password manager like LastPass or Bitwarden) in case you lose your authenticator app.", + "modal_recovery_codes_error": "Confirm you have saved your recovery codes.", + "modal_add_user_device_title_add": "Add new device", + "modal_add_user_device_title_manual": "Manual setup", + "modal_add_user_device_title_save": "Save configuration files", + "modal_add_user_device_show_advanced": "Show advanced option", + "modal_add_user_device_hide_advanced": "Hide advanced option", + "modal_add_user_device_start_client_title": "Client Activation", + "modal_add_user_device_start_client_subtitle": "Use the defguard client to set up your device. Easily configure it with a single token or by scanning a QR code.", + "modal_add_user_device_client_title": "Client activation", + "modal_add_user_device_client_subtitle": "Select the activation type that matches the device where you installed the defguard app/client.", + "modal_add_user_device_client_desktop_title": "Desktop client", + "modal_add_user_device_client_desktop_automatic": "Automatic configuration", + "modal_add_user_device_client_desktop_automatic_explain_1": "Click the button below for automatic configuration.", + "modal_add_user_device_client_desktop_automatic_explain_2": "Before using this option make sure the Defguard desktop client is already installed.", + "modal_add_user_device_client_desktop_manual_title": "Automatic configuration", + "modal_add_user_device_client_desktop_manual_subtitle": "Activate your desktop client manually by entering the URL and token you see bellow.", + "modal_add_user_device_client_desktop_one_click": "One-Click Configuration", + "modal_add_user_device_client_desktop_download": "Get the desktop client", + "modal_add_user_device_client_fold_closed": "Show advanced configuration", + "modal_add_user_device_client_fold_open": "Hide advanced configuration", + "modal_add_user_device_client_mobile_title": "Mobile application", + "modal_add_user_device_client_mobile_subtitle": "Scan QR code bellow to activate Defguard mobile application.", + "modal_add_user_device_client_mobile_get_mobile": "Get mobile app by clicking one of the buttons below.", + "modal_add_user_device_client_close_info": "Once your Defguard client is configured, you can close this window.", + "modal_add_user_device_start_manual_title": "Manual WireGuard Setup", + "modal_add_user_device_start_manual_subtitle": "Download config file or QR code and set up the VPN manually for full control over network and client settings.", + "modal_add_user_device_manual_setup_title": "Create VPN device", + "modal_add_user_device_manual_setup_explain": "Configure WireGuardVPN on your device, please visit documentation if you don't know how to do it.", + "modal_add_user_device_manual_setup_choice": "Choose setup method", + "modal_add_user_device_manual_setup_choice_auto": "Generate key pair", + "modal_add_user_device_manual_setup_choice_manual": "Use your own keys", + "modal_add_user_device_manual_download_warn_title": "Important", + "modal_add_user_device_manual_download_warn_content": "Please download the configuration file now, as we do not store your private keys. After this window is closed, you will not be able to retrieve the full configuration file (only a blank template).", + "modal_add_user_device_manual_download_location_label": "Config file for location", + "modal_add_user_device_manual_download_explain": "Scan the QR code or import the configuration file into your device's WireGuard app.", + "modal_add_user_device_manual_download_actions_download": "Download config file", + "modal_add_user_device_manual_download_actions_download_one": "This location only", + "modal_add_user_device_manual_download_actions_download_all": "All locations", + "modal_add_user_enrollment_details": "Enrollment details", + "modal_add_user_enrollment_explain": "Provide the user with their token and URL in a convenient manner so they can complete the enrollment process independently.", + "modal_add_user_enrollment_form_label_url": "URL", + "modal_add_user_enrollment_form_label_token": "Activation token", + "modal_add_user_enrollment_form_label_send": "Send enrollment details to user by email", + "modal_edit_user_device_title": "Edit device", + "modal_user_device_config_title": "Device VPN configuration", + "modal_add_auth_key_title": "Add authentication key", + "modal_add_auth_key_submit": "Add key", + "modal_rename_auth_key_title": "Rename authentication key", + "modal_add_api_title": "Add API token", + "modal_add_api_token_copy_copy_label": "API Token", + "modal_add_api_token_copy_warning": "Please copy and save the API token below now. You won't be able to see it again.", + "modal_rename_api_title": "Rename API token", + "modal_add_user_title": "Add new user", + "modal_add_user_groups_title": "Assign user to group(s)", + "modal_add_user_enroll_title": "Start enrollment for user", + "modal_add_user_choice_enroll_title": "Add user with self-enrollment option", + "modal_add_user_choice_enroll_content": "User will be able to configure their account during the setup of their desktop client (e.g., set their own password, etc.).", + "modal_add_user_choice_manual_title": "Add user manually", + "modal_add_user_choice_manual_content": "The admin creates the user account by providing the details and assigning a password on their behalf.", + "modal_add_user_form_enable_enroll_label": "Enable user secure remote self-enrollment.", + "modal_add_user_form_enable_enroll_help": "By enabling this option, the user will be able to configure their account during the setup of their desktop client (e.g., set their own password, etc.). ", + "modal_add_user_submit": "Add user", + "modal_add_user_section_login": "Login preferences", + "modal_add_user_section_account": "Account information", + "modal_add_user_groups_select_all": "All groups", + "modal_add_user_groups_search_empty_title": "No groups match your search.", + "modal_add_user_groups_search_empty_subtitle": "Please try another search request.", + "modal_add_user_assign_groups_checkbox": "Assign user to group(s)", + "modal_add_group_title_start": "Add new group", + "modal_add_group_title_users": "Assign users to the group", + "modal_add_group_form_label_name": "Group name", + "modal_add_group_admin_title": "Admin access", + "modal_add_group_admin_explain": "By setting this group as an admin group, you grant extended access rights and permissions to all users assigned to it.", + "modal_add_group_form_label_admin": "Set this group as Admin group", + "modal_edit_user_title": "Edit user details", + "modal_edit_user_login_pref": "Login preferences", + "modal_edit_user_account": "Account information", + "modal_edit_group_title": "Edit group", + "modal_ce_openid_client_title_add": "Add OpenID App", + "modal_ce_openid_client_title_edit": "Edit OpenID App", + "modal_ce_openid_client_label_redirect": "Redirect URL {index}", + "modal_ce_openid_client_label_redirect_add": "Add URL", + "modal_ce_openid_client_label_scopes_title": "Scopes", + "modal_ce_openid_client_label_name": "Application name", + "modal_ce_webhook_create_title": "Add Webhook", + "modal_ce_webhook_edit_title": "Edit Webhook", + "modal_ce_webhook_events_title": "Trigger events", + "modal_ce_webhook_events_text": "", + "modal_assign_users_groups_title": "Assign groups to selected users" +} diff --git a/web/messages/en/openid.json b/web/messages/en/openid.json new file mode 100644 index 000000000..7cf06b8c7 --- /dev/null +++ b/web/messages/en/openid.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://inlang.com/schema/inlang-message-format", + "openid_edit_copy_id": "Copy client ID", + "openid_edit_copy_secret": "Copy client secret", + "openid_title": "OpenID Apps", + "openid_empty_title": "You don't have any OpenID Apps.", + "openid_empty_subtitle": "To add one, click the button below.", + "openid_add": "Add new OpenID App", + "openid_table_top_title": "All apps", + "openid_consent_title": "{name} would like to", + "openid_consent_scope_openid": "Use OpenID.", + "openid_consent_scope_profile": "Know basic information from your profile.", + "openid_consent_scope_phone": "Know your phone information.", + "openid_consent_scope_groups": "Know what groups your are in.", + "openid_consent_scope_email": "Know your email address." +} diff --git a/web/messages/en/profile.json b/web/messages/en/profile.json new file mode 100644 index 000000000..05cb4c112 --- /dev/null +++ b/web/messages/en/profile.json @@ -0,0 +1,52 @@ +{ + "$schema": "https://inlang.com/schema/inlang-message-format", + "profile_my_profile": "My Profile", + "profile_title": "{name} Profile", + "profile_tabs_details": "Details", + "profile_tabs_devices": "Devices", + "profile_tabs_auth_keys": "Authentication Keys", + "profile_tabs_api": "API Tokens", + "profile_general_card_title": "General", + "profile_auth_card_title": "Password and authentication", + "profile_auth_card_section_password": "Password settings", + "profile_auth_card_section_2fa": "Two-factor methods", + "profile_auth_card_2fa_controls_disable_all": "Disable all", + "profile_auth_card_2fa_totp": "Time-based One-Time Password", + "profile_auth_card_2fa_email": "Email", + "profile_auth_card_2fa_passkeys": "Passkeys", + "profile_auth_card_add_passkey": "Add Passkey", + "profile_auth_card_disable_passkeys": "Delete all Passkeys", + "profile_auth_card_delete_passkey": "Delete passkey", + "profile_auth_card_password_change": "Change password", + "profile_auth_card_make_default": "Make default", + "profile_auth_card_biometric_title": "Biometric authentication devices", + "profile_auth_card_devices_link": "Devices", + "profile_apps_no_data_title": "No authorized apps", + "profile_apps_no_data_subtitle": "Applications you allow to log in with Defguard will appear here.", + "profile_apps_card_title": "Authorized apps", + "profile_apps_card_subtitle": "These applications are allowed to log in with DefGuard. You can remove them from the list if you wish.", + "profile_devices_title": "All devices", + "profile_devices_add_new": "Add new device", + "profile_devices_col_name": "Device name", + "profile_devices_col_pub_ip": "Public IP", + "profile_devices_col_location": "Connected through", + "profile_devices_col_connected": "Connected date", + "profile_devices_col_location_name": "Location name", + "profile_devices_col_location_ip": "Assigned IP", + "profile_devices_col_location_connected_from": "Last connected from", + "profile_devices_col_location_connected": "Last connected", + "profile_devices_col_never_connected": "Never connected", + "profile_devices_menu_show_config": "Show configuration", + "profile_auth_keys_no_data_title": "You don't have any added keys.", + "profile_auth_keys_no_data_subtitle": "To add one, click the button below", + "profile_auth_keys_no_data_cta": "Add new key", + "profile_auth_keys_header_title": "Authentication keys", + "profile_auth_keys_table_col_name": "Key name", + "profile_auth_keys_table_menu_download_ssh": "Download SSH key", + "profile_auth_keys_table_menu_download_gpg": "Download GPG key", + "profile_api_title": "API tokens", + "profile_api_add": "Add new API token", + "profile_api_empty_title": "You don't have any API tokens.", + "profile_api_empty_subtitle": "To add one, click the button below.", + "profile_api_col_name": "Token name" +} diff --git a/web/messages/en/users.json b/web/messages/en/users.json new file mode 100644 index 000000000..684406327 --- /dev/null +++ b/web/messages/en/users.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://inlang.com/schema/inlang-message-format", + "users_title": "Users", + "users_header_title": "All users", + "users_search_placeholder": "Find user", + "users_add": "Add new user", + "users_empty_title": "No users here yet.", + "users_empty_subtitle": "Add more users by clicking the button below.", + "users_col_name": "User name", + "users_col_login": "Login", + "users_col_phone": "Phone", + "users_col_groups": "Groups", + "users_col_status": "Status", + "users_col_assigned": "Assigned devices", + "users_col_ip": "Last connected from", + "users_col_connected_through": "Connected through", + "users_col_connected_date": "Connected date", + "users_col_enrolled": "Enrollment", + "users_row_menu_go_profile": "Go to user profile", + "users_row_menu_disable": "Disable account", + "users_row_menu_enable": "Enable account", + "users_row_menu_add_auth": "Add authentication key", + "users_row_menu_delete": "Delete account", + "users_row_menu_edit": "Edit details", + "users_row_menu_change_password": "Change password", + "users_row_menu_edit_groups": "Edit groups", + "modal_edit_user_groups_title": "Edit user groups" +} diff --git a/web/messages/en/webhooks.json b/web/messages/en/webhooks.json new file mode 100644 index 000000000..ce4dd6061 --- /dev/null +++ b/web/messages/en/webhooks.json @@ -0,0 +1,8 @@ +{ + "$schema": "https://inlang.com/schema/inlang-message-format", + "webhooks_title": "Webhooks", + "webhooks_table_title": "All Webhooks", + "webhooks_add": "Add new webhook", + "webhooks_empty_title": "You don't have any Webhooks.", + "webhooks_empty_subtitle": "To add one, click the button below." +} diff --git a/web/nginx.conf b/web/nginx.conf deleted file mode 100644 index aad3a639a..000000000 --- a/web/nginx.conf +++ /dev/null @@ -1,38 +0,0 @@ -http { - server { - listen 80; - server_name prl; - root /web/; - access_log /var/log/nginx/prl.access.log; - error_log /var/log/nginx/prl.error.log; - - ignore_invalid_headers off; - large_client_header_buffers 4 16k; - - gzip on; - gzip_disable "msie6"; - - gzip_vary on; - gzip_proxied any; - gzip_buffers 16 8k; - gzip_http_version 1.1; - gzip_types application/atom+xml application/javascript application/json application/rss+xml - application/vnd.ms-fontobject application/x-font-ttf application/x-web-app-manifest+json application/xhtml+xml - application/xml font/opentype image/svg+xml image/x-icon text/css text/plain text/x-component; - - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_pass_request_headers on; - - location / { - include /etc/nginx/mime.types; - try_files $uri $uri/ /index.html =404; - } - } -} - -events { - worker_connections 1024; ## Default: 1024 -} diff --git a/web/package.json b/web/package.json index 72f2c3acb..1ecc6bd73 100644 --- a/web/package.json +++ b/web/package.json @@ -1,145 +1,80 @@ { "name": "web", + "private": true, + "version": "0.0.1", "type": "module", "scripts": { - "preview": "pnpm run build && pnpm run vite preview", - "dev": "concurrently \"pnpm run vite\" \"pnpm run typesafe-i18n\"", - "build": "pnpm run typecheck && vite build", - "serve": "vite preview", - "typecheck": "tsc --project ./tsconfig.app.json", - "generate-translation-types": "typesafe-i18n --no-watch", - "fix": "biome check ./src --write --assist-enabled=true && prettier src/**/*.scss -w --log-level silent", - "fix-unsafe": "biome check ./src --write --unsafe --assist-enabled=true && prettier src/**/*.scss -w --log-level silent", - "lint": "biome check ./src --error-on-warnings --formatter-enabled=true --enforce-assist=true --linter-enabled=true && pnpm run typecheck && prettier src/**/*.scss --check --log-level error", - "typesafe-i18n": "typesafe-i18n", - "vite": "vite", - "prettier": "prettier", - "biome": "biome" - }, - "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] - }, - "pnpm": { - "peerDependencyRules": { - "ignoreMissing": [ - "react-native" - ] - }, - "ignoredBuiltDependencies": [ - "@swc/core", - "esbuild" - ], - "onlyBuiltDependencies": [ - "@swc/core" - ] + "dev": "vite", + "build": "vite build && tsc -b", + "preview": "vite preview", + "biome": "biome", + "lint": "biome check ./src/ && prettier src/**/*.scss --check --log-level error && stylelint \"src/**/*.scss\" -c ./.stylelintrc.json && tsc -b", + "fix": "biome check ./src/ --write --unsafe && prettier src/**/*.scss -w --log-level silent", + "tsc": "tsc" }, "dependencies": { + "@axa-ch/react-polymorphic-types": "^1.4.1", "@floating-ui/react": "^0.27.16", - "@github/webauthn-json": "^2.1.1", - "@hookform/resolvers": "^5.2.2", + "@inlang/paraglide-js": "^2.7.1", "@react-hook/resize-observer": "^2.0.2", - "@react-rxjs/core": "^0.10.8", + "@shortercode/webzip": "1.1.1-0", "@stablelib/base64": "^2.0.1", "@stablelib/x25519": "^2.0.1", - "@tanstack/query-core": "^5.90.2", - "@tanstack/react-query": "^5.90.2", - "@tanstack/react-virtual": "3.13.12", - "@tanstack/virtual-core": "3.13.12", - "@use-gesture/react": "^10.3.1", - "axios": "^1.12.2", + "@tanstack/react-devtools": "^0.9.0", + "@tanstack/react-form": "^1.27.7", + "@tanstack/react-query": "^5.90.16", + "@tanstack/react-query-devtools": "^5.91.2", + "@tanstack/react-router": "^1.145.7", + "@tanstack/react-router-devtools": "^1.145.7", + "@tanstack/react-table": "^8.21.3", + "@tanstack/react-virtual": "^3.13.16", + "@uidotdev/usehooks": "^2.4.1", + "axios": "^1.13.2", "byte-size": "^9.0.1", - "classnames": "^2.5.1", "clsx": "^2.1.1", - "date-fns": "^4.1.0", - "dayjs": "^1.11.18", - "deepmerge-ts": "^7.1.5", - "detect-browser": "^5.3.0", - "dice-coefficient": "^2.1.1", - "events": "^3.3.0", - "fast-deep-equal": "^3.1.3", - "file-saver": "^2.0.5", - "fuse.js": "^7.1.0", - "get-text-width": "^1.0.3", - "hex-rgb": "^5.0.0", - "html-react-parser": "^5.2.6", - "humanize-duration": "^3.33.1", - "ipaddr.js": "^2.2.0", - "itertools": "^2.5.0", - "js-base64": "^3.7.8", - "lodash-es": "^4.17.21", - "merge-refs": "^2.0.0", - "millify": "^6.1.0", - "motion": "^12.23.22", - "numbro": "^2.5.0", - "qrcode": "^1.5.4", - "qs": "^6.14.0", - "radash": "^12.1.1", - "react": "^19.1.1", - "react-click-away-listener": "^2.4.0", - "react-datepicker": "^8.7.0", - "react-dom": "^19.1.1", - "react-hook-form": "^7.63.0", - "react-idle-timer": "^5.7.2", - "react-intersection-observer": "^9.16.0", - "react-is": "^19.1.1", + "dayjs": "^1.11.19", + "easy-file-picker": "^1.2.0", + "humanize-duration": "^3.33.2", + "ipaddr.js": "^2.3.0", + "lodash-es": "^4.17.22", + "motion": "^12.24.2", + "qrcode.react": "^4.2.0", + "qs": "^6.14.1", + "react": "^19.2.3", + "react-dom": "^19.2.3", + "react-intersection-observer": "^10.0.0", "react-loading-skeleton": "^3.5.0", "react-markdown": "^10.1.0", - "react-qr-code": "^2.0.18", - "react-resize-detector": "^12.3.0", - "react-router": "^6.30.1", - "react-router-dom": "^6.30.1", - "react-tracked": "^2.0.1", - "react-virtualized-auto-sizer": "^1.0.26", - "recharts": "^3.2.1", - "rehype-external-links": "^3.0.0", + "recharts": "^3.6.0", "rehype-raw": "^7.0.0", - "rehype-sanitize": "^6.0.0", "rxjs": "^7.8.2", - "scheduler": "^0.26.0", "text-case": "^1.2.9", - "typesafe-i18n": "^5.26.2", - "use-breakpoint": "^4.0.6", - "zod": "^3.25.76", - "zustand": "^5.0.8" + "zod": "^4.3.5", + "zustand": "^5.0.9" }, "devDependencies": { - "@babel/core": "^7.28.4", - "@biomejs/biome": "2.2.2", - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4", - "@hookform/devtools": "^4.4.0", - "@tanstack/react-query-devtools": "^5.90.2", + "@biomejs/biome": "2.3.11", + "@inlang/paraglide-js": "2.7.1", + "@tanstack/devtools-vite": "^0.4.0", + "@tanstack/router-plugin": "^1.145.7", "@types/byte-size": "^8.1.2", - "@types/file-saver": "^2.0.7", "@types/humanize-duration": "^3.27.4", "@types/lodash-es": "^4.17.12", - "@types/node": "^24.5.2", + "@types/node": "^25.0.3", "@types/qs": "^6.14.0", - "@types/react": "^19.1.13", - "@types/react-dom": "^19.1.9", - "@types/react-router-dom": "^5.3.3", - "@vitejs/plugin-react-swc": "^4.1.0", - "autoprefixer": "^10.4.21", - "concurrently": "^9.2.1", - "dotenv": "^17.2.2", - "esbuild": "^0.25.10", - "globals": "^16.4.0", - "postcss": "^8.5.6", - "prettier": "^3.6.2", - "sass": "~1.70.0", - "standard-version": "^9.5.0", - "type-fest": "^4.41.0", - "typescript": "~5.9.2", - "vite": "^7.1.7", - "vite-plugin-package-version": "^1.1.0" + "@types/react": "^19.2.7", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react-swc": "^4.2.2", + "autoprefixer": "^10.4.23", + "globals": "^17.0.0", + "prettier": "^3.7.4", + "sass": "^1.97.2", + "sharp": "^0.34.5", + "stylelint": "^16.26.1", + "stylelint-config-standard-scss": "^16.0.0", + "stylelint-scss": "^6.14.0", + "typescript": "~5.9.3", + "vite": "^7.3.0", + "vite-plugin-image-optimizer": "^2.0.3" } } diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml index a8738b891..a3f56302b 100644 --- a/web/pnpm-lock.yaml +++ b/web/pnpm-lock.yaml @@ -8,232 +8,133 @@ importers: .: dependencies: + '@axa-ch/react-polymorphic-types': + specifier: ^1.4.1 + version: 1.4.1(@types/react@19.2.7)(react@19.2.3) '@floating-ui/react': specifier: ^0.27.16 - version: 0.27.16(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@github/webauthn-json': - specifier: ^2.1.1 - version: 2.1.1 - '@hookform/resolvers': - specifier: ^5.2.2 - version: 5.2.2(react-hook-form@7.63.0(react@19.1.1)) + version: 0.27.16(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@inlang/paraglide-js': + specifier: ^2.7.1 + version: 2.7.1 '@react-hook/resize-observer': specifier: ^2.0.2 - version: 2.0.2(react@19.1.1) - '@react-rxjs/core': - specifier: ^0.10.8 - version: 0.10.8(react@19.1.1)(rxjs@7.8.2) + version: 2.0.2(react@19.2.3) + '@shortercode/webzip': + specifier: 1.1.1-0 + version: 1.1.1-0 '@stablelib/base64': specifier: ^2.0.1 version: 2.0.1 '@stablelib/x25519': specifier: ^2.0.1 version: 2.0.1 - '@tanstack/query-core': - specifier: ^5.90.2 - version: 5.90.2 + '@tanstack/react-devtools': + specifier: ^0.9.0 + version: 0.9.0(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(csstype@3.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(solid-js@1.9.9) + '@tanstack/react-form': + specifier: ^1.27.7 + version: 1.27.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@tanstack/react-query': - specifier: ^5.90.2 - version: 5.90.2(react@19.1.1) + specifier: ^5.90.16 + version: 5.90.16(react@19.2.3) + '@tanstack/react-query-devtools': + specifier: ^5.91.2 + version: 5.91.2(@tanstack/react-query@5.90.16(react@19.2.3))(react@19.2.3) + '@tanstack/react-router': + specifier: ^1.145.7 + version: 1.145.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@tanstack/react-router-devtools': + specifier: ^1.145.7 + version: 1.145.7(@tanstack/react-router@1.145.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@tanstack/router-core@1.145.7)(csstype@3.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(solid-js@1.9.9) + '@tanstack/react-table': + specifier: ^8.21.3 + version: 8.21.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@tanstack/react-virtual': - specifier: 3.13.12 - version: 3.13.12(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@tanstack/virtual-core': - specifier: 3.13.12 - version: 3.13.12 - '@use-gesture/react': - specifier: ^10.3.1 - version: 10.3.1(react@19.1.1) + specifier: ^3.13.16 + version: 3.13.16(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@uidotdev/usehooks': + specifier: ^2.4.1 + version: 2.4.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3) axios: - specifier: ^1.12.2 - version: 1.12.2 + specifier: ^1.13.2 + version: 1.13.2 byte-size: specifier: ^9.0.1 version: 9.0.1 - classnames: - specifier: ^2.5.1 - version: 2.5.1 clsx: specifier: ^2.1.1 version: 2.1.1 - date-fns: - specifier: ^4.1.0 - version: 4.1.0 dayjs: - specifier: ^1.11.18 - version: 1.11.18 - deepmerge-ts: - specifier: ^7.1.5 - version: 7.1.5 - detect-browser: - specifier: ^5.3.0 - version: 5.3.0 - dice-coefficient: - specifier: ^2.1.1 - version: 2.1.1 - events: - specifier: ^3.3.0 - version: 3.3.0 - fast-deep-equal: - specifier: ^3.1.3 - version: 3.1.3 - file-saver: - specifier: ^2.0.5 - version: 2.0.5 - fuse.js: - specifier: ^7.1.0 - version: 7.1.0 - get-text-width: - specifier: ^1.0.3 - version: 1.0.3 - hex-rgb: - specifier: ^5.0.0 - version: 5.0.0 - html-react-parser: - specifier: ^5.2.6 - version: 5.2.6(@types/react@19.1.13)(react@19.1.1) + specifier: ^1.11.19 + version: 1.11.19 + easy-file-picker: + specifier: ^1.2.0 + version: 1.2.0 humanize-duration: - specifier: ^3.33.1 - version: 3.33.1 + specifier: ^3.33.2 + version: 3.33.2 ipaddr.js: - specifier: ^2.2.0 - version: 2.2.0 - itertools: - specifier: ^2.5.0 - version: 2.5.0 - js-base64: - specifier: ^3.7.8 - version: 3.7.8 + specifier: ^2.3.0 + version: 2.3.0 lodash-es: - specifier: ^4.17.21 - version: 4.17.21 - merge-refs: - specifier: ^2.0.0 - version: 2.0.0(@types/react@19.1.13) - millify: - specifier: ^6.1.0 - version: 6.1.0 + specifier: ^4.17.22 + version: 4.17.22 motion: - specifier: ^12.23.22 - version: 12.23.22(@emotion/is-prop-valid@1.4.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - numbro: - specifier: ^2.5.0 - version: 2.5.0 - qrcode: - specifier: ^1.5.4 - version: 1.5.4 + specifier: ^12.24.2 + version: 12.24.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + qrcode.react: + specifier: ^4.2.0 + version: 4.2.0(react@19.2.3) qs: - specifier: ^6.14.0 - version: 6.14.0 - radash: - specifier: ^12.1.1 - version: 12.1.1 + specifier: ^6.14.1 + version: 6.14.1 react: - specifier: ^19.1.1 - version: 19.1.1 - react-click-away-listener: - specifier: ^2.4.0 - version: 2.4.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - react-datepicker: - specifier: ^8.7.0 - version: 8.7.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + specifier: ^19.2.3 + version: 19.2.3 react-dom: - specifier: ^19.1.1 - version: 19.1.1(react@19.1.1) - react-hook-form: - specifier: ^7.63.0 - version: 7.63.0(react@19.1.1) - react-idle-timer: - specifier: ^5.7.2 - version: 5.7.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + specifier: ^19.2.3 + version: 19.2.3(react@19.2.3) react-intersection-observer: - specifier: ^9.16.0 - version: 9.16.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - react-is: - specifier: ^19.1.1 - version: 19.1.1 + specifier: ^10.0.0 + version: 10.0.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) react-loading-skeleton: specifier: ^3.5.0 - version: 3.5.0(react@19.1.1) + version: 3.5.0(react@19.2.3) react-markdown: specifier: ^10.1.0 - version: 10.1.0(@types/react@19.1.13)(react@19.1.1) - react-qr-code: - specifier: ^2.0.18 - version: 2.0.18(react@19.1.1) - react-resize-detector: - specifier: ^12.3.0 - version: 12.3.0(react@19.1.1) - react-router: - specifier: ^6.30.1 - version: 6.30.1(react@19.1.1) - react-router-dom: - specifier: ^6.30.1 - version: 6.30.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - react-tracked: - specifier: ^2.0.1 - version: 2.0.1(react@19.1.1)(scheduler@0.26.0) - react-virtualized-auto-sizer: - specifier: ^1.0.26 - version: 1.0.26(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + version: 10.1.0(@types/react@19.2.7)(react@19.2.3) recharts: - specifier: ^3.2.1 - version: 3.2.1(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react-is@19.1.1)(react@19.1.1)(redux@5.0.1) - rehype-external-links: - specifier: ^3.0.0 - version: 3.0.0 + specifier: ^3.6.0 + version: 3.6.0(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react-is@19.2.0)(react@19.2.3)(redux@5.0.1) rehype-raw: specifier: ^7.0.0 version: 7.0.0 - rehype-sanitize: - specifier: ^6.0.0 - version: 6.0.0 rxjs: specifier: ^7.8.2 version: 7.8.2 - scheduler: - specifier: ^0.26.0 - version: 0.26.0 text-case: specifier: ^1.2.9 version: 1.2.9 - typesafe-i18n: - specifier: ^5.26.2 - version: 5.26.2(typescript@5.9.2) - use-breakpoint: - specifier: ^4.0.6 - version: 4.0.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1) zod: - specifier: ^3.25.76 - version: 3.25.76 + specifier: ^4.3.5 + version: 4.3.5 zustand: - specifier: ^5.0.8 - version: 5.0.8(@types/react@19.1.13)(immer@10.1.3)(react@19.1.1)(use-sync-external-store@1.5.0(react@19.1.1)) + specifier: ^5.0.9 + version: 5.0.9(@types/react@19.2.7)(immer@11.1.3)(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)) devDependencies: - '@babel/core': - specifier: ^7.28.4 - version: 7.28.4 '@biomejs/biome': - specifier: 2.2.2 - version: 2.2.2 - '@csstools/css-parser-algorithms': - specifier: ^3.0.5 - version: 3.0.5(@csstools/css-tokenizer@3.0.4) - '@csstools/css-tokenizer': - specifier: ^3.0.4 - version: 3.0.4 - '@hookform/devtools': - specifier: ^4.4.0 - version: 4.4.0(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - '@tanstack/react-query-devtools': - specifier: ^5.90.2 - version: 5.90.2(@tanstack/react-query@5.90.2(react@19.1.1))(react@19.1.1) + specifier: 2.3.11 + version: 2.3.11 + '@tanstack/devtools-vite': + specifier: ^0.4.0 + version: 0.4.0(vite@7.3.0(@types/node@25.0.3)(sass@1.97.2)(tsx@4.21.0)) + '@tanstack/router-plugin': + specifier: ^1.145.7 + version: 1.145.7(@tanstack/react-router@1.145.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite@7.3.0(@types/node@25.0.3)(sass@1.97.2)(tsx@4.21.0)) '@types/byte-size': specifier: ^8.1.2 version: 8.1.2 - '@types/file-saver': - specifier: ^2.0.7 - version: 2.0.7 '@types/humanize-duration': specifier: ^3.27.4 version: 3.27.4 @@ -241,79 +142,76 @@ importers: specifier: ^4.17.12 version: 4.17.12 '@types/node': - specifier: ^24.5.2 - version: 24.5.2 + specifier: ^25.0.3 + version: 25.0.3 '@types/qs': specifier: ^6.14.0 version: 6.14.0 '@types/react': - specifier: ^19.1.13 - version: 19.1.13 + specifier: ^19.2.7 + version: 19.2.7 '@types/react-dom': - specifier: ^19.1.9 - version: 19.1.9(@types/react@19.1.13) - '@types/react-router-dom': - specifier: ^5.3.3 - version: 5.3.3 + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.7) '@vitejs/plugin-react-swc': - specifier: ^4.1.0 - version: 4.1.0(vite@7.1.7(@types/node@24.5.2)(jiti@2.4.2)(sass@1.70.0)(terser@5.37.0)(yaml@2.6.1)) + specifier: ^4.2.2 + version: 4.2.2(vite@7.3.0(@types/node@25.0.3)(sass@1.97.2)(tsx@4.21.0)) autoprefixer: - specifier: ^10.4.21 - version: 10.4.21(postcss@8.5.6) - concurrently: - specifier: ^9.2.1 - version: 9.2.1 - dotenv: - specifier: ^17.2.2 - version: 17.2.2 - esbuild: - specifier: ^0.25.10 - version: 0.25.10 + specifier: ^10.4.23 + version: 10.4.23(postcss@8.5.6) globals: - specifier: ^16.4.0 - version: 16.4.0 - postcss: - specifier: ^8.5.6 - version: 8.5.6 + specifier: ^17.0.0 + version: 17.0.0 prettier: - specifier: ^3.6.2 - version: 3.6.2 + specifier: ^3.7.4 + version: 3.7.4 sass: - specifier: ~1.70.0 - version: 1.70.0 - standard-version: - specifier: ^9.5.0 - version: 9.5.0 - type-fest: - specifier: ^4.41.0 - version: 4.41.0 + specifier: ^1.97.2 + version: 1.97.2 + sharp: + specifier: ^0.34.5 + version: 0.34.5 + stylelint: + specifier: ^16.26.1 + version: 16.26.1(typescript@5.9.3) + stylelint-config-standard-scss: + specifier: ^16.0.0 + version: 16.0.0(postcss@8.5.6)(stylelint@16.26.1(typescript@5.9.3)) + stylelint-scss: + specifier: ^6.14.0 + version: 6.14.0(stylelint@16.26.1(typescript@5.9.3)) typescript: - specifier: ~5.9.2 - version: 5.9.2 + specifier: ~5.9.3 + version: 5.9.3 vite: - specifier: ^7.1.7 - version: 7.1.7(@types/node@24.5.2)(jiti@2.4.2)(sass@1.70.0)(terser@5.37.0)(yaml@2.6.1) - vite-plugin-package-version: - specifier: ^1.1.0 - version: 1.1.0(vite@7.1.7(@types/node@24.5.2)(jiti@2.4.2)(sass@1.70.0)(terser@5.37.0)(yaml@2.6.1)) + specifier: ^7.3.0 + version: 7.3.0(@types/node@25.0.3)(sass@1.97.2)(tsx@4.21.0) + vite-plugin-image-optimizer: + specifier: ^2.0.3 + version: 2.0.3(sharp@0.34.5)(vite@7.3.0(@types/node@25.0.3)(sass@1.97.2)(tsx@4.21.0)) packages: + '@axa-ch/react-polymorphic-types@1.4.1': + resolution: {integrity: sha512-2lo5a9slPZbc9hVCC5Z7fXWimd+UWCtETi88uGxsZ6GG5s6sAnwxPDPZKZ8qR/riFSWROglxDGNBtK+8jKz2eQ==} + peerDependencies: + '@types/react': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + '@babel/code-frame@7.27.1': resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.28.4': - resolution: {integrity: sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==} + '@babel/compat-data@7.28.5': + resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==} engines: {node: '>=6.9.0'} - '@babel/core@7.28.4': - resolution: {integrity: sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==} + '@babel/core@7.28.5': + resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==} engines: {node: '>=6.9.0'} - '@babel/generator@7.28.3': - resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==} + '@babel/generator@7.28.5': + resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} engines: {node: '>=6.9.0'} '@babel/helper-compilation-targets@7.27.2': @@ -334,12 +232,16 @@ packages: peerDependencies: '@babel/core': ^7.0.0 + '@babel/helper-plugin-utils@7.27.1': + resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} + engines: {node: '>=6.9.0'} + '@babel/helper-string-parser@7.27.1': resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.27.1': - resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} '@babel/helper-validator-option@7.27.1': @@ -350,296 +252,279 @@ packages: resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} engines: {node: '>=6.9.0'} - '@babel/parser@7.28.4': - resolution: {integrity: sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==} + '@babel/parser@7.28.5': + resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} engines: {node: '>=6.0.0'} hasBin: true - '@babel/runtime@7.28.4': - resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==} + '@babel/plugin-syntax-jsx@7.27.1': + resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==} engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.27.1': + resolution: {integrity: sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 '@babel/template@7.27.2': resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.28.4': - resolution: {integrity: sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==} + '@babel/traverse@7.28.5': + resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==} engines: {node: '>=6.9.0'} - '@babel/types@7.28.4': - resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==} + '@babel/types@7.28.5': + resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} engines: {node: '>=6.9.0'} - '@biomejs/biome@2.2.2': - resolution: {integrity: sha512-j1omAiQWCkhuLgwpMKisNKnsM6W8Xtt1l0WZmqY/dFj8QPNkIoTvk4tSsi40FaAAkBE1PU0AFG2RWFBWenAn+w==} + '@biomejs/biome@2.3.11': + resolution: {integrity: sha512-/zt+6qazBWguPG6+eWmiELqO+9jRsMZ/DBU3lfuU2ngtIQYzymocHhKiZRyrbra4aCOoyTg/BmY+6WH5mv9xmQ==} engines: {node: '>=14.21.3'} hasBin: true - '@biomejs/cli-darwin-arm64@2.2.2': - resolution: {integrity: sha512-6ePfbCeCPryWu0CXlzsWNZgVz/kBEvHiPyNpmViSt6A2eoDf4kXs3YnwQPzGjy8oBgQulrHcLnJL0nkCh80mlQ==} + '@biomejs/cli-darwin-arm64@2.3.11': + resolution: {integrity: sha512-/uXXkBcPKVQY7rc9Ys2CrlirBJYbpESEDme7RKiBD6MmqR2w3j0+ZZXRIL2xiaNPsIMMNhP1YnA+jRRxoOAFrA==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [darwin] - '@biomejs/cli-darwin-x64@2.2.2': - resolution: {integrity: sha512-Tn4JmVO+rXsbRslml7FvKaNrlgUeJot++FkvYIhl1OkslVCofAtS35MPlBMhXgKWF9RNr9cwHanrPTUUXcYGag==} + '@biomejs/cli-darwin-x64@2.3.11': + resolution: {integrity: sha512-fh7nnvbweDPm2xEmFjfmq7zSUiox88plgdHF9OIW4i99WnXrAC3o2P3ag9judoUMv8FCSUnlwJCM1B64nO5Fbg==} engines: {node: '>=14.21.3'} cpu: [x64] os: [darwin] - '@biomejs/cli-linux-arm64-musl@2.2.2': - resolution: {integrity: sha512-/MhYg+Bd6renn6i1ylGFL5snYUn/Ct7zoGVKhxnro3bwekiZYE8Kl39BSb0MeuqM+72sThkQv4TnNubU9njQRw==} + '@biomejs/cli-linux-arm64-musl@2.3.11': + resolution: {integrity: sha512-XPSQ+XIPZMLaZ6zveQdwNjbX+QdROEd1zPgMwD47zvHV+tCGB88VH+aynyGxAHdzL+Tm/+DtKST5SECs4iwCLg==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - '@biomejs/cli-linux-arm64@2.2.2': - resolution: {integrity: sha512-JfrK3gdmWWTh2J5tq/rcWCOsImVyzUnOS2fkjhiYKCQ+v8PqM+du5cfB7G1kXas+7KQeKSWALv18iQqdtIMvzw==} + '@biomejs/cli-linux-arm64@2.3.11': + resolution: {integrity: sha512-l4xkGa9E7Uc0/05qU2lMYfN1H+fzzkHgaJoy98wO+b/7Gl78srbCRRgwYSW+BTLixTBrM6Ede5NSBwt7rd/i6g==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [linux] - '@biomejs/cli-linux-x64-musl@2.2.2': - resolution: {integrity: sha512-ZCLXcZvjZKSiRY/cFANKg+z6Fhsf9MHOzj+NrDQcM+LbqYRT97LyCLWy2AS+W2vP+i89RyRM+kbGpUzbRTYWig==} + '@biomejs/cli-linux-x64-musl@2.3.11': + resolution: {integrity: sha512-vU7a8wLs5C9yJ4CB8a44r12aXYb8yYgBn+WeyzbMjaCMklzCv1oXr8x+VEyWodgJt9bDmhiaW/I0RHbn7rsNmw==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - '@biomejs/cli-linux-x64@2.2.2': - resolution: {integrity: sha512-Ogb+77edO5LEP/xbNicACOWVLt8mgC+E1wmpUakr+O4nKwLt9vXe74YNuT3T1dUBxC/SnrVmlzZFC7kQJEfquQ==} + '@biomejs/cli-linux-x64@2.3.11': + resolution: {integrity: sha512-/1s9V/H3cSe0r0Mv/Z8JryF5x9ywRxywomqZVLHAoa/uN0eY7F8gEngWKNS5vbbN/BsfpCG5yeBT5ENh50Frxg==} engines: {node: '>=14.21.3'} cpu: [x64] os: [linux] - '@biomejs/cli-win32-arm64@2.2.2': - resolution: {integrity: sha512-wBe2wItayw1zvtXysmHJQoQqXlTzHSpQRyPpJKiNIR21HzH/CrZRDFic1C1jDdp+zAPtqhNExa0owKMbNwW9cQ==} + '@biomejs/cli-win32-arm64@2.3.11': + resolution: {integrity: sha512-PZQ6ElCOnkYapSsysiTy0+fYX+agXPlWugh6+eQ6uPKI3vKAqNp6TnMhoM3oY2NltSB89hz59o8xIfOdyhi9Iw==} engines: {node: '>=14.21.3'} cpu: [arm64] os: [win32] - '@biomejs/cli-win32-x64@2.2.2': - resolution: {integrity: sha512-DAuHhHekGfiGb6lCcsT4UyxQmVwQiBCBUMwVra/dcOSs9q8OhfaZgey51MlekT3p8UwRqtXQfFuEJBhJNdLZwg==} + '@biomejs/cli-win32-x64@2.3.11': + resolution: {integrity: sha512-43VrG813EW+b5+YbDbz31uUsheX+qFKCpXeY9kfdAx+ww3naKxeVkTD9zLIWxUPfJquANMHrmW3wbe/037G0Qg==} engines: {node: '>=14.21.3'} cpu: [x64] os: [win32] + '@cacheable/memory@2.0.7': + resolution: {integrity: sha512-RbxnxAMf89Tp1dLhXMS7ceft/PGsDl1Ip7T20z5nZ+pwIAsQ1p2izPjVG69oCLv/jfQ7HDPHTWK0c9rcAWXN3A==} + + '@cacheable/utils@2.3.3': + resolution: {integrity: sha512-JsXDL70gQ+1Vc2W/KUFfkAJzgb4puKwwKehNLuB+HrNKWf91O736kGfxn4KujXCCSuh6mRRL4XEB0PkAFjWS0A==} + '@csstools/css-parser-algorithms@3.0.5': resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==} engines: {node: '>=18'} peerDependencies: '@csstools/css-tokenizer': ^3.0.4 + '@csstools/css-syntax-patches-for-csstree@1.0.22': + resolution: {integrity: sha512-qBcx6zYlhleiFfdtzkRgwNC7VVoAwfK76Vmsw5t+PbvtdknO9StgRk7ROvq9so1iqbdW4uLIDAsXRsTfUrIoOw==} + engines: {node: '>=18'} + '@csstools/css-tokenizer@3.0.4': resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} engines: {node: '>=18'} - '@emotion/babel-plugin@11.13.5': - resolution: {integrity: sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==} - - '@emotion/cache@11.14.0': - resolution: {integrity: sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==} - - '@emotion/hash@0.9.2': - resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==} - - '@emotion/is-prop-valid@1.4.0': - resolution: {integrity: sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==} - - '@emotion/memoize@0.9.0': - resolution: {integrity: sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==} - - '@emotion/react@11.14.0': - resolution: {integrity: sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==} - peerDependencies: - '@types/react': '*' - react: '>=16.8.0' - peerDependenciesMeta: - '@types/react': - optional: true - - '@emotion/serialize@1.3.3': - resolution: {integrity: sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==} - - '@emotion/sheet@1.4.0': - resolution: {integrity: sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==} - - '@emotion/styled@11.14.1': - resolution: {integrity: sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==} + '@csstools/media-query-list-parser@4.0.3': + resolution: {integrity: sha512-HAYH7d3TLRHDOUQK4mZKf9k9Ph/m8Akstg66ywKR4SFAigjs3yBiUeZtFxywiTm5moZMAp/5W/ZuFnNXXYLuuQ==} + engines: {node: '>=18'} peerDependencies: - '@emotion/react': ^11.0.0-rc.0 - '@types/react': '*' - react: '>=16.8.0' - peerDependenciesMeta: - '@types/react': - optional: true - - '@emotion/unitless@0.10.0': - resolution: {integrity: sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==} + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 - '@emotion/use-insertion-effect-with-fallbacks@1.2.0': - resolution: {integrity: sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==} + '@csstools/selector-specificity@5.0.0': + resolution: {integrity: sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==} + engines: {node: '>=18'} peerDependencies: - react: '>=16.8.0' + postcss-selector-parser: ^7.0.0 - '@emotion/utils@1.4.2': - resolution: {integrity: sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==} + '@dual-bundle/import-meta-resolve@4.2.1': + resolution: {integrity: sha512-id+7YRUgoUX6CgV0DtuhirQWodeeA7Lf4i2x71JS/vtA5pRb/hIGWlw+G6MeXvsM+MXrz0VAydTGElX1rAfgPg==} - '@emotion/weak-memoize@0.4.0': - resolution: {integrity: sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==} + '@emnapi/runtime@1.8.1': + resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==} - '@esbuild/aix-ppc64@0.25.10': - resolution: {integrity: sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==} + '@esbuild/aix-ppc64@0.27.2': + resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.25.10': - resolution: {integrity: sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==} + '@esbuild/android-arm64@0.27.2': + resolution: {integrity: sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==} engines: {node: '>=18'} cpu: [arm64] os: [android] - '@esbuild/android-arm@0.25.10': - resolution: {integrity: sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==} + '@esbuild/android-arm@0.27.2': + resolution: {integrity: sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==} engines: {node: '>=18'} cpu: [arm] os: [android] - '@esbuild/android-x64@0.25.10': - resolution: {integrity: sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==} + '@esbuild/android-x64@0.27.2': + resolution: {integrity: sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==} engines: {node: '>=18'} cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.25.10': - resolution: {integrity: sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==} + '@esbuild/darwin-arm64@0.27.2': + resolution: {integrity: sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.25.10': - resolution: {integrity: sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==} + '@esbuild/darwin-x64@0.27.2': + resolution: {integrity: sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==} engines: {node: '>=18'} cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.25.10': - resolution: {integrity: sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==} + '@esbuild/freebsd-arm64@0.27.2': + resolution: {integrity: sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.25.10': - resolution: {integrity: sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==} + '@esbuild/freebsd-x64@0.27.2': + resolution: {integrity: sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.25.10': - resolution: {integrity: sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==} + '@esbuild/linux-arm64@0.27.2': + resolution: {integrity: sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==} engines: {node: '>=18'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.25.10': - resolution: {integrity: sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==} + '@esbuild/linux-arm@0.27.2': + resolution: {integrity: sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==} engines: {node: '>=18'} cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.25.10': - resolution: {integrity: sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==} + '@esbuild/linux-ia32@0.27.2': + resolution: {integrity: sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==} engines: {node: '>=18'} cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.25.10': - resolution: {integrity: sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==} + '@esbuild/linux-loong64@0.27.2': + resolution: {integrity: sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==} engines: {node: '>=18'} cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.25.10': - resolution: {integrity: sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==} + '@esbuild/linux-mips64el@0.27.2': + resolution: {integrity: sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.25.10': - resolution: {integrity: sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==} + '@esbuild/linux-ppc64@0.27.2': + resolution: {integrity: sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.25.10': - resolution: {integrity: sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==} + '@esbuild/linux-riscv64@0.27.2': + resolution: {integrity: sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.25.10': - resolution: {integrity: sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==} + '@esbuild/linux-s390x@0.27.2': + resolution: {integrity: sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==} engines: {node: '>=18'} cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.25.10': - resolution: {integrity: sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==} + '@esbuild/linux-x64@0.27.2': + resolution: {integrity: sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==} engines: {node: '>=18'} cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.25.10': - resolution: {integrity: sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==} + '@esbuild/netbsd-arm64@0.27.2': + resolution: {integrity: sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.25.10': - resolution: {integrity: sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==} + '@esbuild/netbsd-x64@0.27.2': + resolution: {integrity: sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.25.10': - resolution: {integrity: sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==} + '@esbuild/openbsd-arm64@0.27.2': + resolution: {integrity: sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.25.10': - resolution: {integrity: sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==} + '@esbuild/openbsd-x64@0.27.2': + resolution: {integrity: sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.25.10': - resolution: {integrity: sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==} + '@esbuild/openharmony-arm64@0.27.2': + resolution: {integrity: sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.25.10': - resolution: {integrity: sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==} + '@esbuild/sunos-x64@0.27.2': + resolution: {integrity: sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==} engines: {node: '>=18'} cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.25.10': - resolution: {integrity: sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==} + '@esbuild/win32-arm64@0.27.2': + resolution: {integrity: sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==} engines: {node: '>=18'} cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.25.10': - resolution: {integrity: sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==} + '@esbuild/win32-ia32@0.27.2': + resolution: {integrity: sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==} engines: {node: '>=18'} cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.25.10': - resolution: {integrity: sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==} + '@esbuild/win32-x64@0.27.2': + resolution: {integrity: sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==} engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -665,25 +550,153 @@ packages: '@floating-ui/utils@0.2.10': resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} - '@github/webauthn-json@2.1.1': - resolution: {integrity: sha512-XrftRn4z75SnaJOmZQbt7Mk+IIjqVHw+glDGOxuHwXkZBZh/MBoRS7MHjSZMDaLhT4RjN2VqiEU7EOYleuJWSQ==} - deprecated: 'Deprecated: Modern browsers support built-in WebAuthn JSON methods. Please use native browser methods instead. For more information, visit https://github.com/github/webauthn-json' - hasBin: true + '@img/colour@1.0.0': + resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==} + engines: {node: '>=18'} - '@hookform/devtools@4.4.0': - resolution: {integrity: sha512-Mtlic+uigoYBPXlfvPBfiYYUZuyMrD3pTjDpVIhL6eCZTvQkHsKBSKeZCvXWUZr8fqrkzDg27N+ZuazLKq6Vmg==} - peerDependencies: - react: ^16.8.0 || ^17 || ^18 || ^19 - react-dom: ^16.8.0 || ^17 || ^18 || ^19 + '@img/sharp-darwin-arm64@0.34.5': + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] - '@hookform/resolvers@5.2.2': - resolution: {integrity: sha512-A/IxlMLShx3KjV/HeTcTfaMxdwy690+L/ZADoeaTltLx+CVuzkeVIPuybK3jrRfw7YZnmdKsVVHAlEPIAEUNlA==} - peerDependencies: - react-hook-form: ^7.55.0 + '@img/sharp-darwin-x64@0.34.5': + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] - '@hutson/parse-repository-url@3.0.2': - resolution: {integrity: sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q==} - engines: {node: '>=6.9.0'} + '@img/sharp-libvips-darwin-arm64@1.2.4': + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.2.4': + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.2.4': + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linux-arm@1.2.4': + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + cpu: [arm] + os: [linux] + + '@img/sharp-libvips-linux-ppc64@1.2.4': + resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + cpu: [ppc64] + os: [linux] + + '@img/sharp-libvips-linux-riscv64@1.2.4': + resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + cpu: [riscv64] + os: [linux] + + '@img/sharp-libvips-linux-s390x@1.2.4': + resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + cpu: [s390x] + os: [linux] + + '@img/sharp-libvips-linux-x64@1.2.4': + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + cpu: [x64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + cpu: [x64] + os: [linux] + + '@img/sharp-linux-arm64@0.34.5': + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linux-arm@0.34.5': + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + + '@img/sharp-linux-ppc64@0.34.5': + resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + + '@img/sharp-linux-riscv64@0.34.5': + resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [riscv64] + os: [linux] + + '@img/sharp-linux-s390x@0.34.5': + resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + + '@img/sharp-linux-x64@0.34.5': + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-linuxmusl-arm64@0.34.5': + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linuxmusl-x64@0.34.5': + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-wasm32@0.34.5': + resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.34.5': + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.34.5': + resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.34.5': + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + + '@inlang/paraglide-js@2.7.1': + resolution: {integrity: sha512-wCpnS9iRTRYMilvWBjB0ndf8+moon+AXz23Uh4wbpQjWhRJyvCytkGFzm7jeqAGggK4v3oeuyjva91TDMS+qhw==} + hasBin: true + + '@inlang/recommend-sherlock@0.2.1': + resolution: {integrity: sha512-ckv8HvHy/iTqaVAEKrr+gnl+p3XFNwe5D2+6w6wJk2ORV2XkcRkKOJ/XsTUJbPSiyi4PI+p+T3bqbmNx/rDUlg==} + + '@inlang/sdk@2.4.10': + resolution: {integrity: sha512-MLcYSb9ERwvyfxMsMXGjmAYfh6Bn/rkT58ffkibWxbFLiL3ejSdmLGP8Wl7118dMo6nj2PXTcEPzUw+2YPQ62Q==} + engines: {node: '>=18.0.0'} '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -695,15 +708,122 @@ packages: resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} engines: {node: '>=6.0.0'} - '@jridgewell/source-map@0.3.11': - resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==} - '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@keyv/bigmap@1.3.0': + resolution: {integrity: sha512-KT01GjzV6AQD5+IYrcpoYLkCu1Jod3nau1Z7EsEuViO3TZGRacSbO9MfHmbJ1WaOXFtWLxPVj169cn2WNKPkIg==} + engines: {node: '>= 18'} + peerDependencies: + keyv: ^5.5.4 + + '@keyv/serialize@1.1.1': + resolution: {integrity: sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==} + + '@lix-js/sdk@0.4.7': + resolution: {integrity: sha512-pRbW+joG12L0ULfMiWYosIW0plmW4AsUdiPCp+Z8rAsElJ+wJ6in58zhD3UwUcd4BNcpldEGjg6PdA7e0RgsDQ==} + engines: {node: '>=18'} + + '@lix-js/server-protocol-schema@0.1.1': + resolution: {integrity: sha512-jBeALB6prAbtr5q4vTuxnRZZv1M2rKe8iNqRQhFJ4Tv7150unEa0vKyz0hs8Gl3fUGsWaNJBh3J8++fpbrpRBQ==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@parcel/watcher-android-arm64@2.5.1': + resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [android] + + '@parcel/watcher-darwin-arm64@2.5.1': + resolution: {integrity: sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [darwin] + + '@parcel/watcher-darwin-x64@2.5.1': + resolution: {integrity: sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [darwin] + + '@parcel/watcher-freebsd-x64@2.5.1': + resolution: {integrity: sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [freebsd] + + '@parcel/watcher-linux-arm-glibc@2.5.1': + resolution: {integrity: sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + + '@parcel/watcher-linux-arm-musl@2.5.1': + resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} + engines: {node: '>= 10.0.0'} + cpu: [arm] + os: [linux] + + '@parcel/watcher-linux-arm64-glibc@2.5.1': + resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + + '@parcel/watcher-linux-arm64-musl@2.5.1': + resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [linux] + + '@parcel/watcher-linux-x64-glibc@2.5.1': + resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + + '@parcel/watcher-linux-x64-musl@2.5.1': + resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [linux] + + '@parcel/watcher-win32-arm64@2.5.1': + resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==} + engines: {node: '>= 10.0.0'} + cpu: [arm64] + os: [win32] + + '@parcel/watcher-win32-ia32@2.5.1': + resolution: {integrity: sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==} + engines: {node: '>= 10.0.0'} + cpu: [ia32] + os: [win32] + + '@parcel/watcher-win32-x64@2.5.1': + resolution: {integrity: sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==} + engines: {node: '>= 10.0.0'} + cpu: [x64] + os: [win32] + + '@parcel/watcher@2.5.1': + resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==} + engines: {node: '>= 10.0.0'} + '@react-hook/latest@1.0.3': resolution: {integrity: sha512-dy6duzl+JnAZcDbNTfmaP3xHiKtbXYOaz3G51MGVljh548Y8MWzTr+PHLOfvpypEVW9zwvl+VyKjbWKEVbV1Rg==} peerDependencies: @@ -719,14 +839,8 @@ packages: peerDependencies: react: '>=18' - '@react-rxjs/core@0.10.8': - resolution: {integrity: sha512-vCA7dDpJ7whvBJCerCqY5wvrPnIo4EvxYihQNuDy0u0OhN4kYafs2H755sMLeUXBwSihiskd9Z3v8SHpmcEdzQ==} - peerDependencies: - react: '>=16.8.0' - rxjs: '>=7' - - '@reduxjs/toolkit@2.9.0': - resolution: {integrity: sha512-fSfQlSRu9Z5yBkvsNhYF2rPS8cGXn/TZVrlwN1948QyZ8xMZ0JvP50S2acZNaf+o63u6aEeMjipFyksjIcWrog==} + '@reduxjs/toolkit@2.11.2': + resolution: {integrity: sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==} peerDependencies: react: ^16.9.0 || ^17.0.0 || ^18 || ^19 react-redux: ^7.2.1 || ^8.1.3 || ^9.0.0 @@ -736,127 +850,173 @@ packages: react-redux: optional: true - '@remix-run/router@1.23.0': - resolution: {integrity: sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==} - engines: {node: '>=14.0.0'} - - '@rolldown/pluginutils@1.0.0-beta.35': - resolution: {integrity: sha512-slYrCpoxJUqzFDDNlvrOYRazQUNRvWPjXA17dAOISY3rDMxX6k8K4cj2H+hEYMHF81HO3uNd5rHVigAWRM5dSg==} + '@rolldown/pluginutils@1.0.0-beta.47': + resolution: {integrity: sha512-8QagwMH3kNCuzD8EWL8R2YPW5e4OrHNSAHRFDdmFqEwEaD/KcNKjVoumo+gP2vW5eKB2UPbM6vTYiGZX0ixLnw==} - '@rollup/rollup-android-arm-eabi@4.52.2': - resolution: {integrity: sha512-o3pcKzJgSGt4d74lSZ+OCnHwkKBeAbFDmbEm5gg70eA8VkyCuC/zV9TwBnmw6VjDlRdF4Pshfb+WE9E6XY1PoQ==} + '@rollup/rollup-android-arm-eabi@4.55.1': + resolution: {integrity: sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.52.2': - resolution: {integrity: sha512-cqFSWO5tX2vhC9hJTK8WAiPIm4Q8q/cU8j2HQA0L3E1uXvBYbOZMhE2oFL8n2pKB5sOCHY6bBuHaRwG7TkfJyw==} + '@rollup/rollup-android-arm64@4.55.1': + resolution: {integrity: sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.52.2': - resolution: {integrity: sha512-vngduywkkv8Fkh3wIZf5nFPXzWsNsVu1kvtLETWxTFf/5opZmflgVSeLgdHR56RQh71xhPhWoOkEBvbehwTlVA==} + '@rollup/rollup-darwin-arm64@4.55.1': + resolution: {integrity: sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.52.2': - resolution: {integrity: sha512-h11KikYrUCYTrDj6h939hhMNlqU2fo/X4NB0OZcys3fya49o1hmFaczAiJWVAFgrM1NCP6RrO7lQKeVYSKBPSQ==} + '@rollup/rollup-darwin-x64@4.55.1': + resolution: {integrity: sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.52.2': - resolution: {integrity: sha512-/eg4CI61ZUkLXxMHyVlmlGrSQZ34xqWlZNW43IAU4RmdzWEx0mQJ2mN/Cx4IHLVZFL6UBGAh+/GXhgvGb+nVxw==} + '@rollup/rollup-freebsd-arm64@4.55.1': + resolution: {integrity: sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.52.2': - resolution: {integrity: sha512-QOWgFH5X9+p+S1NAfOqc0z8qEpJIoUHf7OWjNUGOeW18Mx22lAUOiA9b6r2/vpzLdfxi/f+VWsYjUOMCcYh0Ng==} + '@rollup/rollup-freebsd-x64@4.55.1': + resolution: {integrity: sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw==} cpu: [x64] os: [freebsd] - '@rollup/rollup-linux-arm-gnueabihf@4.52.2': - resolution: {integrity: sha512-kDWSPafToDd8LcBYd1t5jw7bD5Ojcu12S3uT372e5HKPzQt532vW+rGFFOaiR0opxePyUkHrwz8iWYEyH1IIQA==} + '@rollup/rollup-linux-arm-gnueabihf@4.55.1': + resolution: {integrity: sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.52.2': - resolution: {integrity: sha512-gKm7Mk9wCv6/rkzwCiUC4KnevYhlf8ztBrDRT9g/u//1fZLapSRc+eDZj2Eu2wpJ+0RzUKgtNijnVIB4ZxyL+w==} + '@rollup/rollup-linux-arm-musleabihf@4.55.1': + resolution: {integrity: sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.52.2': - resolution: {integrity: sha512-66lA8vnj5mB/rtDNwPgrrKUOtCLVQypkyDa2gMfOefXK6rcZAxKLO9Fy3GkW8VkPnENv9hBkNOFfGLf6rNKGUg==} + '@rollup/rollup-linux-arm64-gnu@4.55.1': + resolution: {integrity: sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.52.2': - resolution: {integrity: sha512-s+OPucLNdJHvuZHuIz2WwncJ+SfWHFEmlC5nKMUgAelUeBUnlB4wt7rXWiyG4Zn07uY2Dd+SGyVa9oyLkVGOjA==} + '@rollup/rollup-linux-arm64-musl@4.55.1': + resolution: {integrity: sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-loong64-gnu@4.52.2': - resolution: {integrity: sha512-8wTRM3+gVMDLLDdaT6tKmOE3lJyRy9NpJUS/ZRWmLCmOPIJhVyXwjBo+XbrrwtV33Em1/eCTd5TuGJm4+DmYjw==} + '@rollup/rollup-linux-loong64-gnu@4.55.1': + resolution: {integrity: sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-loong64-musl@4.55.1': + resolution: {integrity: sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-ppc64-gnu@4.52.2': - resolution: {integrity: sha512-6yqEfgJ1anIeuP2P/zhtfBlDpXUb80t8DpbYwXQ3bQd95JMvUaqiX+fKqYqUwZXqdJDd8xdilNtsHM2N0cFm6A==} + '@rollup/rollup-linux-ppc64-gnu@4.55.1': + resolution: {integrity: sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.52.2': - resolution: {integrity: sha512-sshYUiYVSEI2B6dp4jMncwxbrUqRdNApF2c3bhtLAU0qA8Lrri0p0NauOsTWh3yCCCDyBOjESHMExonp7Nzc0w==} + '@rollup/rollup-linux-ppc64-musl@4.55.1': + resolution: {integrity: sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.55.1': + resolution: {integrity: sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.52.2': - resolution: {integrity: sha512-duBLgd+3pqC4MMwBrKkFxaZerUxZcYApQVC5SdbF5/e/589GwVvlRUnyqMFbM8iUSb1BaoX/3fRL7hB9m2Pj8Q==} + '@rollup/rollup-linux-riscv64-musl@4.55.1': + resolution: {integrity: sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.52.2': - resolution: {integrity: sha512-tzhYJJidDUVGMgVyE+PmxENPHlvvqm1KILjjZhB8/xHYqAGeizh3GBGf9u6WdJpZrz1aCpIIHG0LgJgH9rVjHQ==} + '@rollup/rollup-linux-s390x-gnu@4.55.1': + resolution: {integrity: sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.52.2': - resolution: {integrity: sha512-opH8GSUuVcCSSyHHcl5hELrmnk4waZoVpgn/4FDao9iyE4WpQhyWJ5ryl5M3ocp4qkRuHfyXnGqg8M9oKCEKRA==} + '@rollup/rollup-linux-x64-gnu@4.55.1': + resolution: {integrity: sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.52.2': - resolution: {integrity: sha512-LSeBHnGli1pPKVJ79ZVJgeZWWZXkEe/5o8kcn23M8eMKCUANejchJbF/JqzM4RRjOJfNRhKJk8FuqL1GKjF5oQ==} + '@rollup/rollup-linux-x64-musl@4.55.1': + resolution: {integrity: sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w==} cpu: [x64] os: [linux] - '@rollup/rollup-openharmony-arm64@4.52.2': - resolution: {integrity: sha512-uPj7MQ6/s+/GOpolavm6BPo+6CbhbKYyZHUDvZ/SmJM7pfDBgdGisFX3bY/CBDMg2ZO4utfhlApkSfZ92yXw7Q==} + '@rollup/rollup-openbsd-x64@4.55.1': + resolution: {integrity: sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.55.1': + resolution: {integrity: sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.52.2': - resolution: {integrity: sha512-Z9MUCrSgIaUeeHAiNkm3cQyst2UhzjPraR3gYYfOjAuZI7tcFRTOD+4cHLPoS/3qinchth+V56vtqz1Tv+6KPA==} + '@rollup/rollup-win32-arm64-msvc@4.55.1': + resolution: {integrity: sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.52.2': - resolution: {integrity: sha512-+GnYBmpjldD3XQd+HMejo+0gJGwYIOfFeoBQv32xF/RUIvccUz20/V6Otdv+57NE70D5pa8W/jVGDoGq0oON4A==} + '@rollup/rollup-win32-ia32-msvc@4.55.1': + resolution: {integrity: sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-gnu@4.52.2': - resolution: {integrity: sha512-ApXFKluSB6kDQkAqZOKXBjiaqdF1BlKi+/eqnYe9Ee7U2K3pUDKsIyr8EYm/QDHTJIM+4X+lI0gJc3TTRhd+dA==} + '@rollup/rollup-win32-x64-gnu@4.55.1': + resolution: {integrity: sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.52.2': - resolution: {integrity: sha512-ARz+Bs8kY6FtitYM96PqPEVvPXqEZmPZsSkXvyX19YzDqkCaIlhCieLLMI5hxO9SRZ2XtCtm8wxhy0iJ2jxNfw==} + '@rollup/rollup-win32-x64-msvc@4.55.1': + resolution: {integrity: sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw==} cpu: [x64] os: [win32] - '@rx-state/core@0.1.4': - resolution: {integrity: sha512-Z+3hjU2xh1HisLxt+W5hlYX/eGSDaXXP+ns82gq/PLZpkXLu0uwcNUh9RLY3Clq4zT+hSsA3vcpIGt6+UAb8rQ==} + '@shortercode/webzip@1.1.1-0': + resolution: {integrity: sha512-c+9LbU7MTqWDXS73Pf7vEm5AyFFfbuuHiMgqmApwkwD5BrlELKaSqKjofG4Gqd3c/NgF8fO0iR5NbPdE26jF2Q==} + + '@sinclair/typebox@0.31.28': + resolution: {integrity: sha512-/s55Jujywdw/Jpan+vsy6JZs1z2ZTGxTmbZTPiuSL2wz9mfzA2gN1zzaqmvfi4pq+uOt7Du85fkiwv5ymW84aQ==} + + '@solid-primitives/event-listener@2.4.3': + resolution: {integrity: sha512-h4VqkYFv6Gf+L7SQj+Y6puigL/5DIi7x5q07VZET7AWcS+9/G3WfIE9WheniHWJs51OEkRB43w6lDys5YeFceg==} peerDependencies: - rxjs: '>=7' + solid-js: ^1.6.12 + + '@solid-primitives/keyboard@1.3.3': + resolution: {integrity: sha512-9dQHTTgLBqyAI7aavtO+HnpTVJgWQA1ghBSrmLtMu1SMxLPDuLfuNr+Tk5udb4AL4Ojg7h9JrKOGEEDqsJXWJA==} + peerDependencies: + solid-js: ^1.6.12 + + '@solid-primitives/resize-observer@2.1.3': + resolution: {integrity: sha512-zBLje5E06TgOg93S7rGPldmhDnouNGhvfZVKOp+oG2XU8snA+GoCSSCz1M+jpNAg5Ek2EakU5UVQqL152WmdXQ==} + peerDependencies: + solid-js: ^1.6.12 + + '@solid-primitives/rootless@1.5.2': + resolution: {integrity: sha512-9HULb0QAzL2r47CCad0M+NKFtQ+LrGGNHZfteX/ThdGvKIg2o2GYhBooZubTCd/RTu2l2+Nw4s+dEfiDGvdrrQ==} + peerDependencies: + solid-js: ^1.6.12 + + '@solid-primitives/static-store@0.1.2': + resolution: {integrity: sha512-ReK+5O38lJ7fT+L6mUFvUr6igFwHBESZF+2Ug842s7fvlVeBdIVEdTCErygff6w7uR6+jrr7J8jQo+cYrEq4Iw==} + peerDependencies: + solid-js: ^1.6.12 + + '@solid-primitives/utils@6.3.2': + resolution: {integrity: sha512-hZ/M/qr25QOCcwDPOHtGjxTD8w2mNyVAYvcfgwzBHq2RwNqHNdDNsMZYap20+ruRwW4A3Cdkczyoz0TSxLCAPQ==} + peerDependencies: + solid-js: ^1.6.12 + + '@sqlite.org/sqlite-wasm@3.48.0-build4': + resolution: {integrity: sha512-hI6twvUkzOmyGZhQMza1gpfqErZxXRw6JEsiVjUbo7tFanVD+8Oil0Ih3l2nGzHdxPI41zFmfUQG7GHqhciKZQ==} + hasBin: true '@stablelib/base64@2.0.1': resolution: {integrity: sha512-P2z89A7N1ETt6RxgpVdDT2xlg8cnm3n6td0lY9gyK7EiWK3wdq388yFX/hLknkCC0we05OZAD1rfxlQJUbl5VQ==} @@ -882,74 +1042,74 @@ packages: '@stablelib/x25519@2.0.1': resolution: {integrity: sha512-qi04HS2puHaBf50kM/kes5QcZFGsx8yF0YmCjLCOa/LPmnBaKEKX9ZR82OnnCwMn72YH13R/bBZgr/UP0aPFfA==} - '@standard-schema/spec@1.0.0': - resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} '@standard-schema/utils@0.3.0': resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==} - '@swc/core-darwin-arm64@1.13.19': - resolution: {integrity: sha512-NxDyte9tCJSJ8+R62WDtqwg8eI57lubD52sHyGOfezpJBOPr36bUSGGLyO3Vod9zTGlOu2CpkuzA/2iVw92u1g==} + '@swc/core-darwin-arm64@1.15.8': + resolution: {integrity: sha512-M9cK5GwyWWRkRGwwCbREuj6r8jKdES/haCZ3Xckgkl8MUQJZA3XB7IXXK1IXRNeLjg6m7cnoMICpXv1v1hlJOg==} engines: {node: '>=10'} cpu: [arm64] os: [darwin] - '@swc/core-darwin-x64@1.13.19': - resolution: {integrity: sha512-+w5DYrJndSygFFRDcuPYmx5BljD6oYnAohZ15K1L6SfORHp/BTSIbgSFRKPoyhjuIkDiq3W0um8RoMTOBAcQjQ==} + '@swc/core-darwin-x64@1.15.8': + resolution: {integrity: sha512-j47DasuOvXl80sKJHSi2X25l44CMc3VDhlJwA7oewC1nV1VsSzwX+KOwE5tLnfORvVJJyeiXgJORNYg4jeIjYQ==} engines: {node: '>=10'} cpu: [x64] os: [darwin] - '@swc/core-linux-arm-gnueabihf@1.13.19': - resolution: {integrity: sha512-7LlfgpdwwYq2q7himNkAAFo4q6jysMLFNoBH6GRP7WL29NcSsl5mPMJjmYZymK+sYq/9MTVieDTQvChzYDsapw==} + '@swc/core-linux-arm-gnueabihf@1.15.8': + resolution: {integrity: sha512-siAzDENu2rUbwr9+fayWa26r5A9fol1iORG53HWxQL1J8ym4k7xt9eME0dMPXlYZDytK5r9sW8zEA10F2U3Xwg==} engines: {node: '>=10'} cpu: [arm] os: [linux] - '@swc/core-linux-arm64-gnu@1.13.19': - resolution: {integrity: sha512-ml3I6Lm2marAQ3UC/TS9t/yILBh/eDSVHAdPpikp652xouWAVW1znUeV6bBSxe1sSZIenv+p55ubKAWq/u84sQ==} + '@swc/core-linux-arm64-gnu@1.15.8': + resolution: {integrity: sha512-o+1y5u6k2FfPYbTRUPvurwzNt5qd0NTumCTFscCNuBksycloXY16J8L+SMW5QRX59n4Hp9EmFa3vpvNHRVv1+Q==} engines: {node: '>=10'} cpu: [arm64] os: [linux] - '@swc/core-linux-arm64-musl@1.13.19': - resolution: {integrity: sha512-M/otFc3/rWWkbF6VgbOXVzUKVoE7MFcphTaStxJp4bwb7oP5slYlxMZN51Dk/OTOfvCDo9pTAFDKNyixbkXMDQ==} + '@swc/core-linux-arm64-musl@1.15.8': + resolution: {integrity: sha512-koiCqL09EwOP1S2RShCI7NbsQuG6r2brTqUYE7pV7kZm9O17wZ0LSz22m6gVibpwEnw8jI3IE1yYsQTVpluALw==} engines: {node: '>=10'} cpu: [arm64] os: [linux] - '@swc/core-linux-x64-gnu@1.13.19': - resolution: {integrity: sha512-NoMUKaOJEdouU4tKF88ggdDHFiRRING+gYLxDqnTfm+sUXaizB5OGBRzvSVDYSXQb1SuUuChnXFPFzwTWbt3ZQ==} + '@swc/core-linux-x64-gnu@1.15.8': + resolution: {integrity: sha512-4p6lOMU3bC+Vd5ARtKJ/FxpIC5G8v3XLoPEZ5s7mLR8h7411HWC/LmTXDHcrSXRC55zvAVia1eldy6zDLz8iFQ==} engines: {node: '>=10'} cpu: [x64] os: [linux] - '@swc/core-linux-x64-musl@1.13.19': - resolution: {integrity: sha512-r6krlZwyu8SBaw24QuS1lau2I9q8M+eJV6ITz0rpb6P1Bx0elf9ii5Bhh8ddmIqXXH8kOGSjC/dwcdHbZqAhgw==} + '@swc/core-linux-x64-musl@1.15.8': + resolution: {integrity: sha512-z3XBnbrZAL+6xDGAhJoN4lOueIxC/8rGrJ9tg+fEaeqLEuAtHSW2QHDHxDwkxZMjuF/pZ6MUTjHjbp8wLbuRLA==} engines: {node: '>=10'} cpu: [x64] os: [linux] - '@swc/core-win32-arm64-msvc@1.13.19': - resolution: {integrity: sha512-awcZSIuxyVn0Dw28VjMvgk1qiDJ6CeQwHkZNUjg2UxVlq23zE01NMMp+zkoGFypmLG9gaGmJSzuoqvk/WCQ5tw==} + '@swc/core-win32-arm64-msvc@1.15.8': + resolution: {integrity: sha512-djQPJ9Rh9vP8GTS/Df3hcc6XP6xnG5c8qsngWId/BLA9oX6C7UzCPAn74BG/wGb9a6j4w3RINuoaieJB3t+7iQ==} engines: {node: '>=10'} cpu: [arm64] os: [win32] - '@swc/core-win32-ia32-msvc@1.13.19': - resolution: {integrity: sha512-H5d+KO7ISoLNgYvTbOcCQjJZNM3R7yaYlrMAF13lUr6GSiOUX+92xtM31B+HvzAWI7HtvVe74d29aC1b1TpXFA==} + '@swc/core-win32-ia32-msvc@1.15.8': + resolution: {integrity: sha512-/wfAgxORg2VBaUoFdytcVBVCgf1isWZIEXB9MZEUty4wwK93M/PxAkjifOho9RN3WrM3inPLabICRCEgdHpKKQ==} engines: {node: '>=10'} cpu: [ia32] os: [win32] - '@swc/core-win32-x64-msvc@1.13.19': - resolution: {integrity: sha512-qNoyCpXvv2O3JqXKanRIeoMn03Fho/As+N4Fhe7u0FsYh4VYqGQah4DGDzEP/yjl4Gx1IElhqLGDhCCGMwWaDw==} + '@swc/core-win32-x64-msvc@1.15.8': + resolution: {integrity: sha512-GpMePrh9Sl4d61o4KAHOOv5is5+zt6BEXCOCgs/H0FLGeii7j9bWDE8ExvKFy2GRRZVNR1ugsnzaGWHKM6kuzA==} engines: {node: '>=10'} cpu: [x64] os: [win32] - '@swc/core@1.13.19': - resolution: {integrity: sha512-V1r4wFdjaZIUIZZrV2Mb/prEeu03xvSm6oatPxsvnXKF9lNh5Jtk9QvUdiVfD9rrvi7bXrAVhg9Wpbmv/2Fl1g==} + '@swc/core@1.15.8': + resolution: {integrity: sha512-T8keoJjXaSUoVBCIjgL6wAnhADIb09GOELzKg10CjNg+vLX48P93SME6jTfte9MZIm5m+Il57H3rTSk/0kzDUw==} engines: {node: '>=10'} peerDependencies: '@swc/helpers': '>=0.5.17' @@ -963,63 +1123,212 @@ packages: '@swc/types@0.1.25': resolution: {integrity: sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==} - '@tanstack/query-core@5.90.2': - resolution: {integrity: sha512-k/TcR3YalnzibscALLwxeiLUub6jN5EDLwKDiO7q5f4ICEoptJ+n9+7vcEFy5/x/i6Q+Lb/tXrsKCggf5uQJXQ==} + '@tanstack/devtools-client@0.0.5': + resolution: {integrity: sha512-hsNDE3iu4frt9cC2ppn1mNRnLKo2uc1/1hXAyY9z4UYb+o40M2clFAhiFoo4HngjfGJDV3x18KVVIq7W4Un+zA==} + engines: {node: '>=18'} + + '@tanstack/devtools-event-bus@0.4.0': + resolution: {integrity: sha512-1t+/csFuDzi+miDxAOh6Xv7VDE80gJEItkTcAZLjV5MRulbO/W8ocjHLI2Do/p2r2/FBU0eKCRTpdqvXaYoHpQ==} + engines: {node: '>=18'} - '@tanstack/query-devtools@5.90.1': - resolution: {integrity: sha512-GtINOPjPUH0OegJExZ70UahT9ykmAhmtNVcmtdnOZbxLwT7R5OmRztR5Ahe3/Cu7LArEmR6/588tAycuaWb1xQ==} + '@tanstack/devtools-event-client@0.4.0': + resolution: {integrity: sha512-RPfGuk2bDZgcu9bAJodvO2lnZeHuz4/71HjZ0bGb/SPg8+lyTA+RLSKQvo7fSmPSi8/vcH3aKQ8EM9ywf1olaw==} + engines: {node: '>=18'} - '@tanstack/react-query-devtools@5.90.2': - resolution: {integrity: sha512-vAXJzZuBXtCQtrY3F/yUNJCV4obT/A/n81kb3+YqLbro5Z2+phdAbceO+deU3ywPw8B42oyJlp4FhO0SoivDFQ==} + '@tanstack/devtools-ui@0.4.4': + resolution: {integrity: sha512-5xHXFyX3nom0UaNfiOM92o6ziaHjGo3mcSGe2HD5Xs8dWRZNpdZ0Smd0B9ddEhy0oB+gXyMzZgUJb9DmrZV0Mg==} + engines: {node: '>=18'} peerDependencies: - '@tanstack/react-query': ^5.90.2 - react: ^18 || ^19 + solid-js: '>=1.9.7' - '@tanstack/react-query@5.90.2': - resolution: {integrity: sha512-CLABiR+h5PYfOWr/z+vWFt5VsOA2ekQeRQBFSKlcoW6Ndx/f8rfyVmq4LbgOM4GG2qtxAxjLYLOpCNTYm4uKzw==} + '@tanstack/devtools-vite@0.4.0': + resolution: {integrity: sha512-vZ5SsjcLSLC+lBb4N6QDJEdrsrORs8OtIcwQafexAR7aJOv6SxGNoqERujEbTzfWY+PAypa1oYxPqtEAOcitDw==} + engines: {node: '>=18'} peerDependencies: - react: ^18 || ^19 + vite: ^6.0.0 || ^7.0.0 - '@tanstack/react-virtual@3.13.12': - resolution: {integrity: sha512-Gd13QdxPSukP8ZrkbgS2RwoZseTTbQPLnQEn7HY/rqtM+8Zt95f7xKC7N0EsKs7aoz0WzZ+fditZux+F8EzYxA==} + '@tanstack/devtools@0.10.1': + resolution: {integrity: sha512-1gtPmCDXV4Pl1nVtoqwjV0tc4E9GMuFtlkBX1Lz1KfqI3W9JojT5YsVifOQ/g8BTQ5w5+tyIANwHU7WYgLq/MQ==} + engines: {node: '>=18'} peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + solid-js: '>=1.9.7' - '@tanstack/virtual-core@3.13.12': - resolution: {integrity: sha512-1YBOJfRHV4sXUmWsFSf5rQor4Ss82G8dQWLRbnk3GA4jeP8hQt1hxXh0tmflpC0dz3VgEv/1+qwPyLeWkQuPFA==} + '@tanstack/form-core@1.27.7': + resolution: {integrity: sha512-nvogpyE98fhb0NDw1Bf2YaCH+L7ZIUgEpqO9TkHucDn6zg3ni521boUpv0i8HKIrmmFwDYjWZoCnrgY4HYWTkw==} - '@types/byte-size@8.1.2': - resolution: {integrity: sha512-jGyVzYu6avI8yuqQCNTZd65tzI8HZrLjKX9sdMqZrGWVlNChu0rf6p368oVEDCYJe5BMx2Ov04tD1wqtgTwGSA==} + '@tanstack/history@1.145.7': + resolution: {integrity: sha512-gMo/ReTUp0a3IOcZoI3hH6PLDC2R/5ELQ7P2yu9F6aEkA0wSQh+Q4qzMrtcKvF2ut0oE+16xWCGDo/TdYd6cEQ==} + engines: {node: '>=12'} - '@types/d3-array@3.2.2': - resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} + '@tanstack/pacer-lite@0.1.1': + resolution: {integrity: sha512-y/xtNPNt/YeyoVxE/JCx+T7yjEzpezmbb+toK8DDD1P4m7Kzs5YR956+7OKexG3f8aXgC3rLZl7b1V+yNUSy5w==} + engines: {node: '>=18'} - '@types/d3-color@3.1.3': - resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + '@tanstack/query-core@5.90.16': + resolution: {integrity: sha512-MvtWckSVufs/ja463/K4PyJeqT+HMlJWtw6PrCpywznd2NSgO3m4KwO9RqbFqGg6iDE8vVMFWMeQI4Io3eEYww==} - '@types/d3-ease@3.0.2': - resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} + '@tanstack/query-devtools@5.92.0': + resolution: {integrity: sha512-N8D27KH1vEpVacvZgJL27xC6yPFUy0Zkezn5gnB3L3gRCxlDeSuiya7fKge8Y91uMTnC8aSxBQhcK6ocY7alpQ==} - '@types/d3-interpolate@3.0.4': - resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + '@tanstack/react-devtools@0.9.0': + resolution: {integrity: sha512-Lq0svXOTG5N61SHgx8F0on6zz2GB0kmFjN/yyfNLrJyRgJ+U3jYFRd9ti3uBPABsXzHQMHYYujnTXrOYp/OaUg==} + engines: {node: '>=18'} + peerDependencies: + '@types/react': '>=16.8' + '@types/react-dom': '>=16.8' + react: '>=16.8' + react-dom: '>=16.8' - '@types/d3-path@3.1.1': - resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} + '@tanstack/react-form@1.27.7': + resolution: {integrity: sha512-xTg4qrUY0fuLaSnkATLZcK3BWlnwLp7IuAb6UTbZKngiDEvvDCNTvVvHgPlgef1O2qN4klZxInRyRY6oEkXZ2A==} + peerDependencies: + '@tanstack/react-start': '*' + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@tanstack/react-start': + optional: true - '@types/d3-scale@4.0.9': - resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} + '@tanstack/react-query-devtools@5.91.2': + resolution: {integrity: sha512-ZJ1503ay5fFeEYFUdo7LMNFzZryi6B0Cacrgr2h1JRkvikK1khgIq6Nq2EcblqEdIlgB/r7XDW8f8DQ89RuUgg==} + peerDependencies: + '@tanstack/react-query': ^5.90.14 + react: ^18 || ^19 - '@types/d3-shape@3.1.7': - resolution: {integrity: sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==} + '@tanstack/react-query@5.90.16': + resolution: {integrity: sha512-bpMGOmV4OPmif7TNMteU/Ehf/hoC0Kf98PDc0F4BZkFrEapRMEqI/V6YS0lyzwSV6PQpY1y4xxArUIfBW5LVxQ==} + peerDependencies: + react: ^18 || ^19 - '@types/d3-time@3.0.4': - resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} + '@tanstack/react-router-devtools@1.145.7': + resolution: {integrity: sha512-crzHSQ/rcGX7RfuYsmm1XG5quurNMDTIApU7jfwDx5J9HnUxCOSJrbFX0L3w0o0VRCw5xhrL2EdCnW78Ic86hg==} + engines: {node: '>=12'} + peerDependencies: + '@tanstack/react-router': ^1.145.7 + '@tanstack/router-core': ^1.145.7 + react: '>=18.0.0 || >=19.0.0' + react-dom: '>=18.0.0 || >=19.0.0' + peerDependenciesMeta: + '@tanstack/router-core': + optional: true - '@types/d3-timer@3.0.2': - resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + '@tanstack/react-router@1.145.7': + resolution: {integrity: sha512-0O+a4TjJSPXd2BsvDPwDPBKRQKYqNIBg5TAg9NzCteqJ0NXRxwohyqCksHqCEEtJe/uItwqmHoqkK4q5MDhEsA==} + engines: {node: '>=12'} + peerDependencies: + react: '>=18.0.0 || >=19.0.0' + react-dom: '>=18.0.0 || >=19.0.0' - '@types/debug@4.1.12': + '@tanstack/react-store@0.8.0': + resolution: {integrity: sha512-1vG9beLIuB7q69skxK9r5xiLN3ztzIPfSQSs0GfeqWGO2tGIyInZx0x1COhpx97RKaONSoAb8C3dxacWksm1ow==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@tanstack/react-table@8.21.3': + resolution: {integrity: sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==} + engines: {node: '>=12'} + peerDependencies: + react: '>=16.8' + react-dom: '>=16.8' + + '@tanstack/react-virtual@3.13.16': + resolution: {integrity: sha512-y4xLKvLu6UZWiGdNcgk3yYlzCznYIV0m8dSyUzr3eAC0dHLos5V74qhUHxutYddFGgGU8sWLkp6H5c2RCrsrXw==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@tanstack/router-core@1.145.7': + resolution: {integrity: sha512-v6jx6JqVUBM0/FcBq1tX22xiPq8Ufc0PDEP582/4deYoq2/RYd+bZstANp3mGSsqdxE/luhoLYuuSQiwi/j1wA==} + engines: {node: '>=12'} + + '@tanstack/router-devtools-core@1.145.7': + resolution: {integrity: sha512-oKeq/6QvN49THCh++FJyPv1X65i20qGS4aJHQTNsl4cu1piW1zWUhab2L3DZVr3G8C40FW3xb6hVw92N/fzZbQ==} + engines: {node: '>=12'} + peerDependencies: + '@tanstack/router-core': ^1.145.7 + csstype: ^3.0.10 + solid-js: '>=1.9.5' + peerDependenciesMeta: + csstype: + optional: true + + '@tanstack/router-generator@1.145.7': + resolution: {integrity: sha512-xg71c1WTku0ro0rgpJWh3Dt+ognV9qWe2KJHAPzrqfOYdUYu9sGq7Ri4jo8Rk0luXWZrWsrFdBP+9Jx6JH6zWA==} + engines: {node: '>=12'} + + '@tanstack/router-plugin@1.145.7': + resolution: {integrity: sha512-Rimo0NragYKHwjoYX9JBLS8VkZD4D/LqzzLIlX9yz93lmWFRu/DbuS7fDZNqX1Ea8naNvo18DlySszYLzC8XDg==} + engines: {node: '>=12'} + peerDependencies: + '@rsbuild/core': '>=1.0.2' + '@tanstack/react-router': ^1.145.7 + vite: '>=5.0.0 || >=6.0.0 || >=7.0.0' + vite-plugin-solid: ^2.11.10 + webpack: '>=5.92.0' + peerDependenciesMeta: + '@rsbuild/core': + optional: true + '@tanstack/react-router': + optional: true + vite: + optional: true + vite-plugin-solid: + optional: true + webpack: + optional: true + + '@tanstack/router-utils@1.143.11': + resolution: {integrity: sha512-N24G4LpfyK8dOlnP8BvNdkuxg1xQljkyl6PcrdiPSA301pOjatRT1y8wuCCJZKVVD8gkd0MpCZ0VEjRMGILOtA==} + engines: {node: '>=12'} + + '@tanstack/store@0.7.7': + resolution: {integrity: sha512-xa6pTan1bcaqYDS9BDpSiS63qa6EoDkPN9RsRaxHuDdVDNntzq3xNwR5YKTU/V3SkSyC9T4YVOPh2zRQN0nhIQ==} + + '@tanstack/store@0.8.0': + resolution: {integrity: sha512-Om+BO0YfMZe//X2z0uLF2j+75nQga6TpTJgLJQBiq85aOyZNIhkCgleNcud2KQg4k4v9Y9l+Uhru3qWMPGTOzQ==} + + '@tanstack/table-core@8.21.3': + resolution: {integrity: sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==} + engines: {node: '>=12'} + + '@tanstack/virtual-core@3.13.16': + resolution: {integrity: sha512-njazUC8mDkrxWmyZmn/3eXrDcP8Msb3chSr4q6a65RmwdSbMlMCdnOphv6/8mLO7O3Fuza5s4M4DclmvAO5w0w==} + + '@tanstack/virtual-file-routes@1.145.4': + resolution: {integrity: sha512-CI75JrfqSluhdGwLssgVeQBaCphgfkMQpi8MCY3UJX1hoGzXa8kHYJcUuIFMOLs1q7zqHy++EVVtMK03osR5wQ==} + engines: {node: '>=12'} + + '@types/byte-size@8.1.2': + resolution: {integrity: sha512-jGyVzYu6avI8yuqQCNTZd65tzI8HZrLjKX9sdMqZrGWVlNChu0rf6p368oVEDCYJe5BMx2Ov04tD1wqtgTwGSA==} + + '@types/d3-array@3.2.2': + resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} + + '@types/d3-color@3.1.3': + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + + '@types/d3-ease@3.0.2': + resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} + + '@types/d3-interpolate@3.0.4': + resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + + '@types/d3-path@3.1.1': + resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} + + '@types/d3-scale@4.0.9': + resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} + + '@types/d3-shape@3.1.7': + resolution: {integrity: sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==} + + '@types/d3-time@3.0.4': + resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} + + '@types/d3-timer@3.0.2': + resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + + '@types/debug@4.1.12': resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} '@types/estree-jsx@1.0.5': @@ -1028,58 +1337,37 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - '@types/file-saver@2.0.7': - resolution: {integrity: sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==} - '@types/hast@3.0.4': resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} - '@types/history@4.7.11': - resolution: {integrity: sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==} - '@types/humanize-duration@3.27.4': resolution: {integrity: sha512-yaf7kan2Sq0goxpbcwTQ+8E9RP6HutFBPv74T/IA/ojcHKhuKVlk2YFYyHhWZeLvZPzzLE3aatuQB4h0iqyyUA==} '@types/lodash-es@4.17.12': resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==} - '@types/lodash@4.17.20': - resolution: {integrity: sha512-H3MHACvFUEiujabxhaI/ImO6gUrd8oOurg7LQtS7mbwIXA/cUqWrvBsaeJ23aZEPk1TAYkurjfMbSELfoCXlGA==} + '@types/lodash@4.17.21': + resolution: {integrity: sha512-FOvQ0YPD5NOfPgMzJihoT+Za5pdkDJWcbpuj1DjaKZIr/gxodQjY/uWEFlTNqW2ugXHUiL8lRQgw63dzKHZdeQ==} '@types/mdast@4.0.4': resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} - '@types/minimist@1.2.5': - resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} - '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} - '@types/node@24.5.2': - resolution: {integrity: sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==} - - '@types/normalize-package-data@2.4.4': - resolution: {integrity: sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==} - - '@types/parse-json@4.0.2': - resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} + '@types/node@25.0.3': + resolution: {integrity: sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==} '@types/qs@6.14.0': resolution: {integrity: sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==} - '@types/react-dom@19.1.9': - resolution: {integrity: sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ==} + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} peerDependencies: - '@types/react': ^19.0.0 + '@types/react': ^19.2.0 - '@types/react-router-dom@5.3.3': - resolution: {integrity: sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==} - - '@types/react-router@5.1.20': - resolution: {integrity: sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==} - - '@types/react@19.1.13': - resolution: {integrity: sha512-hHkbU/eoO3EG5/MZkuFSKmYqPbSVk5byPFa3e7y/8TybHiLMACgI8seVYlicwk7H5K/rI2px9xrQp/C+AUDTiQ==} + '@types/react@19.2.7': + resolution: {integrity: sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==} '@types/unist@2.0.11': resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} @@ -1090,107 +1378,107 @@ packages: '@types/use-sync-external-store@0.0.6': resolution: {integrity: sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==} + '@uidotdev/usehooks@2.4.1': + resolution: {integrity: sha512-1I+RwWyS+kdv3Mv0Vmc+p0dPYH0DTRAo04HLyXReYBL9AeseDWUJyi4THuksBJcu9F0Pih69Ak150VDnqbVnXg==} + engines: {node: '>=16'} + peerDependencies: + react: '>=18.0.0' + react-dom: '>=18.0.0' + '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} - '@use-gesture/core@10.3.1': - resolution: {integrity: sha512-WcINiDt8WjqBdUXye25anHiNxPc0VOrlT8F6LLkU6cycrOGUDyY/yyFmsg3k8i5OLvv25llc0QC45GhR/C8llw==} - - '@use-gesture/react@10.3.1': - resolution: {integrity: sha512-Yy19y6O2GJq8f7CHf7L0nxL8bf4PZCPaVOCgJrusOeFHY1LvHgYXnmnXg6N5iwAnbgbZCDjo60SiM6IPJi9C5g==} - peerDependencies: - react: '>= 16.8.0' - - '@vitejs/plugin-react-swc@4.1.0': - resolution: {integrity: sha512-Ff690TUck0Anlh7wdIcnsVMhofeEVgm44Y4OYdeeEEPSKyZHzDI9gfVBvySEhDfXtBp8tLCbfsVKPWEMEjq8/g==} + '@vitejs/plugin-react-swc@4.2.2': + resolution: {integrity: sha512-x+rE6tsxq/gxrEJN3Nv3dIV60lFflPj94c90b+NNo6n1QV1QQUTLoL0MpaOVasUZ0zqVBn7ead1B5ecx1JAGfA==} engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: vite: ^4 || ^5 || ^6 || ^7 - JSONStream@1.3.5: - resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} - hasBin: true - acorn@8.15.0: resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} engines: {node: '>=0.4.0'} hasBin: true - add-stream@1.0.0: - resolution: {integrity: sha512-qQLMr+8o0WC4FZGQTcJiKBVC59JylcPSrTtk6usvmIDFUOCKegapy1VHQwRbFMOFyb/inzUVqHs+eMYKDM1YeQ==} + ajv@8.17.1: + resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - ansi-styles@3.2.1: - resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} - engines: {node: '>=4'} - ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} + ansis@4.2.0: + resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==} + engines: {node: '>=14'} + anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} - array-ify@1.0.0: - resolution: {integrity: sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==} + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - arrify@1.0.1: - resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} - engines: {node: '>=0.10.0'} + array-timsort@1.0.3: + resolution: {integrity: sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==} + + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + ast-types@0.16.1: + resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==} + engines: {node: '>=4'} + + astral-regex@2.0.0: + resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} + engines: {node: '>=8'} asynckit@0.4.0: resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - autoprefixer@10.4.21: - resolution: {integrity: sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==} + autoprefixer@10.4.23: + resolution: {integrity: sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==} engines: {node: ^10 || ^12 || >=14} hasBin: true peerDependencies: postcss: ^8.1.0 - axios@1.12.2: - resolution: {integrity: sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==} + axios@1.13.2: + resolution: {integrity: sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==} - babel-plugin-macros@3.1.0: - resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} - engines: {node: '>=10', npm: '>=6'} + babel-dead-code-elimination@1.0.11: + resolution: {integrity: sha512-mwq3W3e/pKSI6TG8lXMiDWvEi1VXYlSBlJlB3l+I0bAb5u1RNUl88udos85eOPNK3m5EXK9uO7d2g08pesTySQ==} bail@2.0.2: resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + balanced-match@2.0.0: + resolution: {integrity: sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==} - baseline-browser-mapping@2.8.7: - resolution: {integrity: sha512-bxxN2M3a4d1CRoQC//IqsR5XrLh0IJ8TCv2x6Y9N0nckNz/rTjZB3//GGscZziZOxmjP55rzxg/ze7usFI9FqQ==} + baseline-browser-mapping@2.9.11: + resolution: {integrity: sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==} hasBin: true - bignumber.js@9.3.1: - resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==} - binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} - brace-expansion@1.1.12: - resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} - braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.26.2: - resolution: {integrity: sha512-ECFzp6uFOSB+dcZ5BK/IBaGWssbSYBHvuMeMt3MMFyhI0Z8SqGgEkBLARgpRH3hutIgPVsALcMwbDrJqPxQ65A==} + browserslist@4.28.1: + resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true - buffer-from@1.1.2: - resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - byte-size@9.0.1: resolution: {integrity: sha512-YLe9x3rabBrcI0cueCdLS2l5ONUKywcRpTs02B8KP9/Cimhj7o3ZccGrPnRvcbyHMbb7W79/3MUJl7iGgTXKEw==} engines: {node: '>=12.17'} @@ -1200,6 +1488,9 @@ packages: '@75lb/nature': optional: true + cacheable@2.3.1: + resolution: {integrity: sha512-yr+FSHWn1ZUou5LkULX/S+jhfgfnLbuKQjE40tyEd4fxGZVMbBL5ifno0J0OauykS8UiCSgHi+DV/YD+rjFxFg==} + call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} @@ -1212,27 +1503,15 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} - camelcase-keys@6.2.2: - resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==} - engines: {node: '>=8'} - - camelcase@5.3.1: - resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} - engines: {node: '>=6'} - - caniuse-lite@1.0.30001745: - resolution: {integrity: sha512-ywt6i8FzvdgrrrGbr1jZVObnVv6adj+0if2/omv9cmR2oiZs30zL4DIyaptKcbOrBdOIc74QTMoJvSE2QHh5UQ==} + caniuse-lite@1.0.30001762: + resolution: {integrity: sha512-PxZwGNvH7Ak8WX5iXzoK1KPZttBXNPuaOvI2ZYU7NrlM+d9Ov+TUvlLOBNGzVXAntMSMMlJPd+jY6ovrVjSmUw==} ccount@2.0.1: resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} - chalk@2.4.2: - resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} - engines: {node: '>=4'} - - chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} character-entities-html4@2.1.0: resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} @@ -1250,36 +1529,24 @@ packages: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} - classnames@2.5.1: - resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==} - - cliui@6.0.0: - resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} - - cliui@7.0.4: - resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} - - cliui@8.0.1: - resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} - engines: {node: '>=12'} + chokidar@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} - color-convert@1.9.3: - resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} - color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} - color-name@1.1.3: - resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} - color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + colord@2.9.3: + resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} + combined-stream@1.0.8: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} @@ -1287,109 +1554,51 @@ packages: comma-separated-tokens@2.0.3: resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} - commander@2.20.3: - resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} - - compare-func@2.0.0: - resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} - - concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - - concat-stream@2.0.0: - resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} - engines: {'0': node >= 6.0} - - concurrently@9.2.1: - resolution: {integrity: sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==} - engines: {node: '>=18'} - hasBin: true - - conventional-changelog-angular@5.0.13: - resolution: {integrity: sha512-i/gipMxs7s8L/QeuavPF2hLnJgH6pEZAttySB6aiQLWcX3puWDL3ACVmvBhJGxnAy52Qc15ua26BufY6KpmrVA==} - engines: {node: '>=10'} - - conventional-changelog-atom@2.0.8: - resolution: {integrity: sha512-xo6v46icsFTK3bb7dY/8m2qvc8sZemRgdqLb/bjpBsH2UyOS8rKNTgcb5025Hri6IpANPApbXMg15QLb1LJpBw==} - engines: {node: '>=10'} - - conventional-changelog-codemirror@2.0.8: - resolution: {integrity: sha512-z5DAsn3uj1Vfp7po3gpt2Boc+Bdwmw2++ZHa5Ak9k0UKsYAO5mH1UBTN0qSCuJZREIhX6WU4E1p3IW2oRCNzQw==} - engines: {node: '>=10'} - - conventional-changelog-config-spec@2.1.0: - resolution: {integrity: sha512-IpVePh16EbbB02V+UA+HQnnPIohgXvJRxHcS5+Uwk4AT5LjzCZJm5sp/yqs5C6KZJ1jMsV4paEV13BN1pvDuxQ==} - - conventional-changelog-conventionalcommits@4.6.3: - resolution: {integrity: sha512-LTTQV4fwOM4oLPad317V/QNQ1FY4Hju5qeBIM1uTHbrnCE+Eg4CdRZ3gO2pUeR+tzWdp80M2j3qFFEDWVqOV4g==} - engines: {node: '>=10'} - - conventional-changelog-core@4.2.4: - resolution: {integrity: sha512-gDVS+zVJHE2v4SLc6B0sLsPiloR0ygU7HaDW14aNJE1v4SlqJPILPl/aJC7YdtRE4CybBf8gDwObBvKha8Xlyg==} - engines: {node: '>=10'} - - conventional-changelog-ember@2.0.9: - resolution: {integrity: sha512-ulzIReoZEvZCBDhcNYfDIsLTHzYHc7awh+eI44ZtV5cx6LVxLlVtEmcO+2/kGIHGtw+qVabJYjdI5cJOQgXh1A==} - engines: {node: '>=10'} - - conventional-changelog-eslint@3.0.9: - resolution: {integrity: sha512-6NpUCMgU8qmWmyAMSZO5NrRd7rTgErjrm4VASam2u5jrZS0n38V7Y9CzTtLT2qwz5xEChDR4BduoWIr8TfwvXA==} - engines: {node: '>=10'} + commander@11.1.0: + resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} + engines: {node: '>=16'} - conventional-changelog-express@2.0.6: - resolution: {integrity: sha512-SDez2f3iVJw6V563O3pRtNwXtQaSmEfTCaTBPCqn0oG0mfkq0rX4hHBq5P7De2MncoRixrALj3u3oQsNK+Q0pQ==} - engines: {node: '>=10'} + comment-json@4.5.1: + resolution: {integrity: sha512-taEtr3ozUmOB7it68Jll7s0Pwm+aoiHyXKrEC8SEodL4rNpdfDLqa7PfBlrgFoCNNdR8ImL+muti5IGvktJAAg==} + engines: {node: '>= 6'} - conventional-changelog-jquery@3.0.11: - resolution: {integrity: sha512-x8AWz5/Td55F7+o/9LQ6cQIPwrCjfJQ5Zmfqi8thwUEKHstEn4kTIofXub7plf1xvFA2TqhZlq7fy5OmV6BOMw==} - engines: {node: '>=10'} + consola@3.4.0: + resolution: {integrity: sha512-EiPU8G6dQG0GFHNR8ljnZFki/8a+cQwEQ+7wpxdChl02Q8HXlwEZWD5lqAF8vC2sEC3Tehr8hy7vErz88LHyUA==} + engines: {node: ^14.18.0 || >=16.10.0} - conventional-changelog-jshint@2.0.9: - resolution: {integrity: sha512-wMLdaIzq6TNnMHMy31hql02OEQ8nCQfExw1SE0hYL5KvU+JCTuPaDO+7JiogGT2gJAxiUGATdtYYfh+nT+6riA==} - engines: {node: '>=10'} + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - conventional-changelog-preset-loader@2.3.4: - resolution: {integrity: sha512-GEKRWkrSAZeTq5+YjUZOYxdHq+ci4dNwHvpaBC3+ENalzFWuCWa9EZXSuZBpkr72sMdKB+1fyDV4takK1Lf58g==} - engines: {node: '>=10'} + cookie-es@2.0.0: + resolution: {integrity: sha512-RAj4E421UYRgqokKUmotqAwuplYw15qtdXfY+hGzgCJ/MBjCVZcSoHK/kH9kocfjRjcDME7IiDWR/1WX1TM2Pg==} - conventional-changelog-writer@5.0.1: - resolution: {integrity: sha512-5WsuKUfxW7suLblAbFnxAcrvf6r+0b7GvNaWUwUIk0bXMnENP/PEieGKVUQrjPqwPT4o3EPAASBXiY6iHooLOQ==} - engines: {node: '>=10'} - hasBin: true + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} - conventional-changelog@3.1.25: - resolution: {integrity: sha512-ryhi3fd1mKf3fSjbLXOfK2D06YwKNic1nC9mWqybBHdObPd8KJ2vjaXZfYj1U23t+V8T8n0d7gwnc9XbIdFbyQ==} - engines: {node: '>=10'} + cosmiconfig@9.0.0: + resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true - conventional-commits-filter@2.0.7: - resolution: {integrity: sha512-ASS9SamOP4TbCClsRHxIHXRfcGCnIoQqkvAzCSbZzTFLfcTqJVugB0agRgsEELsqaeWgsXv513eS116wnlSSPA==} - engines: {node: '>=10'} + css-functions-list@3.2.3: + resolution: {integrity: sha512-IQOkD3hbR5KrN93MtcYuad6YPuTSUhntLHDuLEbFWE+ff2/XSZNdZG+LcbbIW5AXKg/WFIfYItIzVoHngHXZzA==} + engines: {node: '>=12 || >=16'} - conventional-commits-parser@3.2.4: - resolution: {integrity: sha512-nK7sAtfi+QXbxHCYfhpZsfRtaitZLIA6889kFIouLvz6repszQDgxBu7wf2WbU+Dco7sAnNCJYERCwt54WPC2Q==} - engines: {node: '>=10'} - hasBin: true + css-tree@3.1.0: + resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} - conventional-recommended-bump@6.1.0: - resolution: {integrity: sha512-uiApbSiNGM/kkdL9GTOLAqC4hbptObFo4wW2QRyHsKciGAfQuLU1ShZ1BIVI/+K2BE/W1AWYQMCXAsv4dyKPaw==} - engines: {node: '>=10'} + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} hasBin: true - convert-source-map@1.9.0: - resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} - - convert-source-map@2.0.0: - resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - - core-util-is@1.0.3: - resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} - - cosmiconfig@7.1.0: - resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} - engines: {node: '>=10'} - - csstype@3.1.3: - resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} d3-array@3.2.4: resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} @@ -1435,18 +1644,8 @@ packages: resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} engines: {node: '>=12'} - dargs@7.0.0: - resolution: {integrity: sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==} - engines: {node: '>=8'} - - date-fns@4.1.0: - resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==} - - dateformat@3.0.3: - resolution: {integrity: sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==} - - dayjs@1.11.18: - resolution: {integrity: sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==} + dayjs@1.11.19: + resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==} debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} @@ -1457,23 +1656,19 @@ packages: supports-color: optional: true - decamelize-keys@1.1.1: - resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} - engines: {node: '>=0.10.0'} - - decamelize@1.2.0: - resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} - engines: {node: '>=0.10.0'} - decimal.js-light@2.5.1: resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} decode-named-character-reference@1.2.0: resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==} - deepmerge-ts@7.1.5: - resolution: {integrity: sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==} - engines: {node: '>=16.0.0'} + dedent@1.5.1: + resolution: {integrity: sha512-+LxW+KLWxu3HW3M2w2ympwtqPrqYRzU8fqi6Fhd18fBALe15blJPI/I4+UHveMVG6lJqB4JNd4UG0S5cnVHwIg==} + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} @@ -1483,70 +1678,47 @@ packages: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} - detect-browser@5.3.0: - resolution: {integrity: sha512-53rsFbGdwMwlF7qvCt0ypLM5V5/Mbl0szB7GPN8y9NCcbknYOeVVXdrXEq+90IwAfrrzt6Hd+u2E2ntakICU8w==} - - detect-indent@6.1.0: - resolution: {integrity: sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==} - engines: {node: '>=8'} + detect-libc@1.0.3: + resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==} + engines: {node: '>=0.10'} + hasBin: true - detect-newline@3.1.0: - resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} - dice-coefficient@2.1.1: - resolution: {integrity: sha512-vPTcHmOQAuGvU6eyBtj7QCBwDJh2I7QpbBU51lbgfv7592KjBl6dm0baRBSh9ekt2X91MNAz7OpJrXCIUtDzlw==} - hasBin: true - - dijkstrajs@1.0.3: - resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==} - - dom-serializer@2.0.0: - resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + diff@8.0.2: + resolution: {integrity: sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==} + engines: {node: '>=0.3.1'} - domelementtype@2.3.0: - resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} - - domhandler@5.0.3: - resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} - engines: {node: '>= 4'} - - domutils@3.2.2: - resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} - - dot-prop@5.3.0: - resolution: {integrity: sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==} + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} - dotenv@17.2.2: - resolution: {integrity: sha512-Sf2LSQP+bOlhKWWyhFsn0UsfdK/kCWRv1iuA2gXAwt3dyNabr6QSj00I2V10pidqz69soatm9ZwZvpQMTIOd5Q==} - engines: {node: '>=12'} - - dotgitignore@2.1.0: - resolution: {integrity: sha512-sCm11ak2oY6DglEPpCB8TixLjWAxd3kJTs6UIcSasNYxXdFPV+YKlye92c8H4kKFqV5qYMIh7d+cYecEg0dIkA==} - engines: {node: '>=6'} - dunder-proto@1.0.1: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} - electron-to-chromium@1.5.224: - resolution: {integrity: sha512-kWAoUu/bwzvnhpdZSIc6KUyvkI1rbRXMT0Eq8pKReyOyaPZcctMli+EgvcN1PAvwVc7Tdo4Fxi2PsLNDU05mdg==} + easy-file-picker@1.2.0: + resolution: {integrity: sha512-GJxOW5s+g/pBr8Ha86a768yx0UZ6fYw+iAOrxK5HOzQ8q9hZxEJF0C8ztdAsH0mcze58FSpzv/d9flRCAuUKHg==} + + electron-to-chromium@1.5.267: + resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - entities@4.5.0: - resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} - engines: {node: '>=0.12'} - entities@6.0.1: resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} engines: {node: '>=0.12'} + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + error-ex@1.3.4: resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} @@ -1566,11 +1738,11 @@ packages: resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} engines: {node: '>= 0.4'} - es-toolkit@1.39.10: - resolution: {integrity: sha512-E0iGnTtbDhkeczB0T+mxmoVlT4YNweEKBLq7oaU4p11mecdsZpNWOglI4895Vh4usbQ+LsJiuLuI2L0Vdmfm2w==} + es-toolkit@1.43.0: + resolution: {integrity: sha512-SKCT8AsWvYzBBuUqMk4NPwFlSdqLpJwmy6AP322ERn8W2YLIB6JBXnwMI2Qsh2gfphT3q7EKAxKb23cvFHFwKA==} - esbuild@0.25.10: - resolution: {integrity: sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==} + esbuild@0.27.2: + resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==} engines: {node: '>=18'} hasBin: true @@ -1578,13 +1750,10 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} - escape-string-regexp@1.0.5: - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} - engines: {node: '>=0.8.0'} - - escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true estree-util-is-identifier-name@3.0.0: resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} @@ -1592,16 +1761,26 @@ packages: eventemitter3@5.0.1: resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} - events@3.3.0: - resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} - engines: {node: '>=0.8.x'} - extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + + fastest-levenshtein@1.0.16: + resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==} + engines: {node: '>= 4.9.1'} + + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} engines: {node: '>=12.0.0'} @@ -1611,35 +1790,18 @@ packages: picomatch: optional: true - figures@3.2.0: - resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} - engines: {node: '>=8'} - - file-saver@2.0.5: - resolution: {integrity: sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==} + file-entry-cache@11.1.1: + resolution: {integrity: sha512-TPVFSDE7q91Dlk1xpFLvFllf8r0HyOMOlnWy7Z2HBku5H3KhIeOGInexrIeg2D64DosVB/JXkrrk6N/7Wriq4A==} fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} - find-root@1.1.0: - resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} - - find-up@2.1.0: - resolution: {integrity: sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==} - engines: {node: '>=4'} - - find-up@3.0.0: - resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==} - engines: {node: '>=6'} - - find-up@4.1.0: - resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} - engines: {node: '>=8'} + flat-cache@6.1.19: + resolution: {integrity: sha512-l/K33newPTZMTGAnnzaiqSl6NnH7Namh8jBNjrgjprWxGmZUuxx/sJNIRaijOh3n7q7ESbhNZC+pvVZMFdeU4A==} - find-up@5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} follow-redirects@1.15.11: resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} @@ -1650,15 +1812,15 @@ packages: debug: optional: true - form-data@4.0.4: - resolution: {integrity: sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==} + form-data@4.0.5: + resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} engines: {node: '>= 6'} - fraction.js@4.3.7: - resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} + fraction.js@5.3.4: + resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} - framer-motion@12.23.22: - resolution: {integrity: sha512-ZgGvdxXCw55ZYvhoZChTlG6pUuehecgvEAJz0BHoC5pQKW1EC5xf1Mul1ej5+ai+pVY0pylyFfdl45qnM1/GsA==} + framer-motion@12.24.2: + resolution: {integrity: sha512-b9sFznsKvvLIsoe1hDtOgNPoQSR8cp5HrzH0B1iGfG8wCMjUA5YCJE8Ofv/UNm/xshUjL3i+o87QUoPH4LmJ9w==} peerDependencies: '@emotion/is-prop-valid': '*' react: ^18.0.0 || ^19.0.0 @@ -1679,78 +1841,52 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - fuse.js@7.1.0: - resolution: {integrity: sha512-trLf4SzuuUxfusZADLINj+dE8clK1frKdmqiJNb1Es75fmI5oY6X2mxLVUciLLjxqw/xr72Dhy+lER6dGd02FQ==} - engines: {node: '>=10'} - gensync@1.0.0-beta.2: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} - get-caller-file@2.0.5: - resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} - engines: {node: 6.* || 8.* || >= 10.*} - get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} - get-pkg-repo@4.2.1: - resolution: {integrity: sha512-2+QbHjFRfGB74v/pYWjd5OhU3TDIC2Gv/YKUTk/tCvAz0pkn/Mz6P3uByuBimLOcPvN2jYdScl3xGFSrx0jEcA==} - engines: {node: '>=6.9.0'} - hasBin: true - get-proto@1.0.1: resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} engines: {node: '>= 0.4'} - get-text-width@1.0.3: - resolution: {integrity: sha512-kv1MaexPcR/qaZ4kN8sUDjG5pRp5ptHvxcDGDBTeGld1cmo7MnlCMH22jevyvs/VV7Ran203o7qAOq2+kWw9cA==} - - git-raw-commits@2.0.11: - resolution: {integrity: sha512-VnctFhw+xfj8Va1xtfEqCUD2XDrbAPSJx+hSrE5K7fGdjZruW7XV+QOrN7LF/RJyvspRiD2I0asWsxFp0ya26A==} - engines: {node: '>=10'} - hasBin: true - - git-remote-origin-url@2.0.0: - resolution: {integrity: sha512-eU+GGrZgccNJcsDH5LkXR3PB9M958hxc7sbA8DFJjrv9j4L2P/eZfKhM+QD6wyzpiv+b1BpK0XrYCxkovtjSLw==} - engines: {node: '>=4'} - - git-semver-tags@4.1.1: - resolution: {integrity: sha512-OWyMt5zBe7xFs8vglMmhM9lRQzCWL3WjHtxNNfJTMngGym7pC1kh8sP6jevfydJ6LP3ZvGxfb6ABYgPUM0mtsA==} - engines: {node: '>=10'} - hasBin: true - - gitconfiglocal@1.0.0: - resolution: {integrity: sha512-spLUXeTAVHxDtKsJc8FkFVgFtMdEN9qPGpL23VfSHx4fP4+Ds097IXLvymbnDH8FnmxX5Nr9bPw3A+AQ6mWEaQ==} + get-tsconfig@4.13.0: + resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} - globals@16.4.0: - resolution: {integrity: sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==} - engines: {node: '>=18'} + global-modules@2.0.0: + resolution: {integrity: sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==} + engines: {node: '>=6'} - gopd@1.2.0: - resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} - engines: {node: '>= 0.4'} + global-prefix@3.0.0: + resolution: {integrity: sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==} + engines: {node: '>=6'} - graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + globals@17.0.0: + resolution: {integrity: sha512-gv5BeD2EssA793rlFWVPMMCqefTlpusw6/2TbAVMy0FzcG8wKJn4O+NqJ4+XWmmwrayJgw5TzrmWjFgmz1XPqw==} + engines: {node: '>=18'} - handlebars@4.7.8: - resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} - engines: {node: '>=0.4.7'} - hasBin: true + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} - hard-rejection@2.1.0: - resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} - engines: {node: '>=6'} + globjoin@0.1.4: + resolution: {integrity: sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==} - has-flag@3.0.0: - resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} - engines: {node: '>=4'} + goober@2.1.18: + resolution: {integrity: sha512-2vFqsaDVIT9Gz7N6kAL++pLpp41l3PfDuusHcjnGLfR6+huZkl6ziX+zgVC3ZxpqWhzH6pyDdGrCeDhMIvwaxw==} + peerDependencies: + csstype: ^3.0.10 + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} @@ -1764,6 +1900,10 @@ packages: resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} engines: {node: '>= 0.4'} + hashery@1.4.0: + resolution: {integrity: sha512-Wn2i1In6XFxl8Az55kkgnFRiAlIAushzh26PTjL2AKtQcEfXrcLa7Hn5QOWGZEf3LU057P9TwwZjFyxfS1VuvQ==} + engines: {node: '>=20'} + hasown@2.0.2: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} @@ -1771,23 +1911,17 @@ packages: hast-util-from-parse5@8.0.3: resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==} - hast-util-is-element@3.0.0: - resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==} - hast-util-parse-selector@4.0.0: resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} hast-util-raw@9.1.0: resolution: {integrity: sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==} - hast-util-sanitize@5.0.2: - resolution: {integrity: sha512-3yTWghByc50aGS7JlGhk61SPenfE/p1oaFeNwkOOyrscaOkMGrcW9+Cy/QAIOBpZxP1yqDIzFMR0+Np0i0+usg==} - hast-util-to-jsx-runtime@2.3.6: resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} - hast-util-to-parse5@8.0.0: - resolution: {integrity: sha512-3KKrV5ZVI8if87DVSi1vDeByYrkGzg4mEfeu4alwgmmIeARiBLKCZS2uw5Gb6nU9x9Yufyj3iudm6i7nl52PFw==} + hast-util-to-parse5@8.0.1: + resolution: {integrity: sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==} hast-util-whitespace@3.0.0: resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} @@ -1795,79 +1929,65 @@ packages: hastscript@9.0.1: resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} - hex-rgb@5.0.0: - resolution: {integrity: sha512-NQO+lgVUCtHxZ792FodgW0zflK+ozS9X9dwGp9XvvmPlH7pyxd588cn24TD3rmPm/N0AIRXF10Otah8yKqGw4w==} - engines: {node: '>=12'} - - hoist-non-react-statics@3.3.2: - resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} - - hosted-git-info@2.8.9: - resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} - - hosted-git-info@4.1.0: - resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} - engines: {node: '>=10'} - - html-dom-parser@5.1.1: - resolution: {integrity: sha512-+o4Y4Z0CLuyemeccvGN4bAO20aauB2N9tFEAep5x4OW34kV4PTarBHm6RL02afYt2BMKcr0D2Agep8S3nJPIBg==} + hookified@1.15.0: + resolution: {integrity: sha512-51w+ZZGt7Zw5q7rM3nC4t3aLn/xvKDETsXqMczndvwyVQhAHfUmUuFBRFcos8Iyebtk7OAE9dL26wFNzZVVOkw==} - html-react-parser@5.2.6: - resolution: {integrity: sha512-qcpPWLaSvqXi+TndiHbCa+z8qt0tVzjMwFGFBAa41ggC+ZA5BHaMIeMJla9g3VSp4SmiZb9qyQbmbpHYpIfPOg==} - peerDependencies: - '@types/react': 0.14 || 15 || 16 || 17 || 18 || 19 - react: 0.14 || 15 || 16 || 17 || 18 || 19 - peerDependenciesMeta: - '@types/react': - optional: true + html-tags@3.3.1: + resolution: {integrity: sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==} + engines: {node: '>=8'} html-url-attributes@3.0.1: resolution: {integrity: sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==} - html-void-elements@3.0.0: - resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + + human-id@4.1.3: + resolution: {integrity: sha512-tsYlhAYpjCKa//8rXZ9DqKEawhPoSytweBC2eNvcaDK+57RZLHGqNs3PZTQO6yekLFSuvA6AlnAfrw1uBvtb+Q==} + hasBin: true + + humanize-duration@3.33.2: + resolution: {integrity: sha512-K7Ny/ULO1hDm2nnhvAY+SJV1skxFb61fd073SG1IWJl+D44ULrruCuTyjHKjBVVcSuTlnY99DKtgEG39CM5QOQ==} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} - htmlparser2@10.0.0: - resolution: {integrity: sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g==} + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} - humanize-duration@3.33.1: - resolution: {integrity: sha512-hwzSCymnRdFx9YdRkQQ0OYequXiVAV6ZGQA2uzocwB0F4309Ke6pO8dg0P8LHhRQJyVjGteRTAA/zNfEcpXn8A==} + immer@10.2.0: + resolution: {integrity: sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==} - immer@10.1.3: - resolution: {integrity: sha512-tmjF/k8QDKydUlm3mZU+tjM6zeq9/fFpPqH9SzWmBnVVKsPBg/V66qsMwb3/Bo90cgUN+ghdVBess+hPsxUyRw==} + immer@11.1.3: + resolution: {integrity: sha512-6jQTc5z0KJFtr1UgFpIL3N9XSC3saRaI9PwWtzM2pSqkNGtiNkYY2OSwkOGDK2XcTRcLb1pi/aNkKZz0nxVH4Q==} - immutable@4.3.7: - resolution: {integrity: sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==} + immutable@5.1.4: + resolution: {integrity: sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==} import-fresh@3.3.1: resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} engines: {node: '>=6'} - indent-string@4.0.0: - resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} - engines: {node: '>=8'} - - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} ini@1.3.8: resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} - inline-style-parser@0.2.4: - resolution: {integrity: sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==} + inline-style-parser@0.2.7: + resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} internmap@2.0.3: resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} engines: {node: '>=12'} - ipaddr.js@2.2.0: - resolution: {integrity: sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==} + ipaddr.js@2.3.0: + resolution: {integrity: sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==} engines: {node: '>= 10'} - is-absolute-url@4.0.1: - resolution: {integrity: sha512-/51/TKE88Lmm7Gc4/8btclNXWS+g50wXhYJq8HWIBAGUBnoAdRu1aXeh364t/O7wXDAcTJDP8PNuNKWUDWie+A==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - is-alphabetical@2.0.1: resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} @@ -1881,10 +2001,6 @@ packages: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} - is-core-module@2.16.1: - resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} - engines: {node: '>= 0.4'} - is-decimal@2.0.1: resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} @@ -1907,128 +2023,86 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} - is-obj@2.0.0: - resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==} - engines: {node: '>=8'} - - is-plain-obj@1.1.0: - resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} - engines: {node: '>=0.10.0'} - is-plain-obj@4.1.0: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} - is-text-path@1.0.1: - resolution: {integrity: sha512-xFuJpne9oFz5qDaodwmmG08e3CawH/2ZV8Qqza1Ko7Sk8POWbkRdwIoAWVhqvq0XeUzANEhKo2n0IXUGBm7A/w==} + is-plain-object@5.0.0: + resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} engines: {node: '>=0.10.0'} - isarray@1.0.0: - resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} - - itertools@2.5.0: - resolution: {integrity: sha512-4ghJEXkRGkw4veNQhfO0cLY8+zePMXbe9wGt3ckSVFtrQVyyoKCUESaG2HsjuEfidVtuIEj1Dt1BlmTL3GUWFg==} + isbot@5.1.32: + resolution: {integrity: sha512-VNfjM73zz2IBZmdShMfAUg10prm6t7HFUQmNAEOAVS4YH92ZrZcvkMcGX6cIgBJAzWDzPent/EeAtYEHNPNPBQ==} + engines: {node: '>=18'} - jiti@2.4.2: - resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} - hasBin: true + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - js-base64@3.7.8: - resolution: {integrity: sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==} + js-sha256@0.11.1: + resolution: {integrity: sha512-o6WSo/LUvY2uC4j7mO50a2ms7E/EAdbP0swigLV+nzHKTTaYnaLIWJ02VdXrsJX0vGedDESQnLsOekr94ryfjg==} js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} hasBin: true - json-parse-better-errors@1.0.2: - resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==} - json-parse-even-better-errors@2.3.1: resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} - json-stringify-safe@5.0.1: - resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} json5@2.2.3: resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} engines: {node: '>=6'} hasBin: true - jsonparse@1.3.1: - resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} - engines: {'0': node >= 0.2.0} + keyv@5.5.5: + resolution: {integrity: sha512-FA5LmZVF1VziNc0bIdCSA1IoSVnDCqE8HJIZZv2/W8YmoAM50+tnUgJR/gQZwEeIMleuIOnRnHA/UaZRNeV4iQ==} kind-of@6.0.3: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'} - lines-and-columns@1.2.4: - resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - - little-state-machine@4.8.1: - resolution: {integrity: sha512-liPHqaWMQ7rzZryQUDnbZ1Gclnnai3dIyaJ0nAgwZRXMzqbYrydrlCI0NDojRUbE5VYh5vu6hygEUZiH77nQkQ==} - peerDependencies: - react: ^16.8.0 || ^17 || ^18 || ^19 - - load-json-file@4.0.0: - resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==} - engines: {node: '>=4'} - - locate-path@2.0.0: - resolution: {integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==} - engines: {node: '>=4'} - - locate-path@3.0.0: - resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} - engines: {node: '>=6'} + known-css-properties@0.37.0: + resolution: {integrity: sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==} - locate-path@5.0.0: - resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} - engines: {node: '>=8'} + kysely@0.27.6: + resolution: {integrity: sha512-FIyV/64EkKhJmjgC0g2hygpBv5RNWVPyNCqSAD7eTCv6eFWNIi4PN1UvdSJGicN/o35bnevgis4Y0UDC0qi8jQ==} + engines: {node: '>=14.0.0'} - locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} + launch-editor@2.12.0: + resolution: {integrity: sha512-giOHXoOtifjdHqUamwKq6c49GzBdLjvxrd2D+Q4V6uOHopJv7p9VJxikDsQ/CBXZbEITgUqSVHXLTG3VhPP1Dg==} - lodash-es@4.17.21: - resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - lodash.ismatch@4.4.0: - resolution: {integrity: sha512-fPMfXjGQEV9Xsq/8MTSgUf255gawYRbjwMyDbcvDhXgV7enSZA0hynz6vMPnpAb5iONEzBHBPsT+0zes5Z301g==} + lodash-es@4.17.22: + resolution: {integrity: sha512-XEawp1t0gxSi9x01glktRZ5HDy0HXqrM0x5pXQM98EaI0NxO6jVM7omDOxsuEo5UIASAnm2bRp1Jt/e0a2XU8Q==} - lodash@4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + lodash.truncate@4.4.2: + resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==} longest-streak@3.1.0: resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} - loose-envify@1.4.0: - resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} - hasBin: true - lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - lru-cache@6.0.0: - resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} - engines: {node: '>=10'} - - map-obj@1.0.1: - resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} - engines: {node: '>=0.10.0'} - - map-obj@4.3.0: - resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} - engines: {node: '>=8'} - math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} + mathml-tag-names@2.1.3: + resolution: {integrity: sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==} + mdast-util-from-markdown@2.0.2: resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} @@ -2044,8 +2118,8 @@ packages: mdast-util-phrasing@4.1.0: resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} - mdast-util-to-hast@13.2.0: - resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==} + mdast-util-to-hast@13.2.1: + resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} mdast-util-to-markdown@2.1.2: resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} @@ -2053,17 +2127,19 @@ packages: mdast-util-to-string@4.0.0: resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} - meow@8.1.2: - resolution: {integrity: sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==} - engines: {node: '>=10'} + mdn-data@2.12.2: + resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==} - merge-refs@2.0.0: - resolution: {integrity: sha512-3+B21mYK2IqUWnd2EivABLT7ueDhb0b8/dGK8LoFQPrU61YITeCMn14F7y7qZafWNZhUEKb24cJdiT5Wxs3prg==} - peerDependencies: - '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@types/react': - optional: true + mdn-data@2.25.0: + resolution: {integrity: sha512-T2LPsjgUE/tgMmRXREVmwsux89DwWfNjiynOeXuLd2mX6jphGQ2YE3Ukz7LQ2VOFKiVZU/Ee1GqzHiipZCjymw==} + + meow@13.2.0: + resolution: {integrity: sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==} + engines: {node: '>=18'} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} micromark-core-commonmark@2.0.3: resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} @@ -2128,9 +2204,9 @@ packages: micromark@4.0.2: resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} - millify@6.1.0: - resolution: {integrity: sha512-H/E3J6t+DQs/F2YgfDhxUVZz/dF8JXPPKTLHL/yHCcLZLtCXJDUaqvhJXQwqOVBvbyNn4T0WjLpIHd7PAw7fBA==} - hasBin: true + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} @@ -2140,32 +2216,14 @@ packages: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} - min-indent@1.0.1: - resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} - engines: {node: '>=4'} - - minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} - - minimist-options@4.1.0: - resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} - engines: {node: '>= 6'} - - minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + motion-dom@12.24.0: + resolution: {integrity: sha512-RD2kZkFd/GH4fITI8IJvypGgn0vIu5vkrJaXIAkYqORGs5P0CKDHKNvswmoY1H+tbUAOPSh6VtUqoAmc/3Gvig==} - modify-values@1.0.1: - resolution: {integrity: sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==} - engines: {node: '>=0.10.0'} - - motion-dom@12.23.21: - resolution: {integrity: sha512-5xDXx/AbhrfgsQmSE7YESMn4Dpo6x5/DTZ4Iyy4xqDvVHWvFVoV+V2Ri2S/ksx+D40wrZ7gPYiMWshkdoqNgNQ==} + motion-utils@12.23.28: + resolution: {integrity: sha512-0W6cWd5Okoyf8jmessVK3spOmbyE0yTdNKujHctHH9XdAE4QDuZ1/LjSXC68rrhsJU+TkzXURC5OdSWh9ibOwQ==} - motion-utils@12.23.6: - resolution: {integrity: sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==} - - motion@12.23.22: - resolution: {integrity: sha512-iSq6X9vLHbeYwmHvhK//+U74ROaPnZmBuy60XZzqNl0QtZkWfoZyMDHYnpKuWFv0sNMqHgED8aCXk94LCoQPGg==} + motion@12.24.2: + resolution: {integrity: sha512-kSkS1v5lI+Br837VGzFLAWPtH0pmXN7CYX4IzGKyaswnf7ItUfkIaSVU2RHF2JPfJZs/D5sufnI+RW5JxxKZrg==} peerDependencies: '@emotion/is-prop-valid': '*' react: ^18.0.0 || ^19.0.0 @@ -2181,82 +2239,25 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - n-gram@2.0.2: - resolution: {integrity: sha512-S24aGsn+HLBxUGVAUFOwGpKs7LBcG4RudKU//eWzt/mQ97/NMKQxDWHyHx63UNWk/OOdihgmzoETn1tf5nQDzQ==} - nanoid@3.3.11: resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - neo-async@2.6.2: - resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + node-addon-api@7.1.1: + resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==} - node-releases@2.0.21: - resolution: {integrity: sha512-5b0pgg78U3hwXkCM8Z9b2FJdPZlr9Psr9V2gQPESdGHqbntyFJKFW4r5TeWGFzafGY3hzs1JC62VEQMbl1JFkw==} - - normalize-package-data@2.5.0: - resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} - - normalize-package-data@3.0.3: - resolution: {integrity: sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==} - engines: {node: '>=10'} + node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} - normalize-range@0.1.2: - resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} - engines: {node: '>=0.10.0'} - - numbro@2.5.0: - resolution: {integrity: sha512-xDcctDimhzko/e+y+Q2/8i3qNC9Svw1QgOkSkQoO0kIPI473tR9QRbo2KP88Ty9p8WbPy+3OpTaAIzehtuHq+A==} - - object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} - object-inspect@1.13.4: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} - p-limit@1.3.0: - resolution: {integrity: sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==} - engines: {node: '>=4'} - - p-limit@2.3.0: - resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} - engines: {node: '>=6'} - - p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} - - p-locate@2.0.0: - resolution: {integrity: sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==} - engines: {node: '>=4'} - - p-locate@3.0.0: - resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==} - engines: {node: '>=6'} - - p-locate@4.1.0: - resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} - engines: {node: '>=8'} - - p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} - - p-try@1.0.0: - resolution: {integrity: sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==} - engines: {node: '>=4'} - - p-try@2.2.0: - resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} - engines: {node: '>=6'} - parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -2264,10 +2265,6 @@ packages: parse-entities@4.0.2: resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} - parse-json@4.0.0: - resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==} - engines: {node: '>=4'} - parse-json@5.2.0: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} @@ -2275,25 +2272,13 @@ packages: parse5@7.3.0: resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} - path-exists@3.0.0: - resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} - engines: {node: '>=4'} - - path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} - - path-parse@1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - - path-type@3.0.0: - resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==} - engines: {node: '>=4'} - path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -2305,17 +2290,27 @@ packages: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} - pify@2.3.0: - resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} - engines: {node: '>=0.10.0'} + postcss-media-query-parser@0.2.3: + resolution: {integrity: sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==} - pify@3.0.0: - resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} - engines: {node: '>=4'} + postcss-resolve-nested-selector@0.1.6: + resolution: {integrity: sha512-0sglIs9Wmkzbr8lQwEyIzlDOOC9bGmfVKcJTaxv3vMmd3uo4o4DerC3En0bnmgceeql9BfC8hRkp7cg0fjdVqw==} + + postcss-safe-parser@7.0.1: + resolution: {integrity: sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==} + engines: {node: '>=18.0'} + peerDependencies: + postcss: ^8.4.31 - pngjs@5.0.0: - resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==} - engines: {node: '>=10.13.0'} + postcss-scss@4.0.9: + resolution: {integrity: sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.4.29 + + postcss-selector-parser@7.1.1: + resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==} + engines: {node: '>=4'} postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} @@ -2324,88 +2319,40 @@ packages: resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} engines: {node: ^10 || ^12 || >=14} - prettier@3.6.2: - resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} + prettier@3.7.4: + resolution: {integrity: sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==} engines: {node: '>=14'} hasBin: true - process-nextick-args@2.0.1: - resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} - - prop-types@15.8.1: - resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} - - property-information@6.5.0: - resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==} - property-information@7.1.0: resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} - proxy-compare@3.0.1: - resolution: {integrity: sha512-V9plBAt3qjMlS1+nC8771KNf6oJ12gExvaxnNzN/9yVRLdTv/lc+oJlnSzrdYDAvBfTStPCoiaCOTmTs0adv7Q==} - proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} - q@1.5.1: - resolution: {integrity: sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==} - engines: {node: '>=0.6.0', teleport: '>=0.2.0'} - deprecated: |- - You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other. - - (For a CapTP with native promises, see @endo/eventual-send and @endo/captp) + qified@0.5.3: + resolution: {integrity: sha512-kXuQdQTB6oN3KhI6V4acnBSZx8D2I4xzZvn9+wFLLFCoBNQY/sFnCW6c43OL7pOQ2HvGV4lnWIXNmgfp7cTWhQ==} + engines: {node: '>=20'} - qr.js@0.0.0: - resolution: {integrity: sha512-c4iYnWb+k2E+vYpRimHqSu575b1/wKl4XFeJGpFmrJQz5I88v9aY2czh7s0w36srfCM1sXgC/xpoJz5dJfq+OQ==} - - qrcode@1.5.4: - resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==} - engines: {node: '>=10.13.0'} - hasBin: true - - qs@6.14.0: - resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} - engines: {node: '>=0.6'} - - quick-lru@4.0.1: - resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} - engines: {node: '>=8'} - - radash@12.1.1: - resolution: {integrity: sha512-h36JMxKRqrAxVD8201FrCpyeNuUY9Y5zZwujr20fFO77tpUtGa6EZzfKw/3WaiBX95fq7+MpsuMLNdSnORAwSA==} - engines: {node: '>=14.18.0'} - - react-click-away-listener@2.4.0: - resolution: {integrity: sha512-jDkXY8Q9qM8e197K7c7AoVhhk2meQO5POyjRJrKN2vUQUvIef49h/paM3JA6q+lf+JygDy9ENOBOsZalARUIeg==} + qrcode.react@4.2.0: + resolution: {integrity: sha512-QpgqWi8rD9DsS9EP3z7BT+5lY5SFhsqGjpgW5DY/i3mK4M9DTBNz3ErMi8BWYEfI3L0d8GIbGmcdFAS1uIRGjA==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - - react-datepicker@8.7.0: - resolution: {integrity: sha512-r5OJbiLWc3YiVNy69Kau07/aVgVGsFVMA6+nlqCV7vyQ8q0FUOnJ+wAI4CgVxHejG3i5djAEiebrF8/Eip4rIw==} - peerDependencies: - react: ^16.9.0 || ^17 || ^18 || ^19 || ^19.0.0-rc - react-dom: ^16.9.0 || ^17 || ^18 || ^19 || ^19.0.0-rc - react-dom@19.1.1: - resolution: {integrity: sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==} - peerDependencies: - react: ^19.1.1 + qs@6.14.1: + resolution: {integrity: sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==} + engines: {node: '>=0.6'} - react-hook-form@7.63.0: - resolution: {integrity: sha512-ZwueDMvUeucovM2VjkCf7zIHcs1aAlDimZu2Hvel5C5907gUzMpm4xCrQXtRzCvsBqFjonB4m3x4LzCFI1ZKWA==} - engines: {node: '>=18.0.0'} - peerDependencies: - react: ^16.8.0 || ^17 || ^18 || ^19 + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - react-idle-timer@5.7.2: - resolution: {integrity: sha512-+BaPfc7XEUU5JFkwZCx6fO1bLVK+RBlFH+iY4X34urvIzZiZINP6v2orePx3E6pAztJGE7t4DzvL7if2SL/0GQ==} + react-dom@19.2.3: + resolution: {integrity: sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==} peerDependencies: - react: '>=16' - react-dom: '>=16' + react: ^19.2.3 - react-intersection-observer@9.16.0: - resolution: {integrity: sha512-w9nJSEp+DrW9KmQmeWHQyfaP6b03v+TdXynaoA964Wxt7mdR3An11z4NNCQgL4gKSK7y1ver2Fq+JKH6CWEzUA==} + react-intersection-observer@10.0.0: + resolution: {integrity: sha512-JJRgcnFQoVXmbE5+GXr1OS1NDD1gHk0HyfpLcRf0575IbJz+io8yzs4mWVlfaqOQq1FiVjLvuYAdEEcrrCfveg==} peerDependencies: react: ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -2413,11 +2360,8 @@ packages: react-dom: optional: true - react-is@16.13.1: - resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - - react-is@19.1.1: - resolution: {integrity: sha512-tr41fA15Vn8p4X9ntI+yCyeGSf1TlYaY5vlTZfQmeLBrFo3psOPX6HhTDnFNL9uj3EhP0KAQ80cugCl4b4BERA==} + react-is@19.2.0: + resolution: {integrity: sha512-x3Ax3kNSMIIkyVYhWPyO09bu0uttcAIoecO/um/rKGQ4EltYWVYtyiGkS/3xMynrbVQdS69Jhlv8FXUEZehlzA==} react-loading-skeleton@3.5.0: resolution: {integrity: sha512-gxxSyLbrEAdXTKgfbpBEFZCO/P153DnqSCQau2+o6lNy1jgMRr2MmRmOzMmyrwSaSYLRB8g7b0waYPmUjz7IhQ==} @@ -2430,14 +2374,6 @@ packages: '@types/react': '>=18' react: '>=18' - react-property@2.0.2: - resolution: {integrity: sha512-+PbtI3VuDV0l6CleQMsx2gtK0JZbZKbpdu5ynr+lbsuvtmgbNcS3VM0tuY2QjFNOcWxvXeHjDpy42RO+4U2rug==} - - react-qr-code@2.0.18: - resolution: {integrity: sha512-v1Jqz7urLMhkO6jkgJuBYhnqvXagzceg3qJUWayuCK/c6LTIonpWbwxR1f1APGd4xrW/QcQEovNrAojbUz65Tg==} - peerDependencies: - react: '*' - react-redux@9.2.0: resolution: {integrity: sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==} peerDependencies: @@ -2450,84 +2386,30 @@ packages: redux: optional: true - react-resize-detector@12.3.0: - resolution: {integrity: sha512-mIDOVrTHKGnKe6qEUWi8dFdfHM5CPyTOpqoHctdMQf89Ljm/0qqDIzkP3vTRZZJi9/raaMiRxDEOqO4you5x+A==} - peerDependencies: - react: ^18.0.0 || ^19.0.0 - - react-router-dom@6.30.1: - resolution: {integrity: sha512-llKsgOkZdbPU1Eg3zK8lCn+sjD9wMRZZPuzmdWWX5SUs8OFkN5HnFVC0u5KMeMaC9aoancFI/KoLuKPqN+hxHw==} - engines: {node: '>=14.0.0'} - peerDependencies: - react: '>=16.8' - react-dom: '>=16.8' - - react-router@6.30.1: - resolution: {integrity: sha512-X1m21aEmxGXqENEPG3T6u0Th7g0aS4ZmoNynhbs+Cn+q+QGTLt+d5IQ2bHAXKzKcxGJjxACpVbnYQSCRcfxHlQ==} - engines: {node: '>=14.0.0'} - peerDependencies: - react: '>=16.8' - - react-simple-animate@3.5.3: - resolution: {integrity: sha512-Ob+SmB5J1tXDEZyOe2Hf950K4M8VaWBBmQ3cS2BUnTORqHjhK0iKG8fB+bo47ZL15t8d3g/Y0roiqH05UBjG7A==} - peerDependencies: - react-dom: ^16.8.0 || ^17 || ^18 || ^19 - - react-tracked@2.0.1: - resolution: {integrity: sha512-qjbmtkO2IcW+rB2cFskRWDTjKs/w9poxvNnduacjQA04LWxOoLy9J8WfIEq1ahifQ/tVJQECrQPBm+UEzKRDtg==} - peerDependencies: - react: '>=18.0.0' - scheduler: '>=0.19.0' - - react-virtualized-auto-sizer@1.0.26: - resolution: {integrity: sha512-CblNyiNVw2o+hsa5/49NH2ogGxZ+t+3aweRvNSq7TVjDIlwk7ir4lencEg5HxHeSzwNarSkNkiu0qJSOXtxm5A==} - peerDependencies: - react: ^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^15.3.0 || ^16.0.0-alpha || ^17.0.0 || ^18.0.0 || ^19.0.0 - - react@19.1.1: - resolution: {integrity: sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==} + react@19.2.3: + resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==} engines: {node: '>=0.10.0'} - read-pkg-up@3.0.0: - resolution: {integrity: sha512-YFzFrVvpC6frF1sz8psoHDBGF7fLPc+llq/8NB43oagqWkx8ar5zYtsTORtOjw9W2RHLpWP+zTWwBvf1bCmcSw==} - engines: {node: '>=4'} - - read-pkg-up@7.0.1: - resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} - engines: {node: '>=8'} - - read-pkg@3.0.0: - resolution: {integrity: sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==} - engines: {node: '>=4'} - - read-pkg@5.2.0: - resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} - engines: {node: '>=8'} - - readable-stream@2.3.8: - resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} - - readable-stream@3.6.2: - resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} - engines: {node: '>= 6'} - readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} - recharts@3.2.1: - resolution: {integrity: sha512-0JKwHRiFZdmLq/6nmilxEZl3pqb4T+aKkOkOi/ZISRZwfBhVMgInxzlYU9D4KnCH3KINScLy68m/OvMXoYGZUw==} + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + recast@0.23.11: + resolution: {integrity: sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==} + engines: {node: '>= 4'} + + recharts@3.6.0: + resolution: {integrity: sha512-L5bjxvQRAe26RlToBAziKUB7whaGKEwD3znoM6fz3DrTowCIC/FnJYnuq1GEzB8Zv2kdTfaxQfi5GoH0tBinyg==} engines: {node: '>=18'} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 react-is: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - redent@3.0.0: - resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} - engines: {node: '>=8'} - redux-thunk@3.1.0: resolution: {integrity: sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==} peerDependencies: @@ -2536,28 +2418,19 @@ packages: redux@5.0.1: resolution: {integrity: sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==} - rehype-external-links@3.0.0: - resolution: {integrity: sha512-yp+e5N9V3C6bwBeAC4n796kc86M4gJCdlVhiMTxIrJG5UHDMh+PJANf9heqORJbt1nrCbDwIlAZKjANIaVBbvw==} - rehype-raw@7.0.0: resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==} - rehype-sanitize@6.0.0: - resolution: {integrity: sha512-CsnhKNsyI8Tub6L4sm5ZFsme4puGfc6pYylvXo1AeqaGbjOYyzNv3qZPwvs0oMJ39eryyeOdmxwUIo94IpEhqg==} - remark-parse@11.0.0: resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} remark-rehype@11.1.2: resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} - require-directory@2.1.1: - resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} - require-main-filename@2.0.0: - resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} - reselect@5.1.1: resolution: {integrity: sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==} @@ -2565,48 +2438,68 @@ packages: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} - resolve@1.22.10: - resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} - engines: {node: '>= 0.4'} - hasBin: true + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} - rollup@4.52.2: - resolution: {integrity: sha512-I25/2QgoROE1vYV+NQ1En9T9UFB9Cmfm2CJ83zZOlaDpvz29wGQSZXWKw7MiNXau7wYgB/T9fVIdIuEQ+KbiiA==} + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rollup@4.55.1: + resolution: {integrity: sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + rxjs@7.8.2: resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} - safe-buffer@5.1.2: - resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} - - safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - - sass@1.70.0: - resolution: {integrity: sha512-uUxNQ3zAHeAx5nRFskBnrWzDUJrrvpCPD5FNAoRvTi0WwremlheES3tg+56PaVtCs5QDRX5CBLxxKMDJMEa1WQ==} + sass@1.97.2: + resolution: {integrity: sha512-y5LWb0IlbO4e97Zr7c3mlpabcbBtS+ieiZ9iwDooShpFKWXf62zz5pEPdwrLYm+Bxn1fnbwFGzHuCLSA9tBmrw==} engines: {node: '>=14.0.0'} hasBin: true - scheduler@0.26.0: - resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} - - semver@5.7.2: - resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} - hasBin: true + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - semver@7.7.2: - resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} engines: {node: '>=10'} hasBin: true - set-blocking@2.0.0: - resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + seroval-plugins@1.3.3: + resolution: {integrity: sha512-16OL3NnUBw8JG1jBLUoZJsLnQq0n5Ua6aHalhJK4fMQkz1lqR7Osz1sA30trBtd9VUDc2NgkuRCn8+/pBwqZ+w==} + engines: {node: '>=10'} + peerDependencies: + seroval: ^1.0 + + seroval-plugins@1.4.2: + resolution: {integrity: sha512-X7p4MEDTi+60o2sXZ4bnDBhgsUYDSkQEvzYZuJyFqWg9jcoPsHts5nrg5O956py2wyt28lUrBxk0M0/wU8URpA==} + engines: {node: '>=10'} + peerDependencies: + seroval: ^1.0 + + seroval@1.3.2: + resolution: {integrity: sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==} + engines: {node: '>=10'} + + seroval@1.4.2: + resolution: {integrity: sha512-N3HEHRCZYn3cQbsC4B5ldj9j+tHdf4JZoYPlcI4rRYu0Xy4qN8MQf1Z08EibzB0WpgRG5BGK08FTrmM66eSzKQ==} + engines: {node: '>=10'} + + sharp@0.34.5: + resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} shell-quote@1.8.3: resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} @@ -2628,108 +2521,118 @@ packages: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} - source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} - engines: {node: '>=0.10.0'} + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + slice-ansi@4.0.0: + resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} + engines: {node: '>=10'} - source-map-support@0.5.21: - resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + solid-js@1.9.9: + resolution: {integrity: sha512-A0ZBPJQldAeGCTW0YRYJmt7RCeh5rbFfPZ2aOttgYnctHE7HgKeHCBB/PVc2P7eOfmNXqMFFFoYYdm3S4dcbkA==} - source-map@0.5.7: - resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} source-map@0.6.1: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + space-separated-tokens@2.0.2: resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} - spdx-correct@3.2.0: - resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} - - spdx-exceptions@2.5.0: - resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} - - spdx-expression-parse@3.0.1: - resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} - - spdx-license-ids@3.0.22: - resolution: {integrity: sha512-4PRT4nh1EImPbt2jASOKHX7PB7I+e4IWNLvkKFDxNhJlfjbYlleYQh285Z/3mPTHSAK/AvdMmw5BNNuYH8ShgQ==} - - split2@3.2.2: - resolution: {integrity: sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==} - - split@1.0.1: - resolution: {integrity: sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==} - - standard-version@9.5.0: - resolution: {integrity: sha512-3zWJ/mmZQsOaO+fOlsa0+QK90pwhNd042qEcw6hKFNoLFs7peGyvPffpEBbK/DSGPbyOvli0mUIFv5A4qTjh2Q==} - engines: {node: '>=10'} - hasBin: true + sqlite-wasm-kysely@0.3.0: + resolution: {integrity: sha512-TzjBNv7KwRw6E3pdKdlRyZiTmUIE0UttT/Sl56MVwVARl/u5gp978KepazCJZewFUnlWHz9i3NQd4kOtP/Afdg==} + peerDependencies: + kysely: '*' string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} - string_decoder@1.1.1: - resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} - - string_decoder@1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - stringify-entities@4.0.4: resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} - stringify-package@1.0.1: - resolution: {integrity: sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg==} - deprecated: This module is not used anymore, and has been replaced by @npmcli/package-json - strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} - strip-bom@3.0.0: - resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} - engines: {node: '>=4'} + style-to-js@1.1.21: + resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==} - strip-indent@3.0.0: - resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} - engines: {node: '>=8'} + style-to-object@1.0.14: + resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} - style-to-js@1.1.17: - resolution: {integrity: sha512-xQcBGDxJb6jjFCTzvQtfiPn6YvvP2O8U1MDIPNfJQlWMYfktPy+iGsHE7cssjs7y84d9fQaK4UF3RIJaAHSoYA==} + stylelint-config-recommended-scss@16.0.2: + resolution: {integrity: sha512-aUTHhPPWCvFyWaxtckJlCPaXTDFsp4pKO8evXNCsW9OwsaUWyMd6jvcUhSmfGWPrTddvzNqK4rS/UuSLcbVGdQ==} + engines: {node: '>=20'} + peerDependencies: + postcss: ^8.3.3 + stylelint: ^16.24.0 + peerDependenciesMeta: + postcss: + optional: true - style-to-object@1.0.9: - resolution: {integrity: sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw==} + stylelint-config-recommended@17.0.0: + resolution: {integrity: sha512-WaMSdEiPfZTSFVoYmJbxorJfA610O0tlYuU2aEwY33UQhSPgFbClrVJYWvy3jGJx+XW37O+LyNLiZOEXhKhJmA==} + engines: {node: '>=18.12.0'} + peerDependencies: + stylelint: ^16.23.0 - stylis@4.2.0: - resolution: {integrity: sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==} + stylelint-config-standard-scss@16.0.0: + resolution: {integrity: sha512-/FHECLUu+med/e6OaPFpprG86ShC4SYT7Tzb2PTVdDjJsehhFBOioSlWqYFqJxmGPIwO3AMBxNo+kY3dxrbczA==} + engines: {node: '>=20'} + peerDependencies: + postcss: ^8.3.3 + stylelint: ^16.23.1 + peerDependenciesMeta: + postcss: + optional: true - supports-color@5.5.0: - resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} - engines: {node: '>=4'} + stylelint-config-standard@39.0.1: + resolution: {integrity: sha512-b7Fja59EYHRNOTa3aXiuWnhUWXFU2Nfg6h61bLfAb5GS5fX3LMUD0U5t4S8N/4tpHQg3Acs2UVPR9jy2l1g/3A==} + engines: {node: '>=18.12.0'} + peerDependencies: + stylelint: ^16.23.0 + + stylelint-scss@6.14.0: + resolution: {integrity: sha512-ZKmHMZolxeuYsnB+PCYrTpFce0/QWX9i9gh0hPXzp73WjuIMqUpzdQaBCrKoLWh6XtCFSaNDErkMPqdjy1/8aA==} + engines: {node: '>=18.12.0'} + peerDependencies: + stylelint: ^16.8.2 + + stylelint@16.26.1: + resolution: {integrity: sha512-v20V59/crfc8sVTAtge0mdafI3AdnzQ2KsWe6v523L4OA1bJO02S7MO2oyXDCS6iWb9ckIPnqAFVItqSBQr7jw==} + engines: {node: '>=18.12.0'} + hasBin: true supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} - supports-color@8.1.1: - resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} - engines: {node: '>=10'} + supports-hyperlinks@3.2.0: + resolution: {integrity: sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==} + engines: {node: '>=14.18'} - supports-preserve-symlinks-flag@1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} + svg-tags@1.0.0: + resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==} - tabbable@6.2.0: - resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==} + tabbable@6.4.0: + resolution: {integrity: sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==} - terser@5.37.0: - resolution: {integrity: sha512-B8wRRkmre4ERucLM/uXx4MOV5cbnOlVAqUst+1+iLKPI0dOgFO28f84ptoQt9HEI537PMzfYa/d+GEPKTRXmYA==} - engines: {node: '>=10'} - hasBin: true + table@6.9.0: + resolution: {integrity: sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==} + engines: {node: '>=10.0.0'} text-camel-case@1.2.9: resolution: {integrity: sha512-wKYs9SgRxYizJE1mneR7BbLNlGw2IYzJAS8XwkWIry0CTbO1gvvPkFsx5Z1/hr+VqUaBqx9q3yKd30HpZLdMsQ==} @@ -2746,10 +2649,6 @@ packages: text-dot-case@1.2.9: resolution: {integrity: sha512-N83hsnvGdSO9q9AfNSB9Cy1LFDNN2MCx53LcxtaPoDWPUTk47fv0JlvIY1tgY0wyzCiThF03kVj3jworvAOScA==} - text-extensions@1.9.0: - resolution: {integrity: sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==} - engines: {node: '>=0.10'} - text-header-case@1.2.9: resolution: {integrity: sha512-TqryEKcYisQAfWLbtT3xPnZlMZ/mySO1uS+LUg+B0eNuqgETrSzVpXIUj5E6Zf/EyJHgpZf4VndbAXtOMJuT4w==} @@ -2798,18 +2697,12 @@ packages: text-upper-case@1.2.9: resolution: {integrity: sha512-K/0DNT7a4z8eah2spARtoJllTZyrNTo6Uc0ujhN/96Ir9uJ/slpahfs13y46H9osL3daaLl3O7iXOkW4xtX6bg==} - through2@2.0.5: - resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} - - through2@4.0.2: - resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} - - through@2.3.8: - resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} - tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + tiny-warning@1.0.3: + resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} + tinyglobby@0.2.15: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} @@ -2818,66 +2711,33 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} - tree-kill@1.2.2: - resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} - hasBin: true - trim-lines@3.0.1: resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} - trim-newlines@3.0.1: - resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} - engines: {node: '>=8'} - - trough@2.2.0: - resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} - - tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - - type-fest@0.18.1: - resolution: {integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==} - engines: {node: '>=10'} - - type-fest@0.6.0: - resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} - engines: {node: '>=8'} - - type-fest@0.8.1: - resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} - engines: {node: '>=8'} - - type-fest@4.41.0: - resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} - engines: {node: '>=16'} + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} - typedarray@0.0.6: - resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - typesafe-i18n@5.26.2: - resolution: {integrity: sha512-2QAriFmiY5JwUAJtG7yufoE/XZ1aFBY++wj7YFS2yo89a3jLBfKoWSdq5JfQYk1V2BS7V2c/u+KEcaCQoE65hw==} + tsx@4.21.0: + resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} + engines: {node: '>=18.0.0'} hasBin: true - peerDependencies: - typescript: '>=3.5.1' - typescript@5.9.2: - resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} hasBin: true - uglify-js@3.19.3: - resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} - engines: {node: '>=0.8.0'} - hasBin: true - - undici-types@7.12.0: - resolution: {integrity: sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==} + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} unified@11.0.5: resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} - unist-util-is@6.0.0: - resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==} + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} unist-util-position@5.0.0: resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} @@ -2885,51 +2745,37 @@ packages: unist-util-stringify-position@4.0.0: resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} - unist-util-visit-parents@6.0.1: - resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==} + unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} unist-util-visit@5.0.0: resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} - update-browserslist-db@1.1.3: - resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} + unplugin@2.3.11: + resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==} + engines: {node: '>=18.12.0'} + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' - use-breakpoint@4.0.6: - resolution: {integrity: sha512-1s7vUjf36eeZYTgY1KkmPNXrTbKJVRA9cjBFQdYjK8+pDr0qJgH6/cuX5qQ2zcfkqxN5LieVd/DTVK6ofnwRTQ==} - peerDependencies: - react: '>=18' - react-dom: '>=18' - - use-context-selector@2.0.0: - resolution: {integrity: sha512-owfuSmUNd3eNp3J9CdDl0kMgfidV+MkDvHPpvthN5ThqM+ibMccNE0k+Iq7TWC6JPFvGZqanqiGCuQx6DyV24g==} - peerDependencies: - react: '>=18.0.0' - scheduler: '>=0.19.0' - - use-deep-compare-effect@1.8.1: - resolution: {integrity: sha512-kbeNVZ9Zkc0RFGpfMN3MNfaKNvcLNyxOAAd9O4CBZ+kCBXXscn9s/4I+8ytUER4RDpEYs5+O6Rs4PqiZ+rHr5Q==} - engines: {node: '>=10', npm: '>=6'} - peerDependencies: - react: '>=16.13' + urlpattern-polyfill@10.1.0: + resolution: {integrity: sha512-IGjKp/o0NL3Bso1PymYURCJxMPNAf/ILOpendP9f5B6e1rTJgdgiOvgfoT8VxCAdY+Wisb9uhGaJJf3yZ2V9nw==} - use-sync-external-store@1.5.0: - resolution: {integrity: sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==} + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - uuid@8.3.2: - resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + uuid@10.0.0: + resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==} hasBin: true - validate-npm-package-license@3.0.4: - resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} - vfile-location@5.0.3: resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} @@ -2942,13 +2788,21 @@ packages: victory-vendor@37.3.6: resolution: {integrity: sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==} - vite-plugin-package-version@1.1.0: - resolution: {integrity: sha512-TPoFZXNanzcaKCIrC3e2L/TVRkkRLB6l4RPN/S7KbG7rWfyLcCEGsnXvxn6qR7fyZwXalnnSN/I9d6pSFjHpEA==} + vite-plugin-image-optimizer@2.0.3: + resolution: {integrity: sha512-1vrFOTcpSvv6DCY7h8UXab4wqMAjTJB/ndOzG/Kmj1oDOuPF6mbjkNQoGzzCEYeWGe7qU93jc8oQqvoJ57al3A==} + engines: {node: '>=18.17.0'} peerDependencies: - vite: '>=2.0.0-beta.69' + sharp: '>=0.34.0' + svgo: '>=4' + vite: '>=5' + peerDependenciesMeta: + sharp: + optional: true + svgo: + optional: true - vite@7.1.7: - resolution: {integrity: sha512-VbA8ScMvAISJNJVbRDTJdCwqQoAareR/wutevKanhR2/1EkoXVZVkkORaYm/tNVCjP/UDTKtcw3bAkwOUdedmA==} + vite@7.3.0: + resolution: {integrity: sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -2990,79 +2844,40 @@ packages: web-namespaces@2.0.1: resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} - which-module@2.0.1: - resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} - - wordwrap@1.0.0: - resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} - - wrap-ansi@6.2.0: - resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} - engines: {node: '>=8'} - - wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} + webpack-virtual-modules@0.6.2: + resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} - xtend@4.0.2: - resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} - engines: {node: '>=0.4'} + which@1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} + hasBin: true - y18n@4.0.3: - resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} + write-file-atomic@5.0.1: + resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - y18n@5.0.8: - resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} - engines: {node: '>=10'} + ws@8.19.0: + resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - yallist@4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - - yaml@1.10.2: - resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} - engines: {node: '>= 6'} - - yaml@2.6.1: - resolution: {integrity: sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==} - engines: {node: '>= 14'} - hasBin: true - - yargs-parser@18.1.3: - resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} - engines: {node: '>=6'} - - yargs-parser@20.2.9: - resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} - engines: {node: '>=10'} - - yargs-parser@21.1.1: - resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} - engines: {node: '>=12'} - - yargs@15.4.1: - resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} - engines: {node: '>=8'} - - yargs@16.2.0: - resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} - engines: {node: '>=10'} - - yargs@17.7.2: - resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} - engines: {node: '>=12'} - - yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} - zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} - zustand@5.0.8: - resolution: {integrity: sha512-gyPKpIaxY9XcO2vSMrLbiER7QMAMGOQZVRdJ6Zi782jkbzZygq5GI9nG8g+sMgitRtndwaBSl7uiqC49o1SSiw==} + zod@4.3.5: + resolution: {integrity: sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==} + + zustand@5.0.9: + resolution: {integrity: sha512-ALBtUj0AfjJt3uNRQoL1tL2tMvj6Gp/6e39dnfT6uzpelGru8v1tPOGBzayOWbPJvujM8JojDk3E1LxeFisBNg==} engines: {node: '>=12.20.0'} peerDependencies: '@types/react': '>=18.0.0' @@ -3084,25 +2899,30 @@ packages: snapshots: + '@axa-ch/react-polymorphic-types@1.4.1(@types/react@19.2.7)(react@19.2.3)': + dependencies: + '@types/react': 19.2.7 + react: 19.2.3 + '@babel/code-frame@7.27.1': dependencies: - '@babel/helper-validator-identifier': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/compat-data@7.28.4': {} + '@babel/compat-data@7.28.5': {} - '@babel/core@7.28.4': + '@babel/core@7.28.5': dependencies: '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.3 + '@babel/generator': 7.28.5 '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.4) + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) '@babel/helpers': 7.28.4 - '@babel/parser': 7.28.4 + '@babel/parser': 7.28.5 '@babel/template': 7.27.2 - '@babel/traverse': 7.28.4 - '@babel/types': 7.28.4 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 debug: 4.4.3 @@ -3112,19 +2932,19 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/generator@7.28.3': + '@babel/generator@7.28.5': dependencies: - '@babel/parser': 7.28.4 - '@babel/types': 7.28.4 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 '@babel/helper-compilation-targets@7.27.2': dependencies: - '@babel/compat-data': 7.28.4 + '@babel/compat-data': 7.28.5 '@babel/helper-validator-option': 7.27.1 - browserslist: 4.26.2 + browserslist: 4.28.1 lru-cache: 5.1.1 semver: 6.3.1 @@ -3132,260 +2952,217 @@ snapshots: '@babel/helper-module-imports@7.27.1': dependencies: - '@babel/traverse': 7.28.4 - '@babel/types': 7.28.4 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.4)': + '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)': dependencies: - '@babel/core': 7.28.4 + '@babel/core': 7.28.5 '@babel/helper-module-imports': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 - '@babel/traverse': 7.28.4 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.28.5 transitivePeerDependencies: - supports-color + '@babel/helper-plugin-utils@7.27.1': {} + '@babel/helper-string-parser@7.27.1': {} - '@babel/helper-validator-identifier@7.27.1': {} + '@babel/helper-validator-identifier@7.28.5': {} '@babel/helper-validator-option@7.27.1': {} '@babel/helpers@7.28.4': dependencies: '@babel/template': 7.27.2 - '@babel/types': 7.28.4 + '@babel/types': 7.28.5 + + '@babel/parser@7.28.5': + dependencies: + '@babel/types': 7.28.5 - '@babel/parser@7.28.4': + '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.5)': dependencies: - '@babel/types': 7.28.4 + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.27.1 - '@babel/runtime@7.28.4': {} + '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.27.1 '@babel/template@7.27.2': dependencies: '@babel/code-frame': 7.27.1 - '@babel/parser': 7.28.4 - '@babel/types': 7.28.4 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 - '@babel/traverse@7.28.4': + '@babel/traverse@7.28.5': dependencies: '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.3 + '@babel/generator': 7.28.5 '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.28.4 + '@babel/parser': 7.28.5 '@babel/template': 7.27.2 - '@babel/types': 7.28.4 + '@babel/types': 7.28.5 debug: 4.4.3 transitivePeerDependencies: - supports-color - '@babel/types@7.28.4': + '@babel/types@7.28.5': dependencies: '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 - '@biomejs/biome@2.2.2': + '@biomejs/biome@2.3.11': optionalDependencies: - '@biomejs/cli-darwin-arm64': 2.2.2 - '@biomejs/cli-darwin-x64': 2.2.2 - '@biomejs/cli-linux-arm64': 2.2.2 - '@biomejs/cli-linux-arm64-musl': 2.2.2 - '@biomejs/cli-linux-x64': 2.2.2 - '@biomejs/cli-linux-x64-musl': 2.2.2 - '@biomejs/cli-win32-arm64': 2.2.2 - '@biomejs/cli-win32-x64': 2.2.2 - - '@biomejs/cli-darwin-arm64@2.2.2': + '@biomejs/cli-darwin-arm64': 2.3.11 + '@biomejs/cli-darwin-x64': 2.3.11 + '@biomejs/cli-linux-arm64': 2.3.11 + '@biomejs/cli-linux-arm64-musl': 2.3.11 + '@biomejs/cli-linux-x64': 2.3.11 + '@biomejs/cli-linux-x64-musl': 2.3.11 + '@biomejs/cli-win32-arm64': 2.3.11 + '@biomejs/cli-win32-x64': 2.3.11 + + '@biomejs/cli-darwin-arm64@2.3.11': optional: true - '@biomejs/cli-darwin-x64@2.2.2': + '@biomejs/cli-darwin-x64@2.3.11': optional: true - '@biomejs/cli-linux-arm64-musl@2.2.2': + '@biomejs/cli-linux-arm64-musl@2.3.11': optional: true - '@biomejs/cli-linux-arm64@2.2.2': + '@biomejs/cli-linux-arm64@2.3.11': optional: true - '@biomejs/cli-linux-x64-musl@2.2.2': + '@biomejs/cli-linux-x64-musl@2.3.11': optional: true - '@biomejs/cli-linux-x64@2.2.2': + '@biomejs/cli-linux-x64@2.3.11': optional: true - '@biomejs/cli-win32-arm64@2.2.2': + '@biomejs/cli-win32-arm64@2.3.11': optional: true - '@biomejs/cli-win32-x64@2.2.2': + '@biomejs/cli-win32-x64@2.3.11': optional: true - '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': - dependencies: - '@csstools/css-tokenizer': 3.0.4 - - '@csstools/css-tokenizer@3.0.4': {} - - '@emotion/babel-plugin@11.13.5': + '@cacheable/memory@2.0.7': dependencies: - '@babel/helper-module-imports': 7.27.1 - '@babel/runtime': 7.28.4 - '@emotion/hash': 0.9.2 - '@emotion/memoize': 0.9.0 - '@emotion/serialize': 1.3.3 - babel-plugin-macros: 3.1.0 - convert-source-map: 1.9.0 - escape-string-regexp: 4.0.0 - find-root: 1.1.0 - source-map: 0.5.7 - stylis: 4.2.0 - transitivePeerDependencies: - - supports-color + '@cacheable/utils': 2.3.3 + '@keyv/bigmap': 1.3.0(keyv@5.5.5) + hookified: 1.15.0 + keyv: 5.5.5 - '@emotion/cache@11.14.0': + '@cacheable/utils@2.3.3': dependencies: - '@emotion/memoize': 0.9.0 - '@emotion/sheet': 1.4.0 - '@emotion/utils': 1.4.2 - '@emotion/weak-memoize': 0.4.0 - stylis: 4.2.0 + hashery: 1.4.0 + keyv: 5.5.5 - '@emotion/hash@0.9.2': {} - - '@emotion/is-prop-valid@1.4.0': + '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': dependencies: - '@emotion/memoize': 0.9.0 + '@csstools/css-tokenizer': 3.0.4 - '@emotion/memoize@0.9.0': {} + '@csstools/css-syntax-patches-for-csstree@1.0.22': {} - '@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1)': - dependencies: - '@babel/runtime': 7.28.4 - '@emotion/babel-plugin': 11.13.5 - '@emotion/cache': 11.14.0 - '@emotion/serialize': 1.3.3 - '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.1.1) - '@emotion/utils': 1.4.2 - '@emotion/weak-memoize': 0.4.0 - hoist-non-react-statics: 3.3.2 - react: 19.1.1 - optionalDependencies: - '@types/react': 19.1.13 - transitivePeerDependencies: - - supports-color + '@csstools/css-tokenizer@3.0.4': {} - '@emotion/serialize@1.3.3': + '@csstools/media-query-list-parser@4.0.3(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': dependencies: - '@emotion/hash': 0.9.2 - '@emotion/memoize': 0.9.0 - '@emotion/unitless': 0.10.0 - '@emotion/utils': 1.4.2 - csstype: 3.1.3 - - '@emotion/sheet@1.4.0': {} + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 - '@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react@19.1.1)': + '@csstools/selector-specificity@5.0.0(postcss-selector-parser@7.1.1)': dependencies: - '@babel/runtime': 7.28.4 - '@emotion/babel-plugin': 11.13.5 - '@emotion/is-prop-valid': 1.4.0 - '@emotion/react': 11.14.0(@types/react@19.1.13)(react@19.1.1) - '@emotion/serialize': 1.3.3 - '@emotion/use-insertion-effect-with-fallbacks': 1.2.0(react@19.1.1) - '@emotion/utils': 1.4.2 - react: 19.1.1 - optionalDependencies: - '@types/react': 19.1.13 - transitivePeerDependencies: - - supports-color + postcss-selector-parser: 7.1.1 - '@emotion/unitless@0.10.0': {} + '@dual-bundle/import-meta-resolve@4.2.1': {} - '@emotion/use-insertion-effect-with-fallbacks@1.2.0(react@19.1.1)': + '@emnapi/runtime@1.8.1': dependencies: - react: 19.1.1 - - '@emotion/utils@1.4.2': {} - - '@emotion/weak-memoize@0.4.0': {} + tslib: 2.8.1 + optional: true - '@esbuild/aix-ppc64@0.25.10': + '@esbuild/aix-ppc64@0.27.2': optional: true - '@esbuild/android-arm64@0.25.10': + '@esbuild/android-arm64@0.27.2': optional: true - '@esbuild/android-arm@0.25.10': + '@esbuild/android-arm@0.27.2': optional: true - '@esbuild/android-x64@0.25.10': + '@esbuild/android-x64@0.27.2': optional: true - '@esbuild/darwin-arm64@0.25.10': + '@esbuild/darwin-arm64@0.27.2': optional: true - '@esbuild/darwin-x64@0.25.10': + '@esbuild/darwin-x64@0.27.2': optional: true - '@esbuild/freebsd-arm64@0.25.10': + '@esbuild/freebsd-arm64@0.27.2': optional: true - '@esbuild/freebsd-x64@0.25.10': + '@esbuild/freebsd-x64@0.27.2': optional: true - '@esbuild/linux-arm64@0.25.10': + '@esbuild/linux-arm64@0.27.2': optional: true - '@esbuild/linux-arm@0.25.10': + '@esbuild/linux-arm@0.27.2': optional: true - '@esbuild/linux-ia32@0.25.10': + '@esbuild/linux-ia32@0.27.2': optional: true - '@esbuild/linux-loong64@0.25.10': + '@esbuild/linux-loong64@0.27.2': optional: true - '@esbuild/linux-mips64el@0.25.10': + '@esbuild/linux-mips64el@0.27.2': optional: true - '@esbuild/linux-ppc64@0.25.10': + '@esbuild/linux-ppc64@0.27.2': optional: true - '@esbuild/linux-riscv64@0.25.10': + '@esbuild/linux-riscv64@0.27.2': optional: true - '@esbuild/linux-s390x@0.25.10': + '@esbuild/linux-s390x@0.27.2': optional: true - '@esbuild/linux-x64@0.25.10': + '@esbuild/linux-x64@0.27.2': optional: true - '@esbuild/netbsd-arm64@0.25.10': + '@esbuild/netbsd-arm64@0.27.2': optional: true - '@esbuild/netbsd-x64@0.25.10': + '@esbuild/netbsd-x64@0.27.2': optional: true - '@esbuild/openbsd-arm64@0.25.10': + '@esbuild/openbsd-arm64@0.27.2': optional: true - '@esbuild/openbsd-x64@0.25.10': + '@esbuild/openbsd-x64@0.27.2': optional: true - '@esbuild/openharmony-arm64@0.25.10': + '@esbuild/openharmony-arm64@0.27.2': optional: true - '@esbuild/sunos-x64@0.25.10': + '@esbuild/sunos-x64@0.27.2': optional: true - '@esbuild/win32-arm64@0.25.10': + '@esbuild/win32-arm64@0.27.2': optional: true - '@esbuild/win32-ia32@0.25.10': + '@esbuild/win32-ia32@0.27.2': optional: true - '@esbuild/win32-x64@0.25.10': + '@esbuild/win32-x64@0.27.2': optional: true '@floating-ui/core@1.7.3': @@ -3397,46 +3174,143 @@ snapshots: '@floating-ui/core': 1.7.3 '@floating-ui/utils': 0.2.10 - '@floating-ui/react-dom@2.1.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@floating-ui/react-dom@2.1.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: '@floating-ui/dom': 1.7.4 - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@floating-ui/react@0.27.16(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@floating-ui/react@0.27.16(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@floating-ui/react-dom': 2.1.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + '@floating-ui/react-dom': 2.1.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) '@floating-ui/utils': 0.2.10 - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - tabbable: 6.2.0 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + tabbable: 6.4.0 '@floating-ui/utils@0.2.10': {} - '@github/webauthn-json@2.1.1': {} + '@img/colour@1.0.0': {} + + '@img/sharp-darwin-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.4 + optional: true + + '@img/sharp-darwin-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.2.4': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-s390x@1.2.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + optional: true + + '@img/sharp-linux-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.4 + optional: true + + '@img/sharp-linux-arm@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.4 + optional: true + + '@img/sharp-linux-ppc64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.2.4 + optional: true + + '@img/sharp-linux-riscv64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.2.4 + optional: true + + '@img/sharp-linux-s390x@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.2.4 + optional: true + + '@img/sharp-linux-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + optional: true + + '@img/sharp-wasm32@0.34.5': + dependencies: + '@emnapi/runtime': 1.8.1 + optional: true - '@hookform/devtools@4.4.0(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@img/sharp-win32-arm64@0.34.5': + optional: true + + '@img/sharp-win32-ia32@0.34.5': + optional: true + + '@img/sharp-win32-x64@0.34.5': + optional: true + + '@inlang/paraglide-js@2.7.1': dependencies: - '@emotion/react': 11.14.0(@types/react@19.1.13)(react@19.1.1) - '@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.1.13)(react@19.1.1))(@types/react@19.1.13)(react@19.1.1) - '@types/lodash': 4.17.20 - little-state-machine: 4.8.1(react@19.1.1) - lodash: 4.17.21 - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - react-simple-animate: 3.5.3(react-dom@19.1.1(react@19.1.1)) - use-deep-compare-effect: 1.8.1(react@19.1.1) - uuid: 8.3.2 + '@inlang/recommend-sherlock': 0.2.1 + '@inlang/sdk': 2.4.10 + commander: 11.1.0 + consola: 3.4.0 + json5: 2.2.3 + unplugin: 2.3.11 + urlpattern-polyfill: 10.1.0 transitivePeerDependencies: - - '@types/react' - - supports-color + - babel-plugin-macros - '@hookform/resolvers@5.2.2(react-hook-form@7.63.0(react@19.1.1))': + '@inlang/recommend-sherlock@0.2.1': dependencies: - '@standard-schema/utils': 0.3.0 - react-hook-form: 7.63.0(react@19.1.1) + comment-json: 4.5.1 - '@hutson/parse-repository-url@3.0.2': {} + '@inlang/sdk@2.4.10': + dependencies: + '@lix-js/sdk': 0.4.7 + '@sinclair/typebox': 0.31.28 + kysely: 0.27.6 + sqlite-wasm-kysely: 0.3.0(kysely@0.27.6) + uuid: 10.0.0 + transitivePeerDependencies: + - babel-plugin-macros '@jridgewell/gen-mapping@0.3.13': dependencies: @@ -3450,12 +3324,6 @@ snapshots: '@jridgewell/resolve-uri@3.1.2': {} - '@jridgewell/source-map@0.3.11': - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - optional: true - '@jridgewell/sourcemap-codec@1.5.5': {} '@jridgewell/trace-mapping@0.3.31': @@ -3463,112 +3331,243 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@react-hook/latest@1.0.3(react@19.1.1)': + '@keyv/bigmap@1.3.0(keyv@5.5.5)': + dependencies: + hashery: 1.4.0 + hookified: 1.15.0 + keyv: 5.5.5 + + '@keyv/serialize@1.1.1': {} + + '@lix-js/sdk@0.4.7': + dependencies: + '@lix-js/server-protocol-schema': 0.1.1 + dedent: 1.5.1 + human-id: 4.1.3 + js-sha256: 0.11.1 + kysely: 0.27.6 + sqlite-wasm-kysely: 0.3.0(kysely@0.27.6) + uuid: 10.0.0 + transitivePeerDependencies: + - babel-plugin-macros + + '@lix-js/server-protocol-schema@0.1.1': {} + + '@nodelib/fs.scandir@2.1.5': dependencies: - react: 19.1.1 + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + '@parcel/watcher-android-arm64@2.5.1': + optional: true + + '@parcel/watcher-darwin-arm64@2.5.1': + optional: true + + '@parcel/watcher-darwin-x64@2.5.1': + optional: true + + '@parcel/watcher-freebsd-x64@2.5.1': + optional: true + + '@parcel/watcher-linux-arm-glibc@2.5.1': + optional: true + + '@parcel/watcher-linux-arm-musl@2.5.1': + optional: true + + '@parcel/watcher-linux-arm64-glibc@2.5.1': + optional: true + + '@parcel/watcher-linux-arm64-musl@2.5.1': + optional: true + + '@parcel/watcher-linux-x64-glibc@2.5.1': + optional: true + + '@parcel/watcher-linux-x64-musl@2.5.1': + optional: true + + '@parcel/watcher-win32-arm64@2.5.1': + optional: true + + '@parcel/watcher-win32-ia32@2.5.1': + optional: true + + '@parcel/watcher-win32-x64@2.5.1': + optional: true + + '@parcel/watcher@2.5.1': + dependencies: + detect-libc: 1.0.3 + is-glob: 4.0.3 + micromatch: 4.0.8 + node-addon-api: 7.1.1 + optionalDependencies: + '@parcel/watcher-android-arm64': 2.5.1 + '@parcel/watcher-darwin-arm64': 2.5.1 + '@parcel/watcher-darwin-x64': 2.5.1 + '@parcel/watcher-freebsd-x64': 2.5.1 + '@parcel/watcher-linux-arm-glibc': 2.5.1 + '@parcel/watcher-linux-arm-musl': 2.5.1 + '@parcel/watcher-linux-arm64-glibc': 2.5.1 + '@parcel/watcher-linux-arm64-musl': 2.5.1 + '@parcel/watcher-linux-x64-glibc': 2.5.1 + '@parcel/watcher-linux-x64-musl': 2.5.1 + '@parcel/watcher-win32-arm64': 2.5.1 + '@parcel/watcher-win32-ia32': 2.5.1 + '@parcel/watcher-win32-x64': 2.5.1 + optional: true - '@react-hook/passive-layout-effect@1.2.1(react@19.1.1)': + '@react-hook/latest@1.0.3(react@19.2.3)': dependencies: - react: 19.1.1 + react: 19.2.3 - '@react-hook/resize-observer@2.0.2(react@19.1.1)': + '@react-hook/passive-layout-effect@1.2.1(react@19.2.3)': dependencies: - '@react-hook/latest': 1.0.3(react@19.1.1) - '@react-hook/passive-layout-effect': 1.2.1(react@19.1.1) - react: 19.1.1 + react: 19.2.3 - '@react-rxjs/core@0.10.8(react@19.1.1)(rxjs@7.8.2)': + '@react-hook/resize-observer@2.0.2(react@19.2.3)': dependencies: - '@rx-state/core': 0.1.4(rxjs@7.8.2) - react: 19.1.1 - rxjs: 7.8.2 - use-sync-external-store: 1.5.0(react@19.1.1) + '@react-hook/latest': 1.0.3(react@19.2.3) + '@react-hook/passive-layout-effect': 1.2.1(react@19.2.3) + react: 19.2.3 - '@reduxjs/toolkit@2.9.0(react-redux@9.2.0(@types/react@19.1.13)(react@19.1.1)(redux@5.0.1))(react@19.1.1)': + '@reduxjs/toolkit@2.11.2(react-redux@9.2.0(@types/react@19.2.7)(react@19.2.3)(redux@5.0.1))(react@19.2.3)': dependencies: - '@standard-schema/spec': 1.0.0 + '@standard-schema/spec': 1.1.0 '@standard-schema/utils': 0.3.0 - immer: 10.1.3 + immer: 11.1.3 redux: 5.0.1 redux-thunk: 3.1.0(redux@5.0.1) reselect: 5.1.1 optionalDependencies: - react: 19.1.1 - react-redux: 9.2.0(@types/react@19.1.13)(react@19.1.1)(redux@5.0.1) + react: 19.2.3 + react-redux: 9.2.0(@types/react@19.2.7)(react@19.2.3)(redux@5.0.1) + + '@rolldown/pluginutils@1.0.0-beta.47': {} + + '@rollup/rollup-android-arm-eabi@4.55.1': + optional: true - '@remix-run/router@1.23.0': {} + '@rollup/rollup-android-arm64@4.55.1': + optional: true - '@rolldown/pluginutils@1.0.0-beta.35': {} + '@rollup/rollup-darwin-arm64@4.55.1': + optional: true - '@rollup/rollup-android-arm-eabi@4.52.2': + '@rollup/rollup-darwin-x64@4.55.1': optional: true - '@rollup/rollup-android-arm64@4.52.2': + '@rollup/rollup-freebsd-arm64@4.55.1': optional: true - '@rollup/rollup-darwin-arm64@4.52.2': + '@rollup/rollup-freebsd-x64@4.55.1': optional: true - '@rollup/rollup-darwin-x64@4.52.2': + '@rollup/rollup-linux-arm-gnueabihf@4.55.1': optional: true - '@rollup/rollup-freebsd-arm64@4.52.2': + '@rollup/rollup-linux-arm-musleabihf@4.55.1': optional: true - '@rollup/rollup-freebsd-x64@4.52.2': + '@rollup/rollup-linux-arm64-gnu@4.55.1': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.52.2': + '@rollup/rollup-linux-arm64-musl@4.55.1': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.52.2': + '@rollup/rollup-linux-loong64-gnu@4.55.1': optional: true - '@rollup/rollup-linux-arm64-gnu@4.52.2': + '@rollup/rollup-linux-loong64-musl@4.55.1': optional: true - '@rollup/rollup-linux-arm64-musl@4.52.2': + '@rollup/rollup-linux-ppc64-gnu@4.55.1': optional: true - '@rollup/rollup-linux-loong64-gnu@4.52.2': + '@rollup/rollup-linux-ppc64-musl@4.55.1': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.52.2': + '@rollup/rollup-linux-riscv64-gnu@4.55.1': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.52.2': + '@rollup/rollup-linux-riscv64-musl@4.55.1': optional: true - '@rollup/rollup-linux-riscv64-musl@4.52.2': + '@rollup/rollup-linux-s390x-gnu@4.55.1': optional: true - '@rollup/rollup-linux-s390x-gnu@4.52.2': + '@rollup/rollup-linux-x64-gnu@4.55.1': optional: true - '@rollup/rollup-linux-x64-gnu@4.52.2': + '@rollup/rollup-linux-x64-musl@4.55.1': optional: true - '@rollup/rollup-linux-x64-musl@4.52.2': + '@rollup/rollup-openbsd-x64@4.55.1': optional: true - '@rollup/rollup-openharmony-arm64@4.52.2': + '@rollup/rollup-openharmony-arm64@4.55.1': optional: true - '@rollup/rollup-win32-arm64-msvc@4.52.2': + '@rollup/rollup-win32-arm64-msvc@4.55.1': optional: true - '@rollup/rollup-win32-ia32-msvc@4.52.2': + '@rollup/rollup-win32-ia32-msvc@4.55.1': optional: true - '@rollup/rollup-win32-x64-gnu@4.52.2': + '@rollup/rollup-win32-x64-gnu@4.55.1': optional: true - '@rollup/rollup-win32-x64-msvc@4.52.2': + '@rollup/rollup-win32-x64-msvc@4.55.1': optional: true - '@rx-state/core@0.1.4(rxjs@7.8.2)': + '@shortercode/webzip@1.1.1-0': {} + + '@sinclair/typebox@0.31.28': {} + + '@solid-primitives/event-listener@2.4.3(solid-js@1.9.9)': + dependencies: + '@solid-primitives/utils': 6.3.2(solid-js@1.9.9) + solid-js: 1.9.9 + + '@solid-primitives/keyboard@1.3.3(solid-js@1.9.9)': + dependencies: + '@solid-primitives/event-listener': 2.4.3(solid-js@1.9.9) + '@solid-primitives/rootless': 1.5.2(solid-js@1.9.9) + '@solid-primitives/utils': 6.3.2(solid-js@1.9.9) + solid-js: 1.9.9 + + '@solid-primitives/resize-observer@2.1.3(solid-js@1.9.9)': + dependencies: + '@solid-primitives/event-listener': 2.4.3(solid-js@1.9.9) + '@solid-primitives/rootless': 1.5.2(solid-js@1.9.9) + '@solid-primitives/static-store': 0.1.2(solid-js@1.9.9) + '@solid-primitives/utils': 6.3.2(solid-js@1.9.9) + solid-js: 1.9.9 + + '@solid-primitives/rootless@1.5.2(solid-js@1.9.9)': + dependencies: + '@solid-primitives/utils': 6.3.2(solid-js@1.9.9) + solid-js: 1.9.9 + + '@solid-primitives/static-store@0.1.2(solid-js@1.9.9)': + dependencies: + '@solid-primitives/utils': 6.3.2(solid-js@1.9.9) + solid-js: 1.9.9 + + '@solid-primitives/utils@6.3.2(solid-js@1.9.9)': dependencies: - rxjs: 7.8.2 + solid-js: 1.9.9 + + '@sqlite.org/sqlite-wasm@3.48.0-build4': {} '@stablelib/base64@2.0.1': {} @@ -3597,84 +3596,281 @@ snapshots: '@stablelib/random': 2.0.1 '@stablelib/wipe': 2.0.1 - '@standard-schema/spec@1.0.0': {} + '@standard-schema/spec@1.1.0': {} '@standard-schema/utils@0.3.0': {} - '@swc/core-darwin-arm64@1.13.19': + '@swc/core-darwin-arm64@1.15.8': + optional: true + + '@swc/core-darwin-x64@1.15.8': + optional: true + + '@swc/core-linux-arm-gnueabihf@1.15.8': + optional: true + + '@swc/core-linux-arm64-gnu@1.15.8': + optional: true + + '@swc/core-linux-arm64-musl@1.15.8': + optional: true + + '@swc/core-linux-x64-gnu@1.15.8': + optional: true + + '@swc/core-linux-x64-musl@1.15.8': optional: true - '@swc/core-darwin-x64@1.13.19': - optional: true + '@swc/core-win32-arm64-msvc@1.15.8': + optional: true + + '@swc/core-win32-ia32-msvc@1.15.8': + optional: true + + '@swc/core-win32-x64-msvc@1.15.8': + optional: true + + '@swc/core@1.15.8': + dependencies: + '@swc/counter': 0.1.3 + '@swc/types': 0.1.25 + optionalDependencies: + '@swc/core-darwin-arm64': 1.15.8 + '@swc/core-darwin-x64': 1.15.8 + '@swc/core-linux-arm-gnueabihf': 1.15.8 + '@swc/core-linux-arm64-gnu': 1.15.8 + '@swc/core-linux-arm64-musl': 1.15.8 + '@swc/core-linux-x64-gnu': 1.15.8 + '@swc/core-linux-x64-musl': 1.15.8 + '@swc/core-win32-arm64-msvc': 1.15.8 + '@swc/core-win32-ia32-msvc': 1.15.8 + '@swc/core-win32-x64-msvc': 1.15.8 + + '@swc/counter@0.1.3': {} + + '@swc/types@0.1.25': + dependencies: + '@swc/counter': 0.1.3 + + '@tanstack/devtools-client@0.0.5': + dependencies: + '@tanstack/devtools-event-client': 0.4.0 + + '@tanstack/devtools-event-bus@0.4.0': + dependencies: + ws: 8.19.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@tanstack/devtools-event-client@0.4.0': {} + + '@tanstack/devtools-ui@0.4.4(csstype@3.2.3)(solid-js@1.9.9)': + dependencies: + clsx: 2.1.1 + goober: 2.1.18(csstype@3.2.3) + solid-js: 1.9.9 + transitivePeerDependencies: + - csstype + + '@tanstack/devtools-vite@0.4.0(vite@7.3.0(@types/node@25.0.3)(sass@1.97.2)(tsx@4.21.0))': + dependencies: + '@babel/core': 7.28.5 + '@babel/generator': 7.28.5 + '@babel/parser': 7.28.5 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 + '@tanstack/devtools-client': 0.0.5 + '@tanstack/devtools-event-bus': 0.4.0 + chalk: 5.6.2 + launch-editor: 2.12.0 + picomatch: 4.0.3 + vite: 7.3.0(@types/node@25.0.3)(sass@1.97.2)(tsx@4.21.0) + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@tanstack/devtools@0.10.1(csstype@3.2.3)(solid-js@1.9.9)': + dependencies: + '@solid-primitives/event-listener': 2.4.3(solid-js@1.9.9) + '@solid-primitives/keyboard': 1.3.3(solid-js@1.9.9) + '@solid-primitives/resize-observer': 2.1.3(solid-js@1.9.9) + '@tanstack/devtools-client': 0.0.5 + '@tanstack/devtools-event-bus': 0.4.0 + '@tanstack/devtools-ui': 0.4.4(csstype@3.2.3)(solid-js@1.9.9) + clsx: 2.1.1 + goober: 2.1.18(csstype@3.2.3) + solid-js: 1.9.9 + transitivePeerDependencies: + - bufferutil + - csstype + - utf-8-validate + + '@tanstack/form-core@1.27.7': + dependencies: + '@tanstack/devtools-event-client': 0.4.0 + '@tanstack/pacer-lite': 0.1.1 + '@tanstack/store': 0.7.7 - '@swc/core-linux-arm-gnueabihf@1.13.19': - optional: true + '@tanstack/history@1.145.7': {} - '@swc/core-linux-arm64-gnu@1.13.19': - optional: true + '@tanstack/pacer-lite@0.1.1': {} - '@swc/core-linux-arm64-musl@1.13.19': - optional: true + '@tanstack/query-core@5.90.16': {} - '@swc/core-linux-x64-gnu@1.13.19': - optional: true + '@tanstack/query-devtools@5.92.0': {} - '@swc/core-linux-x64-musl@1.13.19': - optional: true + '@tanstack/react-devtools@0.9.0(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(csstype@3.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(solid-js@1.9.9)': + dependencies: + '@tanstack/devtools': 0.10.1(csstype@3.2.3)(solid-js@1.9.9) + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + transitivePeerDependencies: + - bufferutil + - csstype + - solid-js + - utf-8-validate - '@swc/core-win32-arm64-msvc@1.13.19': - optional: true + '@tanstack/react-form@1.27.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@tanstack/form-core': 1.27.7 + '@tanstack/react-store': 0.8.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + react: 19.2.3 + transitivePeerDependencies: + - react-dom - '@swc/core-win32-ia32-msvc@1.13.19': - optional: true + '@tanstack/react-query-devtools@5.91.2(@tanstack/react-query@5.90.16(react@19.2.3))(react@19.2.3)': + dependencies: + '@tanstack/query-devtools': 5.92.0 + '@tanstack/react-query': 5.90.16(react@19.2.3) + react: 19.2.3 - '@swc/core-win32-x64-msvc@1.13.19': - optional: true + '@tanstack/react-query@5.90.16(react@19.2.3)': + dependencies: + '@tanstack/query-core': 5.90.16 + react: 19.2.3 - '@swc/core@1.13.19': + '@tanstack/react-router-devtools@1.145.7(@tanstack/react-router@1.145.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@tanstack/router-core@1.145.7)(csstype@3.2.3)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(solid-js@1.9.9)': dependencies: - '@swc/counter': 0.1.3 - '@swc/types': 0.1.25 + '@tanstack/react-router': 1.145.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@tanstack/router-devtools-core': 1.145.7(@tanstack/router-core@1.145.7)(csstype@3.2.3)(solid-js@1.9.9) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) optionalDependencies: - '@swc/core-darwin-arm64': 1.13.19 - '@swc/core-darwin-x64': 1.13.19 - '@swc/core-linux-arm-gnueabihf': 1.13.19 - '@swc/core-linux-arm64-gnu': 1.13.19 - '@swc/core-linux-arm64-musl': 1.13.19 - '@swc/core-linux-x64-gnu': 1.13.19 - '@swc/core-linux-x64-musl': 1.13.19 - '@swc/core-win32-arm64-msvc': 1.13.19 - '@swc/core-win32-ia32-msvc': 1.13.19 - '@swc/core-win32-x64-msvc': 1.13.19 + '@tanstack/router-core': 1.145.7 + transitivePeerDependencies: + - csstype + - solid-js - '@swc/counter@0.1.3': {} + '@tanstack/react-router@1.145.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@tanstack/history': 1.145.7 + '@tanstack/react-store': 0.8.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + '@tanstack/router-core': 1.145.7 + isbot: 5.1.32 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + tiny-invariant: 1.3.3 + tiny-warning: 1.0.3 - '@swc/types@0.1.25': + '@tanstack/react-store@0.8.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@swc/counter': 0.1.3 + '@tanstack/store': 0.8.0 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + use-sync-external-store: 1.6.0(react@19.2.3) + + '@tanstack/react-table@8.21.3(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@tanstack/table-core': 8.21.3 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@tanstack/query-core@5.90.2': {} + '@tanstack/react-virtual@3.13.16(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + '@tanstack/virtual-core': 3.13.16 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@tanstack/query-devtools@5.90.1': {} + '@tanstack/router-core@1.145.7': + dependencies: + '@tanstack/history': 1.145.7 + '@tanstack/store': 0.8.0 + cookie-es: 2.0.0 + seroval: 1.4.2 + seroval-plugins: 1.4.2(seroval@1.4.2) + tiny-invariant: 1.3.3 + tiny-warning: 1.0.3 - '@tanstack/react-query-devtools@5.90.2(@tanstack/react-query@5.90.2(react@19.1.1))(react@19.1.1)': + '@tanstack/router-devtools-core@1.145.7(@tanstack/router-core@1.145.7)(csstype@3.2.3)(solid-js@1.9.9)': dependencies: - '@tanstack/query-devtools': 5.90.1 - '@tanstack/react-query': 5.90.2(react@19.1.1) - react: 19.1.1 + '@tanstack/router-core': 1.145.7 + clsx: 2.1.1 + goober: 2.1.18(csstype@3.2.3) + solid-js: 1.9.9 + tiny-invariant: 1.3.3 + optionalDependencies: + csstype: 3.2.3 + + '@tanstack/router-generator@1.145.7': + dependencies: + '@tanstack/router-core': 1.145.7 + '@tanstack/router-utils': 1.143.11 + '@tanstack/virtual-file-routes': 1.145.4 + prettier: 3.7.4 + recast: 0.23.11 + source-map: 0.7.6 + tsx: 4.21.0 + zod: 3.25.76 + transitivePeerDependencies: + - supports-color - '@tanstack/react-query@5.90.2(react@19.1.1)': + '@tanstack/router-plugin@1.145.7(@tanstack/react-router@1.145.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(vite@7.3.0(@types/node@25.0.3)(sass@1.97.2)(tsx@4.21.0))': dependencies: - '@tanstack/query-core': 5.90.2 - react: 19.1.1 + '@babel/core': 7.28.5 + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.5) + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 + '@tanstack/router-core': 1.145.7 + '@tanstack/router-generator': 1.145.7 + '@tanstack/router-utils': 1.143.11 + '@tanstack/virtual-file-routes': 1.145.4 + babel-dead-code-elimination: 1.0.11 + chokidar: 3.6.0 + unplugin: 2.3.11 + zod: 3.25.76 + optionalDependencies: + '@tanstack/react-router': 1.145.7(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + vite: 7.3.0(@types/node@25.0.3)(sass@1.97.2)(tsx@4.21.0) + transitivePeerDependencies: + - supports-color - '@tanstack/react-virtual@3.13.12(react-dom@19.1.1(react@19.1.1))(react@19.1.1)': + '@tanstack/router-utils@1.143.11': dependencies: - '@tanstack/virtual-core': 3.13.12 - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + '@babel/core': 7.28.5 + '@babel/generator': 7.28.5 + '@babel/parser': 7.28.5 + ansis: 4.2.0 + diff: 8.0.2 + pathe: 2.0.3 + tinyglobby: 0.2.15 + transitivePeerDependencies: + - supports-color + + '@tanstack/store@0.7.7': {} + + '@tanstack/store@0.8.0': {} + + '@tanstack/table-core@8.21.3': {} + + '@tanstack/virtual-core@3.13.16': {} - '@tanstack/virtual-core@3.13.12': {} + '@tanstack/virtual-file-routes@1.145.4': {} '@types/byte-size@8.1.2': {} @@ -3712,58 +3908,37 @@ snapshots: '@types/estree@1.0.8': {} - '@types/file-saver@2.0.7': {} - '@types/hast@3.0.4': dependencies: '@types/unist': 3.0.3 - '@types/history@4.7.11': {} - '@types/humanize-duration@3.27.4': {} '@types/lodash-es@4.17.12': dependencies: - '@types/lodash': 4.17.20 + '@types/lodash': 4.17.21 - '@types/lodash@4.17.20': {} + '@types/lodash@4.17.21': {} '@types/mdast@4.0.4': dependencies: '@types/unist': 3.0.3 - '@types/minimist@1.2.5': {} - '@types/ms@2.1.0': {} - '@types/node@24.5.2': + '@types/node@25.0.3': dependencies: - undici-types: 7.12.0 - - '@types/normalize-package-data@2.4.4': {} - - '@types/parse-json@4.0.2': {} + undici-types: 7.16.0 '@types/qs@6.14.0': {} - '@types/react-dom@19.1.9(@types/react@19.1.13)': - dependencies: - '@types/react': 19.1.13 - - '@types/react-router-dom@5.3.3': - dependencies: - '@types/history': 4.7.11 - '@types/react': 19.1.13 - '@types/react-router': 5.1.20 - - '@types/react-router@5.1.20': + '@types/react-dom@19.2.3(@types/react@19.2.7)': dependencies: - '@types/history': 4.7.11 - '@types/react': 19.1.13 + '@types/react': 19.2.7 - '@types/react@19.1.13': + '@types/react@19.2.7': dependencies: - csstype: 3.1.3 + csstype: 3.2.3 '@types/unist@2.0.11': {} @@ -3771,109 +3946,115 @@ snapshots: '@types/use-sync-external-store@0.0.6': {} - '@ungap/structured-clone@1.3.0': {} - - '@use-gesture/core@10.3.1': {} - - '@use-gesture/react@10.3.1(react@19.1.1)': + '@uidotdev/usehooks@2.4.1(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': dependencies: - '@use-gesture/core': 10.3.1 - react: 19.1.1 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) - '@vitejs/plugin-react-swc@4.1.0(vite@7.1.7(@types/node@24.5.2)(jiti@2.4.2)(sass@1.70.0)(terser@5.37.0)(yaml@2.6.1))': + '@ungap/structured-clone@1.3.0': {} + + '@vitejs/plugin-react-swc@4.2.2(vite@7.3.0(@types/node@25.0.3)(sass@1.97.2)(tsx@4.21.0))': dependencies: - '@rolldown/pluginutils': 1.0.0-beta.35 - '@swc/core': 1.13.19 - vite: 7.1.7(@types/node@24.5.2)(jiti@2.4.2)(sass@1.70.0)(terser@5.37.0)(yaml@2.6.1) + '@rolldown/pluginutils': 1.0.0-beta.47 + '@swc/core': 1.15.8 + vite: 7.3.0(@types/node@25.0.3)(sass@1.97.2)(tsx@4.21.0) transitivePeerDependencies: - '@swc/helpers' - JSONStream@1.3.5: - dependencies: - jsonparse: 1.3.1 - through: 2.3.8 + acorn@8.15.0: {} - acorn@8.15.0: - optional: true + ajv@8.17.1: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 - add-stream@1.0.0: {} + ansi-colors@4.1.3: {} ansi-regex@5.0.1: {} - ansi-styles@3.2.1: - dependencies: - color-convert: 1.9.3 - ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 + ansis@4.2.0: {} + anymatch@3.1.3: dependencies: normalize-path: 3.0.0 picomatch: 2.3.1 - array-ify@1.0.0: {} + argparse@2.0.1: {} + + array-timsort@1.0.3: {} + + array-union@2.1.0: {} + + ast-types@0.16.1: + dependencies: + tslib: 2.8.1 - arrify@1.0.1: {} + astral-regex@2.0.0: {} asynckit@0.4.0: {} - autoprefixer@10.4.21(postcss@8.5.6): + autoprefixer@10.4.23(postcss@8.5.6): dependencies: - browserslist: 4.26.2 - caniuse-lite: 1.0.30001745 - fraction.js: 4.3.7 - normalize-range: 0.1.2 + browserslist: 4.28.1 + caniuse-lite: 1.0.30001762 + fraction.js: 5.3.4 picocolors: 1.1.1 postcss: 8.5.6 postcss-value-parser: 4.2.0 - axios@1.12.2: + axios@1.13.2: dependencies: follow-redirects: 1.15.11 - form-data: 4.0.4 + form-data: 4.0.5 proxy-from-env: 1.1.0 transitivePeerDependencies: - debug - babel-plugin-macros@3.1.0: + babel-dead-code-elimination@1.0.11: dependencies: - '@babel/runtime': 7.28.4 - cosmiconfig: 7.1.0 - resolve: 1.22.10 + '@babel/core': 7.28.5 + '@babel/parser': 7.28.5 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 + transitivePeerDependencies: + - supports-color bail@2.0.2: {} - balanced-match@1.0.2: {} + balanced-match@2.0.0: {} - baseline-browser-mapping@2.8.7: {} - - bignumber.js@9.3.1: {} + baseline-browser-mapping@2.9.11: {} binary-extensions@2.3.0: {} - brace-expansion@1.1.12: - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - braces@3.0.3: dependencies: fill-range: 7.1.1 - browserslist@4.26.2: + browserslist@4.28.1: dependencies: - baseline-browser-mapping: 2.8.7 - caniuse-lite: 1.0.30001745 - electron-to-chromium: 1.5.224 - node-releases: 2.0.21 - update-browserslist-db: 1.1.3(browserslist@4.26.2) - - buffer-from@1.1.2: {} + baseline-browser-mapping: 2.9.11 + caniuse-lite: 1.0.30001762 + electron-to-chromium: 1.5.267 + node-releases: 2.0.27 + update-browserslist-db: 1.2.3(browserslist@4.28.1) byte-size@9.0.1: {} + cacheable@2.3.1: + dependencies: + '@cacheable/memory': 2.0.7 + '@cacheable/utils': 2.3.3 + hookified: 1.15.0 + keyv: 5.5.5 + qified: 0.5.3 + call-bind-apply-helpers@1.0.2: dependencies: es-errors: 1.3.0 @@ -3886,28 +4067,11 @@ snapshots: callsites@3.1.0: {} - camelcase-keys@6.2.2: - dependencies: - camelcase: 5.3.1 - map-obj: 4.3.0 - quick-lru: 4.0.1 - - camelcase@5.3.1: {} - - caniuse-lite@1.0.30001745: {} + caniuse-lite@1.0.30001762: {} ccount@2.0.1: {} - chalk@2.4.2: - dependencies: - ansi-styles: 3.2.1 - escape-string-regexp: 1.0.5 - supports-color: 5.5.0 - - chalk@4.1.2: - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 + chalk@5.6.2: {} character-entities-html4@2.1.0: {} @@ -3929,199 +4093,61 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - classnames@2.5.1: {} - - cliui@6.0.0: - dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 6.2.0 - - cliui@7.0.4: - dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 7.0.0 - - cliui@8.0.1: + chokidar@4.0.3: dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 7.0.0 + readdirp: 4.1.2 clsx@2.1.1: {} - color-convert@1.9.3: - dependencies: - color-name: 1.1.3 - color-convert@2.0.1: dependencies: color-name: 1.1.4 - color-name@1.1.3: {} - color-name@1.1.4: {} + colord@2.9.3: {} + combined-stream@1.0.8: dependencies: delayed-stream: 1.0.0 comma-separated-tokens@2.0.3: {} - commander@2.20.3: - optional: true - - compare-func@2.0.0: - dependencies: - array-ify: 1.0.0 - dot-prop: 5.3.0 - - concat-map@0.0.1: {} - - concat-stream@2.0.0: - dependencies: - buffer-from: 1.1.2 - inherits: 2.0.4 - readable-stream: 3.6.2 - typedarray: 0.0.6 - - concurrently@9.2.1: - dependencies: - chalk: 4.1.2 - rxjs: 7.8.2 - shell-quote: 1.8.3 - supports-color: 8.1.1 - tree-kill: 1.2.2 - yargs: 17.7.2 - - conventional-changelog-angular@5.0.13: - dependencies: - compare-func: 2.0.0 - q: 1.5.1 - - conventional-changelog-atom@2.0.8: - dependencies: - q: 1.5.1 - - conventional-changelog-codemirror@2.0.8: - dependencies: - q: 1.5.1 + commander@11.1.0: {} - conventional-changelog-config-spec@2.1.0: {} - - conventional-changelog-conventionalcommits@4.6.3: - dependencies: - compare-func: 2.0.0 - lodash: 4.17.21 - q: 1.5.1 - - conventional-changelog-core@4.2.4: + comment-json@4.5.1: dependencies: - add-stream: 1.0.0 - conventional-changelog-writer: 5.0.1 - conventional-commits-parser: 3.2.4 - dateformat: 3.0.3 - get-pkg-repo: 4.2.1 - git-raw-commits: 2.0.11 - git-remote-origin-url: 2.0.0 - git-semver-tags: 4.1.1 - lodash: 4.17.21 - normalize-package-data: 3.0.3 - q: 1.5.1 - read-pkg: 3.0.0 - read-pkg-up: 3.0.0 - through2: 4.0.2 + array-timsort: 1.0.3 + core-util-is: 1.0.3 + esprima: 4.0.1 - conventional-changelog-ember@2.0.9: - dependencies: - q: 1.5.1 + consola@3.4.0: {} - conventional-changelog-eslint@3.0.9: - dependencies: - q: 1.5.1 + convert-source-map@2.0.0: {} - conventional-changelog-express@2.0.6: - dependencies: - q: 1.5.1 + cookie-es@2.0.0: {} - conventional-changelog-jquery@3.0.11: - dependencies: - q: 1.5.1 + core-util-is@1.0.3: {} - conventional-changelog-jshint@2.0.9: + cosmiconfig@9.0.0(typescript@5.9.3): dependencies: - compare-func: 2.0.0 - q: 1.5.1 + env-paths: 2.2.1 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + parse-json: 5.2.0 + optionalDependencies: + typescript: 5.9.3 - conventional-changelog-preset-loader@2.3.4: {} + css-functions-list@3.2.3: {} - conventional-changelog-writer@5.0.1: + css-tree@3.1.0: dependencies: - conventional-commits-filter: 2.0.7 - dateformat: 3.0.3 - handlebars: 4.7.8 - json-stringify-safe: 5.0.1 - lodash: 4.17.21 - meow: 8.1.2 - semver: 6.3.1 - split: 1.0.1 - through2: 4.0.2 - - conventional-changelog@3.1.25: - dependencies: - conventional-changelog-angular: 5.0.13 - conventional-changelog-atom: 2.0.8 - conventional-changelog-codemirror: 2.0.8 - conventional-changelog-conventionalcommits: 4.6.3 - conventional-changelog-core: 4.2.4 - conventional-changelog-ember: 2.0.9 - conventional-changelog-eslint: 3.0.9 - conventional-changelog-express: 2.0.6 - conventional-changelog-jquery: 3.0.11 - conventional-changelog-jshint: 2.0.9 - conventional-changelog-preset-loader: 2.3.4 - - conventional-commits-filter@2.0.7: - dependencies: - lodash.ismatch: 4.4.0 - modify-values: 1.0.1 - - conventional-commits-parser@3.2.4: - dependencies: - JSONStream: 1.3.5 - is-text-path: 1.0.1 - lodash: 4.17.21 - meow: 8.1.2 - split2: 3.2.2 - through2: 4.0.2 - - conventional-recommended-bump@6.1.0: - dependencies: - concat-stream: 2.0.0 - conventional-changelog-preset-loader: 2.3.4 - conventional-commits-filter: 2.0.7 - conventional-commits-parser: 3.2.4 - git-raw-commits: 2.0.11 - git-semver-tags: 4.1.1 - meow: 8.1.2 - q: 1.5.1 - - convert-source-map@1.9.0: {} - - convert-source-map@2.0.0: {} - - core-util-is@1.0.3: {} + mdn-data: 2.12.2 + source-map-js: 1.2.1 - cosmiconfig@7.1.0: - dependencies: - '@types/parse-json': 4.0.2 - import-fresh: 3.3.1 - parse-json: 5.2.0 - path-type: 4.0.0 - yaml: 1.10.2 + cssesc@3.0.0: {} - csstype@3.1.3: {} + csstype@3.2.3: {} d3-array@3.2.4: dependencies: @@ -4161,81 +4187,38 @@ snapshots: d3-timer@3.0.1: {} - dargs@7.0.0: {} - - date-fns@4.1.0: {} - - dateformat@3.0.3: {} - - dayjs@1.11.18: {} + dayjs@1.11.19: {} debug@4.4.3: dependencies: ms: 2.1.3 - decamelize-keys@1.1.1: - dependencies: - decamelize: 1.2.0 - map-obj: 1.0.1 - - decamelize@1.2.0: {} - decimal.js-light@2.5.1: {} decode-named-character-reference@1.2.0: dependencies: character-entities: 2.0.2 - deepmerge-ts@7.1.5: {} + dedent@1.5.1: {} delayed-stream@1.0.0: {} dequal@2.0.3: {} - detect-browser@5.3.0: {} - - detect-indent@6.1.0: {} + detect-libc@1.0.3: + optional: true - detect-newline@3.1.0: {} + detect-libc@2.1.2: {} devlop@1.1.0: dependencies: dequal: 2.0.3 - dice-coefficient@2.1.1: - dependencies: - n-gram: 2.0.2 - - dijkstrajs@1.0.3: {} - - dom-serializer@2.0.0: - dependencies: - domelementtype: 2.3.0 - domhandler: 5.0.3 - entities: 4.5.0 - - domelementtype@2.3.0: {} - - domhandler@5.0.3: - dependencies: - domelementtype: 2.3.0 - - domutils@3.2.2: - dependencies: - dom-serializer: 2.0.0 - domelementtype: 2.3.0 - domhandler: 5.0.3 - - dot-prop@5.3.0: - dependencies: - is-obj: 2.0.0 - - dotenv@17.2.2: {} + diff@8.0.2: {} - dotgitignore@2.1.0: + dir-glob@3.0.1: dependencies: - find-up: 3.0.0 - minimatch: 3.1.2 + path-type: 4.0.0 dunder-proto@1.0.1: dependencies: @@ -4243,14 +4226,16 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 - electron-to-chromium@1.5.224: {} + easy-file-picker@1.2.0: {} - emoji-regex@8.0.0: {} + electron-to-chromium@1.5.267: {} - entities@4.5.0: {} + emoji-regex@8.0.0: {} entities@6.0.1: {} + env-paths@2.2.1: {} + error-ex@1.3.4: dependencies: is-arrayish: 0.2.1 @@ -4270,90 +4255,88 @@ snapshots: has-tostringtag: 1.0.2 hasown: 2.0.2 - es-toolkit@1.39.10: {} + es-toolkit@1.43.0: {} - esbuild@0.25.10: + esbuild@0.27.2: optionalDependencies: - '@esbuild/aix-ppc64': 0.25.10 - '@esbuild/android-arm': 0.25.10 - '@esbuild/android-arm64': 0.25.10 - '@esbuild/android-x64': 0.25.10 - '@esbuild/darwin-arm64': 0.25.10 - '@esbuild/darwin-x64': 0.25.10 - '@esbuild/freebsd-arm64': 0.25.10 - '@esbuild/freebsd-x64': 0.25.10 - '@esbuild/linux-arm': 0.25.10 - '@esbuild/linux-arm64': 0.25.10 - '@esbuild/linux-ia32': 0.25.10 - '@esbuild/linux-loong64': 0.25.10 - '@esbuild/linux-mips64el': 0.25.10 - '@esbuild/linux-ppc64': 0.25.10 - '@esbuild/linux-riscv64': 0.25.10 - '@esbuild/linux-s390x': 0.25.10 - '@esbuild/linux-x64': 0.25.10 - '@esbuild/netbsd-arm64': 0.25.10 - '@esbuild/netbsd-x64': 0.25.10 - '@esbuild/openbsd-arm64': 0.25.10 - '@esbuild/openbsd-x64': 0.25.10 - '@esbuild/openharmony-arm64': 0.25.10 - '@esbuild/sunos-x64': 0.25.10 - '@esbuild/win32-arm64': 0.25.10 - '@esbuild/win32-ia32': 0.25.10 - '@esbuild/win32-x64': 0.25.10 + '@esbuild/aix-ppc64': 0.27.2 + '@esbuild/android-arm': 0.27.2 + '@esbuild/android-arm64': 0.27.2 + '@esbuild/android-x64': 0.27.2 + '@esbuild/darwin-arm64': 0.27.2 + '@esbuild/darwin-x64': 0.27.2 + '@esbuild/freebsd-arm64': 0.27.2 + '@esbuild/freebsd-x64': 0.27.2 + '@esbuild/linux-arm': 0.27.2 + '@esbuild/linux-arm64': 0.27.2 + '@esbuild/linux-ia32': 0.27.2 + '@esbuild/linux-loong64': 0.27.2 + '@esbuild/linux-mips64el': 0.27.2 + '@esbuild/linux-ppc64': 0.27.2 + '@esbuild/linux-riscv64': 0.27.2 + '@esbuild/linux-s390x': 0.27.2 + '@esbuild/linux-x64': 0.27.2 + '@esbuild/netbsd-arm64': 0.27.2 + '@esbuild/netbsd-x64': 0.27.2 + '@esbuild/openbsd-arm64': 0.27.2 + '@esbuild/openbsd-x64': 0.27.2 + '@esbuild/openharmony-arm64': 0.27.2 + '@esbuild/sunos-x64': 0.27.2 + '@esbuild/win32-arm64': 0.27.2 + '@esbuild/win32-ia32': 0.27.2 + '@esbuild/win32-x64': 0.27.2 escalade@3.2.0: {} - escape-string-regexp@1.0.5: {} - - escape-string-regexp@4.0.0: {} + esprima@4.0.1: {} estree-util-is-identifier-name@3.0.0: {} eventemitter3@5.0.1: {} - events@3.3.0: {} - extend@3.0.2: {} fast-deep-equal@3.1.3: {} - fdir@6.5.0(picomatch@4.0.3): - optionalDependencies: - picomatch: 4.0.3 - - figures@3.2.0: + fast-glob@3.3.3: dependencies: - escape-string-regexp: 1.0.5 + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 - file-saver@2.0.5: {} + fast-uri@3.1.0: {} - fill-range@7.1.1: + fastest-levenshtein@1.0.16: {} + + fastq@1.20.1: dependencies: - to-regex-range: 5.0.1 + reusify: 1.1.0 - find-root@1.1.0: {} + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 - find-up@2.1.0: + file-entry-cache@11.1.1: dependencies: - locate-path: 2.0.0 + flat-cache: 6.1.19 - find-up@3.0.0: + fill-range@7.1.1: dependencies: - locate-path: 3.0.0 + to-regex-range: 5.0.1 - find-up@4.1.0: + flat-cache@6.1.19: dependencies: - locate-path: 5.0.0 - path-exists: 4.0.0 + cacheable: 2.3.1 + flatted: 3.3.3 + hookified: 1.15.0 - find-up@5.0.0: - dependencies: - locate-path: 6.0.0 - path-exists: 4.0.0 + flatted@3.3.3: {} follow-redirects@1.15.11: {} - form-data@4.0.4: + form-data@4.0.5: dependencies: asynckit: 0.4.0 combined-stream: 1.0.8 @@ -4361,29 +4344,24 @@ snapshots: hasown: 2.0.2 mime-types: 2.1.35 - fraction.js@4.3.7: {} + fraction.js@5.3.4: {} - framer-motion@12.23.22(@emotion/is-prop-valid@1.4.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + framer-motion@12.24.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: - motion-dom: 12.23.21 - motion-utils: 12.23.6 + motion-dom: 12.24.0 + motion-utils: 12.23.28 tslib: 2.8.1 optionalDependencies: - '@emotion/is-prop-valid': 1.4.0 - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) fsevents@2.3.3: optional: true function-bind@1.1.2: {} - fuse.js@7.1.0: {} - gensync@1.0.0-beta.2: {} - get-caller-file@2.0.5: {} - get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -4397,64 +4375,47 @@ snapshots: hasown: 2.0.2 math-intrinsics: 1.1.0 - get-pkg-repo@4.2.1: - dependencies: - '@hutson/parse-repository-url': 3.0.2 - hosted-git-info: 4.1.0 - through2: 2.0.5 - yargs: 16.2.0 - get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 es-object-atoms: 1.1.1 - get-text-width@1.0.3: {} - - git-raw-commits@2.0.11: + get-tsconfig@4.13.0: dependencies: - dargs: 7.0.0 - lodash: 4.17.21 - meow: 8.1.2 - split2: 3.2.2 - through2: 4.0.2 + resolve-pkg-maps: 1.0.0 - git-remote-origin-url@2.0.0: + glob-parent@5.1.2: dependencies: - gitconfiglocal: 1.0.0 - pify: 2.3.0 + is-glob: 4.0.3 - git-semver-tags@4.1.1: + global-modules@2.0.0: dependencies: - meow: 8.1.2 - semver: 6.3.1 + global-prefix: 3.0.0 - gitconfiglocal@1.0.0: + global-prefix@3.0.0: dependencies: ini: 1.3.8 + kind-of: 6.0.3 + which: 1.3.1 - glob-parent@5.1.2: - dependencies: - is-glob: 4.0.3 - - globals@16.4.0: {} + globals@17.0.0: {} - gopd@1.2.0: {} + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.3 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 - graceful-fs@4.2.11: {} + globjoin@0.1.4: {} - handlebars@4.7.8: + goober@2.1.18(csstype@3.2.3): dependencies: - minimist: 1.2.8 - neo-async: 2.6.2 - source-map: 0.6.1 - wordwrap: 1.0.0 - optionalDependencies: - uglify-js: 3.19.3 + csstype: 3.2.3 - hard-rejection@2.1.0: {} - - has-flag@3.0.0: {} + gopd@1.2.0: {} has-flag@4.0.0: {} @@ -4464,6 +4425,10 @@ snapshots: dependencies: has-symbols: 1.1.0 + hashery@1.4.0: + dependencies: + hookified: 1.15.0 + hasown@2.0.2: dependencies: function-bind: 1.1.2 @@ -4479,10 +4444,6 @@ snapshots: vfile-location: 5.0.3 web-namespaces: 2.0.1 - hast-util-is-element@3.0.0: - dependencies: - '@types/hast': 3.0.4 - hast-util-parse-selector@4.0.0: dependencies: '@types/hast': 3.0.4 @@ -4493,9 +4454,9 @@ snapshots: '@types/unist': 3.0.3 '@ungap/structured-clone': 1.3.0 hast-util-from-parse5: 8.0.3 - hast-util-to-parse5: 8.0.0 + hast-util-to-parse5: 8.0.1 html-void-elements: 3.0.0 - mdast-util-to-hast: 13.2.0 + mdast-util-to-hast: 13.2.1 parse5: 7.3.0 unist-util-position: 5.0.0 unist-util-visit: 5.0.0 @@ -4503,12 +4464,6 @@ snapshots: web-namespaces: 2.0.1 zwitch: 2.0.4 - hast-util-sanitize@5.0.2: - dependencies: - '@types/hast': 3.0.4 - '@ungap/structured-clone': 1.3.0 - unist-util-position: 5.0.0 - hast-util-to-jsx-runtime@2.3.6: dependencies: '@types/estree': 1.0.8 @@ -4523,18 +4478,18 @@ snapshots: mdast-util-mdxjs-esm: 2.0.1 property-information: 7.1.0 space-separated-tokens: 2.0.2 - style-to-js: 1.1.17 + style-to-js: 1.1.21 unist-util-position: 5.0.0 vfile-message: 4.0.3 transitivePeerDependencies: - supports-color - hast-util-to-parse5@8.0.0: + hast-util-to-parse5@8.0.1: dependencies: '@types/hast': 3.0.4 comma-separated-tokens: 2.0.3 devlop: 1.1.0 - property-information: 6.5.0 + property-information: 7.1.0 space-separated-tokens: 2.0.2 web-namespaces: 2.0.1 zwitch: 2.0.4 @@ -4551,68 +4506,42 @@ snapshots: property-information: 7.1.0 space-separated-tokens: 2.0.2 - hex-rgb@5.0.0: {} - - hoist-non-react-statics@3.3.2: - dependencies: - react-is: 16.13.1 + hookified@1.15.0: {} - hosted-git-info@2.8.9: {} + html-tags@3.3.1: {} - hosted-git-info@4.1.0: - dependencies: - lru-cache: 6.0.0 + html-url-attributes@3.0.1: {} - html-dom-parser@5.1.1: - dependencies: - domhandler: 5.0.3 - htmlparser2: 10.0.0 + html-void-elements@3.0.0: {} - html-react-parser@5.2.6(@types/react@19.1.13)(react@19.1.1): - dependencies: - domhandler: 5.0.3 - html-dom-parser: 5.1.1 - react: 19.1.1 - react-property: 2.0.2 - style-to-js: 1.1.17 - optionalDependencies: - '@types/react': 19.1.13 + human-id@4.1.3: {} - html-url-attributes@3.0.1: {} + humanize-duration@3.33.2: {} - html-void-elements@3.0.0: {} + ignore@5.3.2: {} - htmlparser2@10.0.0: - dependencies: - domelementtype: 2.3.0 - domhandler: 5.0.3 - domutils: 3.2.2 - entities: 6.0.1 + ignore@7.0.5: {} - humanize-duration@3.33.1: {} + immer@10.2.0: {} - immer@10.1.3: {} + immer@11.1.3: {} - immutable@4.3.7: {} + immutable@5.1.4: {} import-fresh@3.3.1: dependencies: parent-module: 1.0.1 resolve-from: 4.0.0 - indent-string@4.0.0: {} - - inherits@2.0.4: {} + imurmurhash@0.1.4: {} ini@1.3.8: {} - inline-style-parser@0.2.4: {} + inline-style-parser@0.2.7: {} internmap@2.0.3: {} - ipaddr.js@2.2.0: {} - - is-absolute-url@4.0.1: {} + ipaddr.js@2.3.0: {} is-alphabetical@2.0.1: {} @@ -4627,10 +4556,6 @@ snapshots: dependencies: binary-extensions: 2.3.0 - is-core-module@2.16.1: - dependencies: - hasown: 2.0.2 - is-decimal@2.0.1: {} is-extglob@2.1.1: {} @@ -4645,98 +4570,61 @@ snapshots: is-number@7.0.0: {} - is-obj@2.0.0: {} - - is-plain-obj@1.1.0: {} - is-plain-obj@4.1.0: {} - is-text-path@1.0.1: - dependencies: - text-extensions: 1.9.0 - - isarray@1.0.0: {} + is-plain-object@5.0.0: {} - itertools@2.5.0: {} + isbot@5.1.32: {} - jiti@2.4.2: - optional: true + isexe@2.0.0: {} - js-base64@3.7.8: {} + js-sha256@0.11.1: {} js-tokens@4.0.0: {} - jsesc@3.1.0: {} + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 - json-parse-better-errors@1.0.2: {} + jsesc@3.1.0: {} json-parse-even-better-errors@2.3.1: {} - json-stringify-safe@5.0.1: {} + json-schema-traverse@1.0.0: {} json5@2.2.3: {} - jsonparse@1.3.1: {} - - kind-of@6.0.3: {} - - lines-and-columns@1.2.4: {} - - little-state-machine@4.8.1(react@19.1.1): - dependencies: - react: 19.1.1 - - load-json-file@4.0.0: + keyv@5.5.5: dependencies: - graceful-fs: 4.2.11 - parse-json: 4.0.0 - pify: 3.0.0 - strip-bom: 3.0.0 + '@keyv/serialize': 1.1.1 - locate-path@2.0.0: - dependencies: - p-locate: 2.0.0 - path-exists: 3.0.0 + kind-of@6.0.3: {} - locate-path@3.0.0: - dependencies: - p-locate: 3.0.0 - path-exists: 3.0.0 + known-css-properties@0.37.0: {} - locate-path@5.0.0: - dependencies: - p-locate: 4.1.0 + kysely@0.27.6: {} - locate-path@6.0.0: + launch-editor@2.12.0: dependencies: - p-locate: 5.0.0 - - lodash-es@4.17.21: {} + picocolors: 1.1.1 + shell-quote: 1.8.3 - lodash.ismatch@4.4.0: {} + lines-and-columns@1.2.4: {} - lodash@4.17.21: {} + lodash-es@4.17.22: {} - longest-streak@3.1.0: {} + lodash.truncate@4.4.2: {} - loose-envify@1.4.0: - dependencies: - js-tokens: 4.0.0 + longest-streak@3.1.0: {} lru-cache@5.1.1: dependencies: yallist: 3.1.1 - lru-cache@6.0.0: - dependencies: - yallist: 4.0.0 - - map-obj@1.0.1: {} - - map-obj@4.3.0: {} - math-intrinsics@1.1.0: {} + mathml-tag-names@2.1.3: {} + mdast-util-from-markdown@2.0.2: dependencies: '@types/mdast': 4.0.4 @@ -4796,9 +4684,9 @@ snapshots: mdast-util-phrasing@4.1.0: dependencies: '@types/mdast': 4.0.4 - unist-util-is: 6.0.0 + unist-util-is: 6.0.1 - mdast-util-to-hast@13.2.0: + mdast-util-to-hast@13.2.1: dependencies: '@types/hast': 3.0.4 '@types/mdast': 4.0.4 @@ -4826,23 +4714,13 @@ snapshots: dependencies: '@types/mdast': 4.0.4 - meow@8.1.2: - dependencies: - '@types/minimist': 1.2.5 - camelcase-keys: 6.2.2 - decamelize-keys: 1.1.1 - hard-rejection: 2.1.0 - minimist-options: 4.1.0 - normalize-package-data: 3.0.3 - read-pkg-up: 7.0.1 - redent: 3.0.0 - trim-newlines: 3.0.1 - type-fest: 0.18.1 - yargs-parser: 20.2.9 - - merge-refs@2.0.0(@types/react@19.1.13): - optionalDependencies: - '@types/react': 19.1.13 + mdn-data@2.12.2: {} + + mdn-data@2.25.0: {} + + meow@13.2.0: {} + + merge2@1.4.1: {} micromark-core-commonmark@2.0.3: dependencies: @@ -4977,9 +4855,10 @@ snapshots: transitivePeerDependencies: - supports-color - millify@6.1.0: + micromatch@4.0.8: dependencies: - yargs: 17.7.2 + braces: 3.0.3 + picomatch: 2.3.1 mime-db@1.52.0: {} @@ -4987,105 +4866,33 @@ snapshots: dependencies: mime-db: 1.52.0 - min-indent@1.0.1: {} - - minimatch@3.1.2: - dependencies: - brace-expansion: 1.1.12 - - minimist-options@4.1.0: - dependencies: - arrify: 1.0.1 - is-plain-obj: 1.1.0 - kind-of: 6.0.3 - - minimist@1.2.8: {} - - modify-values@1.0.1: {} - - motion-dom@12.23.21: + motion-dom@12.24.0: dependencies: - motion-utils: 12.23.6 + motion-utils: 12.23.28 - motion-utils@12.23.6: {} + motion-utils@12.23.28: {} - motion@12.23.22(@emotion/is-prop-valid@1.4.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + motion@12.24.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: - framer-motion: 12.23.22(@emotion/is-prop-valid@1.4.0)(react-dom@19.1.1(react@19.1.1))(react@19.1.1) + framer-motion: 12.24.2(react-dom@19.2.3(react@19.2.3))(react@19.2.3) tslib: 2.8.1 optionalDependencies: - '@emotion/is-prop-valid': 1.4.0 - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) ms@2.1.3: {} - n-gram@2.0.2: {} - nanoid@3.3.11: {} - neo-async@2.6.2: {} - - node-releases@2.0.21: {} - - normalize-package-data@2.5.0: - dependencies: - hosted-git-info: 2.8.9 - resolve: 1.22.10 - semver: 5.7.2 - validate-npm-package-license: 3.0.4 + node-addon-api@7.1.1: + optional: true - normalize-package-data@3.0.3: - dependencies: - hosted-git-info: 4.1.0 - is-core-module: 2.16.1 - semver: 7.7.2 - validate-npm-package-license: 3.0.4 + node-releases@2.0.27: {} normalize-path@3.0.0: {} - normalize-range@0.1.2: {} - - numbro@2.5.0: - dependencies: - bignumber.js: 9.3.1 - - object-assign@4.1.1: {} - object-inspect@1.13.4: {} - p-limit@1.3.0: - dependencies: - p-try: 1.0.0 - - p-limit@2.3.0: - dependencies: - p-try: 2.2.0 - - p-limit@3.1.0: - dependencies: - yocto-queue: 0.1.0 - - p-locate@2.0.0: - dependencies: - p-limit: 1.3.0 - - p-locate@3.0.0: - dependencies: - p-limit: 2.3.0 - - p-locate@4.1.0: - dependencies: - p-limit: 2.3.0 - - p-locate@5.0.0: - dependencies: - p-limit: 3.1.0 - - p-try@1.0.0: {} - - p-try@2.2.0: {} - parent-module@1.0.1: dependencies: callsites: 3.1.0 @@ -5100,11 +4907,6 @@ snapshots: is-decimal: 2.0.1 is-hexadecimal: 2.0.1 - parse-json@4.0.0: - dependencies: - error-ex: 1.3.4 - json-parse-better-errors: 1.0.2 - parse-json@5.2.0: dependencies: '@babel/code-frame': 7.27.1 @@ -5116,29 +4918,32 @@ snapshots: dependencies: entities: 6.0.1 - path-exists@3.0.0: {} - - path-exists@4.0.0: {} - - path-parse@1.0.7: {} - - path-type@3.0.0: - dependencies: - pify: 3.0.0 - path-type@4.0.0: {} + pathe@2.0.3: {} + picocolors@1.1.1: {} picomatch@2.3.1: {} picomatch@4.0.3: {} - pify@2.3.0: {} + postcss-media-query-parser@0.2.3: {} + + postcss-resolve-nested-selector@0.1.6: {} - pify@3.0.0: {} + postcss-safe-parser@7.0.1(postcss@8.5.6): + dependencies: + postcss: 8.5.6 + + postcss-scss@4.0.9(postcss@8.5.6): + dependencies: + postcss: 8.5.6 - pngjs@5.0.0: {} + postcss-selector-parser@7.1.1: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 postcss-value-parser@4.2.0: {} @@ -5148,93 +4953,53 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.1 - prettier@3.6.2: {} - - process-nextick-args@2.0.1: {} - - prop-types@15.8.1: - dependencies: - loose-envify: 1.4.0 - object-assign: 4.1.1 - react-is: 16.13.1 - - property-information@6.5.0: {} + prettier@3.7.4: {} property-information@7.1.0: {} - proxy-compare@3.0.1: {} - proxy-from-env@1.1.0: {} - q@1.5.1: {} - - qr.js@0.0.0: {} - - qrcode@1.5.4: - dependencies: - dijkstrajs: 1.0.3 - pngjs: 5.0.0 - yargs: 15.4.1 - - qs@6.14.0: + qified@0.5.3: dependencies: - side-channel: 1.1.0 - - quick-lru@4.0.1: {} - - radash@12.1.1: {} - - react-click-away-listener@2.4.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1): - dependencies: - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + hookified: 1.15.0 - react-datepicker@8.7.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + qrcode.react@4.2.0(react@19.2.3): dependencies: - '@floating-ui/react': 0.27.16(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - clsx: 2.1.1 - date-fns: 4.1.0 - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + react: 19.2.3 - react-dom@19.1.1(react@19.1.1): + qs@6.14.1: dependencies: - react: 19.1.1 - scheduler: 0.26.0 + side-channel: 1.1.0 - react-hook-form@7.63.0(react@19.1.1): - dependencies: - react: 19.1.1 + queue-microtask@1.2.3: {} - react-idle-timer@5.7.2(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + react-dom@19.2.3(react@19.2.3): dependencies: - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + react: 19.2.3 + scheduler: 0.27.0 - react-intersection-observer@9.16.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + react-intersection-observer@10.0.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3): dependencies: - react: 19.1.1 + react: 19.2.3 optionalDependencies: - react-dom: 19.1.1(react@19.1.1) + react-dom: 19.2.3(react@19.2.3) - react-is@16.13.1: {} + react-is@19.2.0: {} - react-is@19.1.1: {} - - react-loading-skeleton@3.5.0(react@19.1.1): + react-loading-skeleton@3.5.0(react@19.2.3): dependencies: - react: 19.1.1 + react: 19.2.3 - react-markdown@10.1.0(@types/react@19.1.13)(react@19.1.1): + react-markdown@10.1.0(@types/react@19.2.7)(react@19.2.3): dependencies: '@types/hast': 3.0.4 '@types/mdast': 4.0.4 - '@types/react': 19.1.13 + '@types/react': 19.2.7 devlop: 1.1.0 hast-util-to-jsx-runtime: 2.3.6 html-url-attributes: 3.0.1 - mdast-util-to-hast: 13.2.0 - react: 19.1.1 + mdast-util-to-hast: 13.2.1 + react: 19.2.3 remark-parse: 11.0.0 remark-rehype: 11.1.2 unified: 11.0.5 @@ -5243,153 +5008,63 @@ snapshots: transitivePeerDependencies: - supports-color - react-property@2.0.2: {} - - react-qr-code@2.0.18(react@19.1.1): - dependencies: - prop-types: 15.8.1 - qr.js: 0.0.0 - react: 19.1.1 - - react-redux@9.2.0(@types/react@19.1.13)(react@19.1.1)(redux@5.0.1): + react-redux@9.2.0(@types/react@19.2.7)(react@19.2.3)(redux@5.0.1): dependencies: '@types/use-sync-external-store': 0.0.6 - react: 19.1.1 - use-sync-external-store: 1.5.0(react@19.1.1) + react: 19.2.3 + use-sync-external-store: 1.6.0(react@19.2.3) optionalDependencies: - '@types/react': 19.1.13 + '@types/react': 19.2.7 redux: 5.0.1 - react-resize-detector@12.3.0(react@19.1.1): - dependencies: - es-toolkit: 1.39.10 - react: 19.1.1 - - react-router-dom@6.30.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1): - dependencies: - '@remix-run/router': 1.23.0 - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - react-router: 6.30.1(react@19.1.1) - - react-router@6.30.1(react@19.1.1): - dependencies: - '@remix-run/router': 1.23.0 - react: 19.1.1 - - react-simple-animate@3.5.3(react-dom@19.1.1(react@19.1.1)): - dependencies: - react-dom: 19.1.1(react@19.1.1) - - react-tracked@2.0.1(react@19.1.1)(scheduler@0.26.0): - dependencies: - proxy-compare: 3.0.1 - react: 19.1.1 - scheduler: 0.26.0 - use-context-selector: 2.0.0(react@19.1.1)(scheduler@0.26.0) - - react-virtualized-auto-sizer@1.0.26(react-dom@19.1.1(react@19.1.1))(react@19.1.1): - dependencies: - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - - react@19.1.1: {} + react@19.2.3: {} - read-pkg-up@3.0.0: - dependencies: - find-up: 2.1.0 - read-pkg: 3.0.0 - - read-pkg-up@7.0.1: - dependencies: - find-up: 4.1.0 - read-pkg: 5.2.0 - type-fest: 0.8.1 - - read-pkg@3.0.0: - dependencies: - load-json-file: 4.0.0 - normalize-package-data: 2.5.0 - path-type: 3.0.0 - - read-pkg@5.2.0: - dependencies: - '@types/normalize-package-data': 2.4.4 - normalize-package-data: 2.5.0 - parse-json: 5.2.0 - type-fest: 0.6.0 - - readable-stream@2.3.8: + readdirp@3.6.0: dependencies: - core-util-is: 1.0.3 - inherits: 2.0.4 - isarray: 1.0.0 - process-nextick-args: 2.0.1 - safe-buffer: 5.1.2 - string_decoder: 1.1.1 - util-deprecate: 1.0.2 + picomatch: 2.3.1 - readable-stream@3.6.2: - dependencies: - inherits: 2.0.4 - string_decoder: 1.3.0 - util-deprecate: 1.0.2 + readdirp@4.1.2: {} - readdirp@3.6.0: + recast@0.23.11: dependencies: - picomatch: 2.3.1 + ast-types: 0.16.1 + esprima: 4.0.1 + source-map: 0.6.1 + tiny-invariant: 1.3.3 + tslib: 2.8.1 - recharts@3.2.1(@types/react@19.1.13)(react-dom@19.1.1(react@19.1.1))(react-is@19.1.1)(react@19.1.1)(redux@5.0.1): + recharts@3.6.0(@types/react@19.2.7)(react-dom@19.2.3(react@19.2.3))(react-is@19.2.0)(react@19.2.3)(redux@5.0.1): dependencies: - '@reduxjs/toolkit': 2.9.0(react-redux@9.2.0(@types/react@19.1.13)(react@19.1.1)(redux@5.0.1))(react@19.1.1) + '@reduxjs/toolkit': 2.11.2(react-redux@9.2.0(@types/react@19.2.7)(react@19.2.3)(redux@5.0.1))(react@19.2.3) clsx: 2.1.1 decimal.js-light: 2.5.1 - es-toolkit: 1.39.10 + es-toolkit: 1.43.0 eventemitter3: 5.0.1 - immer: 10.1.3 - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) - react-is: 19.1.1 - react-redux: 9.2.0(@types/react@19.1.13)(react@19.1.1)(redux@5.0.1) + immer: 10.2.0 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + react-is: 19.2.0 + react-redux: 9.2.0(@types/react@19.2.7)(react@19.2.3)(redux@5.0.1) reselect: 5.1.1 tiny-invariant: 1.3.3 - use-sync-external-store: 1.5.0(react@19.1.1) + use-sync-external-store: 1.6.0(react@19.2.3) victory-vendor: 37.3.6 transitivePeerDependencies: - '@types/react' - redux - redent@3.0.0: - dependencies: - indent-string: 4.0.0 - strip-indent: 3.0.0 - redux-thunk@3.1.0(redux@5.0.1): dependencies: redux: 5.0.1 redux@5.0.1: {} - rehype-external-links@3.0.0: - dependencies: - '@types/hast': 3.0.4 - '@ungap/structured-clone': 1.3.0 - hast-util-is-element: 3.0.0 - is-absolute-url: 4.0.1 - space-separated-tokens: 2.0.2 - unist-util-visit: 5.0.0 - rehype-raw@7.0.0: dependencies: '@types/hast': 3.0.4 hast-util-raw: 9.1.0 vfile: 6.0.3 - rehype-sanitize@6.0.0: - dependencies: - '@types/hast': 3.0.4 - hast-util-sanitize: 5.0.2 - remark-parse@11.0.0: dependencies: '@types/mdast': 4.0.4 @@ -5403,75 +5078,117 @@ snapshots: dependencies: '@types/hast': 3.0.4 '@types/mdast': 4.0.4 - mdast-util-to-hast: 13.2.0 + mdast-util-to-hast: 13.2.1 unified: 11.0.5 vfile: 6.0.3 - require-directory@2.1.1: {} - - require-main-filename@2.0.0: {} + require-from-string@2.0.2: {} reselect@5.1.1: {} resolve-from@4.0.0: {} - resolve@1.22.10: - dependencies: - is-core-module: 2.16.1 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 + resolve-from@5.0.0: {} + + resolve-pkg-maps@1.0.0: {} - rollup@4.52.2: + reusify@1.1.0: {} + + rollup@4.55.1: dependencies: '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.52.2 - '@rollup/rollup-android-arm64': 4.52.2 - '@rollup/rollup-darwin-arm64': 4.52.2 - '@rollup/rollup-darwin-x64': 4.52.2 - '@rollup/rollup-freebsd-arm64': 4.52.2 - '@rollup/rollup-freebsd-x64': 4.52.2 - '@rollup/rollup-linux-arm-gnueabihf': 4.52.2 - '@rollup/rollup-linux-arm-musleabihf': 4.52.2 - '@rollup/rollup-linux-arm64-gnu': 4.52.2 - '@rollup/rollup-linux-arm64-musl': 4.52.2 - '@rollup/rollup-linux-loong64-gnu': 4.52.2 - '@rollup/rollup-linux-ppc64-gnu': 4.52.2 - '@rollup/rollup-linux-riscv64-gnu': 4.52.2 - '@rollup/rollup-linux-riscv64-musl': 4.52.2 - '@rollup/rollup-linux-s390x-gnu': 4.52.2 - '@rollup/rollup-linux-x64-gnu': 4.52.2 - '@rollup/rollup-linux-x64-musl': 4.52.2 - '@rollup/rollup-openharmony-arm64': 4.52.2 - '@rollup/rollup-win32-arm64-msvc': 4.52.2 - '@rollup/rollup-win32-ia32-msvc': 4.52.2 - '@rollup/rollup-win32-x64-gnu': 4.52.2 - '@rollup/rollup-win32-x64-msvc': 4.52.2 + '@rollup/rollup-android-arm-eabi': 4.55.1 + '@rollup/rollup-android-arm64': 4.55.1 + '@rollup/rollup-darwin-arm64': 4.55.1 + '@rollup/rollup-darwin-x64': 4.55.1 + '@rollup/rollup-freebsd-arm64': 4.55.1 + '@rollup/rollup-freebsd-x64': 4.55.1 + '@rollup/rollup-linux-arm-gnueabihf': 4.55.1 + '@rollup/rollup-linux-arm-musleabihf': 4.55.1 + '@rollup/rollup-linux-arm64-gnu': 4.55.1 + '@rollup/rollup-linux-arm64-musl': 4.55.1 + '@rollup/rollup-linux-loong64-gnu': 4.55.1 + '@rollup/rollup-linux-loong64-musl': 4.55.1 + '@rollup/rollup-linux-ppc64-gnu': 4.55.1 + '@rollup/rollup-linux-ppc64-musl': 4.55.1 + '@rollup/rollup-linux-riscv64-gnu': 4.55.1 + '@rollup/rollup-linux-riscv64-musl': 4.55.1 + '@rollup/rollup-linux-s390x-gnu': 4.55.1 + '@rollup/rollup-linux-x64-gnu': 4.55.1 + '@rollup/rollup-linux-x64-musl': 4.55.1 + '@rollup/rollup-openbsd-x64': 4.55.1 + '@rollup/rollup-openharmony-arm64': 4.55.1 + '@rollup/rollup-win32-arm64-msvc': 4.55.1 + '@rollup/rollup-win32-ia32-msvc': 4.55.1 + '@rollup/rollup-win32-x64-gnu': 4.55.1 + '@rollup/rollup-win32-x64-msvc': 4.55.1 fsevents: 2.3.3 + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + rxjs@7.8.2: dependencies: tslib: 2.8.1 - safe-buffer@5.1.2: {} - - safe-buffer@5.2.1: {} - - sass@1.70.0: + sass@1.97.2: dependencies: - chokidar: 3.6.0 - immutable: 4.3.7 + chokidar: 4.0.3 + immutable: 5.1.4 source-map-js: 1.2.1 + optionalDependencies: + '@parcel/watcher': 2.5.1 - scheduler@0.26.0: {} - - semver@5.7.2: {} + scheduler@0.27.0: {} semver@6.3.1: {} - semver@7.7.2: {} + semver@7.7.3: {} + + seroval-plugins@1.3.3(seroval@1.3.2): + dependencies: + seroval: 1.3.2 + + seroval-plugins@1.4.2(seroval@1.4.2): + dependencies: + seroval: 1.4.2 + + seroval@1.3.2: {} + + seroval@1.4.2: {} - set-blocking@2.0.0: {} + sharp@0.34.5: + dependencies: + '@img/colour': 1.0.0 + detect-libc: 2.1.2 + semver: 7.7.3 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.5 + '@img/sharp-darwin-x64': 0.34.5 + '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-linux-arm': 0.34.5 + '@img/sharp-linux-arm64': 0.34.5 + '@img/sharp-linux-ppc64': 0.34.5 + '@img/sharp-linux-riscv64': 0.34.5 + '@img/sharp-linux-s390x': 0.34.5 + '@img/sharp-linux-x64': 0.34.5 + '@img/sharp-linuxmusl-arm64': 0.34.5 + '@img/sharp-linuxmusl-x64': 0.34.5 + '@img/sharp-wasm32': 0.34.5 + '@img/sharp-win32-arm64': 0.34.5 + '@img/sharp-win32-ia32': 0.34.5 + '@img/sharp-win32-x64': 0.34.5 shell-quote@1.8.3: {} @@ -5503,58 +5220,34 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 - source-map-js@1.2.1: {} - - source-map-support@0.5.21: - dependencies: - buffer-from: 1.1.2 - source-map: 0.6.1 - optional: true - - source-map@0.5.7: {} + signal-exit@4.1.0: {} - source-map@0.6.1: {} - - space-separated-tokens@2.0.2: {} + slash@3.0.0: {} - spdx-correct@3.2.0: + slice-ansi@4.0.0: dependencies: - spdx-expression-parse: 3.0.1 - spdx-license-ids: 3.0.22 - - spdx-exceptions@2.5.0: {} + ansi-styles: 4.3.0 + astral-regex: 2.0.0 + is-fullwidth-code-point: 3.0.0 - spdx-expression-parse@3.0.1: + solid-js@1.9.9: dependencies: - spdx-exceptions: 2.5.0 - spdx-license-ids: 3.0.22 + csstype: 3.2.3 + seroval: 1.3.2 + seroval-plugins: 1.3.3(seroval@1.3.2) - spdx-license-ids@3.0.22: {} + source-map-js@1.2.1: {} - split2@3.2.2: - dependencies: - readable-stream: 3.6.2 + source-map@0.6.1: {} - split@1.0.1: - dependencies: - through: 2.3.8 + source-map@0.7.6: {} + + space-separated-tokens@2.0.2: {} - standard-version@9.5.0: + sqlite-wasm-kysely@0.3.0(kysely@0.27.6): dependencies: - chalk: 2.4.2 - conventional-changelog: 3.1.25 - conventional-changelog-config-spec: 2.1.0 - conventional-changelog-conventionalcommits: 4.6.3 - conventional-recommended-bump: 6.1.0 - detect-indent: 6.1.0 - detect-newline: 3.1.0 - dotgitignore: 2.1.0 - figures: 3.2.0 - find-up: 5.0.0 - git-semver-tags: 4.1.1 - semver: 7.7.2 - stringify-package: 1.0.1 - yargs: 16.2.0 + '@sqlite.org/sqlite-wasm': 3.48.0-build4 + kysely: 0.27.6 string-width@4.2.3: dependencies: @@ -5562,64 +5255,126 @@ snapshots: is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 - string_decoder@1.1.1: - dependencies: - safe-buffer: 5.1.2 - - string_decoder@1.3.0: - dependencies: - safe-buffer: 5.2.1 - stringify-entities@4.0.4: dependencies: character-entities-html4: 2.1.0 character-entities-legacy: 3.0.0 - stringify-package@1.0.1: {} - strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 - strip-bom@3.0.0: {} + style-to-js@1.1.21: + dependencies: + style-to-object: 1.0.14 + + style-to-object@1.0.14: + dependencies: + inline-style-parser: 0.2.7 + + stylelint-config-recommended-scss@16.0.2(postcss@8.5.6)(stylelint@16.26.1(typescript@5.9.3)): + dependencies: + postcss-scss: 4.0.9(postcss@8.5.6) + stylelint: 16.26.1(typescript@5.9.3) + stylelint-config-recommended: 17.0.0(stylelint@16.26.1(typescript@5.9.3)) + stylelint-scss: 6.14.0(stylelint@16.26.1(typescript@5.9.3)) + optionalDependencies: + postcss: 8.5.6 - strip-indent@3.0.0: + stylelint-config-recommended@17.0.0(stylelint@16.26.1(typescript@5.9.3)): dependencies: - min-indent: 1.0.1 + stylelint: 16.26.1(typescript@5.9.3) - style-to-js@1.1.17: + stylelint-config-standard-scss@16.0.0(postcss@8.5.6)(stylelint@16.26.1(typescript@5.9.3)): dependencies: - style-to-object: 1.0.9 + stylelint: 16.26.1(typescript@5.9.3) + stylelint-config-recommended-scss: 16.0.2(postcss@8.5.6)(stylelint@16.26.1(typescript@5.9.3)) + stylelint-config-standard: 39.0.1(stylelint@16.26.1(typescript@5.9.3)) + optionalDependencies: + postcss: 8.5.6 - style-to-object@1.0.9: + stylelint-config-standard@39.0.1(stylelint@16.26.1(typescript@5.9.3)): dependencies: - inline-style-parser: 0.2.4 + stylelint: 16.26.1(typescript@5.9.3) + stylelint-config-recommended: 17.0.0(stylelint@16.26.1(typescript@5.9.3)) - stylis@4.2.0: {} + stylelint-scss@6.14.0(stylelint@16.26.1(typescript@5.9.3)): + dependencies: + css-tree: 3.1.0 + is-plain-object: 5.0.0 + known-css-properties: 0.37.0 + mdn-data: 2.25.0 + postcss-media-query-parser: 0.2.3 + postcss-resolve-nested-selector: 0.1.6 + postcss-selector-parser: 7.1.1 + postcss-value-parser: 4.2.0 + stylelint: 16.26.1(typescript@5.9.3) - supports-color@5.5.0: + stylelint@16.26.1(typescript@5.9.3): dependencies: - has-flag: 3.0.0 + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-syntax-patches-for-csstree': 1.0.22 + '@csstools/css-tokenizer': 3.0.4 + '@csstools/media-query-list-parser': 4.0.3(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/selector-specificity': 5.0.0(postcss-selector-parser@7.1.1) + '@dual-bundle/import-meta-resolve': 4.2.1 + balanced-match: 2.0.0 + colord: 2.9.3 + cosmiconfig: 9.0.0(typescript@5.9.3) + css-functions-list: 3.2.3 + css-tree: 3.1.0 + debug: 4.4.3 + fast-glob: 3.3.3 + fastest-levenshtein: 1.0.16 + file-entry-cache: 11.1.1 + global-modules: 2.0.0 + globby: 11.1.0 + globjoin: 0.1.4 + html-tags: 3.3.1 + ignore: 7.0.5 + imurmurhash: 0.1.4 + is-plain-object: 5.0.0 + known-css-properties: 0.37.0 + mathml-tag-names: 2.1.3 + meow: 13.2.0 + micromatch: 4.0.8 + normalize-path: 3.0.0 + picocolors: 1.1.1 + postcss: 8.5.6 + postcss-resolve-nested-selector: 0.1.6 + postcss-safe-parser: 7.0.1(postcss@8.5.6) + postcss-selector-parser: 7.1.1 + postcss-value-parser: 4.2.0 + resolve-from: 5.0.0 + string-width: 4.2.3 + supports-hyperlinks: 3.2.0 + svg-tags: 1.0.0 + table: 6.9.0 + write-file-atomic: 5.0.1 + transitivePeerDependencies: + - supports-color + - typescript supports-color@7.2.0: dependencies: has-flag: 4.0.0 - supports-color@8.1.1: + supports-hyperlinks@3.2.0: dependencies: has-flag: 4.0.0 + supports-color: 7.2.0 - supports-preserve-symlinks-flag@1.0.0: {} + svg-tags@1.0.0: {} - tabbable@6.2.0: {} + tabbable@6.4.0: {} - terser@5.37.0: + table@6.9.0: dependencies: - '@jridgewell/source-map': 0.3.11 - acorn: 8.15.0 - commander: 2.20.3 - source-map-support: 0.5.21 - optional: true + ajv: 8.17.1 + lodash.truncate: 4.4.2 + slice-ansi: 4.0.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 text-camel-case@1.2.9: dependencies: @@ -5662,8 +5417,6 @@ snapshots: dependencies: text-no-case: 1.2.9 - text-extensions@1.9.0: {} - text-header-case@1.2.9: dependencies: text-capital-case: 1.2.9 @@ -5716,19 +5469,10 @@ snapshots: text-upper-case@1.2.9: {} - through2@2.0.5: - dependencies: - readable-stream: 2.3.8 - xtend: 4.0.2 - - through2@4.0.2: - dependencies: - readable-stream: 3.6.2 - - through@2.3.8: {} - tiny-invariant@1.3.3: {} + tiny-warning@1.0.3: {} + tinyglobby@0.2.15: dependencies: fdir: 6.5.0(picomatch@4.0.3) @@ -5738,36 +5482,22 @@ snapshots: dependencies: is-number: 7.0.0 - tree-kill@1.2.2: {} - trim-lines@3.0.1: {} - trim-newlines@3.0.1: {} - trough@2.2.0: {} tslib@2.8.1: {} - type-fest@0.18.1: {} - - type-fest@0.6.0: {} - - type-fest@0.8.1: {} - - type-fest@4.41.0: {} - - typedarray@0.0.6: {} - - typesafe-i18n@5.26.2(typescript@5.9.2): + tsx@4.21.0: dependencies: - typescript: 5.9.2 - - typescript@5.9.2: {} + esbuild: 0.27.2 + get-tsconfig: 4.13.0 + optionalDependencies: + fsevents: 2.3.3 - uglify-js@3.19.3: - optional: true + typescript@5.9.3: {} - undici-types@7.12.0: {} + undici-types@7.16.0: {} unified@11.0.5: dependencies: @@ -5779,7 +5509,7 @@ snapshots: trough: 2.2.0 vfile: 6.0.3 - unist-util-is@6.0.0: + unist-util-is@6.0.1: dependencies: '@types/unist': 3.0.3 @@ -5791,51 +5521,39 @@ snapshots: dependencies: '@types/unist': 3.0.3 - unist-util-visit-parents@6.0.1: + unist-util-visit-parents@6.0.2: dependencies: '@types/unist': 3.0.3 - unist-util-is: 6.0.0 + unist-util-is: 6.0.1 unist-util-visit@5.0.0: dependencies: '@types/unist': 3.0.3 - unist-util-is: 6.0.0 - unist-util-visit-parents: 6.0.1 - - update-browserslist-db@1.1.3(browserslist@4.26.2): - dependencies: - browserslist: 4.26.2 - escalade: 3.2.0 - picocolors: 1.1.1 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 - use-breakpoint@4.0.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1): + unplugin@2.3.11: dependencies: - react: 19.1.1 - react-dom: 19.1.1(react@19.1.1) + '@jridgewell/remapping': 2.3.5 + acorn: 8.15.0 + picomatch: 4.0.3 + webpack-virtual-modules: 0.6.2 - use-context-selector@2.0.0(react@19.1.1)(scheduler@0.26.0): + update-browserslist-db@1.2.3(browserslist@4.28.1): dependencies: - react: 19.1.1 - scheduler: 0.26.0 + browserslist: 4.28.1 + escalade: 3.2.0 + picocolors: 1.1.1 - use-deep-compare-effect@1.8.1(react@19.1.1): - dependencies: - '@babel/runtime': 7.28.4 - dequal: 2.0.3 - react: 19.1.1 + urlpattern-polyfill@10.1.0: {} - use-sync-external-store@1.5.0(react@19.1.1): + use-sync-external-store@1.6.0(react@19.2.3): dependencies: - react: 19.1.1 + react: 19.2.3 util-deprecate@1.0.2: {} - uuid@8.3.2: {} - - validate-npm-package-license@3.0.4: - dependencies: - spdx-correct: 3.2.0 - spdx-expression-parse: 3.0.1 + uuid@10.0.0: {} vfile-location@5.0.3: dependencies: @@ -5869,111 +5587,54 @@ snapshots: d3-time: 3.1.0 d3-timer: 3.0.1 - vite-plugin-package-version@1.1.0(vite@7.1.7(@types/node@24.5.2)(jiti@2.4.2)(sass@1.70.0)(terser@5.37.0)(yaml@2.6.1)): + vite-plugin-image-optimizer@2.0.3(sharp@0.34.5)(vite@7.3.0(@types/node@25.0.3)(sass@1.97.2)(tsx@4.21.0)): dependencies: - vite: 7.1.7(@types/node@24.5.2)(jiti@2.4.2)(sass@1.70.0)(terser@5.37.0)(yaml@2.6.1) + ansi-colors: 4.1.3 + pathe: 2.0.3 + vite: 7.3.0(@types/node@25.0.3)(sass@1.97.2)(tsx@4.21.0) + optionalDependencies: + sharp: 0.34.5 - vite@7.1.7(@types/node@24.5.2)(jiti@2.4.2)(sass@1.70.0)(terser@5.37.0)(yaml@2.6.1): + vite@7.3.0(@types/node@25.0.3)(sass@1.97.2)(tsx@4.21.0): dependencies: - esbuild: 0.25.10 + esbuild: 0.27.2 fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 - rollup: 4.52.2 + rollup: 4.55.1 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 24.5.2 + '@types/node': 25.0.3 fsevents: 2.3.3 - jiti: 2.4.2 - sass: 1.70.0 - terser: 5.37.0 - yaml: 2.6.1 + sass: 1.97.2 + tsx: 4.21.0 web-namespaces@2.0.1: {} - which-module@2.0.1: {} + webpack-virtual-modules@0.6.2: {} - wordwrap@1.0.0: {} - - wrap-ansi@6.2.0: + which@1.3.1: dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 + isexe: 2.0.0 - wrap-ansi@7.0.0: + write-file-atomic@5.0.1: dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - - xtend@4.0.2: {} + imurmurhash: 0.1.4 + signal-exit: 4.1.0 - y18n@4.0.3: {} - - y18n@5.0.8: {} + ws@8.19.0: {} yallist@3.1.1: {} - yallist@4.0.0: {} - - yaml@1.10.2: {} - - yaml@2.6.1: - optional: true - - yargs-parser@18.1.3: - dependencies: - camelcase: 5.3.1 - decamelize: 1.2.0 - - yargs-parser@20.2.9: {} - - yargs-parser@21.1.1: {} - - yargs@15.4.1: - dependencies: - cliui: 6.0.0 - decamelize: 1.2.0 - find-up: 4.1.0 - get-caller-file: 2.0.5 - require-directory: 2.1.1 - require-main-filename: 2.0.0 - set-blocking: 2.0.0 - string-width: 4.2.3 - which-module: 2.0.1 - y18n: 4.0.3 - yargs-parser: 18.1.3 - - yargs@16.2.0: - dependencies: - cliui: 7.0.4 - escalade: 3.2.0 - get-caller-file: 2.0.5 - require-directory: 2.1.1 - string-width: 4.2.3 - y18n: 5.0.8 - yargs-parser: 20.2.9 - - yargs@17.7.2: - dependencies: - cliui: 8.0.1 - escalade: 3.2.0 - get-caller-file: 2.0.5 - require-directory: 2.1.1 - string-width: 4.2.3 - y18n: 5.0.8 - yargs-parser: 21.1.1 - - yocto-queue@0.1.0: {} - zod@3.25.76: {} - zustand@5.0.8(@types/react@19.1.13)(immer@10.1.3)(react@19.1.1)(use-sync-external-store@1.5.0(react@19.1.1)): + zod@4.3.5: {} + + zustand@5.0.9(@types/react@19.2.7)(immer@11.1.3)(react@19.2.3)(use-sync-external-store@1.6.0(react@19.2.3)): optionalDependencies: - '@types/react': 19.1.13 - immer: 10.1.3 - react: 19.1.1 - use-sync-external-store: 1.5.0(react@19.1.1) + '@types/react': 19.2.7 + immer: 11.1.3 + react: 19.2.3 + use-sync-external-store: 1.6.0(react@19.2.3) zwitch@2.0.4: {} diff --git a/web/pnpm-workspace.yaml b/web/pnpm-workspace.yaml new file mode 100644 index 000000000..17a31e5ca --- /dev/null +++ b/web/pnpm-workspace.yaml @@ -0,0 +1,5 @@ +onlyBuiltDependencies: + - '@parcel/watcher' + - '@swc/core' + - esbuild + - sharp diff --git a/web/postcss.config.js b/web/postcss.config.js deleted file mode 100644 index a47ef4f95..000000000 --- a/web/postcss.config.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = { - plugins: { - autoprefixer: {}, - }, -}; diff --git a/web/project.inlang/.gitignore b/web/project.inlang/.gitignore new file mode 100644 index 000000000..5e4659675 --- /dev/null +++ b/web/project.inlang/.gitignore @@ -0,0 +1 @@ +cache \ No newline at end of file diff --git a/web/project.inlang/project_id b/web/project.inlang/project_id new file mode 100644 index 000000000..7f59fbcea --- /dev/null +++ b/web/project.inlang/project_id @@ -0,0 +1 @@ +hx4EvECp61fjNPUXtn \ No newline at end of file diff --git a/web/project.inlang/settings.json b/web/project.inlang/settings.json new file mode 100644 index 000000000..6206c1b15 --- /dev/null +++ b/web/project.inlang/settings.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://inlang.com/schema/project-settings", + "baseLocale": "en", + "locales": [ + "en" + ], + "modules": [ + "https://cdn.jsdelivr.net/npm/@inlang/plugin-message-format@latest/dist/index.js" + ], + "plugin.inlang.messageFormat": { + "pathPattern": [ + "./messages/{locale}/auth.json", + "./messages/{locale}/common.json", + "./messages/{locale}/form.json", + "./messages/{locale}/profile.json", + "./messages/{locale}/modal.json", + "./messages/{locale}/components.json", + "./messages/{locale}/users.json", + "./messages/{locale}/webhooks.json", + "./messages/{locale}/groups.json", + "./messages/{locale}/openid.json", + "./messages/{locale}/activity.json" + ] + } +} diff --git a/web/public/fonts/Poppins/Poppins-Black.woff2 b/web/public/fonts/Poppins/Poppins-Black.woff2 deleted file mode 100644 index 2dfde80a5..000000000 Binary files a/web/public/fonts/Poppins/Poppins-Black.woff2 and /dev/null differ diff --git a/web/public/fonts/Poppins/Poppins-BlackItalic.woff2 b/web/public/fonts/Poppins/Poppins-BlackItalic.woff2 deleted file mode 100644 index c53e44b3b..000000000 Binary files a/web/public/fonts/Poppins/Poppins-BlackItalic.woff2 and /dev/null differ diff --git a/web/public/fonts/Poppins/Poppins-Bold.woff2 b/web/public/fonts/Poppins/Poppins-Bold.woff2 deleted file mode 100644 index 13e0e28bc..000000000 Binary files a/web/public/fonts/Poppins/Poppins-Bold.woff2 and /dev/null differ diff --git a/web/public/fonts/Poppins/Poppins-BoldItalic.woff2 b/web/public/fonts/Poppins/Poppins-BoldItalic.woff2 deleted file mode 100644 index f7f7fe4bc..000000000 Binary files a/web/public/fonts/Poppins/Poppins-BoldItalic.woff2 and /dev/null differ diff --git a/web/public/fonts/Poppins/Poppins-ExtraBold.woff2 b/web/public/fonts/Poppins/Poppins-ExtraBold.woff2 deleted file mode 100644 index ad86d0277..000000000 Binary files a/web/public/fonts/Poppins/Poppins-ExtraBold.woff2 and /dev/null differ diff --git a/web/public/fonts/Poppins/Poppins-ExtraBoldItalic.woff2 b/web/public/fonts/Poppins/Poppins-ExtraBoldItalic.woff2 deleted file mode 100644 index 9cf7164d8..000000000 Binary files a/web/public/fonts/Poppins/Poppins-ExtraBoldItalic.woff2 and /dev/null differ diff --git a/web/public/fonts/Poppins/Poppins-ExtraLight.woff2 b/web/public/fonts/Poppins/Poppins-ExtraLight.woff2 deleted file mode 100644 index 5be09909b..000000000 Binary files a/web/public/fonts/Poppins/Poppins-ExtraLight.woff2 and /dev/null differ diff --git a/web/public/fonts/Poppins/Poppins-ExtraLightItalic.woff2 b/web/public/fonts/Poppins/Poppins-ExtraLightItalic.woff2 deleted file mode 100644 index 8b7c5ef1f..000000000 Binary files a/web/public/fonts/Poppins/Poppins-ExtraLightItalic.woff2 and /dev/null differ diff --git a/web/public/fonts/Poppins/Poppins-Italic.woff2 b/web/public/fonts/Poppins/Poppins-Italic.woff2 deleted file mode 100644 index 1db484df9..000000000 Binary files a/web/public/fonts/Poppins/Poppins-Italic.woff2 and /dev/null differ diff --git a/web/public/fonts/Poppins/Poppins-Light.woff2 b/web/public/fonts/Poppins/Poppins-Light.woff2 deleted file mode 100644 index 7eba2c4f7..000000000 Binary files a/web/public/fonts/Poppins/Poppins-Light.woff2 and /dev/null differ diff --git a/web/public/fonts/Poppins/Poppins-LightItalic.woff2 b/web/public/fonts/Poppins/Poppins-LightItalic.woff2 deleted file mode 100644 index 748c1a34a..000000000 Binary files a/web/public/fonts/Poppins/Poppins-LightItalic.woff2 and /dev/null differ diff --git a/web/public/fonts/Poppins/Poppins-Medium.woff2 b/web/public/fonts/Poppins/Poppins-Medium.woff2 deleted file mode 100644 index 406acb667..000000000 Binary files a/web/public/fonts/Poppins/Poppins-Medium.woff2 and /dev/null differ diff --git a/web/public/fonts/Poppins/Poppins-MediumItalic.woff2 b/web/public/fonts/Poppins/Poppins-MediumItalic.woff2 deleted file mode 100644 index 39177dc3f..000000000 Binary files a/web/public/fonts/Poppins/Poppins-MediumItalic.woff2 and /dev/null differ diff --git a/web/public/fonts/Poppins/Poppins-Regular.woff2 b/web/public/fonts/Poppins/Poppins-Regular.woff2 deleted file mode 100644 index 964d6d2f2..000000000 Binary files a/web/public/fonts/Poppins/Poppins-Regular.woff2 and /dev/null differ diff --git a/web/public/fonts/Poppins/Poppins-SemiBold.woff2 b/web/public/fonts/Poppins/Poppins-SemiBold.woff2 deleted file mode 100644 index 9e4d0c0eb..000000000 Binary files a/web/public/fonts/Poppins/Poppins-SemiBold.woff2 and /dev/null differ diff --git a/web/public/fonts/Poppins/Poppins-SemiBoldItalic.woff2 b/web/public/fonts/Poppins/Poppins-SemiBoldItalic.woff2 deleted file mode 100644 index 605065152..000000000 Binary files a/web/public/fonts/Poppins/Poppins-SemiBoldItalic.woff2 and /dev/null differ diff --git a/web/public/fonts/Poppins/Poppins-Thin.woff2 b/web/public/fonts/Poppins/Poppins-Thin.woff2 deleted file mode 100644 index f98239af2..000000000 Binary files a/web/public/fonts/Poppins/Poppins-Thin.woff2 and /dev/null differ diff --git a/web/public/fonts/Poppins/Poppins-ThinItalic.woff2 b/web/public/fonts/Poppins/Poppins-ThinItalic.woff2 deleted file mode 100644 index 86ee4fd46..000000000 Binary files a/web/public/fonts/Poppins/Poppins-ThinItalic.woff2 and /dev/null differ diff --git a/web/public/fonts/Roboto/Roboto-Black.woff2 b/web/public/fonts/Roboto/Roboto-Black.woff2 deleted file mode 100644 index beeec681b..000000000 Binary files a/web/public/fonts/Roboto/Roboto-Black.woff2 and /dev/null differ diff --git a/web/public/fonts/Roboto/Roboto-BlackItalic.woff2 b/web/public/fonts/Roboto/Roboto-BlackItalic.woff2 deleted file mode 100644 index 2ceeea8ad..000000000 Binary files a/web/public/fonts/Roboto/Roboto-BlackItalic.woff2 and /dev/null differ diff --git a/web/public/fonts/Roboto/Roboto-Bold.woff2 b/web/public/fonts/Roboto/Roboto-Bold.woff2 deleted file mode 100644 index b102004e8..000000000 Binary files a/web/public/fonts/Roboto/Roboto-Bold.woff2 and /dev/null differ diff --git a/web/public/fonts/Roboto/Roboto-BoldItalic.woff2 b/web/public/fonts/Roboto/Roboto-BoldItalic.woff2 deleted file mode 100644 index 9a46768a5..000000000 Binary files a/web/public/fonts/Roboto/Roboto-BoldItalic.woff2 and /dev/null differ diff --git a/web/public/fonts/Roboto/Roboto-Italic.woff2 b/web/public/fonts/Roboto/Roboto-Italic.woff2 deleted file mode 100644 index 3d60e7489..000000000 Binary files a/web/public/fonts/Roboto/Roboto-Italic.woff2 and /dev/null differ diff --git a/web/public/fonts/Roboto/Roboto-Light.woff2 b/web/public/fonts/Roboto/Roboto-Light.woff2 deleted file mode 100644 index 3e13c5f31..000000000 Binary files a/web/public/fonts/Roboto/Roboto-Light.woff2 and /dev/null differ diff --git a/web/public/fonts/Roboto/Roboto-LightItalic.woff2 b/web/public/fonts/Roboto/Roboto-LightItalic.woff2 deleted file mode 100644 index a0238dfea..000000000 Binary files a/web/public/fonts/Roboto/Roboto-LightItalic.woff2 and /dev/null differ diff --git a/web/public/fonts/Roboto/Roboto-Medium.woff2 b/web/public/fonts/Roboto/Roboto-Medium.woff2 deleted file mode 100644 index 8b1aebb23..000000000 Binary files a/web/public/fonts/Roboto/Roboto-Medium.woff2 and /dev/null differ diff --git a/web/public/fonts/Roboto/Roboto-MediumItalic.woff2 b/web/public/fonts/Roboto/Roboto-MediumItalic.woff2 deleted file mode 100644 index 1cbf304c4..000000000 Binary files a/web/public/fonts/Roboto/Roboto-MediumItalic.woff2 and /dev/null differ diff --git a/web/public/fonts/Roboto/Roboto-Regular.woff2 b/web/public/fonts/Roboto/Roboto-Regular.woff2 deleted file mode 100644 index 0aa90fc17..000000000 Binary files a/web/public/fonts/Roboto/Roboto-Regular.woff2 and /dev/null differ diff --git a/web/public/fonts/Roboto/Roboto-Thin.woff2 b/web/public/fonts/Roboto/Roboto-Thin.woff2 deleted file mode 100644 index 85c029e11..000000000 Binary files a/web/public/fonts/Roboto/Roboto-Thin.woff2 and /dev/null differ diff --git a/web/public/fonts/Roboto/Roboto-ThinItalic.woff2 b/web/public/fonts/Roboto/Roboto-ThinItalic.woff2 deleted file mode 100644 index f698c77d4..000000000 Binary files a/web/public/fonts/Roboto/Roboto-ThinItalic.woff2 and /dev/null differ diff --git a/web/public/fonts/SourceCodePro/SourceCodePro-Italic.woff2 b/web/public/fonts/SourceCodePro/SourceCodePro-Italic.woff2 deleted file mode 100644 index 6f9d8a73f..000000000 Binary files a/web/public/fonts/SourceCodePro/SourceCodePro-Italic.woff2 and /dev/null differ diff --git a/web/public/fonts/SourceCodePro/SourceCodePro-Regular.woff2 b/web/public/fonts/SourceCodePro/SourceCodePro-Regular.woff2 deleted file mode 100644 index 3f5cf9e08..000000000 Binary files a/web/public/fonts/SourceCodePro/SourceCodePro-Regular.woff2 and /dev/null differ diff --git a/web/public/fonts/geist/Geist-Bold.woff2 b/web/public/fonts/geist/Geist-Bold.woff2 new file mode 100644 index 000000000..46f524f4f Binary files /dev/null and b/web/public/fonts/geist/Geist-Bold.woff2 differ diff --git a/web/public/fonts/geist/Geist-BoldItalic.woff2 b/web/public/fonts/geist/Geist-BoldItalic.woff2 new file mode 100644 index 000000000..240acb99e Binary files /dev/null and b/web/public/fonts/geist/Geist-BoldItalic.woff2 differ diff --git a/web/public/fonts/geist/Geist-Medium.woff2 b/web/public/fonts/geist/Geist-Medium.woff2 new file mode 100644 index 000000000..ef6dbb211 Binary files /dev/null and b/web/public/fonts/geist/Geist-Medium.woff2 differ diff --git a/web/public/fonts/geist/Geist-MediumItalic.woff2 b/web/public/fonts/geist/Geist-MediumItalic.woff2 new file mode 100644 index 000000000..344fedaf9 Binary files /dev/null and b/web/public/fonts/geist/Geist-MediumItalic.woff2 differ diff --git a/web/public/fonts/geist/Geist-Regular.woff2 b/web/public/fonts/geist/Geist-Regular.woff2 new file mode 100644 index 000000000..0db0f1943 Binary files /dev/null and b/web/public/fonts/geist/Geist-Regular.woff2 differ diff --git a/web/public/fonts/geist/Geist-RegularItalic.woff2 b/web/public/fonts/geist/Geist-RegularItalic.woff2 new file mode 100644 index 000000000..33e9948be Binary files /dev/null and b/web/public/fonts/geist/Geist-RegularItalic.woff2 differ diff --git a/web/public/fonts/geist/Geist-SemiBold.woff2 b/web/public/fonts/geist/Geist-SemiBold.woff2 new file mode 100644 index 000000000..838452303 Binary files /dev/null and b/web/public/fonts/geist/Geist-SemiBold.woff2 differ diff --git a/web/public/fonts/geist/Geist-SemiBoldItalic.woff2 b/web/public/fonts/geist/Geist-SemiBoldItalic.woff2 new file mode 100644 index 000000000..2f53ced42 Binary files /dev/null and b/web/public/fonts/geist/Geist-SemiBoldItalic.woff2 differ diff --git a/web/public/fonts/source_code_pro/SourceCodePro-Regular.woff2 b/web/public/fonts/source_code_pro/SourceCodePro-Regular.woff2 new file mode 100644 index 000000000..40826f1a6 Binary files /dev/null and b/web/public/fonts/source_code_pro/SourceCodePro-Regular.woff2 differ diff --git a/web/src/app/App.tsx b/web/src/app/App.tsx new file mode 100644 index 000000000..46956be78 --- /dev/null +++ b/web/src/app/App.tsx @@ -0,0 +1,17 @@ +import './day'; + +import { QueryClientProvider } from '@tanstack/react-query'; +import { RouterProvider } from '@tanstack/react-router'; +import { AppThemeProvider } from '../shared/providers/AppThemeProvider'; +import { queryClient } from './query'; +import { router } from './router'; + +export const App = () => { + return ( + + + + + + ); +}; diff --git a/web/src/app/DefaultNotFound.tsx b/web/src/app/DefaultNotFound.tsx new file mode 100644 index 000000000..66d34b11e --- /dev/null +++ b/web/src/app/DefaultNotFound.tsx @@ -0,0 +1,5 @@ +import { Navigate } from '@tanstack/react-router'; + +export const DefaultNotFound = () => { + return ; +}; diff --git a/web/src/app/day.ts b/web/src/app/day.ts new file mode 100644 index 000000000..8a2552ea8 --- /dev/null +++ b/web/src/app/day.ts @@ -0,0 +1,4 @@ +import dayjs from 'dayjs'; +import utc from 'dayjs/plugin/utc'; + +dayjs.extend(utc); diff --git a/web/src/app/query.ts b/web/src/app/query.ts new file mode 100644 index 000000000..59dd0d6c1 --- /dev/null +++ b/web/src/app/query.ts @@ -0,0 +1,40 @@ +import { MutationCache, QueryClient, type QueryKey } from '@tanstack/react-query'; + +type InvalidateMeta = { invalidate?: QueryKey[] | QueryKey }; + +let queryClient: QueryClient; + +type RO = readonly unknown[]; + +const isArrayFlat = (arr: RO | readonly RO[]): boolean => + arr.every((item) => !Array.isArray(item)); + +const mutationCache = new MutationCache({ + onSuccess: async (_data, _variables, _context, mutation) => { + const keys = (mutation.meta as InvalidateMeta | undefined)?.invalidate; + if (!Array.isArray(keys) || keys.length === 0) return; + if (isArrayFlat(keys)) { + await queryClient.invalidateQueries({ queryKey: keys }); + } else { + await Promise.all( + keys.map((key) => queryClient.invalidateQueries({ queryKey: key as QueryKey })), + ); + } + }, +}); + +queryClient = new QueryClient({ + mutationCache, + defaultOptions: { + queries: { + staleTime: Infinity, + refetchOnWindowFocus: false, + refetchOnReconnect: false, + retry: false, + // @ts-expect-error + placeholderData: (perv) => perv, + }, + }, +}); + +export { queryClient }; diff --git a/web/src/app/router.ts b/web/src/app/router.ts new file mode 100644 index 000000000..5bd1fde9e --- /dev/null +++ b/web/src/app/router.ts @@ -0,0 +1,19 @@ +import { createRouter } from '@tanstack/react-router'; +import { routeTree } from '../routeTree.gen'; +import { DefaultNotFound } from './DefaultNotFound'; +import { queryClient } from './query'; + +export const router = createRouter({ + routeTree, + defaultPreloadStaleTime: 0, + defaultNotFoundComponent: DefaultNotFound, + context: { + queryClient, + }, +}); + +declare module '@tanstack/react-router' { + interface Register { + router: typeof router; + } +} diff --git a/web/src/components/App/App.tsx b/web/src/components/App/App.tsx deleted file mode 100644 index 1f05e4b30..000000000 --- a/web/src/components/App/App.tsx +++ /dev/null @@ -1,216 +0,0 @@ -import 'react-loading-skeleton/dist/skeleton.css'; - -import { Navigate, Route, BrowserRouter as Router, Routes } from 'react-router-dom'; - -import { AclRoutes } from '../../pages/acl/AclRoutes'; -import { ActivityLogPage } from '../../pages/activity-log/ActivityLogPage'; -import { AddDevicePage } from '../../pages/addDevice/AddDevicePage'; -import { OpenidAllowPage } from '../../pages/allow/OpenidAllowPage'; -import { AuthPage } from '../../pages/auth/AuthPage'; -import { DevicesPage } from '../../pages/devices/DevicesPage'; -import { EnrollmentPage } from '../../pages/enrollment/EnrollmentPage'; -import { GroupsPage } from '../../pages/groups/GroupsPage'; -import { NetworkPage } from '../../pages/network/NetworkPage'; -import { OpenidClientsListPage } from '../../pages/openid/OpenidClientsListPage/OpenidClientsListPage'; -import { OverviewPage } from '../../pages/overview/OverviewPage'; -import { OverviewIndexPage } from '../../pages/overview-index/OverviewIndexPage'; -import { ProvisionersPage } from '../../pages/provisioners/ProvisionersPage'; -import { SettingsPage } from '../../pages/settings/SettingsPage'; -import { SupportPage } from '../../pages/support/SupportPage'; -import { UserProfile } from '../../pages/users/UserProfile/UserProfile'; -import { UsersPage } from '../../pages/users/UsersPage'; -import { UsersSharedModals } from '../../pages/users/UsersSharedModals'; -import { WebhooksListPage } from '../../pages/webhooks/WebhooksListPage'; -import { WizardPage } from '../../pages/wizard/WizardPage'; -import { PageContainer } from '../../shared/components/Layout/PageContainer/PageContainer'; -import { UpgradeLicenseModal } from '../../shared/components/Layout/UpgradeLicenseModal/UpgradeLicenseModal'; -import { OutdatedComponentsModal } from '../../shared/components/modals/OutdatedComponentsModal/OutdatedComponentsModal'; -import { UpdateNotificationModal } from '../../shared/components/modals/UpdateNotificationModal/UpdateNotificationModal'; -import { ProtectedRoute } from '../../shared/components/Router/Guards/ProtectedRoute/ProtectedRoute'; -import { ToastManager } from '../../shared/defguard-ui/components/Layout/ToastManager/ToastManager'; -import { useAuthStore } from '../../shared/hooks/store/useAuthStore'; -import { Navigation } from '../Navigation/Navigation'; - -const App = () => { - const currentUser = useAuthStore((state) => state.user); - const isAdmin = useAuthStore((state) => state.user?.is_admin); - return ( - <> -
- - - - - - } - /> - - - - } - /> - } /> - - } /> - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - - - - } - /> - } /> - - - - - } - /> - - - - - - - } - /> - - - - } - /> - - ) : ( - - ) - } - /> - - - - - - -
- - - ); -}; - -export default App; diff --git a/web/src/components/AppLoader.tsx b/web/src/components/AppLoader.tsx deleted file mode 100644 index dd78829b1..000000000 --- a/web/src/components/AppLoader.tsx +++ /dev/null @@ -1,171 +0,0 @@ -import { useQuery } from '@tanstack/react-query'; -import { isUndefined } from 'lodash-es'; -import { lazy, Suspense, useEffect } from 'react'; -import { shallow } from 'zustand/shallow'; - -import { useI18nContext } from '../i18n/i18n-react'; -import { LoaderPage } from '../pages/loader/LoaderPage'; -import { useOutdatedComponentsModal } from '../shared/components/modals/OutdatedComponentsModal/useOutdatedComponentsModal'; -import { useToaster } from '../shared/defguard-ui/hooks/toasts/useToaster'; -import { isPresent } from '../shared/defguard-ui/utils/isPresent'; -import { useAppStore } from '../shared/hooks/store/useAppStore'; -import { useAuthStore } from '../shared/hooks/store/useAuthStore'; -import { useUpdatesStore } from '../shared/hooks/store/useUpdatesStore'; -import useApi from '../shared/hooks/useApi'; -import { QueryKeys } from '../shared/queries'; - -/** - * Fetches data needed by app before it's rendered. - * **/ -export const AppLoader = () => { - const toaster = useToaster(); - const [currentUser, resetAuthState, setAuthState] = useAuthStore( - (state) => [state.user, state.resetState, state.setState], - shallow, - ); - const appSettings = useAppStore((state) => state.settings); - const { - getAppInfo, - getNewVersion, - getOutdatedInfo, - user: { getMe }, - settings: { getEssentialSettings, getEnterpriseSettings }, - } = useApi(); - const setAppStore = useAppStore((state) => state.setState); - const { LL } = useI18nContext(); - const setUpdateStore = useUpdatesStore((s) => s.setUpdate); - const openOutdatedComponentsModal = useOutdatedComponentsModal((s) => s.open); - - const { data: outdatedInfo } = useQuery({ - queryFn: getOutdatedInfo, - queryKey: ['outdated'], - enabled: isPresent(currentUser) && currentUser.is_admin, - refetchOnWindowFocus: false, - refetchOnMount: true, - }); - - const { - data: meData, - isLoading: userLoading, - error: meFetchError, - } = useQuery({ - queryFn: getMe, - queryKey: [QueryKeys.FETCH_ME], - refetchOnMount: true, - refetchOnWindowFocus: false, - retry: false, - }); - - // biome-ignore lint/correctness/useExhaustiveDependencies: sideEffect - useEffect(() => { - if (meFetchError && currentUser) { - if (currentUser) { - resetAuthState(); - } - } - }, [meFetchError]); - - useEffect(() => { - if (meData) { - setAuthState({ user: meData }); - } - }, [meData, setAuthState]); - - const { data: appInfoData, error: appInfoError } = useQuery({ - queryFn: getAppInfo, - queryKey: [QueryKeys.FETCH_APP_INFO], - refetchOnWindowFocus: true, - refetchOnMount: true, - enabled: !isUndefined(currentUser), - }); - - // biome-ignore lint/correctness/useExhaustiveDependencies: sideEffect - useEffect(() => { - if (appInfoError) { - toaster.error(LL.messages.errorVersion()); - console.error(appInfoError); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [appInfoError]); - - useEffect(() => { - if (appInfoData) { - setAppStore({ appInfo: appInfoData }); - } - }, [appInfoData, setAppStore]); - - const { data: enterpriseSettingsData, error: enterpriseSettingsError } = useQuery({ - queryFn: getEnterpriseSettings, - queryKey: [QueryKeys.FETCH_ENTERPRISE_SETTINGS], - refetchOnWindowFocus: true, - retry: false, - enabled: !isUndefined(currentUser), - }); - - useEffect(() => { - if (enterpriseSettingsError) { - console.error(enterpriseSettingsError); - } - }, [enterpriseSettingsError]); - - useEffect(() => { - setAppStore({ enterprise_settings: enterpriseSettingsData }); - }, [setAppStore, enterpriseSettingsData]); - - const { isLoading: settingsLoading, data: essentialSettings } = useQuery({ - queryFn: getEssentialSettings, - queryKey: [QueryKeys.FETCH_ESSENTIAL_SETTINGS], - refetchOnMount: true, - }); - - // setAppSettings - useEffect(() => { - if (essentialSettings) { - if (document.title !== essentialSettings.instance_name) { - document.title = essentialSettings.instance_name; - } - setAppStore({ settings: essentialSettings }); - } - }, [essentialSettings, setAppStore]); - - const { data: newVersionData, error: newVersionError } = useQuery({ - queryFn: getNewVersion, - queryKey: [QueryKeys.FETCH_NEW_VERSION], - refetchOnWindowFocus: false, - refetchOnMount: true, - enabled: !isUndefined(currentUser) && currentUser.is_admin, - }); - - useEffect(() => { - if (newVersionError) { - console.error(newVersionError); - } - }, [newVersionError]); - - useEffect(() => { - if (newVersionData) { - setUpdateStore(newVersionData); - } - }, [newVersionData, setUpdateStore]); - - useEffect(() => { - if ( - outdatedInfo && - (outdatedInfo.proxy != null || outdatedInfo.gateways.length > 0) - ) { - openOutdatedComponentsModal(outdatedInfo); - } - }, [outdatedInfo, openOutdatedComponentsModal]); - - if (userLoading || (settingsLoading && isUndefined(appSettings))) { - return ; - } - - return ( - }> - - - ); -}; - -const App = lazy(() => import('./App/App')); diff --git a/web/src/components/I18nProvider.tsx b/web/src/components/I18nProvider.tsx deleted file mode 100644 index 23b54ed28..000000000 --- a/web/src/components/I18nProvider.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import { enUS as datePickerLocaleEnUS } from 'date-fns/locale/en-US'; -import { ko as datePickerLocaleKO } from 'date-fns/locale/ko'; -import { pl as datePickerLocalePL } from 'date-fns/locale/pl'; -import { type PropsWithChildren, useEffect, useState } from 'react'; -import { registerLocale, setDefaultLocale } from 'react-datepicker'; -import { navigatorDetector } from 'typesafe-i18n/detectors'; -import { shallow } from 'zustand/shallow'; - -import TypesafeI18n from '../i18n/i18n-react'; -import { baseLocale, detectLocale } from '../i18n/i18n-util'; -import { loadLocale } from '../i18n/i18n-util.sync'; -import { useAppStore } from '../shared/hooks/store/useAppStore'; -import { localeToDatePicker } from '../shared/utils/localeToDatepicker'; - -// Setups i18n so useI18nContext hooks can work -export const I18nProvider = ({ children }: PropsWithChildren) => { - const setAppState = useAppStore((s) => s.setState, shallow); - const detectedLocale = detectLocale(navigatorDetector); - const [localeLoaded, setLocaleLoaded] = useState(false); - - // biome-ignore lint/correctness/useExhaustiveDependencies: migration - useEffect(() => { - const lang = detectedLocale ?? baseLocale; - loadLocale(lang); - setLocaleLoaded(true); - setAppState({ language: lang }); - document.documentElement.lang = lang; - //react-datepicker - switch (lang) { - case 'en': - registerLocale('en-US', datePickerLocaleEnUS); - break; - case 'ko': - registerLocale('ko', datePickerLocaleKO); - break; - case 'pl': - registerLocale('pl', datePickerLocalePL); - } - setDefaultLocale(localeToDatePicker(lang)); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [detectedLocale]); - - if (!localeLoaded) return null; - - return {children}; -}; diff --git a/web/src/components/Navigation/Navigation.tsx b/web/src/components/Navigation/Navigation.tsx deleted file mode 100644 index 4f67f60b0..000000000 --- a/web/src/components/Navigation/Navigation.tsx +++ /dev/null @@ -1,267 +0,0 @@ -import './style.scss'; - -import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; -import { useMemo } from 'react'; -import { useLocation } from 'react-router'; -import { useBreakpoint } from 'use-breakpoint'; -import { shallow } from 'zustand/shallow'; - -import { useI18nContext } from '../../i18n/i18n-react'; -import SvgIconNavGroups from '../../shared/components/svg/IconNavGroups'; -import SvgIconNavKey from '../../shared/components/svg/IconNavKey'; -import SvgIconNavOpenId from '../../shared/components/svg/IconNavOpenid'; -import SvgIconNavProfile from '../../shared/components/svg/IconNavProfile'; -import SvgIconNavProvisioners from '../../shared/components/svg/IconNavProvisioners'; -import SvgIconNavSettings from '../../shared/components/svg/IconNavSettings'; -import SvgIconNavSupport from '../../shared/components/svg/IconNavSupport'; -import SvgIconNavUsers from '../../shared/components/svg/IconNavUsers'; -import SvgIconNavVpn from '../../shared/components/svg/IconNavVpn'; -import SvgIconNavWebhooks from '../../shared/components/svg/IconNavWebhooks'; -import { deviceBreakpoints } from '../../shared/constants'; -import { useAppStore } from '../../shared/hooks/store/useAppStore'; -import { useAuthStore } from '../../shared/hooks/store/useAuthStore'; -import { useUserProfileStore } from '../../shared/hooks/store/useUserProfileStore'; -import useApi from '../../shared/hooks/useApi'; -import { QueryKeys } from '../../shared/queries'; -import type { User } from '../../shared/types'; -import { invalidateMultipleQueries } from '../../shared/utils/invalidateMultipleQueries'; -import { DevicePageNavigationIcon } from './components/DevicesPageNavigationIcon'; -import { NavigationActivityLogPageIcon } from './components/icons/NavigationActivityLogPageIcon'; -import { NavigationDesktop } from './components/NavigationDesktop/NavigationDesktop'; -import { NavigationMobile } from './components/NavigationMobile/NavigationMobile'; -import { navigationExcludedRoutes } from './config'; -import { useNavigationStore } from './hooks/useNavigationStore'; -import type { NavigationItem, NavigationItems } from './types'; - -export const Navigation = () => { - const { pathname } = useLocation(); - const { LL } = useI18nContext(); - const [currentUser, resetAuthStore] = useAuthStore( - (state) => [state.user, state.resetState], - shallow, - ); - const setStore = useNavigationStore((state) => state.setState); - const networksPresent = useAppStore((state) => state.appInfo?.network_present); - const resetUserProfile = useUserProfileStore((state) => state.reset); - const queryClient = useQueryClient(); - const isAdmin = useAuthStore((s) => s.user?.is_admin ?? false); - - const { - auth: { logout }, - network: { getNetworks }, - } = useApi(); - - const { data: networks } = useQuery({ - queryKey: ['network'], - queryFn: getNetworks, - enabled: isAdmin, - }); - - const onlyOneNetworkPresent = useMemo(() => { - if (networks) { - return networks.length === 1; - } - return false; - }, [networks]); - - const { mutate: logOutMutation } = useMutation({ - mutationFn: logout, - onSuccess: () => { - resetAuthStore(); - resetUserProfile(); - setStore({ isOpen: false }); - }, - }); - - const settings = useAppStore((state) => state.settings); - const { breakpoint } = useBreakpoint(deviceBreakpoints); - - const navItems = useMemo((): NavigationItems => { - if (!currentUser) { - return { - middle: [], - bottom: [], - }; - } - - let overviewLink = '/admin/overview'; - - if (!networksPresent) { - overviewLink = '/admin/overview'; - } - - if (networks && onlyOneNetworkPresent) { - const networkId = networks[0].id; - overviewLink = `/admin/overview/${networkId}`; - } - - let bottom: NavigationItem[] = [ - { - title: LL.navigation.bar.settings(), - linkPath: '/admin/settings', - icon: , - adminOnly: true, - enabled: true, - }, - { - title: LL.navigation.bar.support(), - icon: , - linkPath: '/support', - adminOnly: false, - enabled: true, - className: 'support', - }, - ]; - let middle: NavigationItem[] = [ - { - title: LL.navigation.bar.overview(), - linkPath: overviewLink, - icon: , - adminOnly: true, - enabled: settings?.wireguard_enabled, - }, - { - title: LL.navigation.bar.users(), - linkPath: '/admin/users', - icon: , - adminOnly: true, - enabled: true, - }, - { - title: LL.navigation.bar.groups(), - linkPath: '/admin/groups', - icon: , - adminOnly: true, - enabled: true, - }, - { - title: LL.navigation.bar.acl(), - linkPath: '/admin/acl', - icon: ( - - - - ), - adminOnly: true, - enabled: true, - enterpriseOnly: true, - }, - { - title: LL.navigation.bar.devices(), - linkPath: '/admin/devices', - icon: , - adminOnly: true, - enabled: true, - }, - { - title: LL.navigation.bar.openId(), - linkPath: '/admin/openid', - icon: , - adminOnly: true, - enabled: settings?.openid_enabled, - }, - { - title: LL.navigation.bar.webhooks(), - linkPath: '/admin/webhooks', - icon: , - adminOnly: true, - enabled: settings?.webhooks_enabled, - }, - { - title: LL.navigation.bar.provisioners(), - linkPath: '/admin/provisioners', - icon: , - adminOnly: true, - enabled: settings?.worker_enabled, - }, - { - title: LL.navigation.bar.enrollment(), - linkPath: '/admin/enrollment', - icon: , - adminOnly: true, - enabled: true, - }, - { - title: LL.navigation.bar.activity(), - linkPath: '/activity', - icon: , - adminOnly: false, - enabled: true, - }, - { - title: LL.navigation.bar.myProfile(), - linkPath: `/me`, - icon: , - adminOnly: false, - enabled: true, - onClick: () => { - invalidateMultipleQueries(queryClient, [ - [QueryKeys.FETCH_ME], - [QueryKeys.FETCH_USER_PROFILE], - ]); - }, - }, - ]; - middle = filterNavItems(middle, currentUser); - bottom = filterNavItems(bottom, currentUser); - return { - middle, - bottom, - }; - }, [ - LL.navigation.bar, - currentUser, - networks, - networksPresent, - onlyOneNetworkPresent, - queryClient, - settings?.openid_enabled, - settings?.webhooks_enabled, - settings?.wireguard_enabled, - settings?.worker_enabled, - ]); - - const renderNav = useMemo(() => { - for (const path of navigationExcludedRoutes) { - if (pathname.includes(path)) { - return false; - } - } - return true; - }, [pathname]); - - if (!renderNav) return null; - - return ( - <> - {breakpoint === 'desktop' && ( - logOutMutation()} /> - )} - {breakpoint !== 'desktop' && ( - logOutMutation()} /> - )} - - ); -}; - -const filterNavItems = (items: NavigationItem[], currentUser: User): NavigationItem[] => - items - .filter((item) => item.enabled) - .filter((item) => { - if (item.adminOnly) { - return currentUser ? currentUser.is_admin : false; - } else { - return true; - } - }); diff --git a/web/src/components/Navigation/components/ApplicationVersion/ApplicationVersion.tsx b/web/src/components/Navigation/components/ApplicationVersion/ApplicationVersion.tsx deleted file mode 100644 index 462f6f668..000000000 --- a/web/src/components/Navigation/components/ApplicationVersion/ApplicationVersion.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import './style.scss'; - -import { useI18nContext } from '../../../../i18n/i18n-react'; -import { useAppStore } from '../../../../shared/hooks/store/useAppStore'; - -type Props = { - isOpen: boolean; -}; - -export const ApplicationVersion = ({ isOpen }: Props) => { - const version = useAppStore((store) => store.appInfo?.version); - const { LL } = useI18nContext(); - - return ( - - ); -}; diff --git a/web/src/components/Navigation/components/ApplicationVersion/style.scss b/web/src/components/Navigation/components/ApplicationVersion/style.scss deleted file mode 100644 index e0dbbd316..000000000 --- a/web/src/components/Navigation/components/ApplicationVersion/style.scss +++ /dev/null @@ -1,30 +0,0 @@ -@use '@scssutils' as *; - -.app-version { - display: flex; - flex-flow: column; - width: 100%; - align-items: center; - justify-content: center; - align-content: center; - box-sizing: border-box; - min-height: 60px; - max-width: 100%; - overflow: hidden; - - span, - a { - @include typography-legacy(10px, 13px, regular, var(--gray-light)); - display: inline-block; - } - - p { - @include typography-legacy(10px, 13px, regular, var(--gray-light)); - } - - a { - text-wrap: auto; - word-break: break-word; - text-align: center; - } -} diff --git a/web/src/components/Navigation/components/DevicesPageNavigationIcon.tsx b/web/src/components/Navigation/components/DevicesPageNavigationIcon.tsx deleted file mode 100644 index 5f16eb874..000000000 --- a/web/src/components/Navigation/components/DevicesPageNavigationIcon.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import { useId } from 'react'; - -export const DevicePageNavigationIcon = () => { - const id = useId(); - return ( - - - - - - - - - - - - ); -}; diff --git a/web/src/components/Navigation/components/NavigationBar/NavigationBar.tsx b/web/src/components/Navigation/components/NavigationBar/NavigationBar.tsx deleted file mode 100644 index 2ecee40ac..000000000 --- a/web/src/components/Navigation/components/NavigationBar/NavigationBar.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import './style.scss'; - -import classNames from 'classnames'; -import clsx from 'clsx'; -import { useMemo } from 'react'; - -import { useI18nContext } from '../../../../i18n/i18n-react'; -import SvgDefguardNavLogoCollapsed from '../../../../shared/components/svg/DefguardNavLogoCollapsed'; -import SvgIconNavLogout from '../../../../shared/components/svg/IconNavLogout'; -import { useAppStore } from '../../../../shared/hooks/store/useAppStore'; -import type { NavigationItems } from '../../types'; -import { ApplicationVersion } from '../ApplicationVersion/ApplicationVersion'; -import { NavigationLink } from '../NavigationLink/NavigationLink'; - -type Props = { - navItems: NavigationItems; - onLogout: () => void; - isOpen: boolean; -}; - -export const NavigationBar = ({ navItems, onLogout, isOpen }: Props) => { - const settings = useAppStore((state) => state.settings); - const { LL } = useI18nContext(); - - const cn = useMemo( - () => - classNames('nav-bar', { - open: isOpen, - }), - [isOpen], - ); - - return ( - - ); -}; diff --git a/web/src/components/Navigation/components/NavigationBar/style.scss b/web/src/components/Navigation/components/NavigationBar/style.scss deleted file mode 100644 index 233f358d8..000000000 --- a/web/src/components/Navigation/components/NavigationBar/style.scss +++ /dev/null @@ -1,123 +0,0 @@ -.nav-bar { - display: grid; - grid-template-rows: 108px 1fr 70px; - grid-template-columns: 1fr; - height: 100%; - width: 88px; - position: fixed; - inset: 0; - overflow-x: hidden; - overflow-y: auto; - max-height: 100%; - background-color: var(--white); - box-sizing: border-box; - border-right: 1px solid var(--gray-border); - - &::-webkit-scrollbar { - display: none; - } - - -ms-overflow-style: none; - scrollbar-width: none; - - .logo-container { - user-select: none; - height: 100%; - width: 100%; - display: flex; - flex-flow: row nowrap; - overflow: hidden; - align-items: center; - justify-content: center; - box-sizing: border-box; - border-bottom: 1px solid var(--gray-border); - } - - &.open { - width: 230px; - } - - .links { - border-bottom: 1px solid var(--gray-border); - width: 100%; - box-sizing: border-box; - display: grid; - grid-template-rows: 1fr auto; - - .middle, - .bottom { - width: 100%; - } - - .middle { - display: flex; - flex-flow: column; - height: 100%; - align-items: center; - justify-content: center; - } - - a, - .log-out { - user-select: none; - position: relative; - display: grid; - grid-template-rows: 1fr; - align-items: center; - justify-items: start; - width: 100%; - column-gap: 18px; - box-sizing: border-box; - padding: var(--spacing-xs) var(--spacing-s); - max-width: 100%; - overflow: hidden; - background-color: transparent; - border: 0px solid transparent; - text-decoration: none; - cursor: pointer; - color: var(--text-body-tertiary); - grid-template-columns: 24px 1fr; - - &.compact { - grid-template-columns: 1fr; - align-items: center; - justify-items: center; - } - - & > span { - @include typography(app-side-bar); - color: inherit; - overflow: hidden; - max-width: 100%; - display: inline-block; - word-break: normal; - text-wrap: wrap; - text-align: left; - } - - &:hover, - &.active { - color: var(--text-main); - - & > svg { - g, - path, - rect { - fill: var(--primary); - } - } - } - - & > .active-line { - content: ' '; - display: block; - height: 100%; - width: 2px; - background-color: var(--primary); - position: absolute; - right: 0; - top: 0; - } - } - } -} diff --git a/web/src/components/Navigation/components/NavigationDesktop/NavigationCollapse/NavigationCollapse.tsx b/web/src/components/Navigation/components/NavigationDesktop/NavigationCollapse/NavigationCollapse.tsx deleted file mode 100644 index 5eb8dcafe..000000000 --- a/web/src/components/Navigation/components/NavigationDesktop/NavigationCollapse/NavigationCollapse.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import './style.scss'; - -import classNames from 'classnames'; -import { motion, type TargetAndTransition } from 'motion/react'; -import { useMemo, useState } from 'react'; - -import { ColorsRGB } from '../../../../../shared/constants'; -import { useNavigationStore } from '../../../hooks/useNavigationStore'; - -export const NavigationCollapse = () => { - const [hovered, setHovered] = useState(false); - const isOpen = useNavigationStore((state) => state.isOpen); - const setState = useNavigationStore((state) => state.setState); - const getAnimate = useMemo((): TargetAndTransition => { - const res: TargetAndTransition = { - borderColor: ColorsRGB.GrayBorder, - backgroundColor: ColorsRGB.White, - }; - if (hovered) { - res.borderColor = ColorsRGB.Primary; - res.backgroundColor = ColorsRGB.Primary; - } - return res; - }, [hovered]); - - const cn = classNames('navigation-collapse', { - open: isOpen, - }); - - return ( - setState({ isOpen: !isOpen })} - animate={getAnimate} - initial={false} - onMouseEnter={() => setHovered(true)} - onMouseLeave={() => setHovered(false)} - > - - - - - - - - ); -}; diff --git a/web/src/components/Navigation/components/NavigationDesktop/NavigationCollapse/style.scss b/web/src/components/Navigation/components/NavigationDesktop/NavigationCollapse/style.scss deleted file mode 100644 index 266779740..000000000 --- a/web/src/components/Navigation/components/NavigationDesktop/NavigationCollapse/style.scss +++ /dev/null @@ -1,31 +0,0 @@ -@use '@scssutils' as *; - -.navigation-collapse { - width: 30px; - height: 30px; - box-sizing: border-box; - border: 1px solid var(--gray-border); - border-radius: 10px; - display: flex; - flex-flow: row nowrap; - overflow: hidden; - align-items: center; - justify-content: center; - cursor: pointer; - position: fixed; - top: 40px; - left: 73px; - background-color: var(--white); - z-index: 3; - padding: 0; - margin: 0; - - &.open { - left: 215px; - } - - &:hover { - border-color: var(--primary); - background-color: var(--primary); - } -} diff --git a/web/src/components/Navigation/components/NavigationDesktop/NavigationDesktop.tsx b/web/src/components/Navigation/components/NavigationDesktop/NavigationDesktop.tsx deleted file mode 100644 index 2509cd77d..000000000 --- a/web/src/components/Navigation/components/NavigationDesktop/NavigationDesktop.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { useNavigationStore } from '../../hooks/useNavigationStore'; -import type { NavigationItems } from '../../types'; -import { NavigationBar } from '../NavigationBar/NavigationBar'; -import { NavigationCollapse } from './NavigationCollapse/NavigationCollapse'; - -type Props = { - navItems: NavigationItems; - onLogout: () => void; -}; - -export const NavigationDesktop = ({ navItems, onLogout }: Props) => { - const isOpen = useNavigationStore((state) => state.isOpen); - return ( - <> - - - - ); -}; diff --git a/web/src/components/Navigation/components/NavigationLink/NavigationLink.tsx b/web/src/components/Navigation/components/NavigationLink/NavigationLink.tsx deleted file mode 100644 index 54f7e7189..000000000 --- a/web/src/components/Navigation/components/NavigationLink/NavigationLink.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import './style.scss'; - -import clsx from 'clsx'; -import { useMatch } from 'react-router'; -import { Link } from 'react-router-dom'; -import { shallow } from 'zustand/shallow'; - -import { useUpgradeLicenseModal } from '../../../../shared/components/Layout/UpgradeLicenseModal/store'; -import { UpgradeLicenseModalVariant } from '../../../../shared/components/Layout/UpgradeLicenseModal/types'; -import { useAppStore } from '../../../../shared/hooks/store/useAppStore'; -import { useNavigationStore } from '../../hooks/useNavigationStore'; -import type { NavigationItem } from '../../types'; - -interface NavigationLinkProps { - item: NavigationItem; - callback?: () => void; -} - -export const NavigationLink = ({ item, callback }: NavigationLinkProps) => { - const isOpen = useNavigationStore((s) => s.isOpen); - const openUpgradeLicenseModal = useUpgradeLicenseModal((s) => s.open, shallow); - const enterpriseEnabled = useAppStore((s) => s.appInfo?.license_info.enterprise); - const match = useMatch(item.linkPath); - - return ( - { - if (item.enterpriseOnly && !enterpriseEnabled) { - event.preventDefault(); - openUpgradeLicenseModal({ - modalVariant: UpgradeLicenseModalVariant.ENTERPRISE_NOTICE, - }); - } - if (callback) { - callback(); - } - if (item.onClick) { - item.onClick(); - } - }} - > - {item.icon} - {isOpen && {item.title}} - {match ?
: null} - - ); -}; diff --git a/web/src/components/Navigation/components/NavigationLink/style.scss b/web/src/components/Navigation/components/NavigationLink/style.scss deleted file mode 100644 index cd1d84e41..000000000 --- a/web/src/components/Navigation/components/NavigationLink/style.scss +++ /dev/null @@ -1,32 +0,0 @@ -.navigation-link { - svg { - rect { - fill: var(--surface-icon-primary); - } - - path { - fill: var(--surface-icon-primary); - } - - circle { - stroke: var(--surface-icon-primary); - } - } - - &:hover, - &.active { - svg { - rect { - fill: var(--surface-main-primary); - } - - path { - fill: var(--surface-main-primary); - } - - circle { - stroke: var(--surface-main-primary); - } - } - } -} diff --git a/web/src/components/Navigation/components/NavigationMobile/MobileNavModal/MobileNavModal.tsx b/web/src/components/Navigation/components/NavigationMobile/MobileNavModal/MobileNavModal.tsx deleted file mode 100644 index 877f27b8c..000000000 --- a/web/src/components/Navigation/components/NavigationMobile/MobileNavModal/MobileNavModal.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import './style.scss'; - -import SvgIconHamburgerClose from '../../../../../shared/components/svg/IconHamburgerClose'; -import { Modal } from '../../../../../shared/defguard-ui/components/Layout/modals/Modal/Modal'; -import { useNavigationStore } from '../../../hooks/useNavigationStore'; -import type { NavigationItems } from '../../../types'; -import { NavigationBar } from '../../NavigationBar/NavigationBar'; - -interface Props { - navItems: NavigationItems; - onLogout: () => void; -} - -export const MobileNavModal = ({ navItems, onLogout }: Props) => { - const setStore = useNavigationStore((state) => state.setState); - const isOpen = useNavigationStore((state) => state.isOpen); - return ( - setStore({ isOpen: val })} - backdrop - > - - - - ); -}; diff --git a/web/src/components/Navigation/components/NavigationMobile/MobileNavModal/style.scss b/web/src/components/Navigation/components/NavigationMobile/MobileNavModal/style.scss deleted file mode 100644 index f92f93ebe..000000000 --- a/web/src/components/Navigation/components/NavigationMobile/MobileNavModal/style.scss +++ /dev/null @@ -1,37 +0,0 @@ -.modal-root { - & > .modal-wrap { - & > .modal { - &.mobile-nav { - height: 100%; - padding: 0; - - .modal-content { - width: 230px; - position: relative; - border-radius: 0; - display: flex; - flex-direction: column; - background-color: transparent; - - .close-mobile-nav { - position: fixed; - left: 240px; - top: 15px; - height: 40px; - width: 40px; - border-radius: 1rem; - box-shadow: none; - border: 0 solid transparent; - background-color: var(--white); - display: flex; - flex-direction: column; - align-content: center; - align-items: center; - justify-content: center; - cursor: pointer; - } - } - } - } - } -} diff --git a/web/src/components/Navigation/components/NavigationMobile/NavigationMobile.tsx b/web/src/components/Navigation/components/NavigationMobile/NavigationMobile.tsx deleted file mode 100644 index 07bd0e0f9..000000000 --- a/web/src/components/Navigation/components/NavigationMobile/NavigationMobile.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import './style.scss'; - -import { useMemo } from 'react'; -import { useLocation } from 'react-router'; - -import { useI18nContext } from '../../../../i18n/i18n-react'; -import SvgDefguardNavLogoCollapsed from '../../../../shared/components/svg/DefguardNavLogoCollapsed'; -import SvgIconNavHamburger from '../../../../shared/components/svg/IconNavHamburger'; -import { useNavigationStore } from '../../hooks/useNavigationStore'; -import type { NavigationItems } from '../../types'; -import { MobileNavModal } from './MobileNavModal/MobileNavModal'; - -type Props = { - onLogout: () => void; - navItems: NavigationItems; -}; - -export const NavigationMobile = ({ navItems, onLogout }: Props) => { - const { LL } = useI18nContext(); - const { pathname } = useLocation(); - const setStore = useNavigationStore((state) => state.setState); - - const titleMap = useMemo( - () => [ - { - path: '/admin/settings', - title: LL.navigation.mobileTitles.settings(), - }, - { - path: '/admin/users', - title: LL.navigation.mobileTitles.users(), - }, - { - path: '/admin/user', - title: LL.navigation.mobileTitles.user(), - }, - { - path: '/admin/me', - title: LL.navigation.mobileTitles.user(), - }, - { - path: '/admin/provisioners', - title: LL.navigation.mobileTitles.provisioners(), - }, - { - path: '/admin/webhooks', - title: LL.navigation.mobileTitles.webhooks(), - }, - { - path: '/admin/wizard', - title: LL.navigation.mobileTitles.wizard(), - }, - { - path: '/admin/activity', - title: LL.navigation.mobileTitles.activity(), - }, - { - path: '/admin/network', - title: LL.navigation.mobileTitles.networkSettings(), - }, - { - path: '/admin/overview', - title: LL.navigation.mobileTitles.overview(), - }, - { - path: '/admin/enrollment', - title: LL.navigation.mobileTitles.enrollment(), - }, - { - path: '/admin/openid', - title: LL.navigation.mobileTitles.openId(), - }, - { - path: '/admin/groups', - title: LL.navigation.mobileTitles.groups(), - }, - { - path: '/admin/devices', - title: LL.navigation.mobileTitles.devices(), - }, - ], - [LL.navigation.mobileTitles], - ); - - const getPageTitle = useMemo(() => { - for (const item of titleMap) { - if (pathname.includes(item.path)) { - return item.title; - } - } - return ''; - }, [pathname, titleMap]); - - return ( - <> - - - - ); -}; diff --git a/web/src/components/Navigation/components/NavigationMobile/style.scss b/web/src/components/Navigation/components/NavigationMobile/style.scss deleted file mode 100644 index 7ee817068..000000000 --- a/web/src/components/Navigation/components/NavigationMobile/style.scss +++ /dev/null @@ -1,39 +0,0 @@ -.nav-mobile { - height: 60px; - position: fixed; - inset: 0; - width: 100%; - box-sizing: border-box; - padding: 0 20px; - display: grid; - grid-template-rows: 1fr; - grid-template-columns: 40px 1fr 40px; - align-items: center; - justify-items: center; - background-color: var(--white); - column-gap: 10px; - - .page-title { - width: 100%; - max-width: 100%; - text-align: center; - user-select: none; - - @include text-overflow-dots; - @include typography-legacy(20px, 1.2, semiBold, var(--text-main), 'Poppins'); - } - - .hamburger { - width: 40px; - height: 40px; - display: flex; - flex-flow: row nowrap; - align-items: center; - justify-content: center; - padding: 0; - margin: 0; - background-color: transparent; - border: 0 solid transparent; - cursor: pointer; - } -} diff --git a/web/src/components/Navigation/components/icons/NavigationActivityLogPageIcon.tsx b/web/src/components/Navigation/components/icons/NavigationActivityLogPageIcon.tsx deleted file mode 100644 index 3cc0b40f3..000000000 --- a/web/src/components/Navigation/components/icons/NavigationActivityLogPageIcon.tsx +++ /dev/null @@ -1,50 +0,0 @@ -export const NavigationActivityLogPageIcon = () => { - return ( - - - - - - - - - - - - ); -}; diff --git a/web/src/components/Navigation/config.ts b/web/src/components/Navigation/config.ts deleted file mode 100644 index a4d02754b..000000000 --- a/web/src/components/Navigation/config.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const navigationExcludedRoutes: string[] = [ - '/auth', - '/authorize', - '/redirect', - '/consent', -]; diff --git a/web/src/components/Navigation/hooks/useNavigationStore.ts b/web/src/components/Navigation/hooks/useNavigationStore.ts deleted file mode 100644 index 2ac18853e..000000000 --- a/web/src/components/Navigation/hooks/useNavigationStore.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { pick } from 'lodash-es'; -import { persist } from 'zustand/middleware'; -import { createWithEqualityFn } from 'zustand/traditional'; - -const defaultState: StoreValues = { - isOpen: false, -}; - -export const useNavigationStore = createWithEqualityFn()( - persist( - (set) => ({ - ...defaultState, - setState: (values) => set((old) => ({ ...old, ...values })), - reset: () => set(defaultState), - }), - { - version: 1.5, - name: 'navigation-store', - partialize: (state) => pick(state, ['isOpen']), - }, - ), - Object.is, -); - -type Store = StoreValues & StoreMethods; - -type StoreValues = { - isOpen: boolean; -}; - -type StoreMethods = { - setState: (values: Partial) => void; - reset: () => void; -}; diff --git a/web/src/components/Navigation/style.scss b/web/src/components/Navigation/style.scss deleted file mode 100644 index 007442690..000000000 --- a/web/src/components/Navigation/style.scss +++ /dev/null @@ -1 +0,0 @@ -@use '@scssutils' as *; diff --git a/web/src/components/Navigation/types.ts b/web/src/components/Navigation/types.ts deleted file mode 100644 index 601945d5d..000000000 --- a/web/src/components/Navigation/types.ts +++ /dev/null @@ -1,20 +0,0 @@ -export interface NavigationItem { - title: string; - linkPath: string; - icon?: React.ReactNode; - adminOnly?: boolean; - enabled: boolean | undefined; - onClick?: () => void; - className?: string; - enterpriseOnly?: boolean; -} - -export type NavigationTitleMapItem = { - path: string; - title: string; -}; - -export type NavigationItems = { - middle: NavigationItem[]; - bottom: NavigationItem[]; -}; diff --git a/web/src/gif.d.ts b/web/src/gif.d.ts deleted file mode 100644 index 3b59542ad..000000000 --- a/web/src/gif.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -declare module '*.gif' { - const val: string; - export default val; -} diff --git a/web/src/i18n/en/index.ts b/web/src/i18n/en/index.ts deleted file mode 100644 index b8a7a4472..000000000 --- a/web/src/i18n/en/index.ts +++ /dev/null @@ -1,2759 +0,0 @@ -import type { BaseTranslation } from '../i18n-types'; - -const en: BaseTranslation = { - common: { - conditions: { - or: 'or', - and: 'and', - equal: 'equal', - }, - controls: { - timeRange: 'Time range', - addNew: 'Add new', - add: 'Add', - accept: 'Accept', - next: 'Next', - back: 'Back', - cancel: 'Cancel', - confirm: 'Confirm', - submit: 'Submit', - close: 'Close', - select: 'Select', - finish: 'Finish', - saveChanges: 'Save changes', - save: 'Save', - RestoreDefault: 'Restore default', - delete: 'Delete', - rename: 'Rename', - copy: 'Copy', - edit: 'Edit', - dismiss: 'Dismiss', - show: 'Show', - enable: 'Enable', - enabled: 'Enabled', - disable: 'Disable', - disabled: 'Disabled', - selectAll: 'Select all', - clear: 'Clear', - clearAll: 'Clear all', - filter: 'Filter', - filters: 'Filters', - }, - key: 'Key', - name: 'Name', - noData: 'No data', - unavailable: 'Unavailable', - notSet: 'Not set', - search: 'Search', - time: 'Time', - from: 'From', - until: 'Until', - }, - messages: { - error: 'Error has occurred.', - success: 'Operation succeeded', - errorVersion: 'Failed to get application version.', - insecureContext: 'Context is not secure.', - details: 'Details:', - clipboard: { - error: 'Clipboard is not accessible.', - success: 'Content copied to clipboard.', - }, - }, - modals: { - outdatedComponentsModal: { - title: 'Version mismatch', - subtitle: 'Defguard detected unsupported version in some components.', - content: { - title: 'Incompatible components:', - unknownVersion: 'Unknown version', - unknownHostname: 'Unknown hostname', - }, - }, - upgradeLicenseModal: { - enterprise: { - title: 'Upgrade to Enterprise', - //md - subTitle: `This functionality is an **enterprise feature** and you've exceeded the user, device or network limits to use it. In order to use this feature, purchase an enterprise license or upgrade your existing one.`, - }, - limit: { - title: 'Upgrade', - //md - subTitle: ` - You have **reached the limit** of this functionality. To **[ manage more locations/users/devices ]** purchase of the Enterprise license is required. - `, - }, - //md - content: ` -You can find out more about features like: -- Real time and automatic client synchronization -- External SSO -- Controlling VPN clients behavior - -Full enterprise feature list: [https://docs.defguard.net/enterprise/enterprise-features](https://docs.defguard.net/enterprise/enterprise-features)
-Licensing information: [https://docs.defguard.net/enterprise/license](https://docs.defguard.net/enterprise/license) - `, - controls: { - cancel: 'Maybe later', - confirm: 'See all Enterprise plans', - }, - }, - standaloneDeviceEnrollmentModal: { - title: 'Network device token', - toasters: { - error: 'Token generation failed.', - }, - }, - standaloneDeviceConfigModal: { - title: 'Network device config', - cardTitle: 'Config', - toasters: { - getConfig: { - error: 'Failed to get device config.', - }, - }, - }, - editStandaloneModal: { - title: 'Edit network device', - toasts: { - success: 'Device modified', - failure: 'Modifying the device failed', - }, - }, - deleteStandaloneDevice: { - title: 'Delete network device', - content: 'Device {name: string} will be deleted.', - messages: { - success: 'Device deleted', - error: 'Failed to remove device.', - }, - }, - addStandaloneDevice: { - toasts: { - deviceCreated: 'Device added', - creationFailed: 'Device could not be added.', - }, - infoBox: { - setup: - 'Here you can add definitions or generate configurations for devices that can connect to your VPN. Only locations without Multi-Factor Authentication are available here, as MFA is only supported in Defguard Desktop Client for now.', - }, - form: { - submit: 'Add Device', - labels: { - deviceName: 'Device Name', - location: 'Location', - assignedAddress: 'Assigned IP Address', - description: 'Description', - generation: { - auto: 'Generate key pair', - manual: 'Use my own public key', - }, - publicKey: 'Provide Your Public Key', - }, - }, - steps: { - method: { - title: 'Choose a preferred method', - cards: { - cli: { - title: 'Defguard Command Line Client', - subtitle: - 'When using defguard-cli your device will be automatically configured.', - docs: 'Defguard CLI download and documentation', - }, - manual: { - title: 'Manual WireGuard Client', - subtitle: - 'If your device does not support our CLI binaries you can always generate a WireGuard configuration file and configure it manually - but any updates to the VPN location configuration will require manual changes in device configuration.', - }, - }, - }, - manual: { - title: 'Add new VPN device using WireGuard Client', - finish: { - messageTop: - 'Download the provided configuration file to your device and import it into your VPN client to complete the setup.', - ctaInstruction: - "Use provided configuration file below by scanning QR code or importing it as file on your device's WireGuard app.", - // MD - warningMessage: ` - Please remember that Defguard **doesn't store private keys**. We will securely generate the public and private key pair in your browser, but only store the public key in Defguard database. Please download the configuration generated with the private key for the device, as it will not be accessible later. - `, - actionCard: { - title: 'Config', - }, - }, - }, - cli: { - title: 'Add device using Defguard Command Line Client', - finish: { - topMessage: - 'First download Defguard command line client binary and install it on your server.', - downloadButton: 'Download Defguard CLI Client', - commandCopy: 'Copy and paste this command in your terminal on the device', - }, - setup: { - stepMessage: - 'Here you can add definitions or generate configurations for devices that can connect to your VPN. Only locations without Multi-Factor Authentication are available here, as MFA is only supported in Defguard Desktop Client for now.', - form: { - submit: 'Add Device', - }, - }, - }, - }, - }, - updatesNotificationToaster: { - title: 'New version available {version: string}', - controls: { - more: "See what's new", - }, - }, - enterpriseUpgradeToaster: { - title: `You've reached the enterprise functionality limit.`, - message: `You've exceeded the limit of your current Defguard plan and the enterprise - features will be disabled. Purchase an enterprise license or upgrade your - existing one to continue using these features.`, - link: 'See all enterprise plans', - }, - updatesNotification: { - header: { - title: 'Update Available', - newVersion: 'new version {version: string}', - criticalBadge: 'critical update', - }, - controls: { - visitRelease: 'Visit release page', - }, - }, - addGroup: { - title: 'Add group', - selectAll: 'Select all users', - groupName: 'Group name', - searchPlaceholder: 'Filter/Search', - submit: 'Create group', - groupSettings: 'Group settings', - adminGroup: 'Admin group', - }, - editGroup: { - title: 'Edit group', - selectAll: 'Select all users', - groupName: 'Group name', - searchPlaceholder: 'Filter/Search', - submit: 'Update group', - groupSettings: 'Group settings', - adminGroup: 'Admin group', - }, - deleteGroup: { - title: 'Delete group {name:string}', - subTitle: 'This action will permanently delete this group.', - locationListHeader: 'This group is currently assigned to following VPN Locations:', - locationListFooter: `If this is the only allowed group for a given location, the location will become accessible to all users.`, - submit: 'Delete group', - cancel: 'Cancel', - }, - deviceConfig: { - title: 'Device VPN configurations', - }, - changePasswordSelf: { - title: 'Change password', - messages: { - success: 'Password has been changed', - error: 'Failed to changed password', - }, - form: { - labels: { - newPassword: 'New password', - oldPassword: 'Current password', - repeat: 'Confirm new password', - }, - }, - controls: { - submit: 'Change password', - cancel: 'Cancel', - }, - }, - disableMfa: { - title: 'Disable MFA', - message: 'Do you want to disable MFA for user {username: string}?', - messages: { - success: 'MFA for user {username: string} has been disabled', - error: 'Failed to disable MFA for user {username: string}', - }, - controls: { - submit: 'Disable MFA', - cancel: 'Cancel', - }, - }, - startEnrollment: { - title: 'Start enrollment', - desktopTitle: 'Desktop activation', - messages: { - success: 'User enrollment started', - successDesktop: 'Desktop configuration started', - error: 'Failed to start user enrollment', - errorDesktop: 'Failed to start desktop activation', - }, - messageBox: { - clientForm: - 'You can share the following URL and token with the user to configure their Defguard desktop or mobile client.', - clientQr: - 'You can share this QR code for easy Defguard mobile client configuration.', - }, - form: { - email: { - label: 'Email', - }, - mode: { - options: { - email: 'Send token by email', - manual: 'Deliver token yourself', - }, - }, - submit: 'Start enrollment', - submitDesktop: 'Activate desktop', - smtpDisabled: 'Configure SMTP to send token by email. Go to Settings -> SMTP.', - }, - tokenCard: { - title: 'Activation token', - }, - urlCard: { - title: 'Defguard Instance URL', - }, - }, - deleteNetwork: { - title: 'Delete {name:string} location', - subTitle: 'This action will permanently delete this location.', - submit: 'Delete location', - cancel: 'Cancel', - }, - changeWebhook: { - messages: { - success: 'Webhook changed.', - }, - }, - manageWebAuthNKeys: { - title: 'Security keys', - messages: { - deleted: 'WebAuthN key has been deleted.', - duplicateKeyError: 'Key is already registered', - }, - infoMessage: ` -

- Security keys can be used as your second factor of authentication - instead of a verification code. Learn more about configuring a - security key. -

-`, - form: { - messages: { - success: 'Security key added.', - }, - fields: { - name: { - label: 'New key name', - }, - }, - controls: { - submit: 'Add new Key', - }, - }, - }, - recoveryCodes: { - title: 'Recovery codes', - submit: 'I have saved my codes', - messages: { - copied: 'Codes copied.', - }, - infoMessage: ` -

- Treat your recovery codes with the same level of attention as you - would your password! We recommend saving them with a password manager - such as Lastpass, bitwarden or Keeper. -

-`, - }, - registerTOTP: { - title: 'Authenticator App Setup', - infoMessage: ` -

- To setup your MFA, scan this QR code with your authenticator app, then - enter the code in the field below: -

-`, - messages: { - totpCopied: 'TOTP path copied.', - success: 'TOTP Enabled', - }, - copyPath: 'Copy TOTP path', - form: { - fields: { - code: { - label: 'Authenticator code', - error: 'Code is invalid', - }, - }, - controls: { - submit: 'Verify code', - }, - }, - }, - registerEmailMFA: { - title: 'Email MFA Setup', - infoMessage: ` -

- To setup your MFA enter the code that was sent to your account email: {email: string} -

-`, - messages: { - success: 'Email MFA Enabled', - resend: 'Verification code resent', - }, - form: { - fields: { - code: { - label: 'Email code', - error: 'Code is invalid', - }, - }, - controls: { - submit: 'Verify code', - resend: 'Resend email', - }, - }, - }, - editDevice: { - title: 'Edit device', - messages: { - success: 'Device has been updated.', - }, - form: { - fields: { - name: { - label: 'Device Name', - }, - publicKey: { - label: 'Device Public Key (WireGuard)', - }, - }, - controls: { - submit: 'Edit device', - }, - }, - }, - deleteDevice: { - title: 'Delete device', - message: 'Do you want to delete {deviceName} device ?', - submit: 'Delete device', - messages: { - success: 'Device has been deleted.', - }, - }, - keyDetails: { - title: 'YubiKey details', - downloadAll: 'Download all keys', - }, - deleteUser: { - title: 'Delete account', - controls: { - submit: 'Delete account', - }, - message: 'Do you want to delete {username: string} account permanently?', - messages: { - success: '{username: string} deleted.', - }, - }, - disableUser: { - title: 'Disable account', - controls: { - submit: 'Disable account', - }, - message: 'Do you want to disable {username: string} account?', - messages: { - success: '{username: string} disabled.', - }, - }, - enableUser: { - title: 'Enable account', - controls: { - submit: 'Enable account', - }, - message: 'Do you want to enable {username: string} account?', - messages: { - success: '{username: string} enabled.', - }, - }, - deleteProvisioner: { - title: 'Delete provisioner', - controls: { - submit: 'Delete provisioner', - }, - message: 'Do you want to delete {id: string} provisioner?', - messages: { - success: '{provisioner: string} deleted.', - }, - }, - changeUserPassword: { - messages: { - success: 'Password changed.', - }, - title: 'Change user password', - form: { - controls: { - submit: 'Save new password', - }, - fields: { - newPassword: { - label: 'New password', - }, - confirmPassword: { - label: 'Repeat password', - }, - }, - }, - }, - provisionKeys: { - title: 'Yubikey provisioning:', - warning: - 'Please be advised that this operation wll wipe openpgp application on yubikey and reconfigure it.', - infoBox: `The selected provisioner must have a clean YubiKey - plugged in be provisioned. To clean a used YubiKey - gpg --card-edit before provisioning.`, - selectionLabel: 'Select one of the following provisioners to provision a YubiKey:', - noData: { - workers: 'No workers found, waiting...', - }, - controls: { - submit: 'Provision YubiKey', - }, - messages: { - success: 'Keys provisioned', - errorStatus: 'Error while getting worker status.', - }, - }, - addUser: { - title: 'Add new user', - messages: { - userAdded: 'User added', - }, - form: { - submit: 'Add user', - error: { - emailReserved: 'Email already taken', - }, - fields: { - username: { - placeholder: 'login', - label: 'Login', - }, - password: { - placeholder: 'Password', - label: 'Password', - }, - email: { - placeholder: 'User e-mail', - label: 'User e-mail', - }, - firstName: { - placeholder: 'First name', - label: 'First name', - }, - lastName: { - placeholder: 'Last name', - label: 'Last name', - }, - phone: { - placeholder: 'Phone', - label: 'Phone', - }, - enableEnrollment: { - label: 'Use user self-enrollment process', - link: 'more information here', - }, - }, - }, - }, - webhookModal: { - title: { - addWebhook: 'Add webhook.', - editWebhook: 'Edit webhook', - }, - messages: { - clientIdCopy: 'Client ID copied.', - clientSecretCopy: 'Client secret copied.', - }, - form: { - triggers: 'Trigger events:', - messages: { - successAdd: 'Webhook created.', - successModify: 'Webhook modified.', - }, - error: { - urlRequired: 'URL is required.', - validUrl: 'Must be a valid URL.', - scopeValidation: 'Must have at least one trigger.', - tokenRequired: 'Token is required.', - }, - fields: { - description: { - label: 'Description', - placeholder: 'Webhook to create gmail account on new user', - }, - token: { - label: 'Secret token', - placeholder: 'Authorization token', - }, - url: { - label: 'Webhook URL', - placeholder: 'https://example.com/webhook', - }, - userCreated: { - label: 'New user Created', - }, - userDeleted: { - label: 'User deleted', - }, - userModified: { - label: 'User modified', - }, - hwkeyProvision: { - label: 'User Yubikey provision', - }, - }, - }, - }, - deleteWebhook: { - title: 'Delete webhook', - message: 'Do you want to delete {name: string} webhook ?', - submit: 'Delete', - messages: { - success: 'Webhook deleted.', - }, - }, - }, - addDevicePage: { - title: 'Add device', - helpers: { - setupOpt: `You can add a device using this wizard. Opt for our native application "defguard" or any other WireGuard client. If you're unsure, we recommend using defguard for simplicity.`, - client: `Please download defguard desktop client here and then follow this guide.`, - }, - messages: { - deviceAdded: 'Device added', - }, - steps: { - setupMethod: { - title: 'Choose Your Connection Method', - message: - "You can add a device using this wizard. To proceed, you'll need to install the defguard Client on the device you're adding. You can also use any standard WireGuard® client, but for the best experience and ease of setup, we recommend using our native defguard Client.", - methods: { - client: { - title: 'Remote Device Activation', - description: - 'Use the Defguard Client to set up your device. Easily configure it with a single token or by scanning a QR code.', - }, - wg: { - title: 'Manual WireGuard Client', - description: - 'For advanced users, get a unique config via download or QR code. Download any WireGuard® client and take control of your VPN setup.', - }, - }, - }, - client: { - title: 'Client Activation', - desktopDeepLinkHelp: - 'If you want to configure your Defguard desktop client, please install the client (links below), open it and just press the One-Click Desktop Configuration button', - //md - message: - 'If you are having trouble with the One-Click configuration you can do it manually by clicking *Add Instance* in the desktop client, and entering the following URL and Token:', - qrDescription: - "Scan the QR code with your installed Defguard app. If you haven't installed it yet, use your device's app store or the link below.", - qrHelp: - 'If you want to configure your Mobile Defguard Client, please just scan this QR code in the app:', - desktopDownload: 'Download for Desktop', - tokenCopy: 'Token copied to clipboard', - tokenFailure: 'Failed to prepare client setup', - labels: { - mergedToken: 'Defguard Instance Token (new)', - token: 'Authentication Token', - url: 'URL', - }, - }, - configDevice: { - title: 'Configure device', - messages: { - copyConfig: 'Configuration has been copied to the clipboard', - }, - helpers: { - warningAutoMode: ` -

- Please be advised that you have to download the configuration now, - since we do not store your private key. After this - page is closed, you will not be able to get your - full configuration file (with private keys, only blank template). -

-`, - warningManualMode: ` -

- Please be advised that configuration provided here does not include private key and uses public key to fill it's place you will need to replace it on your own for configuration to work properly. -

-`, - warningNoNetworks: "You don't have access to any network.", - qrHelper: ` -

- You can setup your device faster with wireguard application by scanning this QR code. -

`, - }, - qrInfo: - 'Use provided configuration file below by scanning QR Code or importing it as file on your devices WireGuard instance.', - inputNameLabel: 'Device Name', - qrLabel: 'WireGuard Config File', - }, - setupDevice: { - title: 'Create VPN device', - infoMessage: ` -

- You need to configure WireGuard® VPN on your device, please visit  - documentation if you don't know how to do it. -

-`, - options: { - auto: 'Generate key pair', - manual: 'Use my own public key', - }, - form: { - fields: { - name: { - label: 'Device Name', - }, - publicKey: { - label: 'Provide Your Public Key', - }, - }, - errors: { - name: { - duplicatedName: 'Device with this name already exists', - }, - }, - }, - }, - copyToken: { - title: 'Client activation', - tokenCardTitle: 'Activation token', - urlCardTitle: 'Defguard Instance URL', - }, - }, - }, - userPage: { - title: { - view: 'User Profile', - edit: 'Edit User Profile', - }, - messages: { - editSuccess: 'User updated.', - failedToFetchUserData: 'Could not get user information.', - passwordResetEmailSent: 'Password reset email has been sent.', - }, - userDetails: { - header: 'Profile Details', - messages: { - deleteApp: 'App and all tokens deleted.', - }, - warningModals: { - title: 'Warning', - content: { - usernameChange: `Changing the username has a significant impact on services the user has logged into using Defguard. After changing it, the user may lose access to applications (since they will not recognize them). Are you sure you want to proceed?`, - emailChange: `If you are using external OpenID Connect (OIDC) providers to authenticate users, changing a user's email address may have a significant impact on their ability to log in to Defguard. Are you sure you want to proceed?`, - }, - buttons: { - proceed: 'Proceed', - cancel: 'Cancel', - }, - }, - fields: { - username: { - label: 'Username', - }, - firstName: { - label: 'First name', - }, - lastName: { - label: 'Last name', - }, - phone: { - label: 'Phone number', - }, - email: { - label: 'E-mail', - }, - status: { - label: 'Status', - active: 'Active', - disabled: 'Disabled', - }, - groups: { - label: 'User groups', - noData: 'No groups', - }, - apps: { - label: 'Authorized apps', - noData: 'No authorized apps', - }, - }, - }, - userAuthInfo: { - header: 'Password and authentication', - password: { - header: 'Password settings', - changePassword: 'Change password', - ldap_change_heading: '{ldapName:string} password update required', - ldap_change_message: - "Defguard doesn't store your password in plain text, so we can’t retrieve it for automatic synchronization with your {ldapName:string} credentials. To enable {ldapName:string} login to other services, please update your Defguard password for your {ldapName:string} password to be set — you can re-enter your current password if you wish. This step is necessary to ensure consistent and secure authentication across both systems.", - }, - recovery: { - header: 'Recovery options', - codes: { - label: 'Recovery Codes', - viewed: 'Viewed', - }, - }, - mfa: { - header: 'Two-factor methods', - edit: { - disable: 'Disable MFA', - }, - messages: { - mfaDisabled: 'MFA disabled.', - OTPDisabled: 'One time password disabled.', - EmailMFADisabled: 'Email MFA disabled.', - changeMFAMethod: 'MFA method changed', - }, - securityKey: { - singular: 'security key', - plural: 'security keys', - }, - default: 'default', - enabled: 'Enabled', - disabled: 'Disabled', - labels: { - totp: 'Time based one time passwords', - email: 'Email', - webauth: 'Security keys', - }, - editMode: { - enable: 'Enable', - disable: 'Disable', - makeDefault: 'Make default', - webauth: { - manage: 'Manage security keys', - }, - }, - }, - }, - controls: { - editButton: 'Edit profile', - deleteAccount: 'Delete account', - }, - devices: { - header: 'User devices', - addDevice: { - web: 'Add new device', - desktop: 'Add this device', - }, - card: { - labels: { - publicIP: 'Public IP', - connectedThrough: 'Connected through', - connectionDate: 'Connected date', - lastLocation: 'Last connected from', - lastConnected: 'Last connected', - assignedIp: 'Assigned IP', - active: 'active', - noData: 'Never connected', - }, - edit: { - edit: 'Edit device', - delete: 'Delete device', - showConfigurations: 'Show configuration', - }, - }, - }, - yubiKey: { - header: 'User YubiKey', - provision: 'Provision a YubiKey', - keys: { - pgp: 'PGP key', - ssh: 'SSH key', - }, - noLicense: { - moduleName: 'YubiKey module', - line1: 'This is enterprise module for YubiKey', - line2: 'management and provisioning.', - }, - }, - authenticationKeys: { - header: 'User Authentication Keys', - addKey: 'Add new Key', - keysList: { - common: { - rename: 'Rename', - key: 'Key', - download: 'Download', - copy: 'Copy', - serialNumber: 'Serial Number', - delete: 'Delete', - }, - }, - deleteModal: { - title: 'Delete Authentication Key', - confirmMessage: 'Key {name: string} will be deleted permanently.', - }, - addModal: { - header: 'Add new Authentication Key', - keyType: 'Key Type', - keyForm: { - placeholders: { - title: 'Key Name', - key: { - ssh: 'Begins with ssh-rsa, ecdsa-sha2-nistp256, ...', - gpg: 'Begins with -----BEGIN PGP PUBLIC KEY BLOCK-----', - }, - }, - labels: { - title: 'Name', - key: 'Key', - }, - submit: 'Add {name: string} key', - }, - yubikeyForm: { - selectWorker: { - info: 'Please be advised that this operation will wipe openpgp application on YubiKey and reconfigure it.', - selectLabel: 'Select on of the following provisioners to provision a YubiKey', - noData: 'No workers are registered right now.', - available: 'Available', - unavailable: 'Unavailable', - }, - provisioning: { - inProgress: 'Provisioning in progress, please wait.', - error: 'Provisioning failed !', - success: 'Yubikey provisioned successfully', - }, - submit: 'Provision Yubikey', - }, - messages: { - keyAdded: 'Key added.', - keyExists: 'Key has already been added.', - unsupportedKeyFormat: 'Unsupported key format.', - genericError: 'Could not add the key. Please try again later.', - }, - }, - }, - apiTokens: { - header: 'User API Tokens', - addToken: 'Add new API Token', - tokensList: { - common: { - rename: 'Rename', - token: 'Token', - copy: 'Copy', - delete: 'Delete', - createdAt: 'Created at', - }, - }, - deleteModal: { - title: 'Delete API Token', - confirmMessage: 'API token {name: string} will be deleted permanently.', - }, - addModal: { - header: 'Add new API Token', - tokenForm: { - placeholders: { - name: 'API Token Name', - }, - labels: { - name: 'Name', - }, - submit: 'Add API token', - }, - copyToken: { - warningMessage: - "Please copy the API token below now. You won't be able to see it again.", - header: 'Copy new API Token', - }, - messages: { - tokenAdded: 'API token added.', - genericError: 'Could not add API token. Please try again later.', - }, - }, - }, - }, - usersOverview: { - pageTitle: 'Users', - grid: { - usersTitle: 'Connected Users', - devicesTitle: 'Connected Network Devices', - }, - search: { - placeholder: 'Find users', - }, - filterLabels: { - all: 'All users', - admin: 'Admins only', - users: 'Users only', - }, - usersCount: 'All users', - addNewUser: 'Add new', - list: { - headers: { - name: 'User name', - username: 'Login', - phone: 'Phone', - actions: 'Actions', - }, - editButton: { - changePassword: 'Change password', - edit: 'Edit account', - addYubikey: 'Add YubiKey', - addSSH: 'Add SSH Key', - addGPG: 'Add GPG Key', - delete: 'Delete account', - startEnrollment: 'Start enrollment', - activateDesktop: 'Configure Desktop Client', - resetPassword: 'Reset password', - disableMfa: 'Disable MFA', - }, - }, - }, - navigation: { - bar: { - overview: 'VPN Overview', - users: 'Users', - provisioners: 'YubiKeys', - webhooks: 'Webhooks', - openId: 'OpenID Apps', - myProfile: 'My Profile', - settings: 'Settings', - logOut: 'Log out', - enrollment: 'Enrollment', - support: 'Support', - groups: 'Groups', - devices: 'Network Devices', - acl: 'Access Control', - activity: 'Activity log', - }, - mobileTitles: { - activity: 'Activity log', - groups: 'Groups', - wizard: 'Create location', - users: 'Users', - settings: 'Settings', - user: 'User Profile', - provisioners: 'Yubikey', - webhooks: 'Webhooks', - openId: 'OpenId Apps', - overview: 'Location Overview', - networkSettings: 'Edit Location', - enrollment: 'Enrollment', - support: 'Support', - devices: 'Network Devices', - }, - copyright: 'Copyright ©2023-2025', - version: { - open: 'Application version: {version: string}', - closed: 'v{version: string}', - }, - }, - form: { - download: 'Download', - copy: 'Copy', - saveChanges: 'Save changes', - submit: 'Submit', - login: 'Sign in', - cancel: 'Cancel', - close: 'Close', - placeholders: { - password: 'Password', - username: 'Username', - username_or_email: 'Username or email', - }, - error: { - urlInvalid: 'Enter valid URL', - reservedName: 'Name is already taken.', - invalidIp: 'IP is invalid.', - reservedIp: 'IP is already in use.', - forbiddenCharacter: 'Field contains forbidden characters.', - usernameTaken: 'Username is already in use.', - invalidKey: 'Key is invalid.', - invalid: 'Field is invalid.', - required: 'Field is required.', - invalidCode: 'Submitted code is invalid.', - maximumLength: 'Maximum length exceeded.', - maximumLengthOf: `Field length cannot exceed {length: number}`, - minimumLength: 'Minimum length not reached.', - minimumLengthOf: `Minimum length of {length: number} not reached.`, - noSpecialChars: 'No special characters are allowed.', - oneDigit: 'One digit required.', - oneSpecial: 'Special character required.', - oneUppercase: 'One uppercase character required.', - oneLowercase: 'One lowercase character required.', - portMax: 'Maximum port is 65535.', - endpoint: 'Enter a valid endpoint.', - address: 'Enter a valid address.', - addressNetmask: 'Enter a valid address with a netmask.', - validPort: 'Enter a valid port.', - validCode: 'Code should have 6 digits.', - allowedIps: 'Only valid IP or domain is allowed.', - startFromNumber: 'Cannot start from number.', - repeat: `Fields don't match.`, - number: 'Expected a valid number.', - minimumValue: `Minimum value of {value: number} not reached.`, - maximumValue: 'Maximum value of {value: number} exceeded.', - tooManyBadLoginAttempts: `Too many bad login attempts. Please try again in a few minutes.`, - }, - floatingErrors: { - title: 'Please correct the following:', - }, - }, - components: { - openClientDeepLink: 'One-Click Desktop Configuration', - aclDefaultPolicySelect: { - label: 'Default ACL Policy', - options: { - allow: 'Allow', - deny: 'Deny', - }, - }, - standaloneDeviceTokenModalContent: { - headerMessage: - 'First download defguard command line client binaries and install them on your server.', - downloadButton: 'Download Defguard CLI Client', - expandableCard: { - title: 'Copy and paste this command in your terminal on the device', - }, - }, - deviceConfigsCard: { - cardTitle: 'WireGuard Config for location:', - messages: { - copyConfig: 'Configuration copied to the clipboard', - }, - }, - gatewaysStatus: { - label: 'Gateways', - states: { - all: 'All ({count: number}) Connected', - some: 'Some ({count: number}) Connected', - none: 'None connected', - error: 'Status check failed', - }, - messages: { - error: 'Failed to get gateways status', - deleteError: 'Failed to delete gateway', - }, - }, - noLicenseBox: { - footer: { - get: 'Get an enterprise license', - contact: 'by contacting:', - }, - }, - locationMfaModeSelect: { - label: 'MFA Requirement', - options: { - disabled: 'Do not enforce MFA', - internal: 'Internal MFA', - external: 'External MFA', - }, - }, - }, - settingsPage: { - title: 'Settings', - tabs: { - smtp: 'SMTP', - global: 'Global settings', - ldap: 'LDAP', - openid: 'OpenID', - enterprise: 'Enterprise features', - gatewayNotifications: 'Gateway notifications', - activityLogStream: 'Activity log streaming', - }, - messages: { - editSuccess: 'Settings updated', - challengeSuccess: 'Challenge message changed', - }, - enterpriseOnly: { - title: 'This feature is available only in Defguard Enterprise.', - currentExpired: 'Your current license has expired.', - subtitle: 'To learn more, visit our ', - website: 'website', - }, - activityLogStreamSettings: { - messages: { - destinationCrud: { - create: '{destination: string} destination added', - modify: '{destination: string} destination modified', - delete: '{destination: string} destination removed', - }, - }, - modals: { - selectDestination: { - title: 'Select destination', - }, - vector: { - create: 'Add Vector destination', - modify: 'Edit Vector destination', - }, - logstash: { - create: 'Add Logstash destination', - modify: 'Edit Logstash destination', - }, - shared: { - formLabels: { - name: 'Name', - url: 'Url', - username: 'Username', - password: 'Password', - cert: 'Certificate', - }, - }, - }, - title: 'Activity log streaming', - list: { - noData: 'No destinations', - headers: { - name: 'Name', - destination: 'Destination', - }, - }, - }, - ldapSettings: { - title: 'LDAP Settings', - sync: { - header: 'LDAP two-way synchronization', - info: 'Before enabling synchronization, please read more about it in our [documentation](https://docs.defguard.net/features/ldap-and-active-directory-integration/two-way-ldap-and-active-directory-synchronization).', - info_enterprise: 'This feature is available only in Defguard Enterprise.', - helpers: { - heading: - 'Configure LDAP synchronization settings here. If configured, Defguard will pull user information from LDAP and synchronize it with local users.', - sync_enabled: - 'If enabled, Defguard will attempt to pull LDAP user data at the specified interval.', - authority: `Defguard will use the selected server as the authoritative source of - user data, meaning that if LDAP is selected, Defguard data will be overwritten with the LDAP - data in case of a desynchronization. If Defguard was selected as the authority, it's data will - overwrite LDAP data if necessary. - Make sure to check the documentation to understand the implications of this - setting.`, - interval: 'The interval with which the synchronization will be attempted.', - groups: `Defguard will attempt to synchronize only users belonging to the provided groups. Provide a comma-separated list of groups. If empty, all users will be synchronized.`, - }, - }, - form: { - labels: { - ldap_enable: 'Enable LDAP integration', - ldap_url: 'URL', - ldap_bind_username: 'Bind Username', - ldap_bind_password: 'Bind Password', - ldap_member_attr: 'Member Attribute', - ldap_username_attr: 'Username Attribute', - ldap_user_obj_class: 'User Object Class', - ldap_user_search_base: 'User Search Base', - ldap_user_auxiliary_obj_classes: 'Additional User Object Classes', - ldap_groupname_attr: 'Groupname Attribute', - ldap_group_search_base: 'Group Search Base', - ldap_group_member_attr: 'Group Member Attribute', - ldap_group_obj_class: 'Group Object Class', - ldap_sync_enabled: 'Enable LDAP two-way synchronization', - ldap_authoritative_source: 'Consider the following source as the authority', - ldap_sync_interval: 'Synchronization interval', - ldap_use_starttls: 'Use StartTLS', - ldap_tls_verify_cert: 'Verify TLS certificate', - ldap_uses_ad: 'LDAP server is Active Directory', - ldap_user_rdn_attr: 'User RDN Attribute', - ldap_sync_groups: 'Limit synchronization to these groups', - }, - helpers: { - ldap_user_obj_class: - 'The object class that will be added to the user object during its creation. This is used to determine if an LDAP object is a user.', - ldap_user_auxiliary_obj_classes: - "The additional object classes that will be added to the user object during its creation. They may also influence the added user's attributes (e.g. simpleSecurityObject class will add userPassword attribute).", - user_settings: - 'Configure LDAP user settings here. These settings determine how Defguard maps and synchronizes LDAP user information with local users.', - connection_settings: - 'Configure LDAP connection settings here. These settings determine how Defguard connects to your LDAP server. Encrypted connections are also supported (StartTLS, LDAPS).', - group_settings: - 'Configure LDAP group settings here. These settings determine how Defguard maps and synchronizes LDAP group information with local groups.', - ldap_group_obj_class: - 'The object class that represents a group in LDAP. This is used to determine if an LDAP object is a group.', - ldap_user_rdn_attr: - "If your user's RDN attribute is different than your username attribute, please provide it here, otherwise leave it empty to use the username attribute as the user's RDN.", - }, - headings: { - user_settings: 'User settings', - connection_settings: 'Connection settings', - group_settings: 'Group settings', - }, - delete: 'Delete configuration', - }, - test: { - title: 'Test LDAP Connection', - submit: 'Test', - messages: { - success: 'LDAP connected successfully', - error: 'LDAP connection rejected', - }, - }, - }, - openIdSettings: { - heading: 'External OpenID settings', - general: { - title: 'General settings', - helper: 'Here you can change general OpenID behavior in your Defguard instance.', - createAccount: { - label: - 'Automatically create user account when logging in for the first time through external OpenID.', - helper: - 'If this option is enabled, Defguard automatically creates new accounts for users who log in for the first time using an external OpenID provider. Otherwise, the user account must first be created by an administrator.', - }, - usernameHandling: { - label: 'Username handling', - helper: - 'Configure the method for handling invalid characters in usernames provided by your identity provider.', - options: { - remove: 'Remove forbidden characters', - replace: 'Replace forbidden characters', - prune_email: 'Prune email domain', - }, - }, - }, - form: { - title: 'Client settings', - helper: - 'Here you can configure the OpenID client settings with values provided by your external OpenID provider.', - custom: 'Custom', - none: 'None', - documentation: - 'Make sure to check our [documentation](https://docs.defguard.net/features/external-openid-providers) for more information and examples.', - delete: 'Delete provider', - directory_sync_settings: { - title: 'Directory synchronization settings', - helper: - "Directory synchronization allows you to automatically synchronize users' status and groups from an external provider.", - notSupported: 'Directory sync is not supported for this provider.', - connectionTest: { - success: 'Connection successful', - error: 'Connection failed with error:', - }, - }, - selects: { - synchronize: { - all: 'All', - users: 'Users', - groups: 'Groups', - }, - behavior: { - keep: 'Keep', - disable: 'Disable', - delete: 'Delete', - }, - }, - labels: { - provider: { - label: 'Provider', - helper: - 'Select your OpenID provider. You can use custom provider and fill in the base URL by yourself.', - }, - client_id: { - label: 'Client ID', - helper: 'Client ID provided by your OpenID provider.', - }, - client_secret: { - label: 'Client Secret', - helper: 'Client Secret provided by your OpenID provider.', - }, - base_url: { - label: 'Base URL', - helper: - 'Base URL of your OpenID provider, e.g. https://accounts.google.com. Make sure to check our documentation for more information and examples.', - }, - display_name: { - label: 'Display Name', - helper: - "Name of the OpenID provider to display on the login's page button. If not provided, the button will display generic 'Login with OIDC' text.", - }, - enable_directory_sync: { - label: 'Enable directory synchronization', - }, - sync_target: { - label: 'Synchronize', - helper: - "What to synchronize from the external provider. You can choose between synchronizing both users' state and group memberships, or narrow it down to just one of these.", - }, - sync_interval: { - label: 'Synchronization interval', - helper: 'Interval in seconds between directory synchronizations.', - }, - user_behavior: { - label: 'User behavior', - helper: - 'Choose how to handle users that are not present in the external provider anymore. You can select between keeping, disabling, or deleting them.', - }, - admin_behavior: { - label: 'Admin behavior', - helper: - 'Choose how to handle Defguard admins that are not present in the external provider anymore. You can select between keeping them, disabling them or completely deleting them.', - }, - admin_email: { - label: 'Admin email', - helper: - 'Email address of the account on which behalf the synchronization checks will be performed, e.g. the person who setup the Google service account. See our documentation for more details.', - }, - service_account_used: { - label: 'Service account in use', - helper: - 'The service account currently being used for synchronization. You can change it by uploading a new service account key file.', - }, - service_account_key_file: { - label: 'Service Account Key file', - helper: - "Upload a new service account key file to set the service account used for synchronization. NOTE: The uploaded file won't be visible after saving the settings and reloading the page as it's contents are sensitive and are never sent back to the dashboard.", - uploaded: 'File uploaded', - uploadPrompt: 'Upload a service account key file', - }, - okta_client_id: { - label: 'Directory Sync Client ID', - helper: 'Client ID for the Okta directory sync application.', - }, - okta_client_key: { - label: 'Directory Sync Client Private Key', - helper: - "Client private key for the Okta directory sync application in the JWK format. It won't be shown again here.", - }, - jumpcloud_api_key: { - label: 'JumpCloud API Key', - helper: - 'API Key for the JumpCloud directory sync. It will be used to periodically query JumpCloud for user state and group membership changes.', - }, - group_match: { - label: 'Sync only matching groups', - helper: - 'Provide a comma separated list of group names that should be synchronized. If left empty, all groups from the provider will be synchronized.', - }, - }, - }, - }, - modulesVisibility: { - header: 'Modules Visibility', - helper: `

- Hide unused modules. -

- - Read more in documentation. - `, - fields: { - wireguard_enabled: { - label: 'WireGuard VPN', - }, - webhooks_enabled: { - label: 'Webhooks', - }, - worker_enabled: { - label: 'Yubikey provisioning', - }, - openid_enabled: { - label: 'OpenID Connect', - }, - }, - }, - defaultNetworkSelect: { - header: 'Default location view', - helper: `

Here you can change your default location view.

- - Read more in documentation. - `, - filterLabels: { - grid: 'Grid view', - list: 'List view', - }, - }, - instanceBranding: { - header: 'Instance Branding', - form: { - title: 'Name & Logo:', - fields: { - instanceName: { - label: 'Instance name', - placeholder: 'Defguard', - }, - mainLogoUrl: { - label: 'Login logo url', - helper: 'Maximum picture size is 250x100 px', - placeholder: 'Default image', - }, - navLogoUrl: { - label: 'Menu & navigation small logo', - helper: 'Maximum picture size is 100x100 px', - placeholder: 'Default image', - }, - }, - controls: { - restoreDefault: 'Restore default', - submit: 'Save changes', - }, - }, - helper: ` -

- Here you can add url of your logo and name for your defguard - instance it will be displayed instead of defguard. -

- - Read more in documentation. - - `, - }, - license: { - header: 'Enterprise', - helpers: { - enterpriseHeader: { - text: 'Here you can manage your Defguard Enterprise version license.', - link: 'To learn more about Defguard Enterprise, visit our webiste.', - }, - licenseKey: { - text: 'Enter your Defguard Enterprise license key below. You should receive it via email after purchasing the license.', - link: 'You can purchase the license here.', - }, - }, - form: { - title: 'License', - fields: { - key: { - label: 'License key', - placeholder: 'Your Defguard license key', - }, - }, - }, - licenseInfo: { - title: 'License information', - status: { - noLicense: 'No valid license', - expired: 'Expired', - limitsExceeded: 'Limits Exceeded', - active: 'Active', - }, - licenseNotRequired: - "

You have access to this enterprise feature, as you haven't exceeded any of the usage limits yet. Check the documentation for more information.

", - types: { - subscription: { - label: 'Subscription', - helper: 'A license that automatically renews at regular intervals', - }, - offline: { - label: 'Offline', - helper: - 'The license is valid until the expiry date and does not automatically renew', - }, - }, - fields: { - status: { - label: 'Status', - active: 'Active', - expired: 'Expired', - subscriptionHelper: - 'A subscription license is considered valid for some time after the expiration date to account for possible automatic payment delays.', - }, - type: { - label: 'Type', - }, - validUntil: { - label: 'Valid until', - }, - }, - }, - }, - smtp: { - form: { - title: 'SMTP configuration', - sections: { - server: 'Server settings', - }, - fields: { - encryption: { - label: 'Encryption', - }, - server: { - label: 'Server address', - placeholder: 'Address', - }, - port: { - label: 'Server port', - placeholder: 'Port', - }, - user: { - label: 'Server username', - placeholder: 'Username', - }, - password: { - label: 'Server password', - placeholder: 'Password', - }, - sender: { - label: 'Sender email address', - placeholder: 'Address', - helper: ` -

- System messages will be sent from this address. - E.g. no-reply@my-company.com. -

- `, - }, - }, - controls: { - submit: 'Save changes', - }, - }, - delete: 'Delete configuration', - testForm: { - title: 'Send test email', - subtitle: 'Enter recipent email address', - fields: { - to: { - label: 'Send test email to', - placeholder: 'Address', - }, - }, - controls: { - submit: 'Send', - resend: 'Resend', - retry: 'Retry', - success: 'Test email sent', - error: 'Error sending email', - }, - success: { - message: 'Test email has been sent successully.', - }, - error: { - message: - 'There was an error sending the test email. Please check your SMTP configuration and try again.', - fullError: 'Error: {error: string}', - }, - }, - helper: `Here you can configure SMTP server used to send system messages to the users.`, - }, - enrollment: { - helper: - 'Enrollment is a process by which a new employee will be able to activate their new account, create a password and configure a VPN device.', - vpnOptionality: { - header: 'VPN step optionality', - helper: - 'You can choose whether creating a VPN device is optional or mandatory during enrollment', - }, - welcomeMessage: { - header: 'Welcome message', - helper: ` -

In this text input you can use Markdown:

-
    -
  • Headings start with a hash #
  • -
  • Use asterisks for *italics*
  • -
  • Use two asterisks for **bold**
  • -
- `, - }, - welcomeEmail: { - header: 'Welcome e-mail', - helper: ` -

In this text input you can use Markdown:

-
    -
  • Headings start with a hash #
  • -
  • Use asterisks for *italics*
  • -
  • Use two asterisks for **bold**
  • -
- `, - }, - form: { - controls: { - submit: 'Save changes', - }, - welcomeMessage: { - helper: - 'This information will be displayed for the user once enrollment is completed. We advise you to insert relevant links and explain next steps briefly.', - placeholder: 'Please input welcome message', - }, - welcomeEmail: { - helper: - 'This information will be sent to the user once enrollment is completed. We advise you to insert relevant links and explain next steps briefly. You can reuse the welcome message here.', - placeholder: 'Please input welcome email', - }, - welcomeEmailSubject: { - label: 'Subject', - }, - useMessageAsEmail: { - label: 'Same as welcome message', - }, - }, - }, - enterprise: { - header: 'Enterprise Features', - helper: 'Here you can change enterprise settings.', - fields: { - deviceManagement: { - label: "Disable users' ability to manage their devices", - helper: - "When this option is enabled, only users in the Admin group can manage devices in user profile (it's disabled for all other users)", - }, - disableAllTraffic: { - label: 'Disable the option to route all traffic through VPN', - helper: - 'When this option is enabled, users will not be able to route all traffic through the VPN using the defguard client.', - }, - manualConfig: { - label: "Disable users' ability to manually configure WireGuard client", - helper: - "When this option is enabled, users won't be able to view or download configuration for the manual WireGuard client setup. Only the Defguard desktop client configuration will be available.", - }, - }, - }, - gatewayNotifications: { - smtpWarning: 'To enable notifications you must first configure an SMTP server', - header: 'Notifications', - sections: { - gateway: 'Gateway disconnect notifications', - }, - helper: 'Here you can manage email notifications.', - form: { - submit: 'Save changes', - fields: { - disconnectNotificationsEnabled: { - label: 'Enable gateway disconnect notifications', - help: 'Send email notification to admin users once a gateway is disconnected', - }, - inactivityThreshold: { - label: 'Gateway inactivity time [minutes]', - help: 'Time (in minutes) that a gateway needs to stay disconnected before a notification is sent', - }, - reconnectNotificationsEnabled: { - label: 'Enable gateway reconnect notifications', - help: 'Send email notification to admin users once a gateway is reconnected', - }, - }, - }, - }, - }, - openidOverview: { - pageTitle: 'OpenID Apps', - search: { - placeholder: 'Find apps', - }, - filterLabels: { - all: 'All apps', - enabled: 'Enabled', - disabled: 'Disabled', - }, - clientCount: 'All apps', - addNewApp: 'Add new', - list: { - headers: { - name: 'Name', - status: 'Status', - actions: 'Actions', - }, - editButton: { - edit: 'Edit app', - delete: 'Delete app', - disable: 'Disable', - enable: 'Enable', - copy: 'Copy client ID', - }, - status: { - enabled: 'Enabled', - disabled: 'Disabled', - }, - }, - messages: { - copySuccess: 'Client ID copied.', - noLicenseMessage: "You don't have a license for this feature.", - noClientsFound: 'No results found.', - }, - deleteApp: { - title: 'Delete app', - message: 'Do you want to delete {appName: string} app ?', - submit: 'Delete app', - messages: { - success: 'App deleted.', - }, - }, - enableApp: { - messages: { - success: 'App enabled.', - }, - }, - disableApp: { - messages: { - success: 'App disabled.', - }, - }, - modals: { - openidClientModal: { - title: { - addApp: 'Add Application', - editApp: 'Edit {appName: string} app', - }, - scopes: 'Scopes:', - messages: { - clientIdCopy: 'Client ID copied.', - clientSecretCopy: 'Client secret copied.', - }, - form: { - messages: { - successAdd: 'App created.', - successModify: 'App modified.', - }, - error: { - urlRequired: 'URL is required.', - validUrl: 'Must be a valid URL.', - scopeValidation: 'Must have at least one scope.', - }, - fields: { - name: { - label: 'App name', - }, - redirectUri: { - label: 'Redirect URL {count: number}', - placeholder: 'https://example.com/redirect', - }, - openid: { - label: 'OpenID', - }, - profile: { - label: 'Profile', - }, - email: { - label: 'Email', - }, - phone: { - label: 'Phone', - }, - groups: { - label: 'Groups', - }, - }, - controls: { - addUrl: 'Add URL', - }, - }, - clientId: 'Client ID', - clientSecret: 'Client secret', - }, - }, - }, - webhooksOverview: { - pageTitle: 'Webhooks', - search: { - placeholder: 'Find webhooks by url', - }, - filterLabels: { - all: 'All webhooks', - enabled: 'Enabled', - disabled: 'Disabled', - }, - webhooksCount: 'All webhooks', - addNewWebhook: 'Add new', - noWebhooksFound: 'No webhooks found.', - list: { - headers: { - name: 'Name', - description: 'Description', - status: 'Status', - actions: 'Actions', - }, - editButton: { - edit: 'Edit', - delete: 'Delete webhook', - disable: 'Disable', - enable: 'Enable', - }, - status: { - enabled: 'Enabled', - disabled: 'Disabled', - }, - }, - }, - provisionersOverview: { - pageTitle: 'Provisioners', - search: { - placeholder: 'Find provisioners', - }, - filterLabels: { - all: 'All', - available: 'Available', - unavailable: 'Unavailable', - }, - provisionersCount: 'All provisioners', - noProvisionersFound: 'No provisioners found.', - noLicenseMessage: "You don't have a license for this feature.", - provisioningStation: { - header: 'YubiKey provisioning station', - content: `In order to be able to provision your YubiKeys, first you need to set up - physical machine with USB slot. Run provided command on your chosen - machine to register it and start provisioning your keys.`, - dockerCard: { - title: 'Provisioning station docker setup command', - }, - tokenCard: { - title: 'Access token', - }, - }, - list: { - headers: { - name: 'Name', - ip: 'IP address', - status: 'Status', - actions: 'Actions', - }, - editButton: { - delete: 'Delete provisioner', - }, - status: { - available: 'Available', - unavailable: 'Unavailable', - }, - }, - messages: { - copy: { - token: 'Token copied', - command: 'Command copied', - }, - }, - }, - openidAllow: { - header: '{name: string} would like to:', - scopes: { - openid: 'Use your profile data for future logins.', - profile: 'Know basic information from your profile like name, profile picture etc.', - email: 'Know your email address.', - phone: 'Know your phone number.', - groups: 'Know your groups membership.', - }, - controls: { - accept: 'Accept', - cancel: 'Cancel', - }, - }, - networkOverview: { - networkSelection: { - all: 'All locations summary', - placeholder: 'Select location', - }, - timeRangeSelectionLabel: '{value: number}h period', - pageTitle: 'Location overview', - controls: { - editNetworks: 'Edit Locations settings', - selectNetwork: { - placeholder: 'Loading locations', - }, - }, - filterLabels: { - grid: 'Grid view', - list: 'List view', - }, - gatewayStatus: { - all: 'All ({count: number}) Connected', - some: 'Some ({count: number}) Connected', - none: 'None connected', - }, - stats: { - currentlyActiveUsers: 'Currently active users', - currentlyActiveNetworkDevices: 'Currently active network devices', - totalUserDevices: 'Total user devices: {count: number}', - activeNetworkDevices: 'Active network devices in {hour: number}h', - activeUsersFilter: 'Active users in {hour: number}h', - activeDevicesFilter: 'Active devices in {hour: number}h', - activityIn: 'Activity in {hour: number}H', - networkUsage: 'Network usage', - peak: 'Peak', - in: 'In:', - out: 'Out:', - gatewayDisconnected: 'Gateway disconnected', - }, - cardsLabels: { - users: 'Connected Users', - devices: 'Connected Network Devices', - }, - }, - connectedUsersOverview: { - pageTitle: 'Connected users', - noUsersMessage: 'Currently there are no connected users', - userList: { - username: 'Username', - device: 'Device', - connected: 'Connected', - deviceLocation: 'Device location', - networkUsage: 'Network usage', - }, - }, - networkPage: { - pageTitle: 'Edit Location', - addNetwork: '+ Add new location', - controls: { - networkSelect: { - label: 'Location choice', - }, - }, - }, - activityOverview: { - header: 'Activity stream', - noData: 'Currently there is no activity detected', - }, - networkConfiguration: { - messages: { - delete: { - success: 'Network deleted', - error: 'Failed to delete network', - }, - }, - header: 'Location configuration', - importHeader: 'Location import', - form: { - helpers: { - address: - 'Based on this address VPN network address will be defined, eg. 10.10.10.1/24 (and VPN network will be: 10.10.10.0/24). You can optionally specify multiple addresses separated by a comma. The first address is the primary address, and this one will be used for IP address assignment for devices. The other IP addresses are auxiliary and are not managed by Defguard.', - endpoint: - 'Public IP address or domain name to which the remote peers/users will connect to. This address will be used in the configuration for the clients, but Defguard Gateways do not bind to this address.', - gateway: 'Gateway public address, used by VPN users to connect', - dns: 'Specify the DNS resolvers to query when the wireguard interface is up.', - allowedIps: - 'List of addresses/masks that should be routed through the VPN network.', - allowedGroups: - 'By default, all users will be allowed to connect to this location. If you want to restrict access to this location to a specific group, please select it below.', - aclFeatureDisabled: - "ACL functionality is an enterprise feature and you've exceeded the user, device or network limits to use it. In order to use this feature, purchase an enterprise license or upgrade your existing one.", - peerDisconnectThreshold: - 'Clients authorized with MFA will be disconnected from the location once there has been no network activity detected between them and the VPN gateway for a length of time configured below.', - locationMfaMode: { - description: 'Choose how MFA is enforced when connecting to this location:', - internal: - "Internal MFA - MFA is enforced using Defguard's built-in MFA (e.g. TOTP, WebAuthn) with internal identity", - external: - 'External MFA - If configured (see [OpenID settings](settings)) this option uses external identity provider for MFA', - }, - }, - sections: { - accessControl: { - header: 'Access Control & Firewall', - }, - mfa: { - header: 'Multi-Factor Authentication', - }, - }, - messages: { - networkModified: 'Location modified.', - networkCreated: 'Location created', - }, - fields: { - name: { - label: 'Location name', - }, - address: { - label: 'Gateway VPN IP address and netmask', - }, - endpoint: { - label: 'Gateway IP address or domain name', - }, - allowedIps: { - label: 'Allowed Ips', - }, - port: { - label: 'Gateway port', - }, - dns: { - label: 'DNS', - }, - allowedGroups: { - label: 'Allowed groups', - placeholder: 'All groups', - }, - keepalive_interval: { - label: 'Keepalive interval [seconds]', - }, - peer_disconnect_threshold: { - label: 'Client disconnect threshold [seconds]', - }, - acl_enabled: { - label: 'Enable ACL for this location', - }, - acl_default_allow: { - label: 'Default ACL policy', - }, - location_mfa_mode: { - label: 'MFA requirement', - }, - }, - controls: { - submit: 'Save changes', - cancel: 'Back to Overview', - delete: 'Remove location', - }, - }, - }, - gatewaySetup: { - header: { - main: 'Gateway server setup', - dockerBasedGatewaySetup: `Docker Based Gateway Setup`, - fromPackage: `From Package`, - oneLineInstall: `One Line Install`, - }, - card: { - title: 'Docker based gateway setup', - authToken: `Authentication Token`, - }, - button: { - availablePackages: `Available Packages`, - }, - controls: { - status: 'Check connection status', - }, - messages: { - runCommand: `Defguard requires to deploy a gateway node to control wireguard VPN on the vpn server. - More details can be found in the [documentation]({setupGatewayDocs:string}). - There are several ways to deploy the gateway server, - below is a Docker based example, for other examples please visit [documentation]({setupGatewayDocs:string}).`, - createNetwork: `Please create the network before running the gateway process.`, - noConnection: `No connection established, please run provided command.`, - connected: `Gateway connected.`, - statusError: 'Failed to get gateway status', - oneLineInstall: `If you are doing one line install: https://docs.defguard.net/getting-started/one-line-install - you don't need to do anything.`, - fromPackage: `Install the package available at https://github.com/DefGuard/gateway/releases/latest and configure \`/etc/defguard/gateway.toml\` - according to the [documentation]({setupGatewayDocs:string}).`, - authToken: `Token below is required to authenticate and configure the gateway node. Ensure you keep this token secure and follow the deployment instructions - provided in the [documentation]({setupGatewayDocs:string}) to successfully set up the gateway server. - For more details and exact steps, please refer to the [documentation]({setupGatewayDocs:string}).`, - dockerBasedGatewaySetup: `Below is a Docker based example. For more details and exact steps, please refer to the [documentation]({setupGatewayDocs:string}).`, - }, - }, - loginPage: { - pageTitle: 'Enter your credentials', - oidcLogin: 'Sign in with', - callback: { - return: 'Go back to login', - error: 'An error occurred during external OpenID login', - }, - mfa: { - title: 'Two-factor authentication', - controls: { - useAuthenticator: 'Use Authenticator app instead', - useWebauthn: 'Use security key instead', - useRecoveryCode: 'Use recovery code instead', - useEmail: 'Use E-mail instead', - }, - email: { - header: 'Use code we sent to your e-mail to proceed.', - form: { - labels: { - code: 'Code', - }, - controls: { - resendCode: 'Resend Code', - }, - }, - }, - totp: { - header: 'Use code from your authentication app and click button to proceed.', - form: { - fields: { - code: { - placeholder: 'Enter Authenticator code', - }, - }, - controls: { - submit: 'Use authenticator code', - }, - }, - }, - recoveryCode: { - header: 'Enter one of active recovery codes and click button to log in.', - form: { - fields: { - code: { - placeholder: 'Recovery code', - }, - }, - controls: { - submit: 'Use recovery code', - }, - }, - }, - webauthn: { - header: 'When you are ready to authenticate, press the button below.', - controls: { - submit: 'Use security key', - }, - messages: { - error: 'Failed to read key. Please try again.', - }, - }, - }, - }, - wizard: { - completed: 'Location setup completed', - configuration: { - successMessage: 'Location created', - }, - welcome: { - header: 'Welcome to location wizard!', - sub: 'Before you start using VPN you need to setup your first location. When in doubt click on icon.', - button: 'Setup location', - }, - navigation: { - top: 'Location setup', - titles: { - welcome: 'Location setup', - choseNetworkSetup: 'Chose Location setup', - importConfig: 'Import existing location', - manualConfig: 'Configure location', - mapDevices: 'Map imported devices', - }, - buttons: { - next: 'Next', - back: 'Back', - }, - }, - deviceMap: { - messages: { - crateSuccess: 'Devices added', - errorsInForm: 'Please fill marked fields.', - }, - list: { - headers: { - deviceName: 'Device Name', - deviceIP: 'IP', - user: 'User', - }, - }, - }, - wizardType: { - manual: { - title: 'Manual Configuration', - description: 'Manual location configuration', - }, - import: { - title: 'Import From File', - description: 'Import from WireGuard config file', - }, - createNetwork: 'Create location', - }, - common: { - select: 'Select', - }, - locations: { - form: { - name: 'Name', - ip: 'IP address', - user: 'User', - fileName: 'File', - selectFile: 'Select file', - messages: { devicesCreated: 'Devices created' }, - validation: { invalidAddress: 'Invalid address' }, - }, - }, - }, - layout: { - select: { - addNewOptionDefault: 'Add new +', - }, - }, - redirectPage: { - title: 'You have been logged in', - subtitle: 'You will be redirected in a moment...', - }, - enrollmentPage: { - title: 'Enrollment', - controls: { - default: 'Restore default', - save: 'Save changes', - }, - messages: { - edit: { - success: 'Settings changed', - error: 'Save failed', - }, - }, - messageBox: - 'Enrollment is a process by which a new employee will be able to activate their new account, create a password and configure a VPN device. You can customize it here.', - settings: { - welcomeMessage: { - title: 'Welcome message', - messageBox: - 'This information will be displayed for user in service once enrollment is completed. We advise to insert links and explain next steps briefly. You can use same message as in the e-mail.', - }, - vpnOptionality: { - title: 'VPN set optionallity', - select: { - options: { - optional: 'Optional', - mandatory: 'Mandatory', - }, - }, - }, - welcomeEmail: { - title: 'Welcome e-mail', - subject: { - label: 'E-mail subject', - }, - messageBox: - 'This information will be sent to user once enrollment is completed. We advise to insert links and explain next steps briefly.', - controls: { - duplicateWelcome: 'Same as welcome message', - }, - }, - }, - }, - supportPage: { - title: 'Support', - modals: { - confirmDataSend: { - title: 'Send Support Data', - subTitle: - 'Please confirm that you actually want to send support debug information. None of your private information will be sent (wireguard keys, email addresses, etc. will not be sent).', - submit: 'Send support data', - }, - }, - debugDataCard: { - title: 'Support data', - body: ` -If you need assistance or you were asked to generate support data by our team (for example on our Matrix support channel: **#defguard-support:teonite.com**), you have two options: -* Either you can configure SMTP settings and click "Send support data" -* Or click "Download support data" and create a bug report in our GitHub attaching this file. -`, - downloadSupportData: 'Download support data', - downloadLogs: 'Download logs', - sendMail: 'Send support data', - mailSent: 'Email sent', - mailError: 'Error sending email', - }, - supportCard: { - title: 'Support', - body: ` -Before contacting or submitting any issues to GitHub please get familiar with Defguard documentation available at [docs.defguard.net](https://docs.defguard.net/) - -To submit: -* Bugs - please go to [GitHub](https://github.com/DefGuard/defguard/issues/new?assignees=&labels=bug&template=bug_report.md&title=) -* Feature request - please go to [GitHub](https://github.com/DefGuard/defguard/issues/new?assignees=&labels=feature&template=feature_request.md&title=) - -Any other requests you can reach us at: support@defguard.net -`, - }, - }, - devicesPage: { - title: 'Network Devices', - search: { - placeholder: 'Find', - }, - bar: { - itemsCount: 'All devices', - filters: {}, - actions: { - addNewDevice: 'Add new', - }, - }, - list: { - columns: { - labels: { - name: 'Device Name', - location: 'Location', - assignedIps: 'IP Addresses', - description: 'Description', - addedBy: 'Added By', - addedAt: 'Add Date', - edit: 'Edit', - }, - }, - edit: { - actionLabels: { - config: 'View config', - generateToken: 'Generate auth token', - }, - }, - }, - }, - acl: { - messageBoxes: { - aclAliasKind: { - component: { - name: 'Component', - description: 'combined with manually configured destination fields in ACL', - }, - destination: { - name: 'Destination', - description: 'translated into a separate set of firewall rules', - }, - }, - networkSelectionIndicatorsHelper: { - //md - denied: ` - Location access **denied** by default – network traffic not explicitly defined by the rules will be blocked. - `, - //md - allowed: ` - Location access **allowed** by default – network traffic not explicitly defined by the rules will be passed. - `, - //md - unmanaged: ` - Location access unmanaged (ACL disabled) - `, - }, - }, - sharedTitle: 'Access Control List', - fieldsSelectionLabels: { - ports: 'All ports', - protocols: 'All protocols', - }, - ruleStatus: { - new: 'New', - applied: 'Applied', - modified: 'Pending Change', - deleted: 'Pending Deletion', - enable: 'Enable', - enabled: 'Enabled', - disable: 'Disable', - disabled: 'Disabled', - expired: 'Expired', - }, - listPage: { - tabs: { - rules: 'Rules', - aliases: 'Aliases', - }, - message: { - changeDiscarded: 'Change discarded', - changeAdded: 'Pending change added', - changeFail: 'Failed to make change', - applyChanges: 'Pending changes applied', - applyFail: 'Failed to apply changes', - }, - rules: { - modals: { - applyConfirm: { - title: 'Deploy pending changes', - subtitle: '{count: number} changes will be deployed', - submit: 'Deploy changes', - }, - filterGroupsModal: { - groupHeaders: { - alias: 'Aliases', - location: 'Locations', - groups: 'Groups', - status: 'Status', - }, - submit: 'Save Filter', - }, - }, - listControls: { - searchPlaceholder: 'Find name', - addNew: 'Add new', - filter: { - nothingApplied: 'Filter', - applied: 'Filters ({count: number})', - }, - apply: { - noChanges: 'Deploy pending changes', - all: 'Deploy pending changes ({count: number})', - selective: 'Deploy selected changes ({count: number})', - }, - }, - list: { - pendingList: { - title: 'Pending Changes', - noData: 'No pending changes', - noDataSearch: 'No pending changes found', - }, - deployedList: { - title: 'Deployed Rules', - noData: 'No deployed rules', - noDataSearch: 'No deployed rules found', - }, - headers: { - name: 'Rule name', - id: 'ID', - destination: 'Destination', - allowed: 'Allowed', - denied: 'Denied', - locations: 'Locations', - status: 'Status', - edit: 'Edit', - }, - tags: { - all: 'All', - allDenied: 'All denied', - allAllowed: 'All allowed', - }, - editMenu: { - discard: 'Discard Changes', - delete: 'Mark for Deletion', - }, - }, - }, - aliases: { - message: { - rulesApply: 'Pending changes applied', - rulesApplyFail: 'Failed to apply changes', - aliasDeleted: 'Alias deleted', - aliasDeleteFail: 'Alias deletion failed', - }, - modals: { - applyConfirm: { - title: 'Confirm Alias Deployment', - message: `The updated aliases will modify the following rule(s) currently deployed on the gateway.\nPlease ensure these changes are intended before proceeding.`, - listLabel: 'Affected Rules', - submit: 'Deploy Changes', - }, - deleteBlock: { - title: 'Deletion blocked', - //md - content: ` -This alias is currently in use by the following rule(s) and cannot be deleted. To proceed with deletion, you must first remove it from these rules({rulesCount: number}): -`, - }, - filterGroupsModal: { - groupLabels: { - rules: 'Rules', - status: 'Status', - }, - }, - create: { - labels: { - name: 'Alias name', - kind: 'Alias kind', - ip: 'IPv4/6 CIDR range address', - ports: 'Ports or Port Ranges', - protocols: 'Protocols', - }, - placeholders: { - protocols: 'All Protocols', - ports: 'All Ports', - ip: 'All IP addresses', - }, - kindOptions: { - destination: 'Destination', - component: 'Component', - }, - controls: { - cancel: 'Cancel', - edit: 'Edit Alias', - create: 'Create Alias', - }, - messages: { - modified: 'Alias modified', - created: 'Alias created', - }, - }, - }, - listControls: { - searchPlaceholder: 'Find name', - addNew: 'Add new', - filter: { - nothingApplied: 'Filter', - applied: 'Filters ({count: number})', - }, - apply: { - noChanges: 'Deploy pending changes', - all: 'Deploy pending changes ({count: number})', - selective: 'Deploy selected changes ({count: number})', - }, - }, - list: { - pendingList: { - title: 'Pending Changes', - noData: 'No pending changes', - noDataSearch: 'No pending changes found', - }, - deployedList: { - title: 'Deployed Aliases', - noData: 'No deployed aliases', - noDataSearch: 'No deployed aliases found', - }, - headers: { - id: 'ID', - name: 'Alias name', - kind: 'Alias kind', - ip: 'IPv4/6 CIDR range address', - ports: 'Ports', - protocols: 'Protocols', - status: 'Status', - edit: 'Edit', - rules: 'Rules', - }, - status: { - applied: 'Applied', - changed: 'Modified', - }, - tags: { - allDenied: 'All denied', - allAllowed: 'All allowed', - }, - editMenu: { - discardChanges: 'Discard changes', - delete: 'Delete alias', - }, - }, - }, - }, - createPage: { - formError: { - allowDenyConflict: 'Conflicting members', - allowNotConfigured: 'Must configure some allowed users, groups or devices', - }, - infoBox: { - // md - allowInstructions: ` - Specify one or more fields (Users, Groups or Devices) to define this rule. The rule will consider all inputs provided for matching conditions. Leave any fields blank if not needed.`, - // md - destinationInstructions: ` - Specify one or more fields (IP Addresses or Ports) to define this rule. The rule will consider all inputs provided for matching conditions. Leave any fields blank if not needed.`, - }, - message: { - create: 'Rule created and added to pending changes.', - createFail: 'Rule creation failed', - }, - headers: { - rule: 'Rule', - createRule: 'Create Rule', - allowed: 'Allowed Users/Groups/Devices', - denied: 'Denied Users/Groups/Devices', - destination: 'Destination', - }, - labels: { - name: 'Rule name', - priority: 'Priority', - status: 'Status', - locations: 'Locations', - allowAllUsers: 'Allow all users', - allowAllNetworks: 'Include all locations', - allowAllNetworkDevices: 'Allow all network devices', - denyAllUsers: 'Deny all users', - denyAllNetworkDevices: 'Deny all network devices', - users: 'Users', - groups: 'Groups', - devices: 'Network devices', - protocols: 'Protocols', - manualIp: 'IPv4/6 CIDR range or address', - ports: 'Ports', - aliases: 'Aliases', - expires: 'Expiration Date', - manualInput: 'Manual Input', - }, - placeholders: { - allProtocols: 'All protocols', - allIps: 'All IP addresses', - }, - }, - }, - activity: { - title: 'Activity log', - modals: { - timeRange: { - title: 'Activity time', - }, - }, - list: { - allLabel: 'All activity', - headers: { - date: 'Date', - user: 'User', - ip: 'IP', - location: 'Location', - event: 'Event', - module: 'Module', - device: 'Device', - description: 'Description', - }, - noData: { - data: 'No activities present', - search: 'No activities found', - }, - }, - }, - enums: { - activityLogEventType: { - user_login: 'User login', - user_login_failed: 'User login failed', - user_mfa_login: 'User MFA login', - user_mfa_login_failed: 'User MFA login failed', - recovery_code_used: 'Recovery code used', - user_logout: 'User logout', - user_added: 'User added', - user_removed: 'User removed', - user_modified: 'User modified', - user_groups_modified: 'User groups modified', - mfa_enabled: 'MFA enabled', - mfa_disabled: 'MFA disabled', - user_mfa_disabled: 'User MFA disabled', - mfa_totp_enabled: 'MFA TOTP enabled', - mfa_totp_disabled: 'MFA TOTP disabled', - mfa_email_enabled: 'MFA email enabled', - mfa_email_disabled: 'MFA email disabled', - mfa_security_key_added: 'MFA security key added', - mfa_security_key_removed: 'MFA security key removed', - device_added: 'Device added', - device_removed: 'Device removed', - device_modified: 'Device modified', - network_device_added: 'Network device added', - network_device_removed: 'Network device removed', - network_device_modified: 'Network device modified', - activity_log_stream_created: 'Activity log stream created', - activity_log_stream_modified: 'Activity log stream modified', - activity_log_stream_removed: 'Activity log stream removed', - vpn_client_connected: 'VPN client connected', - vpn_client_disconnected: 'VPN client disconnected', - vpn_client_connected_mfa: 'VPN client connected to MFA location', - vpn_client_disconnected_mfa: 'VPN client disconnected from MFA location', - vpn_client_mfa_failed: 'VPN client failed MFA authentication', - enrollment_token_added: 'Enrollment token added', - enrollment_started: 'Enrollment started', - enrollment_device_added: 'Device added', - enrollment_completed: 'Enrollment completed', - password_reset_requested: 'Password reset requested', - password_reset_started: 'Password reset started', - password_reset_completed: 'Password reset completed', - vpn_location_added: 'VPN location added', - vpn_location_removed: 'VPN location removed', - vpn_location_modified: 'VPN location modified', - api_token_added: 'API token added', - api_token_removed: 'API token removed', - api_token_renamed: 'API token renamed', - open_id_app_added: 'OpenID app added', - open_id_app_removed: 'OpenID app removed', - open_id_app_modified: 'OpenID app modified', - open_id_app_state_changed: 'OpenID app state changed', - open_id_provider_removed: 'OpenID provider removed', - open_id_provider_modified: 'OpenID provider modified', - settings_updated: 'Settings updated', - settings_updated_partial: 'Settings partially updated', - settings_default_branding_restored: 'Default branding restored', - groups_bulk_assigned: 'Groups bulk assigned', - group_added: 'Group added', - group_modified: 'Group modified', - group_removed: 'Group removed', - group_member_added: 'Group member added', - group_member_removed: 'Group member removed', - group_members_modified: 'Group members modified', - web_hook_added: 'Webhook added', - web_hook_modified: 'Webhook modified', - web_hook_removed: 'Webhook removed', - web_hook_state_changed: 'Webhook state changed', - authentication_key_added: 'Authentication key added', - authentication_key_removed: 'Authentication key removed', - authentication_key_renamed: 'Authentication key renamed', - password_changed: 'Password changed', - password_changed_by_admin: 'Password changed by admin', - password_reset: 'Password reset', - client_configuration_token_added: 'Client configuration token added', - user_snat_binding_added: 'User SNAT binding added', - user_snat_binding_modified: 'User SNAT binding modified', - user_snat_binding_removed: 'User SNAT binding removed', - }, - activityLogModule: { - defguard: 'Defguard', - client: 'Client', - enrollment: 'Enrollment', - vpn: 'VPN', - }, - }, -}; - -export default en; diff --git a/web/src/i18n/formatters.ts b/web/src/i18n/formatters.ts deleted file mode 100644 index 6f3d0997e..000000000 --- a/web/src/i18n/formatters.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { FormattersInitializer } from 'typesafe-i18n'; - -import type { Formatters, Locales } from './i18n-types'; - -export const initFormatters: FormattersInitializer = ( - //@ts-ignore - locale: Locales, -) => { - const formatters: Formatters = { - // add your formatter functions here - }; - - return formatters; -}; diff --git a/web/src/i18n/i18n-react.tsx b/web/src/i18n/i18n-react.tsx deleted file mode 100644 index f113051fa..000000000 --- a/web/src/i18n/i18n-react.tsx +++ /dev/null @@ -1,16 +0,0 @@ -// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. -/* eslint-disable */ - -import { useContext } from 'react' -import { initI18nReact } from 'typesafe-i18n/react' -import type { I18nContextType } from 'typesafe-i18n/react' -import type { Formatters, Locales, TranslationFunctions, Translations } from './i18n-types' -import { loadedFormatters, loadedLocales } from './i18n-util' - -const { component: TypesafeI18n, context: I18nContext } = initI18nReact(loadedLocales, loadedFormatters) - -const useI18nContext = (): I18nContextType => useContext(I18nContext) - -export { I18nContext, useI18nContext } - -export default TypesafeI18n diff --git a/web/src/i18n/i18n-types.ts b/web/src/i18n/i18n-types.ts deleted file mode 100644 index 2bd9e2058..000000000 --- a/web/src/i18n/i18n-types.ts +++ /dev/null @@ -1,13367 +0,0 @@ -// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. -/* eslint-disable */ -import type { BaseTranslation as BaseTranslationType, LocalizedString, RequiredParams } from 'typesafe-i18n' - -export type BaseTranslation = BaseTranslationType -export type BaseLocale = 'en' - -export type Locales = - | 'en' - | 'ko' - | 'pl' - -export type Translation = RootTranslation - -export type Translations = RootTranslation - -type RootTranslation = { - common: { - conditions: { - /** - * o​r - */ - or: string - /** - * a​n​d - */ - and: string - /** - * e​q​u​a​l - */ - equal: string - } - controls: { - /** - * T​i​m​e​ ​r​a​n​g​e - */ - timeRange: string - /** - * A​d​d​ ​n​e​w - */ - addNew: string - /** - * A​d​d - */ - add: string - /** - * A​c​c​e​p​t - */ - accept: string - /** - * N​e​x​t - */ - next: string - /** - * B​a​c​k - */ - back: string - /** - * C​a​n​c​e​l - */ - cancel: string - /** - * C​o​n​f​i​r​m - */ - confirm: string - /** - * S​u​b​m​i​t - */ - submit: string - /** - * C​l​o​s​e - */ - close: string - /** - * S​e​l​e​c​t - */ - select: string - /** - * F​i​n​i​s​h - */ - finish: string - /** - * S​a​v​e​ ​c​h​a​n​g​e​s - */ - saveChanges: string - /** - * S​a​v​e - */ - save: string - /** - * R​e​s​t​o​r​e​ ​d​e​f​a​u​l​t - */ - RestoreDefault: string - /** - * D​e​l​e​t​e - */ - 'delete': string - /** - * R​e​n​a​m​e - */ - rename: string - /** - * C​o​p​y - */ - copy: string - /** - * E​d​i​t - */ - edit: string - /** - * D​i​s​m​i​s​s - */ - dismiss: string - /** - * S​h​o​w - */ - show: string - /** - * E​n​a​b​l​e - */ - enable: string - /** - * E​n​a​b​l​e​d - */ - enabled: string - /** - * D​i​s​a​b​l​e - */ - disable: string - /** - * D​i​s​a​b​l​e​d - */ - disabled: string - /** - * S​e​l​e​c​t​ ​a​l​l - */ - selectAll: string - /** - * C​l​e​a​r - */ - clear: string - /** - * C​l​e​a​r​ ​a​l​l - */ - clearAll: string - /** - * F​i​l​t​e​r - */ - filter: string - /** - * F​i​l​t​e​r​s - */ - filters: string - } - /** - * K​e​y - */ - key: string - /** - * N​a​m​e - */ - name: string - /** - * N​o​ ​d​a​t​a - */ - noData: string - /** - * U​n​a​v​a​i​l​a​b​l​e - */ - unavailable: string - /** - * N​o​t​ ​s​e​t - */ - notSet: string - /** - * S​e​a​r​c​h - */ - search: string - /** - * T​i​m​e - */ - time: string - /** - * F​r​o​m - */ - from: string - /** - * U​n​t​i​l - */ - until: string - } - messages: { - /** - * E​r​r​o​r​ ​h​a​s​ ​o​c​c​u​r​r​e​d​. - */ - error: string - /** - * O​p​e​r​a​t​i​o​n​ ​s​u​c​c​e​e​d​e​d - */ - success: string - /** - * F​a​i​l​e​d​ ​t​o​ ​g​e​t​ ​a​p​p​l​i​c​a​t​i​o​n​ ​v​e​r​s​i​o​n​. - */ - errorVersion: string - /** - * C​o​n​t​e​x​t​ ​i​s​ ​n​o​t​ ​s​e​c​u​r​e​. - */ - insecureContext: string - /** - * D​e​t​a​i​l​s​: - */ - details: string - clipboard: { - /** - * C​l​i​p​b​o​a​r​d​ ​i​s​ ​n​o​t​ ​a​c​c​e​s​s​i​b​l​e​. - */ - error: string - /** - * C​o​n​t​e​n​t​ ​c​o​p​i​e​d​ ​t​o​ ​c​l​i​p​b​o​a​r​d​. - */ - success: string - } - } - modals: { - outdatedComponentsModal: { - /** - * V​e​r​s​i​o​n​ ​m​i​s​m​a​t​c​h - */ - title: string - /** - * D​e​f​g​u​a​r​d​ ​d​e​t​e​c​t​e​d​ ​u​n​s​u​p​p​o​r​t​e​d​ ​v​e​r​s​i​o​n​ ​i​n​ ​s​o​m​e​ ​c​o​m​p​o​n​e​n​t​s​. - */ - subtitle: string - content: { - /** - * I​n​c​o​m​p​a​t​i​b​l​e​ ​c​o​m​p​o​n​e​n​t​s​: - */ - title: string - /** - * U​n​k​n​o​w​n​ ​v​e​r​s​i​o​n - */ - unknownVersion: string - /** - * U​n​k​n​o​w​n​ ​h​o​s​t​n​a​m​e - */ - unknownHostname: string - } - } - upgradeLicenseModal: { - enterprise: { - /** - * U​p​g​r​a​d​e​ ​t​o​ ​E​n​t​e​r​p​r​i​s​e - */ - title: string - /** - * T​h​i​s​ ​f​u​n​c​t​i​o​n​a​l​i​t​y​ ​i​s​ ​a​n​ ​*​*​e​n​t​e​r​p​r​i​s​e​ ​f​e​a​t​u​r​e​*​*​ ​a​n​d​ ​y​o​u​'​v​e​ ​e​x​c​e​e​d​e​d​ ​t​h​e​ ​u​s​e​r​,​ ​d​e​v​i​c​e​ ​o​r​ ​n​e​t​w​o​r​k​ ​l​i​m​i​t​s​ ​t​o​ ​u​s​e​ ​i​t​.​ ​I​n​ ​o​r​d​e​r​ ​t​o​ ​u​s​e​ ​t​h​i​s​ ​f​e​a​t​u​r​e​,​ ​p​u​r​c​h​a​s​e​ ​a​n​ ​e​n​t​e​r​p​r​i​s​e​ ​l​i​c​e​n​s​e​ ​o​r​ ​u​p​g​r​a​d​e​ ​y​o​u​r​ ​e​x​i​s​t​i​n​g​ ​o​n​e​. - */ - subTitle: string - } - limit: { - /** - * U​p​g​r​a​d​e - */ - title: string - /** - * - ​ ​ ​ ​ ​ ​ ​ ​ ​Y​o​u​ ​h​a​v​e​ ​*​*​r​e​a​c​h​e​d​ ​t​h​e​ ​l​i​m​i​t​*​*​ ​o​f​ ​t​h​i​s​ ​f​u​n​c​t​i​o​n​a​l​i​t​y​.​ ​T​o​ ​*​*​[​ ​m​a​n​a​g​e​ ​m​o​r​e​ ​l​o​c​a​t​i​o​n​s​/​u​s​e​r​s​/​d​e​v​i​c​e​s​ ​]​*​*​ ​p​u​r​c​h​a​s​e​ ​o​f​ ​t​h​e​ ​E​n​t​e​r​p​r​i​s​e​ ​l​i​c​e​n​s​e​ ​i​s​ ​r​e​q​u​i​r​e​d​.​ - ​ ​ ​ ​ ​ ​ ​ ​ - */ - subTitle: string - } - /** - * - ​Y​o​u​ ​c​a​n​ ​f​i​n​d​ ​o​u​t​ ​m​o​r​e​ ​a​b​o​u​t​ ​f​e​a​t​u​r​e​s​ ​l​i​k​e​:​ - ​-​ ​R​e​a​l​ ​t​i​m​e​ ​a​n​d​ ​a​u​t​o​m​a​t​i​c​ ​c​l​i​e​n​t​ ​s​y​n​c​h​r​o​n​i​z​a​t​i​o​n​ - ​-​ ​E​x​t​e​r​n​a​l​ ​S​S​O​ - ​-​ ​C​o​n​t​r​o​l​l​i​n​g​ ​V​P​N​ ​c​l​i​e​n​t​s​ ​b​e​h​a​v​i​o​r​ - ​ - ​F​u​l​l​ ​e​n​t​e​r​p​r​i​s​e​ ​f​e​a​t​u​r​e​ ​l​i​s​t​:​ ​[​h​t​t​p​s​:​/​/​d​o​c​s​.​d​e​f​g​u​a​r​d​.​n​e​t​/​e​n​t​e​r​p​r​i​s​e​/​e​n​t​e​r​p​r​i​s​e​-​f​e​a​t​u​r​e​s​]​(​h​t​t​p​s​:​/​/​d​o​c​s​.​d​e​f​g​u​a​r​d​.​n​e​t​/​e​n​t​e​r​p​r​i​s​e​/​e​n​t​e​r​p​r​i​s​e​-​f​e​a​t​u​r​e​s​)​<​/​b​r​>​ - ​L​i​c​e​n​s​i​n​g​ ​i​n​f​o​r​m​a​t​i​o​n​:​ ​[​h​t​t​p​s​:​/​/​d​o​c​s​.​d​e​f​g​u​a​r​d​.​n​e​t​/​e​n​t​e​r​p​r​i​s​e​/​l​i​c​e​n​s​e​]​(​h​t​t​p​s​:​/​/​d​o​c​s​.​d​e​f​g​u​a​r​d​.​n​e​t​/​e​n​t​e​r​p​r​i​s​e​/​l​i​c​e​n​s​e​)​ - ​ ​ ​ ​ ​ ​ - */ - content: string - controls: { - /** - * M​a​y​b​e​ ​l​a​t​e​r - */ - cancel: string - /** - * S​e​e​ ​a​l​l​ ​E​n​t​e​r​p​r​i​s​e​ ​p​l​a​n​s - */ - confirm: string - } - } - standaloneDeviceEnrollmentModal: { - /** - * N​e​t​w​o​r​k​ ​d​e​v​i​c​e​ ​t​o​k​e​n - */ - title: string - toasters: { - /** - * T​o​k​e​n​ ​g​e​n​e​r​a​t​i​o​n​ ​f​a​i​l​e​d​. - */ - error: string - } - } - standaloneDeviceConfigModal: { - /** - * N​e​t​w​o​r​k​ ​d​e​v​i​c​e​ ​c​o​n​f​i​g - */ - title: string - /** - * C​o​n​f​i​g - */ - cardTitle: string - toasters: { - getConfig: { - /** - * F​a​i​l​e​d​ ​t​o​ ​g​e​t​ ​d​e​v​i​c​e​ ​c​o​n​f​i​g​. - */ - error: string - } - } - } - editStandaloneModal: { - /** - * E​d​i​t​ ​n​e​t​w​o​r​k​ ​d​e​v​i​c​e - */ - title: string - toasts: { - /** - * D​e​v​i​c​e​ ​m​o​d​i​f​i​e​d - */ - success: string - /** - * M​o​d​i​f​y​i​n​g​ ​t​h​e​ ​d​e​v​i​c​e​ ​f​a​i​l​e​d - */ - failure: string - } - } - deleteStandaloneDevice: { - /** - * D​e​l​e​t​e​ ​n​e​t​w​o​r​k​ ​d​e​v​i​c​e - */ - title: string - /** - * D​e​v​i​c​e​ ​{​n​a​m​e​}​ ​w​i​l​l​ ​b​e​ ​d​e​l​e​t​e​d​. - * @param {string} name - */ - content: RequiredParams<'name'> - messages: { - /** - * D​e​v​i​c​e​ ​d​e​l​e​t​e​d - */ - success: string - /** - * F​a​i​l​e​d​ ​t​o​ ​r​e​m​o​v​e​ ​d​e​v​i​c​e​. - */ - error: string - } - } - addStandaloneDevice: { - toasts: { - /** - * D​e​v​i​c​e​ ​a​d​d​e​d - */ - deviceCreated: string - /** - * D​e​v​i​c​e​ ​c​o​u​l​d​ ​n​o​t​ ​b​e​ ​a​d​d​e​d​. - */ - creationFailed: string - } - infoBox: { - /** - * H​e​r​e​ ​y​o​u​ ​c​a​n​ ​a​d​d​ ​d​e​f​i​n​i​t​i​o​n​s​ ​o​r​ ​g​e​n​e​r​a​t​e​ ​c​o​n​f​i​g​u​r​a​t​i​o​n​s​ ​f​o​r​ ​d​e​v​i​c​e​s​ ​t​h​a​t​ ​c​a​n​ ​c​o​n​n​e​c​t​ ​t​o​ ​y​o​u​r​ ​V​P​N​.​ ​O​n​l​y​ ​l​o​c​a​t​i​o​n​s​ ​w​i​t​h​o​u​t​ ​M​u​l​t​i​-​F​a​c​t​o​r​ ​A​u​t​h​e​n​t​i​c​a​t​i​o​n​ ​a​r​e​ ​a​v​a​i​l​a​b​l​e​ ​h​e​r​e​,​ ​a​s​ ​M​F​A​ ​i​s​ ​o​n​l​y​ ​s​u​p​p​o​r​t​e​d​ ​i​n​ ​D​e​f​g​u​a​r​d​ ​D​e​s​k​t​o​p​ ​C​l​i​e​n​t​ ​f​o​r​ ​n​o​w​. - */ - setup: string - } - form: { - /** - * A​d​d​ ​D​e​v​i​c​e - */ - submit: string - labels: { - /** - * D​e​v​i​c​e​ ​N​a​m​e - */ - deviceName: string - /** - * L​o​c​a​t​i​o​n - */ - location: string - /** - * A​s​s​i​g​n​e​d​ ​I​P​ ​A​d​d​r​e​s​s - */ - assignedAddress: string - /** - * D​e​s​c​r​i​p​t​i​o​n - */ - description: string - generation: { - /** - * G​e​n​e​r​a​t​e​ ​k​e​y​ ​p​a​i​r - */ - auto: string - /** - * U​s​e​ ​m​y​ ​o​w​n​ ​p​u​b​l​i​c​ ​k​e​y - */ - manual: string - } - /** - * P​r​o​v​i​d​e​ ​Y​o​u​r​ ​P​u​b​l​i​c​ ​K​e​y - */ - publicKey: string - } - } - steps: { - method: { - /** - * C​h​o​o​s​e​ ​a​ ​p​r​e​f​e​r​r​e​d​ ​m​e​t​h​o​d - */ - title: string - cards: { - cli: { - /** - * D​e​f​g​u​a​r​d​ ​C​o​m​m​a​n​d​ ​L​i​n​e​ ​C​l​i​e​n​t - */ - title: string - /** - * W​h​e​n​ ​u​s​i​n​g​ ​d​e​f​g​u​a​r​d​-​c​l​i​ ​y​o​u​r​ ​d​e​v​i​c​e​ ​w​i​l​l​ ​b​e​ ​a​u​t​o​m​a​t​i​c​a​l​l​y​ ​c​o​n​f​i​g​u​r​e​d​. - */ - subtitle: string - /** - * D​e​f​g​u​a​r​d​ ​C​L​I​ ​d​o​w​n​l​o​a​d​ ​a​n​d​ ​d​o​c​u​m​e​n​t​a​t​i​o​n - */ - docs: string - } - manual: { - /** - * M​a​n​u​a​l​ ​W​i​r​e​G​u​a​r​d​ ​C​l​i​e​n​t - */ - title: string - /** - * I​f​ ​y​o​u​r​ ​d​e​v​i​c​e​ ​d​o​e​s​ ​n​o​t​ ​s​u​p​p​o​r​t​ ​o​u​r​ ​C​L​I​ ​b​i​n​a​r​i​e​s​ ​y​o​u​ ​c​a​n​ ​a​l​w​a​y​s​ ​g​e​n​e​r​a​t​e​ ​a​ ​W​i​r​e​G​u​a​r​d​ ​c​o​n​f​i​g​u​r​a​t​i​o​n​ ​f​i​l​e​ ​a​n​d​ ​c​o​n​f​i​g​u​r​e​ ​i​t​ ​m​a​n​u​a​l​l​y​ ​-​ ​b​u​t​ ​a​n​y​ ​u​p​d​a​t​e​s​ ​t​o​ ​t​h​e​ ​V​P​N​ ​l​o​c​a​t​i​o​n​ ​c​o​n​f​i​g​u​r​a​t​i​o​n​ ​w​i​l​l​ ​r​e​q​u​i​r​e​ ​m​a​n​u​a​l​ ​c​h​a​n​g​e​s​ ​i​n​ ​d​e​v​i​c​e​ ​c​o​n​f​i​g​u​r​a​t​i​o​n​. - */ - subtitle: string - } - } - } - manual: { - /** - * A​d​d​ ​n​e​w​ ​V​P​N​ ​d​e​v​i​c​e​ ​u​s​i​n​g​ ​W​i​r​e​G​u​a​r​d​ ​C​l​i​e​n​t - */ - title: string - finish: { - /** - * D​o​w​n​l​o​a​d​ ​t​h​e​ ​p​r​o​v​i​d​e​d​ ​c​o​n​f​i​g​u​r​a​t​i​o​n​ ​f​i​l​e​ ​t​o​ ​y​o​u​r​ ​d​e​v​i​c​e​ ​a​n​d​ ​i​m​p​o​r​t​ ​i​t​ ​i​n​t​o​ ​y​o​u​r​ ​V​P​N​ ​c​l​i​e​n​t​ ​t​o​ ​c​o​m​p​l​e​t​e​ ​t​h​e​ ​s​e​t​u​p​. - */ - messageTop: string - /** - * U​s​e​ ​p​r​o​v​i​d​e​d​ ​c​o​n​f​i​g​u​r​a​t​i​o​n​ ​f​i​l​e​ ​b​e​l​o​w​ ​b​y​ ​s​c​a​n​n​i​n​g​ ​Q​R​ ​c​o​d​e​ ​o​r​ ​i​m​p​o​r​t​i​n​g​ ​i​t​ ​a​s​ ​f​i​l​e​ ​o​n​ ​y​o​u​r​ ​d​e​v​i​c​e​'​s​ ​W​i​r​e​G​u​a​r​d​ ​a​p​p​. - */ - ctaInstruction: string - /** - * - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​P​l​e​a​s​e​ ​r​e​m​e​m​b​e​r​ ​t​h​a​t​ ​D​e​f​g​u​a​r​d​ ​*​*​d​o​e​s​n​'​t​ ​s​t​o​r​e​ ​p​r​i​v​a​t​e​ ​k​e​y​s​*​*​.​ ​W​e​ ​w​i​l​l​ ​s​e​c​u​r​e​l​y​ ​g​e​n​e​r​a​t​e​ ​t​h​e​ ​p​u​b​l​i​c​ ​a​n​d​ ​p​r​i​v​a​t​e​ ​k​e​y​ ​p​a​i​r​ ​i​n​ ​y​o​u​r​ ​b​r​o​w​s​e​r​,​ ​b​u​t​ ​o​n​l​y​ ​s​t​o​r​e​ ​t​h​e​ ​p​u​b​l​i​c​ ​k​e​y​ ​i​n​ ​D​e​f​g​u​a​r​d​ ​d​a​t​a​b​a​s​e​.​ ​P​l​e​a​s​e​ ​d​o​w​n​l​o​a​d​ ​t​h​e​ ​c​o​n​f​i​g​u​r​a​t​i​o​n​ ​g​e​n​e​r​a​t​e​d​ ​w​i​t​h​ ​t​h​e​ ​p​r​i​v​a​t​e​ ​k​e​y​ ​f​o​r​ ​t​h​e​ ​d​e​v​i​c​e​,​ ​a​s​ ​i​t​ ​w​i​l​l​ ​n​o​t​ ​b​e​ ​a​c​c​e​s​s​i​b​l​e​ ​l​a​t​e​r​.​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ - */ - warningMessage: string - actionCard: { - /** - * C​o​n​f​i​g - */ - title: string - } - } - } - cli: { - /** - * A​d​d​ ​d​e​v​i​c​e​ ​u​s​i​n​g​ ​D​e​f​g​u​a​r​d​ ​C​o​m​m​a​n​d​ ​L​i​n​e​ ​C​l​i​e​n​t - */ - title: string - finish: { - /** - * F​i​r​s​t​ ​d​o​w​n​l​o​a​d​ ​D​e​f​g​u​a​r​d​ ​c​o​m​m​a​n​d​ ​l​i​n​e​ ​c​l​i​e​n​t​ ​b​i​n​a​r​y​ ​a​n​d​ ​i​n​s​t​a​l​l​ ​i​t​ ​o​n​ ​y​o​u​r​ ​s​e​r​v​e​r​. - */ - topMessage: string - /** - * D​o​w​n​l​o​a​d​ ​D​e​f​g​u​a​r​d​ ​C​L​I​ ​C​l​i​e​n​t - */ - downloadButton: string - /** - * C​o​p​y​ ​a​n​d​ ​p​a​s​t​e​ ​t​h​i​s​ ​c​o​m​m​a​n​d​ ​i​n​ ​y​o​u​r​ ​t​e​r​m​i​n​a​l​ ​o​n​ ​t​h​e​ ​d​e​v​i​c​e - */ - commandCopy: string - } - setup: { - /** - * H​e​r​e​ ​y​o​u​ ​c​a​n​ ​a​d​d​ ​d​e​f​i​n​i​t​i​o​n​s​ ​o​r​ ​g​e​n​e​r​a​t​e​ ​c​o​n​f​i​g​u​r​a​t​i​o​n​s​ ​f​o​r​ ​d​e​v​i​c​e​s​ ​t​h​a​t​ ​c​a​n​ ​c​o​n​n​e​c​t​ ​t​o​ ​y​o​u​r​ ​V​P​N​.​ ​O​n​l​y​ ​l​o​c​a​t​i​o​n​s​ ​w​i​t​h​o​u​t​ ​M​u​l​t​i​-​F​a​c​t​o​r​ ​A​u​t​h​e​n​t​i​c​a​t​i​o​n​ ​a​r​e​ ​a​v​a​i​l​a​b​l​e​ ​h​e​r​e​,​ ​a​s​ ​M​F​A​ ​i​s​ ​o​n​l​y​ ​s​u​p​p​o​r​t​e​d​ ​i​n​ ​D​e​f​g​u​a​r​d​ ​D​e​s​k​t​o​p​ ​C​l​i​e​n​t​ ​f​o​r​ ​n​o​w​. - */ - stepMessage: string - form: { - /** - * A​d​d​ ​D​e​v​i​c​e - */ - submit: string - } - } - } - } - } - updatesNotificationToaster: { - /** - * N​e​w​ ​v​e​r​s​i​o​n​ ​a​v​a​i​l​a​b​l​e​ ​{​v​e​r​s​i​o​n​} - * @param {string} version - */ - title: RequiredParams<'version'> - controls: { - /** - * S​e​e​ ​w​h​a​t​'​s​ ​n​e​w - */ - more: string - } - } - enterpriseUpgradeToaster: { - /** - * Y​o​u​'​v​e​ ​r​e​a​c​h​e​d​ ​t​h​e​ ​e​n​t​e​r​p​r​i​s​e​ ​f​u​n​c​t​i​o​n​a​l​i​t​y​ ​l​i​m​i​t​. - */ - title: string - /** - * Y​o​u​'​v​e​ ​e​x​c​e​e​d​e​d​ ​t​h​e​ ​l​i​m​i​t​ ​o​f​ ​y​o​u​r​ ​c​u​r​r​e​n​t​ ​D​e​f​g​u​a​r​d​ ​p​l​a​n​ ​a​n​d​ ​t​h​e​ ​e​n​t​e​r​p​r​i​s​e​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​f​e​a​t​u​r​e​s​ ​w​i​l​l​ ​b​e​ ​d​i​s​a​b​l​e​d​.​ ​P​u​r​c​h​a​s​e​ ​a​n​ ​e​n​t​e​r​p​r​i​s​e​ ​l​i​c​e​n​s​e​ ​o​r​ ​u​p​g​r​a​d​e​ ​y​o​u​r​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​e​x​i​s​t​i​n​g​ ​o​n​e​ ​t​o​ ​c​o​n​t​i​n​u​e​ ​u​s​i​n​g​ ​t​h​e​s​e​ ​f​e​a​t​u​r​e​s​. - */ - message: string - /** - * S​e​e​ ​a​l​l​ ​e​n​t​e​r​p​r​i​s​e​ ​p​l​a​n​s - */ - link: string - } - updatesNotification: { - header: { - /** - * U​p​d​a​t​e​ ​A​v​a​i​l​a​b​l​e - */ - title: string - /** - * n​e​w​ ​v​e​r​s​i​o​n​ ​{​v​e​r​s​i​o​n​} - * @param {string} version - */ - newVersion: RequiredParams<'version'> - /** - * c​r​i​t​i​c​a​l​ ​u​p​d​a​t​e - */ - criticalBadge: string - } - controls: { - /** - * V​i​s​i​t​ ​r​e​l​e​a​s​e​ ​p​a​g​e - */ - visitRelease: string - } - } - addGroup: { - /** - * A​d​d​ ​g​r​o​u​p - */ - title: string - /** - * S​e​l​e​c​t​ ​a​l​l​ ​u​s​e​r​s - */ - selectAll: string - /** - * G​r​o​u​p​ ​n​a​m​e - */ - groupName: string - /** - * F​i​l​t​e​r​/​S​e​a​r​c​h - */ - searchPlaceholder: string - /** - * C​r​e​a​t​e​ ​g​r​o​u​p - */ - submit: string - /** - * G​r​o​u​p​ ​s​e​t​t​i​n​g​s - */ - groupSettings: string - /** - * A​d​m​i​n​ ​g​r​o​u​p - */ - adminGroup: string - } - editGroup: { - /** - * E​d​i​t​ ​g​r​o​u​p - */ - title: string - /** - * S​e​l​e​c​t​ ​a​l​l​ ​u​s​e​r​s - */ - selectAll: string - /** - * G​r​o​u​p​ ​n​a​m​e - */ - groupName: string - /** - * F​i​l​t​e​r​/​S​e​a​r​c​h - */ - searchPlaceholder: string - /** - * U​p​d​a​t​e​ ​g​r​o​u​p - */ - submit: string - /** - * G​r​o​u​p​ ​s​e​t​t​i​n​g​s - */ - groupSettings: string - /** - * A​d​m​i​n​ ​g​r​o​u​p - */ - adminGroup: string - } - deleteGroup: { - /** - * D​e​l​e​t​e​ ​g​r​o​u​p​ ​{​n​a​m​e​} - * @param {string} name - */ - title: RequiredParams<'name'> - /** - * T​h​i​s​ ​a​c​t​i​o​n​ ​w​i​l​l​ ​p​e​r​m​a​n​e​n​t​l​y​ ​d​e​l​e​t​e​ ​t​h​i​s​ ​g​r​o​u​p​. - */ - subTitle: string - /** - * T​h​i​s​ ​g​r​o​u​p​ ​i​s​ ​c​u​r​r​e​n​t​l​y​ ​a​s​s​i​g​n​e​d​ ​t​o​ ​f​o​l​l​o​w​i​n​g​ ​V​P​N​ ​L​o​c​a​t​i​o​n​s​: - */ - locationListHeader: string - /** - * I​f​ ​t​h​i​s​ ​i​s​ ​t​h​e​ ​o​n​l​y​ ​a​l​l​o​w​e​d​ ​g​r​o​u​p​ ​f​o​r​ ​a​ ​g​i​v​e​n​ ​l​o​c​a​t​i​o​n​,​ ​t​h​e​ ​l​o​c​a​t​i​o​n​ ​w​i​l​l​ ​b​e​c​o​m​e​ ​<​b​>​a​c​c​e​s​s​i​b​l​e​ ​t​o​ ​a​l​l​ ​u​s​e​r​s​<​/​b​>​. - */ - locationListFooter: string - /** - * D​e​l​e​t​e​ ​g​r​o​u​p - */ - submit: string - /** - * C​a​n​c​e​l - */ - cancel: string - } - deviceConfig: { - /** - * D​e​v​i​c​e​ ​V​P​N​ ​c​o​n​f​i​g​u​r​a​t​i​o​n​s - */ - title: string - } - changePasswordSelf: { - /** - * C​h​a​n​g​e​ ​p​a​s​s​w​o​r​d - */ - title: string - messages: { - /** - * P​a​s​s​w​o​r​d​ ​h​a​s​ ​b​e​e​n​ ​c​h​a​n​g​e​d - */ - success: string - /** - * F​a​i​l​e​d​ ​t​o​ ​c​h​a​n​g​e​d​ ​p​a​s​s​w​o​r​d - */ - error: string - } - form: { - labels: { - /** - * N​e​w​ ​p​a​s​s​w​o​r​d - */ - newPassword: string - /** - * C​u​r​r​e​n​t​ ​p​a​s​s​w​o​r​d - */ - oldPassword: string - /** - * C​o​n​f​i​r​m​ ​n​e​w​ ​p​a​s​s​w​o​r​d - */ - repeat: string - } - } - controls: { - /** - * C​h​a​n​g​e​ ​p​a​s​s​w​o​r​d - */ - submit: string - /** - * C​a​n​c​e​l - */ - cancel: string - } - } - disableMfa: { - /** - * D​i​s​a​b​l​e​ ​M​F​A - */ - title: string - /** - * D​o​ ​y​o​u​ ​w​a​n​t​ ​t​o​ ​d​i​s​a​b​l​e​ ​M​F​A​ ​f​o​r​ ​u​s​e​r​ ​{​u​s​e​r​n​a​m​e​}​? - * @param {string} username - */ - message: RequiredParams<'username'> - messages: { - /** - * M​F​A​ ​f​o​r​ ​u​s​e​r​ ​{​u​s​e​r​n​a​m​e​}​ ​h​a​s​ ​b​e​e​n​ ​d​i​s​a​b​l​e​d - * @param {string} username - */ - success: RequiredParams<'username'> - /** - * F​a​i​l​e​d​ ​t​o​ ​d​i​s​a​b​l​e​ ​M​F​A​ ​f​o​r​ ​u​s​e​r​ ​{​u​s​e​r​n​a​m​e​} - * @param {string} username - */ - error: RequiredParams<'username'> - } - controls: { - /** - * D​i​s​a​b​l​e​ ​M​F​A - */ - submit: string - /** - * C​a​n​c​e​l - */ - cancel: string - } - } - startEnrollment: { - /** - * S​t​a​r​t​ ​e​n​r​o​l​l​m​e​n​t - */ - title: string - /** - * D​e​s​k​t​o​p​ ​a​c​t​i​v​a​t​i​o​n - */ - desktopTitle: string - messages: { - /** - * U​s​e​r​ ​e​n​r​o​l​l​m​e​n​t​ ​s​t​a​r​t​e​d - */ - success: string - /** - * D​e​s​k​t​o​p​ ​c​o​n​f​i​g​u​r​a​t​i​o​n​ ​s​t​a​r​t​e​d - */ - successDesktop: string - /** - * F​a​i​l​e​d​ ​t​o​ ​s​t​a​r​t​ ​u​s​e​r​ ​e​n​r​o​l​l​m​e​n​t - */ - error: string - /** - * F​a​i​l​e​d​ ​t​o​ ​s​t​a​r​t​ ​d​e​s​k​t​o​p​ ​a​c​t​i​v​a​t​i​o​n - */ - errorDesktop: string - } - messageBox: { - /** - * Y​o​u​ ​c​a​n​ ​s​h​a​r​e​ ​t​h​e​ ​f​o​l​l​o​w​i​n​g​ ​U​R​L​ ​a​n​d​ ​t​o​k​e​n​ ​w​i​t​h​ ​t​h​e​ ​u​s​e​r​ ​t​o​ ​c​o​n​f​i​g​u​r​e​ ​t​h​e​i​r​ ​D​e​f​g​u​a​r​d​ ​d​e​s​k​t​o​p​ ​o​r​ ​m​o​b​i​l​e​ ​c​l​i​e​n​t​. - */ - clientForm: string - /** - * Y​o​u​ ​c​a​n​ ​s​h​a​r​e​ ​t​h​i​s​ ​Q​R​ ​c​o​d​e​ ​f​o​r​ ​e​a​s​y​ ​D​e​f​g​u​a​r​d​ ​m​o​b​i​l​e​ ​c​l​i​e​n​t​ ​c​o​n​f​i​g​u​r​a​t​i​o​n​. - */ - clientQr: string - } - form: { - email: { - /** - * E​m​a​i​l - */ - label: string - } - mode: { - options: { - /** - * S​e​n​d​ ​t​o​k​e​n​ ​b​y​ ​e​m​a​i​l - */ - email: string - /** - * D​e​l​i​v​e​r​ ​t​o​k​e​n​ ​y​o​u​r​s​e​l​f - */ - manual: string - } - } - /** - * S​t​a​r​t​ ​e​n​r​o​l​l​m​e​n​t - */ - submit: string - /** - * A​c​t​i​v​a​t​e​ ​d​e​s​k​t​o​p - */ - submitDesktop: string - /** - * C​o​n​f​i​g​u​r​e​ ​S​M​T​P​ ​t​o​ ​s​e​n​d​ ​t​o​k​e​n​ ​b​y​ ​e​m​a​i​l​.​ ​G​o​ ​t​o​ ​S​e​t​t​i​n​g​s​ ​-​>​ ​S​M​T​P​. - */ - smtpDisabled: string - } - tokenCard: { - /** - * A​c​t​i​v​a​t​i​o​n​ ​t​o​k​e​n - */ - title: string - } - urlCard: { - /** - * D​e​f​g​u​a​r​d​ ​I​n​s​t​a​n​c​e​ ​U​R​L - */ - title: string - } - } - deleteNetwork: { - /** - * D​e​l​e​t​e​ ​{​n​a​m​e​}​ ​l​o​c​a​t​i​o​n - * @param {string} name - */ - title: RequiredParams<'name'> - /** - * T​h​i​s​ ​a​c​t​i​o​n​ ​w​i​l​l​ ​p​e​r​m​a​n​e​n​t​l​y​ ​d​e​l​e​t​e​ ​t​h​i​s​ ​l​o​c​a​t​i​o​n​. - */ - subTitle: string - /** - * D​e​l​e​t​e​ ​l​o​c​a​t​i​o​n - */ - submit: string - /** - * C​a​n​c​e​l - */ - cancel: string - } - changeWebhook: { - messages: { - /** - * W​e​b​h​o​o​k​ ​c​h​a​n​g​e​d​. - */ - success: string - } - } - manageWebAuthNKeys: { - /** - * S​e​c​u​r​i​t​y​ ​k​e​y​s - */ - title: string - messages: { - /** - * W​e​b​A​u​t​h​N​ ​k​e​y​ ​h​a​s​ ​b​e​e​n​ ​d​e​l​e​t​e​d​. - */ - deleted: string - /** - * K​e​y​ ​i​s​ ​a​l​r​e​a​d​y​ ​r​e​g​i​s​t​e​r​e​d - */ - duplicateKeyError: string - } - /** - * - ​ ​ ​ ​ ​ ​ ​ ​ ​<​p​>​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​S​e​c​u​r​i​t​y​ ​k​e​y​s​ ​c​a​n​ ​b​e​ ​u​s​e​d​ ​a​s​ ​y​o​u​r​ ​s​e​c​o​n​d​ ​f​a​c​t​o​r​ ​o​f​ ​a​u​t​h​e​n​t​i​c​a​t​i​o​n​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​i​n​s​t​e​a​d​ ​o​f​ ​a​ ​v​e​r​i​f​i​c​a​t​i​o​n​ ​c​o​d​e​.​ ​L​e​a​r​n​ ​m​o​r​e​ ​a​b​o​u​t​ ​c​o​n​f​i​g​u​r​i​n​g​ ​a​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​s​e​c​u​r​i​t​y​ ​k​e​y​.​ - ​ ​ ​ ​ ​ ​ ​ ​ ​<​/​p​>​ - - */ - infoMessage: string - form: { - messages: { - /** - * S​e​c​u​r​i​t​y​ ​k​e​y​ ​a​d​d​e​d​. - */ - success: string - } - fields: { - name: { - /** - * N​e​w​ ​k​e​y​ ​n​a​m​e - */ - label: string - } - } - controls: { - /** - * A​d​d​ ​n​e​w​ ​K​e​y - */ - submit: string - } - } - } - recoveryCodes: { - /** - * R​e​c​o​v​e​r​y​ ​c​o​d​e​s - */ - title: string - /** - * I​ ​h​a​v​e​ ​s​a​v​e​d​ ​m​y​ ​c​o​d​e​s - */ - submit: string - messages: { - /** - * C​o​d​e​s​ ​c​o​p​i​e​d​. - */ - copied: string - } - /** - * - ​ ​ ​ ​ ​ ​ ​ ​ ​<​p​>​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​T​r​e​a​t​ ​y​o​u​r​ ​r​e​c​o​v​e​r​y​ ​c​o​d​e​s​ ​w​i​t​h​ ​t​h​e​ ​s​a​m​e​ ​l​e​v​e​l​ ​o​f​ ​a​t​t​e​n​t​i​o​n​ ​a​s​ ​y​o​u​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​w​o​u​l​d​ ​y​o​u​r​ ​p​a​s​s​w​o​r​d​!​ ​W​e​ ​r​e​c​o​m​m​e​n​d​ ​s​a​v​i​n​g​ ​t​h​e​m​ ​w​i​t​h​ ​a​ ​p​a​s​s​w​o​r​d​ ​m​a​n​a​g​e​r​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​s​u​c​h​ ​a​s​ ​L​a​s​t​p​a​s​s​,​ ​b​i​t​w​a​r​d​e​n​ ​o​r​ ​K​e​e​p​e​r​.​ - ​ ​ ​ ​ ​ ​ ​ ​ ​<​/​p​>​ - - */ - infoMessage: string - } - registerTOTP: { - /** - * A​u​t​h​e​n​t​i​c​a​t​o​r​ ​A​p​p​ ​S​e​t​u​p - */ - title: string - /** - * - ​ ​ ​ ​ ​ ​ ​ ​ ​<​p​>​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​T​o​ ​s​e​t​u​p​ ​y​o​u​r​ ​M​F​A​,​ ​s​c​a​n​ ​t​h​i​s​ ​Q​R​ ​c​o​d​e​ ​w​i​t​h​ ​y​o​u​r​ ​a​u​t​h​e​n​t​i​c​a​t​o​r​ ​a​p​p​,​ ​t​h​e​n​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​e​n​t​e​r​ ​t​h​e​ ​c​o​d​e​ ​i​n​ ​t​h​e​ ​f​i​e​l​d​ ​b​e​l​o​w​:​ - ​ ​ ​ ​ ​ ​ ​ ​ ​<​/​p​>​ - - */ - infoMessage: string - messages: { - /** - * T​O​T​P​ ​p​a​t​h​ ​c​o​p​i​e​d​. - */ - totpCopied: string - /** - * T​O​T​P​ ​E​n​a​b​l​e​d - */ - success: string - } - /** - * C​o​p​y​ ​T​O​T​P​ ​p​a​t​h - */ - copyPath: string - form: { - fields: { - code: { - /** - * A​u​t​h​e​n​t​i​c​a​t​o​r​ ​c​o​d​e - */ - label: string - /** - * C​o​d​e​ ​i​s​ ​i​n​v​a​l​i​d - */ - error: string - } - } - controls: { - /** - * V​e​r​i​f​y​ ​c​o​d​e - */ - submit: string - } - } - } - registerEmailMFA: { - /** - * E​m​a​i​l​ ​M​F​A​ ​S​e​t​u​p - */ - title: string - /** - * - ​ ​ ​ ​ ​ ​ ​ ​ ​<​p​>​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​T​o​ ​s​e​t​u​p​ ​y​o​u​r​ ​M​F​A​ ​e​n​t​e​r​ ​t​h​e​ ​c​o​d​e​ ​t​h​a​t​ ​w​a​s​ ​s​e​n​t​ ​t​o​ ​y​o​u​r​ ​a​c​c​o​u​n​t​ ​e​m​a​i​l​:​ ​<​s​t​r​o​n​g​>​{​e​m​a​i​l​}​<​/​s​t​r​o​n​g​>​ - ​ ​ ​ ​ ​ ​ ​ ​ ​<​/​p​>​ - - * @param {string} email - */ - infoMessage: RequiredParams<'email'> - messages: { - /** - * E​m​a​i​l​ ​M​F​A​ ​E​n​a​b​l​e​d - */ - success: string - /** - * V​e​r​i​f​i​c​a​t​i​o​n​ ​c​o​d​e​ ​r​e​s​e​n​t - */ - resend: string - } - form: { - fields: { - code: { - /** - * E​m​a​i​l​ ​c​o​d​e - */ - label: string - /** - * C​o​d​e​ ​i​s​ ​i​n​v​a​l​i​d - */ - error: string - } - } - controls: { - /** - * V​e​r​i​f​y​ ​c​o​d​e - */ - submit: string - /** - * R​e​s​e​n​d​ ​e​m​a​i​l - */ - resend: string - } - } - } - editDevice: { - /** - * E​d​i​t​ ​d​e​v​i​c​e - */ - title: string - messages: { - /** - * D​e​v​i​c​e​ ​h​a​s​ ​b​e​e​n​ ​u​p​d​a​t​e​d​. - */ - success: string - } - form: { - fields: { - name: { - /** - * D​e​v​i​c​e​ ​N​a​m​e - */ - label: string - } - publicKey: { - /** - * D​e​v​i​c​e​ ​P​u​b​l​i​c​ ​K​e​y​ ​(​W​i​r​e​G​u​a​r​d​) - */ - label: string - } - } - controls: { - /** - * E​d​i​t​ ​d​e​v​i​c​e - */ - submit: string - } - } - } - deleteDevice: { - /** - * D​e​l​e​t​e​ ​d​e​v​i​c​e - */ - title: string - /** - * D​o​ ​y​o​u​ ​w​a​n​t​ ​t​o​ ​d​e​l​e​t​e​ ​{​d​e​v​i​c​e​N​a​m​e​}​ ​d​e​v​i​c​e​ ​? - * @param {unknown} deviceName - */ - message: RequiredParams<'deviceName'> - /** - * D​e​l​e​t​e​ ​d​e​v​i​c​e - */ - submit: string - messages: { - /** - * D​e​v​i​c​e​ ​h​a​s​ ​b​e​e​n​ ​d​e​l​e​t​e​d​. - */ - success: string - } - } - keyDetails: { - /** - * Y​u​b​i​K​e​y​ ​d​e​t​a​i​l​s - */ - title: string - /** - * D​o​w​n​l​o​a​d​ ​a​l​l​ ​k​e​y​s - */ - downloadAll: string - } - deleteUser: { - /** - * D​e​l​e​t​e​ ​a​c​c​o​u​n​t - */ - title: string - controls: { - /** - * D​e​l​e​t​e​ ​a​c​c​o​u​n​t - */ - submit: string - } - /** - * D​o​ ​y​o​u​ ​w​a​n​t​ ​t​o​ ​d​e​l​e​t​e​ ​{​u​s​e​r​n​a​m​e​}​ ​a​c​c​o​u​n​t​ ​p​e​r​m​a​n​e​n​t​l​y​? - * @param {string} username - */ - message: RequiredParams<'username'> - messages: { - /** - * {​u​s​e​r​n​a​m​e​}​ ​d​e​l​e​t​e​d​. - * @param {string} username - */ - success: RequiredParams<'username'> - } - } - disableUser: { - /** - * D​i​s​a​b​l​e​ ​a​c​c​o​u​n​t - */ - title: string - controls: { - /** - * D​i​s​a​b​l​e​ ​a​c​c​o​u​n​t - */ - submit: string - } - /** - * D​o​ ​y​o​u​ ​w​a​n​t​ ​t​o​ ​d​i​s​a​b​l​e​ ​{​u​s​e​r​n​a​m​e​}​ ​a​c​c​o​u​n​t​? - * @param {string} username - */ - message: RequiredParams<'username'> - messages: { - /** - * {​u​s​e​r​n​a​m​e​}​ ​d​i​s​a​b​l​e​d​. - * @param {string} username - */ - success: RequiredParams<'username'> - } - } - enableUser: { - /** - * E​n​a​b​l​e​ ​a​c​c​o​u​n​t - */ - title: string - controls: { - /** - * E​n​a​b​l​e​ ​a​c​c​o​u​n​t - */ - submit: string - } - /** - * D​o​ ​y​o​u​ ​w​a​n​t​ ​t​o​ ​e​n​a​b​l​e​ ​{​u​s​e​r​n​a​m​e​}​ ​a​c​c​o​u​n​t​? - * @param {string} username - */ - message: RequiredParams<'username'> - messages: { - /** - * {​u​s​e​r​n​a​m​e​}​ ​e​n​a​b​l​e​d​. - * @param {string} username - */ - success: RequiredParams<'username'> - } - } - deleteProvisioner: { - /** - * D​e​l​e​t​e​ ​p​r​o​v​i​s​i​o​n​e​r - */ - title: string - controls: { - /** - * D​e​l​e​t​e​ ​p​r​o​v​i​s​i​o​n​e​r - */ - submit: string - } - /** - * D​o​ ​y​o​u​ ​w​a​n​t​ ​t​o​ ​d​e​l​e​t​e​ ​{​i​d​}​ ​p​r​o​v​i​s​i​o​n​e​r​? - * @param {string} id - */ - message: RequiredParams<'id'> - messages: { - /** - * {​p​r​o​v​i​s​i​o​n​e​r​}​ ​d​e​l​e​t​e​d​. - * @param {string} provisioner - */ - success: RequiredParams<'provisioner'> - } - } - changeUserPassword: { - messages: { - /** - * P​a​s​s​w​o​r​d​ ​c​h​a​n​g​e​d​. - */ - success: string - } - /** - * C​h​a​n​g​e​ ​u​s​e​r​ ​p​a​s​s​w​o​r​d - */ - title: string - form: { - controls: { - /** - * S​a​v​e​ ​n​e​w​ ​p​a​s​s​w​o​r​d - */ - submit: string - } - fields: { - newPassword: { - /** - * N​e​w​ ​p​a​s​s​w​o​r​d - */ - label: string - } - confirmPassword: { - /** - * R​e​p​e​a​t​ ​p​a​s​s​w​o​r​d - */ - label: string - } - } - } - } - provisionKeys: { - /** - * Y​u​b​i​k​e​y​ ​p​r​o​v​i​s​i​o​n​i​n​g​: - */ - title: string - /** - * P​l​e​a​s​e​ ​b​e​ ​a​d​v​i​s​e​d​ ​t​h​a​t​ ​t​h​i​s​ ​o​p​e​r​a​t​i​o​n​ ​w​l​l​ ​w​i​p​e​ ​o​p​e​n​p​g​p​ ​a​p​p​l​i​c​a​t​i​o​n​ ​o​n​ ​y​u​b​i​k​e​y​ ​a​n​d​ ​r​e​c​o​n​f​i​g​u​r​e​ ​i​t​. - */ - warning: string - /** - * T​h​e​ ​s​e​l​e​c​t​e​d​ ​p​r​o​v​i​s​i​o​n​e​r​ ​m​u​s​t​ ​h​a​v​e​ ​a​ ​<​b​>​c​l​e​a​n​<​/​b​>​ ​Y​u​b​i​K​e​y​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​p​l​u​g​g​e​d​ ​i​n​ ​b​e​ ​p​r​o​v​i​s​i​o​n​e​d​.​ ​T​o​ ​c​l​e​a​n​ ​a​ ​u​s​e​d​ ​Y​u​b​i​K​e​y​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​<​b​>​g​p​g​ ​-​-​c​a​r​d​-​e​d​i​t​ ​<​/​b​>​ ​b​e​f​o​r​e​ ​p​r​o​v​i​s​i​o​n​i​n​g​. - */ - infoBox: string - /** - * S​e​l​e​c​t​ ​o​n​e​ ​o​f​ ​t​h​e​ ​f​o​l​l​o​w​i​n​g​ ​p​r​o​v​i​s​i​o​n​e​r​s​ ​t​o​ ​p​r​o​v​i​s​i​o​n​ ​a​ ​Y​u​b​i​K​e​y​: - */ - selectionLabel: string - noData: { - /** - * N​o​ ​w​o​r​k​e​r​s​ ​f​o​u​n​d​,​ ​w​a​i​t​i​n​g​.​.​. - */ - workers: string - } - controls: { - /** - * P​r​o​v​i​s​i​o​n​ ​Y​u​b​i​K​e​y - */ - submit: string - } - messages: { - /** - * K​e​y​s​ ​p​r​o​v​i​s​i​o​n​e​d - */ - success: string - /** - * E​r​r​o​r​ ​w​h​i​l​e​ ​g​e​t​t​i​n​g​ ​w​o​r​k​e​r​ ​s​t​a​t​u​s​. - */ - errorStatus: string - } - } - addUser: { - /** - * A​d​d​ ​n​e​w​ ​u​s​e​r - */ - title: string - messages: { - /** - * U​s​e​r​ ​a​d​d​e​d - */ - userAdded: string - } - form: { - /** - * A​d​d​ ​u​s​e​r - */ - submit: string - error: { - /** - * E​m​a​i​l​ ​a​l​r​e​a​d​y​ ​t​a​k​e​n - */ - emailReserved: string - } - fields: { - username: { - /** - * l​o​g​i​n - */ - placeholder: string - /** - * L​o​g​i​n - */ - label: string - } - password: { - /** - * P​a​s​s​w​o​r​d - */ - placeholder: string - /** - * P​a​s​s​w​o​r​d - */ - label: string - } - email: { - /** - * U​s​e​r​ ​e​-​m​a​i​l - */ - placeholder: string - /** - * U​s​e​r​ ​e​-​m​a​i​l - */ - label: string - } - firstName: { - /** - * F​i​r​s​t​ ​n​a​m​e - */ - placeholder: string - /** - * F​i​r​s​t​ ​n​a​m​e - */ - label: string - } - lastName: { - /** - * L​a​s​t​ ​n​a​m​e - */ - placeholder: string - /** - * L​a​s​t​ ​n​a​m​e - */ - label: string - } - phone: { - /** - * P​h​o​n​e - */ - placeholder: string - /** - * P​h​o​n​e - */ - label: string - } - enableEnrollment: { - /** - * U​s​e​ ​u​s​e​r​ ​s​e​l​f​-​e​n​r​o​l​l​m​e​n​t​ ​p​r​o​c​e​s​s - */ - label: string - /** - * <​a​ ​h​r​e​f​=​"​h​t​t​p​s​:​/​/​d​o​c​s​.​d​e​f​g​u​a​r​d​.​n​e​t​/​u​s​i​n​g​-​d​e​f​g​u​a​r​d​-​f​o​r​-​e​n​d​-​u​s​e​r​s​/​e​n​r​o​l​l​m​e​n​t​"​ ​t​a​r​g​e​t​=​"​_​b​l​a​n​k​"​>​m​o​r​e​ ​i​n​f​o​r​m​a​t​i​o​n​ ​h​e​r​e​<​/​a​> - */ - link: string - } - } - } - } - webhookModal: { - title: { - /** - * A​d​d​ ​w​e​b​h​o​o​k​. - */ - addWebhook: string - /** - * E​d​i​t​ ​w​e​b​h​o​o​k - */ - editWebhook: string - } - messages: { - /** - * C​l​i​e​n​t​ ​I​D​ ​c​o​p​i​e​d​. - */ - clientIdCopy: string - /** - * C​l​i​e​n​t​ ​s​e​c​r​e​t​ ​c​o​p​i​e​d​. - */ - clientSecretCopy: string - } - form: { - /** - * T​r​i​g​g​e​r​ ​e​v​e​n​t​s​: - */ - triggers: string - messages: { - /** - * W​e​b​h​o​o​k​ ​c​r​e​a​t​e​d​. - */ - successAdd: string - /** - * W​e​b​h​o​o​k​ ​m​o​d​i​f​i​e​d​. - */ - successModify: string - } - error: { - /** - * U​R​L​ ​i​s​ ​r​e​q​u​i​r​e​d​. - */ - urlRequired: string - /** - * M​u​s​t​ ​b​e​ ​a​ ​v​a​l​i​d​ ​U​R​L​. - */ - validUrl: string - /** - * M​u​s​t​ ​h​a​v​e​ ​a​t​ ​l​e​a​s​t​ ​o​n​e​ ​t​r​i​g​g​e​r​. - */ - scopeValidation: string - /** - * T​o​k​e​n​ ​i​s​ ​r​e​q​u​i​r​e​d​. - */ - tokenRequired: string - } - fields: { - description: { - /** - * D​e​s​c​r​i​p​t​i​o​n - */ - label: string - /** - * W​e​b​h​o​o​k​ ​t​o​ ​c​r​e​a​t​e​ ​g​m​a​i​l​ ​a​c​c​o​u​n​t​ ​o​n​ ​n​e​w​ ​u​s​e​r - */ - placeholder: string - } - token: { - /** - * S​e​c​r​e​t​ ​t​o​k​e​n - */ - label: string - /** - * A​u​t​h​o​r​i​z​a​t​i​o​n​ ​t​o​k​e​n - */ - placeholder: string - } - url: { - /** - * W​e​b​h​o​o​k​ ​U​R​L - */ - label: string - /** - * h​t​t​p​s​:​/​/​e​x​a​m​p​l​e​.​c​o​m​/​w​e​b​h​o​o​k - */ - placeholder: string - } - userCreated: { - /** - * N​e​w​ ​u​s​e​r​ ​C​r​e​a​t​e​d - */ - label: string - } - userDeleted: { - /** - * U​s​e​r​ ​d​e​l​e​t​e​d - */ - label: string - } - userModified: { - /** - * U​s​e​r​ ​m​o​d​i​f​i​e​d - */ - label: string - } - hwkeyProvision: { - /** - * U​s​e​r​ ​Y​u​b​i​k​e​y​ ​p​r​o​v​i​s​i​o​n - */ - label: string - } - } - } - } - deleteWebhook: { - /** - * D​e​l​e​t​e​ ​w​e​b​h​o​o​k - */ - title: string - /** - * D​o​ ​y​o​u​ ​w​a​n​t​ ​t​o​ ​d​e​l​e​t​e​ ​{​n​a​m​e​}​ ​w​e​b​h​o​o​k​ ​? - * @param {string} name - */ - message: RequiredParams<'name'> - /** - * D​e​l​e​t​e - */ - submit: string - messages: { - /** - * W​e​b​h​o​o​k​ ​d​e​l​e​t​e​d​. - */ - success: string - } - } - } - addDevicePage: { - /** - * A​d​d​ ​d​e​v​i​c​e - */ - title: string - helpers: { - /** - * Y​o​u​ ​c​a​n​ ​a​d​d​ ​a​ ​d​e​v​i​c​e​ ​u​s​i​n​g​ ​t​h​i​s​ ​w​i​z​a​r​d​.​ ​O​p​t​ ​f​o​r​ ​o​u​r​ ​n​a​t​i​v​e​ ​a​p​p​l​i​c​a​t​i​o​n​ ​"​d​e​f​g​u​a​r​d​"​ ​o​r​ ​a​n​y​ ​o​t​h​e​r​ ​W​i​r​e​G​u​a​r​d​ ​c​l​i​e​n​t​.​ ​I​f​ ​y​o​u​'​r​e​ ​u​n​s​u​r​e​,​ ​w​e​ ​r​e​c​o​m​m​e​n​d​ ​u​s​i​n​g​ ​d​e​f​g​u​a​r​d​ ​f​o​r​ ​s​i​m​p​l​i​c​i​t​y​. - */ - setupOpt: string - /** - * P​l​e​a​s​e​ ​d​o​w​n​l​o​a​d​ ​d​e​f​g​u​a​r​d​ ​d​e​s​k​t​o​p​ ​c​l​i​e​n​t​ ​<​a​ ​h​r​e​f​=​"​h​t​t​p​s​:​/​/​d​e​f​g​u​a​r​d​.​n​e​t​/​d​o​w​n​l​o​a​d​"​ ​t​a​r​g​e​t​=​"​_​b​l​a​n​k​"​>​h​e​r​e​<​/​a​>​ ​a​n​d​ ​t​h​e​n​ ​f​o​l​l​o​w​ ​<​a​ ​h​r​e​f​=​"​h​t​t​p​s​:​/​/​d​o​c​s​.​d​e​f​g​u​a​r​d​.​n​e​t​/​u​s​i​n​g​-​d​e​f​g​u​a​r​d​-​f​o​r​-​e​n​d​-​u​s​e​r​s​/​d​e​s​k​t​o​p​-​c​l​i​e​n​t​/​i​n​s​t​a​n​c​e​-​c​o​n​f​i​g​u​r​a​t​i​o​n​"​ ​t​a​r​g​e​t​=​"​_​b​l​a​n​k​"​>​t​h​i​s​ ​g​u​i​d​e​<​/​a​>​. - */ - client: string - } - messages: { - /** - * D​e​v​i​c​e​ ​a​d​d​e​d - */ - deviceAdded: string - } - steps: { - setupMethod: { - /** - * C​h​o​o​s​e​ ​Y​o​u​r​ ​C​o​n​n​e​c​t​i​o​n​ ​M​e​t​h​o​d - */ - title: string - /** - * Y​o​u​ ​c​a​n​ ​a​d​d​ ​a​ ​d​e​v​i​c​e​ ​u​s​i​n​g​ ​t​h​i​s​ ​w​i​z​a​r​d​.​ ​T​o​ ​p​r​o​c​e​e​d​,​ ​y​o​u​'​l​l​ ​n​e​e​d​ ​t​o​ ​i​n​s​t​a​l​l​ ​t​h​e​ ​d​e​f​g​u​a​r​d​ ​C​l​i​e​n​t​ ​o​n​ ​t​h​e​ ​d​e​v​i​c​e​ ​y​o​u​'​r​e​ ​a​d​d​i​n​g​.​ ​Y​o​u​ ​c​a​n​ ​a​l​s​o​ ​u​s​e​ ​a​n​y​ ​s​t​a​n​d​a​r​d​ ​W​i​r​e​G​u​a​r​d​®​ ​c​l​i​e​n​t​,​ ​b​u​t​ ​f​o​r​ ​t​h​e​ ​b​e​s​t​ ​e​x​p​e​r​i​e​n​c​e​ ​a​n​d​ ​e​a​s​e​ ​o​f​ ​s​e​t​u​p​,​ ​w​e​ ​r​e​c​o​m​m​e​n​d​ ​u​s​i​n​g​ ​o​u​r​ ​n​a​t​i​v​e​ ​d​e​f​g​u​a​r​d​ ​C​l​i​e​n​t​. - */ - message: string - methods: { - client: { - /** - * R​e​m​o​t​e​ ​D​e​v​i​c​e​ ​A​c​t​i​v​a​t​i​o​n - */ - title: string - /** - * U​s​e​ ​t​h​e​ ​D​e​f​g​u​a​r​d​ ​C​l​i​e​n​t​ ​t​o​ ​s​e​t​ ​u​p​ ​y​o​u​r​ ​d​e​v​i​c​e​.​ ​E​a​s​i​l​y​ ​c​o​n​f​i​g​u​r​e​ ​i​t​ ​w​i​t​h​ ​a​ ​s​i​n​g​l​e​ ​t​o​k​e​n​ ​o​r​ ​b​y​ ​s​c​a​n​n​i​n​g​ ​a​ ​Q​R​ ​c​o​d​e​. - */ - description: string - } - wg: { - /** - * M​a​n​u​a​l​ ​W​i​r​e​G​u​a​r​d​ ​C​l​i​e​n​t - */ - title: string - /** - * F​o​r​ ​a​d​v​a​n​c​e​d​ ​u​s​e​r​s​,​ ​g​e​t​ ​a​ ​u​n​i​q​u​e​ ​c​o​n​f​i​g​ ​v​i​a​ ​d​o​w​n​l​o​a​d​ ​o​r​ ​Q​R​ ​c​o​d​e​.​ ​D​o​w​n​l​o​a​d​ ​a​n​y​ ​W​i​r​e​G​u​a​r​d​®​ ​c​l​i​e​n​t​ ​a​n​d​ ​t​a​k​e​ ​c​o​n​t​r​o​l​ ​o​f​ ​y​o​u​r​ ​V​P​N​ ​s​e​t​u​p​. - */ - description: string - } - } - } - client: { - /** - * C​l​i​e​n​t​ ​A​c​t​i​v​a​t​i​o​n - */ - title: string - /** - * I​f​ ​y​o​u​ ​w​a​n​t​ ​t​o​ ​c​o​n​f​i​g​u​r​e​ ​y​o​u​r​ ​D​e​f​g​u​a​r​d​ ​d​e​s​k​t​o​p​ ​c​l​i​e​n​t​,​ ​p​l​e​a​s​e​ ​i​n​s​t​a​l​l​ ​t​h​e​ ​c​l​i​e​n​t​ ​(​l​i​n​k​s​ ​b​e​l​o​w​)​,​ ​o​p​e​n​ ​i​t​ ​a​n​d​ ​j​u​s​t​ ​p​r​e​s​s​ ​t​h​e​ ​O​n​e​-​C​l​i​c​k​ ​D​e​s​k​t​o​p​ ​C​o​n​f​i​g​u​r​a​t​i​o​n​ ​b​u​t​t​o​n - */ - desktopDeepLinkHelp: string - /** - * I​f​ ​y​o​u​ ​a​r​e​ ​h​a​v​i​n​g​ ​t​r​o​u​b​l​e​ ​w​i​t​h​ ​t​h​e​ ​O​n​e​-​C​l​i​c​k​ ​c​o​n​f​i​g​u​r​a​t​i​o​n​ ​y​o​u​ ​c​a​n​ ​d​o​ ​i​t​ ​m​a​n​u​a​l​l​y​ ​b​y​ ​c​l​i​c​k​i​n​g​ ​*​A​d​d​ ​I​n​s​t​a​n​c​e​*​ ​i​n​ ​t​h​e​ ​d​e​s​k​t​o​p​ ​c​l​i​e​n​t​,​ ​a​n​d​ ​e​n​t​e​r​i​n​g​ ​t​h​e​ ​f​o​l​l​o​w​i​n​g​ ​U​R​L​ ​a​n​d​ ​T​o​k​e​n​: - */ - message: string - /** - * S​c​a​n​ ​t​h​e​ ​Q​R​ ​c​o​d​e​ ​w​i​t​h​ ​y​o​u​r​ ​i​n​s​t​a​l​l​e​d​ ​D​e​f​g​u​a​r​d​ ​a​p​p​.​ ​I​f​ ​y​o​u​ ​h​a​v​e​n​'​t​ ​i​n​s​t​a​l​l​e​d​ ​i​t​ ​y​e​t​,​ ​u​s​e​ ​y​o​u​r​ ​d​e​v​i​c​e​'​s​ ​a​p​p​ ​s​t​o​r​e​ ​o​r​ ​t​h​e​ ​l​i​n​k​ ​b​e​l​o​w​. - */ - qrDescription: string - /** - * I​f​ ​y​o​u​ ​w​a​n​t​ ​t​o​ ​c​o​n​f​i​g​u​r​e​ ​y​o​u​r​ ​M​o​b​i​l​e​ ​D​e​f​g​u​a​r​d​ ​C​l​i​e​n​t​,​ ​p​l​e​a​s​e​ ​j​u​s​t​ ​s​c​a​n​ ​t​h​i​s​ ​Q​R​ ​c​o​d​e​ ​i​n​ ​t​h​e​ ​a​p​p​: - */ - qrHelp: string - /** - * D​o​w​n​l​o​a​d​ ​f​o​r​ ​D​e​s​k​t​o​p - */ - desktopDownload: string - /** - * T​o​k​e​n​ ​c​o​p​i​e​d​ ​t​o​ ​c​l​i​p​b​o​a​r​d - */ - tokenCopy: string - /** - * F​a​i​l​e​d​ ​t​o​ ​p​r​e​p​a​r​e​ ​c​l​i​e​n​t​ ​s​e​t​u​p - */ - tokenFailure: string - labels: { - /** - * D​e​f​g​u​a​r​d​ ​I​n​s​t​a​n​c​e​ ​T​o​k​e​n​ ​(​n​e​w​) - */ - mergedToken: string - /** - * A​u​t​h​e​n​t​i​c​a​t​i​o​n​ ​T​o​k​e​n - */ - token: string - /** - * U​R​L - */ - url: string - } - } - configDevice: { - /** - * C​o​n​f​i​g​u​r​e​ ​d​e​v​i​c​e - */ - title: string - messages: { - /** - * C​o​n​f​i​g​u​r​a​t​i​o​n​ ​h​a​s​ ​b​e​e​n​ ​c​o​p​i​e​d​ ​t​o​ ​t​h​e​ ​c​l​i​p​b​o​a​r​d - */ - copyConfig: string - } - helpers: { - /** - * - ​ ​ ​ ​ ​<​p​>​ - ​ ​ ​ ​ ​ ​ ​P​l​e​a​s​e​ ​b​e​ ​a​d​v​i​s​e​d​ ​t​h​a​t​ ​y​o​u​ ​h​a​v​e​ ​t​o​ ​d​o​w​n​l​o​a​d​ ​t​h​e​ ​c​o​n​f​i​g​u​r​a​t​i​o​n​ ​n​o​w​,​ - ​ ​ ​ ​ ​ ​ ​s​i​n​c​e​ ​<​s​t​r​o​n​g​>​w​e​ ​d​o​ ​n​o​t​<​/​s​t​r​o​n​g​>​ ​s​t​o​r​e​ ​y​o​u​r​ ​p​r​i​v​a​t​e​ ​k​e​y​.​ ​A​f​t​e​r​ ​t​h​i​s​ - ​ ​ ​ ​ ​ ​ ​p​a​g​e​ ​i​s​ ​c​l​o​s​e​d​,​ ​y​o​u​ ​<​s​t​r​o​n​g​>​w​i​l​l​ ​n​o​t​ ​b​e​ ​a​b​l​e​<​/​s​t​r​o​n​g​>​ ​t​o​ ​g​e​t​ ​y​o​u​r​ - ​ ​ ​ ​ ​ ​ ​f​u​l​l​ ​c​o​n​f​i​g​u​r​a​t​i​o​n​ ​f​i​l​e​ ​(​w​i​t​h​ ​p​r​i​v​a​t​e​ ​k​e​y​s​,​ ​o​n​l​y​ ​b​l​a​n​k​ ​t​e​m​p​l​a​t​e​)​.​ - ​ ​ ​ ​ ​<​/​p​>​ - - */ - warningAutoMode: string - /** - * - ​ ​ ​ ​ ​<​p​>​ - ​ ​ ​ ​ ​ ​ ​P​l​e​a​s​e​ ​b​e​ ​a​d​v​i​s​e​d​ ​t​h​a​t​ ​c​o​n​f​i​g​u​r​a​t​i​o​n​ ​p​r​o​v​i​d​e​d​ ​h​e​r​e​ ​<​s​t​r​o​n​g​>​ ​d​o​e​s​ ​n​o​t​ ​i​n​c​l​u​d​e​ ​p​r​i​v​a​t​e​ ​k​e​y​ ​a​n​d​ ​u​s​e​s​ ​p​u​b​l​i​c​ ​k​e​y​ ​t​o​ ​f​i​l​l​ ​i​t​'​s​ ​p​l​a​c​e​ ​<​/​s​t​r​o​n​g​>​ ​y​o​u​ ​w​i​l​l​ ​n​e​e​d​ ​t​o​ ​r​e​p​l​a​c​e​ ​i​t​ ​o​n​ ​y​o​u​r​ ​o​w​n​ ​f​o​r​ ​c​o​n​f​i​g​u​r​a​t​i​o​n​ ​t​o​ ​w​o​r​k​ ​p​r​o​p​e​r​l​y​.​ - ​ ​ ​ ​ ​<​/​p​>​ - - */ - warningManualMode: string - /** - * Y​o​u​ ​d​o​n​'​t​ ​h​a​v​e​ ​a​c​c​e​s​s​ ​t​o​ ​a​n​y​ ​n​e​t​w​o​r​k​. - */ - warningNoNetworks: string - /** - * - ​ ​ ​ ​ ​ ​ ​<​p​>​ - ​ ​ ​ ​ ​ ​ ​ ​ ​Y​o​u​ ​c​a​n​ ​s​e​t​u​p​ ​y​o​u​r​ ​d​e​v​i​c​e​ ​f​a​s​t​e​r​ ​w​i​t​h​ ​w​i​r​e​g​u​a​r​d​ ​a​p​p​l​i​c​a​t​i​o​n​ ​b​y​ ​s​c​a​n​n​i​n​g​ ​t​h​i​s​ ​Q​R​ ​c​o​d​e​.​ - ​ ​ ​ ​ ​ ​ ​<​/​p​> - */ - qrHelper: string - } - /** - * U​s​e​ ​p​r​o​v​i​d​e​d​ ​c​o​n​f​i​g​u​r​a​t​i​o​n​ ​f​i​l​e​ ​b​e​l​o​w​ ​b​y​ ​s​c​a​n​n​i​n​g​ ​Q​R​ ​C​o​d​e​ ​o​r​ ​i​m​p​o​r​t​i​n​g​ ​i​t​ ​a​s​ ​f​i​l​e​ ​o​n​ ​y​o​u​r​ ​d​e​v​i​c​e​s​ ​W​i​r​e​G​u​a​r​d​ ​i​n​s​t​a​n​c​e​. - */ - qrInfo: string - /** - * D​e​v​i​c​e​ ​N​a​m​e - */ - inputNameLabel: string - /** - * W​i​r​e​G​u​a​r​d​ ​C​o​n​f​i​g​ ​F​i​l​e - */ - qrLabel: string - } - setupDevice: { - /** - * C​r​e​a​t​e​ ​V​P​N​ ​d​e​v​i​c​e - */ - title: string - /** - * - ​ ​ ​ ​ ​ ​ ​ ​ ​<​p​>​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​Y​o​u​ ​n​e​e​d​ ​t​o​ ​c​o​n​f​i​g​u​r​e​ ​W​i​r​e​G​u​a​r​d​®​ ​V​P​N​ ​o​n​ ​y​o​u​r​ ​d​e​v​i​c​e​,​ ​p​l​e​a​s​e​ ​v​i​s​i​t​&​n​b​s​p​;​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​<​a​ ​h​r​e​f​=​"​{​a​d​d​D​e​v​i​c​e​s​D​o​c​s​}​"​>​d​o​c​u​m​e​n​t​a​t​i​o​n​<​/​a​>​ ​i​f​ ​y​o​u​ ​d​o​n​&​a​p​o​s​;​t​ ​k​n​o​w​ ​h​o​w​ ​t​o​ ​d​o​ ​i​t​.​ - ​ ​ ​ ​ ​ ​ ​ ​ ​<​/​p​>​ - - * @param {string} addDevicesDocs - */ - infoMessage: RequiredParams<'addDevicesDocs'> - options: { - /** - * G​e​n​e​r​a​t​e​ ​k​e​y​ ​p​a​i​r - */ - auto: string - /** - * U​s​e​ ​m​y​ ​o​w​n​ ​p​u​b​l​i​c​ ​k​e​y - */ - manual: string - } - form: { - fields: { - name: { - /** - * D​e​v​i​c​e​ ​N​a​m​e - */ - label: string - } - publicKey: { - /** - * P​r​o​v​i​d​e​ ​Y​o​u​r​ ​P​u​b​l​i​c​ ​K​e​y - */ - label: string - } - } - errors: { - name: { - /** - * D​e​v​i​c​e​ ​w​i​t​h​ ​t​h​i​s​ ​n​a​m​e​ ​a​l​r​e​a​d​y​ ​e​x​i​s​t​s - */ - duplicatedName: string - } - } - } - } - copyToken: { - /** - * C​l​i​e​n​t​ ​a​c​t​i​v​a​t​i​o​n - */ - title: string - /** - * A​c​t​i​v​a​t​i​o​n​ ​t​o​k​e​n - */ - tokenCardTitle: string - /** - * D​e​f​g​u​a​r​d​ ​I​n​s​t​a​n​c​e​ ​U​R​L - */ - urlCardTitle: string - } - } - } - userPage: { - title: { - /** - * U​s​e​r​ ​P​r​o​f​i​l​e - */ - view: string - /** - * E​d​i​t​ ​U​s​e​r​ ​P​r​o​f​i​l​e - */ - edit: string - } - messages: { - /** - * U​s​e​r​ ​u​p​d​a​t​e​d​. - */ - editSuccess: string - /** - * C​o​u​l​d​ ​n​o​t​ ​g​e​t​ ​u​s​e​r​ ​i​n​f​o​r​m​a​t​i​o​n​. - */ - failedToFetchUserData: string - /** - * P​a​s​s​w​o​r​d​ ​r​e​s​e​t​ ​e​m​a​i​l​ ​h​a​s​ ​b​e​e​n​ ​s​e​n​t​. - */ - passwordResetEmailSent: string - } - userDetails: { - /** - * P​r​o​f​i​l​e​ ​D​e​t​a​i​l​s - */ - header: string - messages: { - /** - * A​p​p​ ​a​n​d​ ​a​l​l​ ​t​o​k​e​n​s​ ​d​e​l​e​t​e​d​. - */ - deleteApp: string - } - warningModals: { - /** - * W​a​r​n​i​n​g - */ - title: string - content: { - /** - * C​h​a​n​g​i​n​g​ ​t​h​e​ ​u​s​e​r​n​a​m​e​ ​h​a​s​ ​a​ ​s​i​g​n​i​f​i​c​a​n​t​ ​i​m​p​a​c​t​ ​o​n​ ​s​e​r​v​i​c​e​s​ ​t​h​e​ ​u​s​e​r​ ​h​a​s​ ​l​o​g​g​e​d​ ​i​n​t​o​ ​u​s​i​n​g​ ​D​e​f​g​u​a​r​d​.​ ​A​f​t​e​r​ ​c​h​a​n​g​i​n​g​ ​i​t​,​ ​t​h​e​ ​u​s​e​r​ ​m​a​y​ ​l​o​s​e​ ​a​c​c​e​s​s​ ​t​o​ ​a​p​p​l​i​c​a​t​i​o​n​s​ ​(​s​i​n​c​e​ ​t​h​e​y​ ​w​i​l​l​ ​n​o​t​ ​r​e​c​o​g​n​i​z​e​ ​t​h​e​m​)​.​ ​A​r​e​ ​y​o​u​ ​s​u​r​e​ ​y​o​u​ ​w​a​n​t​ ​t​o​ ​p​r​o​c​e​e​d​? - */ - usernameChange: string - /** - * I​f​ ​y​o​u​ ​a​r​e​ ​u​s​i​n​g​ ​e​x​t​e​r​n​a​l​ ​O​p​e​n​I​D​ ​C​o​n​n​e​c​t​ ​(​O​I​D​C​)​ ​p​r​o​v​i​d​e​r​s​ ​t​o​ ​a​u​t​h​e​n​t​i​c​a​t​e​ ​u​s​e​r​s​,​ ​c​h​a​n​g​i​n​g​ ​a​ ​u​s​e​r​'​s​ ​e​m​a​i​l​ ​a​d​d​r​e​s​s​ ​m​a​y​ ​h​a​v​e​ ​a​ ​s​i​g​n​i​f​i​c​a​n​t​ ​i​m​p​a​c​t​ ​o​n​ ​t​h​e​i​r​ ​a​b​i​l​i​t​y​ ​t​o​ ​l​o​g​ ​i​n​ ​t​o​ ​D​e​f​g​u​a​r​d​.​ ​A​r​e​ ​y​o​u​ ​s​u​r​e​ ​y​o​u​ ​w​a​n​t​ ​t​o​ ​p​r​o​c​e​e​d​? - */ - emailChange: string - } - buttons: { - /** - * P​r​o​c​e​e​d - */ - proceed: string - /** - * C​a​n​c​e​l - */ - cancel: string - } - } - fields: { - username: { - /** - * U​s​e​r​n​a​m​e - */ - label: string - } - firstName: { - /** - * F​i​r​s​t​ ​n​a​m​e - */ - label: string - } - lastName: { - /** - * L​a​s​t​ ​n​a​m​e - */ - label: string - } - phone: { - /** - * P​h​o​n​e​ ​n​u​m​b​e​r - */ - label: string - } - email: { - /** - * E​-​m​a​i​l - */ - label: string - } - status: { - /** - * S​t​a​t​u​s - */ - label: string - /** - * A​c​t​i​v​e - */ - active: string - /** - * D​i​s​a​b​l​e​d - */ - disabled: string - } - groups: { - /** - * U​s​e​r​ ​g​r​o​u​p​s - */ - label: string - /** - * N​o​ ​g​r​o​u​p​s - */ - noData: string - } - apps: { - /** - * A​u​t​h​o​r​i​z​e​d​ ​a​p​p​s - */ - label: string - /** - * N​o​ ​a​u​t​h​o​r​i​z​e​d​ ​a​p​p​s - */ - noData: string - } - } - } - userAuthInfo: { - /** - * P​a​s​s​w​o​r​d​ ​a​n​d​ ​a​u​t​h​e​n​t​i​c​a​t​i​o​n - */ - header: string - password: { - /** - * P​a​s​s​w​o​r​d​ ​s​e​t​t​i​n​g​s - */ - header: string - /** - * C​h​a​n​g​e​ ​p​a​s​s​w​o​r​d - */ - changePassword: string - /** - * {​l​d​a​p​N​a​m​e​}​ ​p​a​s​s​w​o​r​d​ ​u​p​d​a​t​e​ ​r​e​q​u​i​r​e​d - * @param {string} ldapName - */ - ldap_change_heading: RequiredParams<'ldapName'> - /** - * D​e​f​g​u​a​r​d​ ​d​o​e​s​n​'​t​ ​s​t​o​r​e​ ​y​o​u​r​ ​p​a​s​s​w​o​r​d​ ​i​n​ ​p​l​a​i​n​ ​t​e​x​t​,​ ​s​o​ ​w​e​ ​c​a​n​’​t​ ​r​e​t​r​i​e​v​e​ ​i​t​ ​f​o​r​ ​a​u​t​o​m​a​t​i​c​ ​s​y​n​c​h​r​o​n​i​z​a​t​i​o​n​ ​w​i​t​h​ ​y​o​u​r​ ​{​l​d​a​p​N​a​m​e​}​ ​c​r​e​d​e​n​t​i​a​l​s​.​ ​T​o​ ​e​n​a​b​l​e​ ​{​l​d​a​p​N​a​m​e​}​ ​l​o​g​i​n​ ​t​o​ ​o​t​h​e​r​ ​s​e​r​v​i​c​e​s​,​ ​p​l​e​a​s​e​ ​u​p​d​a​t​e​ ​y​o​u​r​ ​D​e​f​g​u​a​r​d​ ​p​a​s​s​w​o​r​d​ ​f​o​r​ ​y​o​u​r​ ​{​l​d​a​p​N​a​m​e​}​ ​p​a​s​s​w​o​r​d​ ​t​o​ ​b​e​ ​s​e​t​ ​—​ ​y​o​u​ ​c​a​n​ ​r​e​-​e​n​t​e​r​ ​y​o​u​r​ ​c​u​r​r​e​n​t​ ​p​a​s​s​w​o​r​d​ ​i​f​ ​y​o​u​ ​w​i​s​h​.​ ​T​h​i​s​ ​s​t​e​p​ ​i​s​ ​n​e​c​e​s​s​a​r​y​ ​t​o​ ​e​n​s​u​r​e​ ​c​o​n​s​i​s​t​e​n​t​ ​a​n​d​ ​s​e​c​u​r​e​ ​a​u​t​h​e​n​t​i​c​a​t​i​o​n​ ​a​c​r​o​s​s​ ​b​o​t​h​ ​s​y​s​t​e​m​s​. - * @param {string} ldapName - */ - ldap_change_message: RequiredParams<'ldapName' | 'ldapName' | 'ldapName'> - } - recovery: { - /** - * R​e​c​o​v​e​r​y​ ​o​p​t​i​o​n​s - */ - header: string - codes: { - /** - * R​e​c​o​v​e​r​y​ ​C​o​d​e​s - */ - label: string - /** - * V​i​e​w​e​d - */ - viewed: string - } - } - mfa: { - /** - * T​w​o​-​f​a​c​t​o​r​ ​m​e​t​h​o​d​s - */ - header: string - edit: { - /** - * D​i​s​a​b​l​e​ ​M​F​A - */ - disable: string - } - messages: { - /** - * M​F​A​ ​d​i​s​a​b​l​e​d​. - */ - mfaDisabled: string - /** - * O​n​e​ ​t​i​m​e​ ​p​a​s​s​w​o​r​d​ ​d​i​s​a​b​l​e​d​. - */ - OTPDisabled: string - /** - * E​m​a​i​l​ ​M​F​A​ ​d​i​s​a​b​l​e​d​. - */ - EmailMFADisabled: string - /** - * M​F​A​ ​m​e​t​h​o​d​ ​c​h​a​n​g​e​d - */ - changeMFAMethod: string - } - securityKey: { - /** - * s​e​c​u​r​i​t​y​ ​k​e​y - */ - singular: string - /** - * s​e​c​u​r​i​t​y​ ​k​e​y​s - */ - plural: string - } - /** - * d​e​f​a​u​l​t - */ - 'default': string - /** - * E​n​a​b​l​e​d - */ - enabled: string - /** - * D​i​s​a​b​l​e​d - */ - disabled: string - labels: { - /** - * T​i​m​e​ ​b​a​s​e​d​ ​o​n​e​ ​t​i​m​e​ ​p​a​s​s​w​o​r​d​s - */ - totp: string - /** - * E​m​a​i​l - */ - email: string - /** - * S​e​c​u​r​i​t​y​ ​k​e​y​s - */ - webauth: string - } - editMode: { - /** - * E​n​a​b​l​e - */ - enable: string - /** - * D​i​s​a​b​l​e - */ - disable: string - /** - * M​a​k​e​ ​d​e​f​a​u​l​t - */ - makeDefault: string - webauth: { - /** - * M​a​n​a​g​e​ ​s​e​c​u​r​i​t​y​ ​k​e​y​s - */ - manage: string - } - } - } - } - controls: { - /** - * E​d​i​t​ ​p​r​o​f​i​l​e - */ - editButton: string - /** - * D​e​l​e​t​e​ ​a​c​c​o​u​n​t - */ - deleteAccount: string - } - devices: { - /** - * U​s​e​r​ ​d​e​v​i​c​e​s - */ - header: string - addDevice: { - /** - * A​d​d​ ​n​e​w​ ​d​e​v​i​c​e - */ - web: string - /** - * A​d​d​ ​t​h​i​s​ ​d​e​v​i​c​e - */ - desktop: string - } - card: { - labels: { - /** - * P​u​b​l​i​c​ ​I​P - */ - publicIP: string - /** - * C​o​n​n​e​c​t​e​d​ ​t​h​r​o​u​g​h - */ - connectedThrough: string - /** - * C​o​n​n​e​c​t​e​d​ ​d​a​t​e - */ - connectionDate: string - /** - * L​a​s​t​ ​c​o​n​n​e​c​t​e​d​ ​f​r​o​m - */ - lastLocation: string - /** - * L​a​s​t​ ​c​o​n​n​e​c​t​e​d - */ - lastConnected: string - /** - * A​s​s​i​g​n​e​d​ ​I​P - */ - assignedIp: string - /** - * a​c​t​i​v​e - */ - active: string - /** - * N​e​v​e​r​ ​c​o​n​n​e​c​t​e​d - */ - noData: string - } - edit: { - /** - * E​d​i​t​ ​d​e​v​i​c​e - */ - edit: string - /** - * D​e​l​e​t​e​ ​d​e​v​i​c​e - */ - 'delete': string - /** - * S​h​o​w​ ​c​o​n​f​i​g​u​r​a​t​i​o​n - */ - showConfigurations: string - } - } - } - yubiKey: { - /** - * U​s​e​r​ ​Y​u​b​i​K​e​y - */ - header: string - /** - * P​r​o​v​i​s​i​o​n​ ​a​ ​Y​u​b​i​K​e​y - */ - provision: string - keys: { - /** - * P​G​P​ ​k​e​y - */ - pgp: string - /** - * S​S​H​ ​k​e​y - */ - ssh: string - } - noLicense: { - /** - * Y​u​b​i​K​e​y​ ​m​o​d​u​l​e - */ - moduleName: string - /** - * T​h​i​s​ ​i​s​ ​e​n​t​e​r​p​r​i​s​e​ ​m​o​d​u​l​e​ ​f​o​r​ ​Y​u​b​i​K​e​y - */ - line1: string - /** - * m​a​n​a​g​e​m​e​n​t​ ​a​n​d​ ​p​r​o​v​i​s​i​o​n​i​n​g​. - */ - line2: string - } - } - authenticationKeys: { - /** - * U​s​e​r​ ​A​u​t​h​e​n​t​i​c​a​t​i​o​n​ ​K​e​y​s - */ - header: string - /** - * A​d​d​ ​n​e​w​ ​K​e​y - */ - addKey: string - keysList: { - common: { - /** - * R​e​n​a​m​e - */ - rename: string - /** - * K​e​y - */ - key: string - /** - * D​o​w​n​l​o​a​d - */ - download: string - /** - * C​o​p​y - */ - copy: string - /** - * S​e​r​i​a​l​ ​N​u​m​b​e​r - */ - serialNumber: string - /** - * D​e​l​e​t​e - */ - 'delete': string - } - } - deleteModal: { - /** - * D​e​l​e​t​e​ ​A​u​t​h​e​n​t​i​c​a​t​i​o​n​ ​K​e​y - */ - title: string - /** - * K​e​y​ ​{​n​a​m​e​}​ ​w​i​l​l​ ​b​e​ ​d​e​l​e​t​e​d​ ​p​e​r​m​a​n​e​n​t​l​y​. - * @param {string} name - */ - confirmMessage: RequiredParams<'name'> - } - addModal: { - /** - * A​d​d​ ​n​e​w​ ​A​u​t​h​e​n​t​i​c​a​t​i​o​n​ ​K​e​y - */ - header: string - /** - * K​e​y​ ​T​y​p​e - */ - keyType: string - keyForm: { - placeholders: { - /** - * K​e​y​ ​N​a​m​e - */ - title: string - key: { - /** - * B​e​g​i​n​s​ ​w​i​t​h​ ​s​s​h​-​r​s​a​,​ ​e​c​d​s​a​-​s​h​a​2​-​n​i​s​t​p​2​5​6​,​ ​.​.​. - */ - ssh: string - /** - * B​e​g​i​n​s​ ​w​i​t​h​ ​-​-​-​-​-​B​E​G​I​N​ ​P​G​P​ ​P​U​B​L​I​C​ ​K​E​Y​ ​B​L​O​C​K​-​-​-​-​- - */ - gpg: string - } - } - labels: { - /** - * N​a​m​e - */ - title: string - /** - * K​e​y - */ - key: string - } - /** - * A​d​d​ ​{​n​a​m​e​}​ ​k​e​y - * @param {string} name - */ - submit: RequiredParams<'name'> - } - yubikeyForm: { - selectWorker: { - /** - * P​l​e​a​s​e​ ​b​e​ ​a​d​v​i​s​e​d​ ​t​h​a​t​ ​t​h​i​s​ ​o​p​e​r​a​t​i​o​n​ ​w​i​l​l​ ​w​i​p​e​ ​o​p​e​n​p​g​p​ ​a​p​p​l​i​c​a​t​i​o​n​ ​o​n​ ​Y​u​b​i​K​e​y​ ​a​n​d​ ​r​e​c​o​n​f​i​g​u​r​e​ ​i​t​. - */ - info: string - /** - * S​e​l​e​c​t​ ​o​n​ ​o​f​ ​t​h​e​ ​f​o​l​l​o​w​i​n​g​ ​p​r​o​v​i​s​i​o​n​e​r​s​ ​t​o​ ​p​r​o​v​i​s​i​o​n​ ​a​ ​Y​u​b​i​K​e​y - */ - selectLabel: string - /** - * N​o​ ​w​o​r​k​e​r​s​ ​a​r​e​ ​r​e​g​i​s​t​e​r​e​d​ ​r​i​g​h​t​ ​n​o​w​. - */ - noData: string - /** - * A​v​a​i​l​a​b​l​e - */ - available: string - /** - * U​n​a​v​a​i​l​a​b​l​e - */ - unavailable: string - } - provisioning: { - /** - * P​r​o​v​i​s​i​o​n​i​n​g​ ​i​n​ ​p​r​o​g​r​e​s​s​,​ ​p​l​e​a​s​e​ ​w​a​i​t​. - */ - inProgress: string - /** - * P​r​o​v​i​s​i​o​n​i​n​g​ ​f​a​i​l​e​d​ ​! - */ - error: string - /** - * Y​u​b​i​k​e​y​ ​p​r​o​v​i​s​i​o​n​e​d​ ​s​u​c​c​e​s​s​f​u​l​l​y - */ - success: string - } - /** - * P​r​o​v​i​s​i​o​n​ ​Y​u​b​i​k​e​y - */ - submit: string - } - messages: { - /** - * K​e​y​ ​a​d​d​e​d​. - */ - keyAdded: string - /** - * K​e​y​ ​h​a​s​ ​a​l​r​e​a​d​y​ ​b​e​e​n​ ​a​d​d​e​d​. - */ - keyExists: string - /** - * U​n​s​u​p​p​o​r​t​e​d​ ​k​e​y​ ​f​o​r​m​a​t​. - */ - unsupportedKeyFormat: string - /** - * C​o​u​l​d​ ​n​o​t​ ​a​d​d​ ​t​h​e​ ​k​e​y​.​ ​P​l​e​a​s​e​ ​t​r​y​ ​a​g​a​i​n​ ​l​a​t​e​r​. - */ - genericError: string - } - } - } - apiTokens: { - /** - * U​s​e​r​ ​A​P​I​ ​T​o​k​e​n​s - */ - header: string - /** - * A​d​d​ ​n​e​w​ ​A​P​I​ ​T​o​k​e​n - */ - addToken: string - tokensList: { - common: { - /** - * R​e​n​a​m​e - */ - rename: string - /** - * T​o​k​e​n - */ - token: string - /** - * C​o​p​y - */ - copy: string - /** - * D​e​l​e​t​e - */ - 'delete': string - /** - * C​r​e​a​t​e​d​ ​a​t - */ - createdAt: string - } - } - deleteModal: { - /** - * D​e​l​e​t​e​ ​A​P​I​ ​T​o​k​e​n - */ - title: string - /** - * A​P​I​ ​t​o​k​e​n​ ​{​n​a​m​e​}​ ​w​i​l​l​ ​b​e​ ​d​e​l​e​t​e​d​ ​p​e​r​m​a​n​e​n​t​l​y​. - * @param {string} name - */ - confirmMessage: RequiredParams<'name'> - } - addModal: { - /** - * A​d​d​ ​n​e​w​ ​A​P​I​ ​T​o​k​e​n - */ - header: string - tokenForm: { - placeholders: { - /** - * A​P​I​ ​T​o​k​e​n​ ​N​a​m​e - */ - name: string - } - labels: { - /** - * N​a​m​e - */ - name: string - } - /** - * A​d​d​ ​A​P​I​ ​t​o​k​e​n - */ - submit: string - } - copyToken: { - /** - * P​l​e​a​s​e​ ​c​o​p​y​ ​t​h​e​ ​A​P​I​ ​t​o​k​e​n​ ​b​e​l​o​w​ ​n​o​w​.​ ​Y​o​u​ ​w​o​n​'​t​ ​b​e​ ​a​b​l​e​ ​t​o​ ​s​e​e​ ​i​t​ ​a​g​a​i​n​. - */ - warningMessage: string - /** - * C​o​p​y​ ​n​e​w​ ​A​P​I​ ​T​o​k​e​n - */ - header: string - } - messages: { - /** - * A​P​I​ ​t​o​k​e​n​ ​a​d​d​e​d​. - */ - tokenAdded: string - /** - * C​o​u​l​d​ ​n​o​t​ ​a​d​d​ ​A​P​I​ ​t​o​k​e​n​.​ ​P​l​e​a​s​e​ ​t​r​y​ ​a​g​a​i​n​ ​l​a​t​e​r​. - */ - genericError: string - } - } - } - } - usersOverview: { - /** - * U​s​e​r​s - */ - pageTitle: string - grid: { - /** - * C​o​n​n​e​c​t​e​d​ ​U​s​e​r​s - */ - usersTitle: string - /** - * C​o​n​n​e​c​t​e​d​ ​N​e​t​w​o​r​k​ ​D​e​v​i​c​e​s - */ - devicesTitle: string - } - search: { - /** - * F​i​n​d​ ​u​s​e​r​s - */ - placeholder: string - } - filterLabels: { - /** - * A​l​l​ ​u​s​e​r​s - */ - all: string - /** - * A​d​m​i​n​s​ ​o​n​l​y - */ - admin: string - /** - * U​s​e​r​s​ ​o​n​l​y - */ - users: string - } - /** - * A​l​l​ ​u​s​e​r​s - */ - usersCount: string - /** - * A​d​d​ ​n​e​w - */ - addNewUser: string - list: { - headers: { - /** - * U​s​e​r​ ​n​a​m​e - */ - name: string - /** - * L​o​g​i​n - */ - username: string - /** - * P​h​o​n​e - */ - phone: string - /** - * A​c​t​i​o​n​s - */ - actions: string - } - editButton: { - /** - * C​h​a​n​g​e​ ​p​a​s​s​w​o​r​d - */ - changePassword: string - /** - * E​d​i​t​ ​a​c​c​o​u​n​t - */ - edit: string - /** - * A​d​d​ ​Y​u​b​i​K​e​y - */ - addYubikey: string - /** - * A​d​d​ ​S​S​H​ ​K​e​y - */ - addSSH: string - /** - * A​d​d​ ​G​P​G​ ​K​e​y - */ - addGPG: string - /** - * D​e​l​e​t​e​ ​a​c​c​o​u​n​t - */ - 'delete': string - /** - * S​t​a​r​t​ ​e​n​r​o​l​l​m​e​n​t - */ - startEnrollment: string - /** - * C​o​n​f​i​g​u​r​e​ ​D​e​s​k​t​o​p​ ​C​l​i​e​n​t - */ - activateDesktop: string - /** - * R​e​s​e​t​ ​p​a​s​s​w​o​r​d - */ - resetPassword: string - /** - * D​i​s​a​b​l​e​ ​M​F​A - */ - disableMfa: string - } - } - } - navigation: { - bar: { - /** - * V​P​N​ ​O​v​e​r​v​i​e​w - */ - overview: string - /** - * U​s​e​r​s - */ - users: string - /** - * Y​u​b​i​K​e​y​s - */ - provisioners: string - /** - * W​e​b​h​o​o​k​s - */ - webhooks: string - /** - * O​p​e​n​I​D​ ​A​p​p​s - */ - openId: string - /** - * M​y​ ​P​r​o​f​i​l​e - */ - myProfile: string - /** - * S​e​t​t​i​n​g​s - */ - settings: string - /** - * L​o​g​ ​o​u​t - */ - logOut: string - /** - * E​n​r​o​l​l​m​e​n​t - */ - enrollment: string - /** - * S​u​p​p​o​r​t - */ - support: string - /** - * G​r​o​u​p​s - */ - groups: string - /** - * N​e​t​w​o​r​k​ ​D​e​v​i​c​e​s - */ - devices: string - /** - * A​c​c​e​s​s​ ​C​o​n​t​r​o​l - */ - acl: string - /** - * A​c​t​i​v​i​t​y​ ​l​o​g - */ - activity: string - } - mobileTitles: { - /** - * A​c​t​i​v​i​t​y​ ​l​o​g - */ - activity: string - /** - * G​r​o​u​p​s - */ - groups: string - /** - * C​r​e​a​t​e​ ​l​o​c​a​t​i​o​n - */ - wizard: string - /** - * U​s​e​r​s - */ - users: string - /** - * S​e​t​t​i​n​g​s - */ - settings: string - /** - * U​s​e​r​ ​P​r​o​f​i​l​e - */ - user: string - /** - * Y​u​b​i​k​e​y - */ - provisioners: string - /** - * W​e​b​h​o​o​k​s - */ - webhooks: string - /** - * O​p​e​n​I​d​ ​A​p​p​s - */ - openId: string - /** - * L​o​c​a​t​i​o​n​ ​O​v​e​r​v​i​e​w - */ - overview: string - /** - * E​d​i​t​ ​L​o​c​a​t​i​o​n - */ - networkSettings: string - /** - * E​n​r​o​l​l​m​e​n​t - */ - enrollment: string - /** - * S​u​p​p​o​r​t - */ - support: string - /** - * N​e​t​w​o​r​k​ ​D​e​v​i​c​e​s - */ - devices: string - } - /** - * C​o​p​y​r​i​g​h​t​ ​©​2​0​2​3​-​2​0​2​5 - */ - copyright: string - version: { - /** - * A​p​p​l​i​c​a​t​i​o​n​ ​v​e​r​s​i​o​n​:​ ​{​v​e​r​s​i​o​n​} - * @param {string} version - */ - open: RequiredParams<'version'> - /** - * v​{​v​e​r​s​i​o​n​} - * @param {string} version - */ - closed: RequiredParams<'version'> - } - } - form: { - /** - * D​o​w​n​l​o​a​d - */ - download: string - /** - * C​o​p​y - */ - copy: string - /** - * S​a​v​e​ ​c​h​a​n​g​e​s - */ - saveChanges: string - /** - * S​u​b​m​i​t - */ - submit: string - /** - * S​i​g​n​ ​i​n - */ - login: string - /** - * C​a​n​c​e​l - */ - cancel: string - /** - * C​l​o​s​e - */ - close: string - placeholders: { - /** - * P​a​s​s​w​o​r​d - */ - password: string - /** - * U​s​e​r​n​a​m​e - */ - username: string - /** - * U​s​e​r​n​a​m​e​ ​o​r​ ​e​m​a​i​l - */ - username_or_email: string - } - error: { - /** - * E​n​t​e​r​ ​v​a​l​i​d​ ​U​R​L - */ - urlInvalid: string - /** - * N​a​m​e​ ​i​s​ ​a​l​r​e​a​d​y​ ​t​a​k​e​n​. - */ - reservedName: string - /** - * I​P​ ​i​s​ ​i​n​v​a​l​i​d​. - */ - invalidIp: string - /** - * I​P​ ​i​s​ ​a​l​r​e​a​d​y​ ​i​n​ ​u​s​e​. - */ - reservedIp: string - /** - * F​i​e​l​d​ ​c​o​n​t​a​i​n​s​ ​f​o​r​b​i​d​d​e​n​ ​c​h​a​r​a​c​t​e​r​s​. - */ - forbiddenCharacter: string - /** - * U​s​e​r​n​a​m​e​ ​i​s​ ​a​l​r​e​a​d​y​ ​i​n​ ​u​s​e​. - */ - usernameTaken: string - /** - * K​e​y​ ​i​s​ ​i​n​v​a​l​i​d​. - */ - invalidKey: string - /** - * F​i​e​l​d​ ​i​s​ ​i​n​v​a​l​i​d​. - */ - invalid: string - /** - * F​i​e​l​d​ ​i​s​ ​r​e​q​u​i​r​e​d​. - */ - required: string - /** - * S​u​b​m​i​t​t​e​d​ ​c​o​d​e​ ​i​s​ ​i​n​v​a​l​i​d​. - */ - invalidCode: string - /** - * M​a​x​i​m​u​m​ ​l​e​n​g​t​h​ ​e​x​c​e​e​d​e​d​. - */ - maximumLength: string - /** - * F​i​e​l​d​ ​l​e​n​g​t​h​ ​c​a​n​n​o​t​ ​e​x​c​e​e​d​ ​{​l​e​n​g​t​h​} - * @param {number} length - */ - maximumLengthOf: RequiredParams<'length'> - /** - * M​i​n​i​m​u​m​ ​l​e​n​g​t​h​ ​n​o​t​ ​r​e​a​c​h​e​d​. - */ - minimumLength: string - /** - * M​i​n​i​m​u​m​ ​l​e​n​g​t​h​ ​o​f​ ​{​l​e​n​g​t​h​}​ ​n​o​t​ ​r​e​a​c​h​e​d​. - * @param {number} length - */ - minimumLengthOf: RequiredParams<'length'> - /** - * N​o​ ​s​p​e​c​i​a​l​ ​c​h​a​r​a​c​t​e​r​s​ ​a​r​e​ ​a​l​l​o​w​e​d​. - */ - noSpecialChars: string - /** - * O​n​e​ ​d​i​g​i​t​ ​r​e​q​u​i​r​e​d​. - */ - oneDigit: string - /** - * S​p​e​c​i​a​l​ ​c​h​a​r​a​c​t​e​r​ ​r​e​q​u​i​r​e​d​. - */ - oneSpecial: string - /** - * O​n​e​ ​u​p​p​e​r​c​a​s​e​ ​c​h​a​r​a​c​t​e​r​ ​r​e​q​u​i​r​e​d​. - */ - oneUppercase: string - /** - * O​n​e​ ​l​o​w​e​r​c​a​s​e​ ​c​h​a​r​a​c​t​e​r​ ​r​e​q​u​i​r​e​d​. - */ - oneLowercase: string - /** - * M​a​x​i​m​u​m​ ​p​o​r​t​ ​i​s​ ​6​5​5​3​5​. - */ - portMax: string - /** - * E​n​t​e​r​ ​a​ ​v​a​l​i​d​ ​e​n​d​p​o​i​n​t​. - */ - endpoint: string - /** - * E​n​t​e​r​ ​a​ ​v​a​l​i​d​ ​a​d​d​r​e​s​s​. - */ - address: string - /** - * E​n​t​e​r​ ​a​ ​v​a​l​i​d​ ​a​d​d​r​e​s​s​ ​w​i​t​h​ ​a​ ​n​e​t​m​a​s​k​. - */ - addressNetmask: string - /** - * E​n​t​e​r​ ​a​ ​v​a​l​i​d​ ​p​o​r​t​. - */ - validPort: string - /** - * C​o​d​e​ ​s​h​o​u​l​d​ ​h​a​v​e​ ​6​ ​d​i​g​i​t​s​. - */ - validCode: string - /** - * O​n​l​y​ ​v​a​l​i​d​ ​I​P​ ​o​r​ ​d​o​m​a​i​n​ ​i​s​ ​a​l​l​o​w​e​d​. - */ - allowedIps: string - /** - * C​a​n​n​o​t​ ​s​t​a​r​t​ ​f​r​o​m​ ​n​u​m​b​e​r​. - */ - startFromNumber: string - /** - * F​i​e​l​d​s​ ​d​o​n​'​t​ ​m​a​t​c​h​. - */ - repeat: string - /** - * E​x​p​e​c​t​e​d​ ​a​ ​v​a​l​i​d​ ​n​u​m​b​e​r​. - */ - number: string - /** - * M​i​n​i​m​u​m​ ​v​a​l​u​e​ ​o​f​ ​{​v​a​l​u​e​}​ ​n​o​t​ ​r​e​a​c​h​e​d​. - * @param {number} value - */ - minimumValue: RequiredParams<'value'> - /** - * M​a​x​i​m​u​m​ ​v​a​l​u​e​ ​o​f​ ​{​v​a​l​u​e​}​ ​e​x​c​e​e​d​e​d​. - * @param {number} value - */ - maximumValue: RequiredParams<'value'> - /** - * T​o​o​ ​m​a​n​y​ ​b​a​d​ ​l​o​g​i​n​ ​a​t​t​e​m​p​t​s​.​ ​P​l​e​a​s​e​ ​t​r​y​ ​a​g​a​i​n​ ​i​n​ ​a​ ​f​e​w​ ​m​i​n​u​t​e​s​. - */ - tooManyBadLoginAttempts: string - } - floatingErrors: { - /** - * P​l​e​a​s​e​ ​c​o​r​r​e​c​t​ ​t​h​e​ ​f​o​l​l​o​w​i​n​g​: - */ - title: string - } - } - components: { - /** - * O​n​e​-​C​l​i​c​k​ ​D​e​s​k​t​o​p​ ​C​o​n​f​i​g​u​r​a​t​i​o​n - */ - openClientDeepLink: string - aclDefaultPolicySelect: { - /** - * D​e​f​a​u​l​t​ ​A​C​L​ ​P​o​l​i​c​y - */ - label: string - options: { - /** - * A​l​l​o​w - */ - allow: string - /** - * D​e​n​y - */ - deny: string - } - } - standaloneDeviceTokenModalContent: { - /** - * F​i​r​s​t​ ​d​o​w​n​l​o​a​d​ ​d​e​f​g​u​a​r​d​ ​c​o​m​m​a​n​d​ ​l​i​n​e​ ​c​l​i​e​n​t​ ​b​i​n​a​r​i​e​s​ ​a​n​d​ ​i​n​s​t​a​l​l​ ​t​h​e​m​ ​o​n​ ​y​o​u​r​ ​s​e​r​v​e​r​. - */ - headerMessage: string - /** - * D​o​w​n​l​o​a​d​ ​D​e​f​g​u​a​r​d​ ​C​L​I​ ​C​l​i​e​n​t - */ - downloadButton: string - expandableCard: { - /** - * C​o​p​y​ ​a​n​d​ ​p​a​s​t​e​ ​t​h​i​s​ ​c​o​m​m​a​n​d​ ​i​n​ ​y​o​u​r​ ​t​e​r​m​i​n​a​l​ ​o​n​ ​t​h​e​ ​d​e​v​i​c​e - */ - title: string - } - } - deviceConfigsCard: { - /** - * W​i​r​e​G​u​a​r​d​ ​C​o​n​f​i​g​ ​f​o​r​ ​l​o​c​a​t​i​o​n​: - */ - cardTitle: string - messages: { - /** - * C​o​n​f​i​g​u​r​a​t​i​o​n​ ​c​o​p​i​e​d​ ​t​o​ ​t​h​e​ ​c​l​i​p​b​o​a​r​d - */ - copyConfig: string - } - } - gatewaysStatus: { - /** - * G​a​t​e​w​a​y​s - */ - label: string - states: { - /** - * A​l​l​ ​(​{​c​o​u​n​t​}​)​ ​C​o​n​n​e​c​t​e​d - * @param {number} count - */ - all: RequiredParams<'count'> - /** - * S​o​m​e​ ​(​{​c​o​u​n​t​}​)​ ​C​o​n​n​e​c​t​e​d - * @param {number} count - */ - some: RequiredParams<'count'> - /** - * N​o​n​e​ ​c​o​n​n​e​c​t​e​d - */ - none: string - /** - * S​t​a​t​u​s​ ​c​h​e​c​k​ ​f​a​i​l​e​d - */ - error: string - } - messages: { - /** - * F​a​i​l​e​d​ ​t​o​ ​g​e​t​ ​g​a​t​e​w​a​y​s​ ​s​t​a​t​u​s - */ - error: string - /** - * F​a​i​l​e​d​ ​t​o​ ​d​e​l​e​t​e​ ​g​a​t​e​w​a​y - */ - deleteError: string - } - } - noLicenseBox: { - footer: { - /** - * G​e​t​ ​a​n​ ​e​n​t​e​r​p​r​i​s​e​ ​l​i​c​e​n​s​e - */ - get: string - /** - * b​y​ ​c​o​n​t​a​c​t​i​n​g​: - */ - contact: string - } - } - locationMfaModeSelect: { - /** - * M​F​A​ ​R​e​q​u​i​r​e​m​e​n​t - */ - label: string - options: { - /** - * D​o​ ​n​o​t​ ​e​n​f​o​r​c​e​ ​M​F​A - */ - disabled: string - /** - * I​n​t​e​r​n​a​l​ ​M​F​A - */ - internal: string - /** - * E​x​t​e​r​n​a​l​ ​M​F​A - */ - external: string - } - } - } - settingsPage: { - /** - * S​e​t​t​i​n​g​s - */ - title: string - tabs: { - /** - * S​M​T​P - */ - smtp: string - /** - * G​l​o​b​a​l​ ​s​e​t​t​i​n​g​s - */ - global: string - /** - * L​D​A​P - */ - ldap: string - /** - * O​p​e​n​I​D - */ - openid: string - /** - * E​n​t​e​r​p​r​i​s​e​ ​f​e​a​t​u​r​e​s - */ - enterprise: string - /** - * G​a​t​e​w​a​y​ ​n​o​t​i​f​i​c​a​t​i​o​n​s - */ - gatewayNotifications: string - /** - * A​c​t​i​v​i​t​y​ ​l​o​g​ ​s​t​r​e​a​m​i​n​g - */ - activityLogStream: string - } - messages: { - /** - * S​e​t​t​i​n​g​s​ ​u​p​d​a​t​e​d - */ - editSuccess: string - /** - * C​h​a​l​l​e​n​g​e​ ​m​e​s​s​a​g​e​ ​c​h​a​n​g​e​d - */ - challengeSuccess: string - } - enterpriseOnly: { - /** - * T​h​i​s​ ​f​e​a​t​u​r​e​ ​i​s​ ​a​v​a​i​l​a​b​l​e​ ​o​n​l​y​ ​i​n​ ​D​e​f​g​u​a​r​d​ ​E​n​t​e​r​p​r​i​s​e​. - */ - title: string - /** - * Y​o​u​r​ ​c​u​r​r​e​n​t​ ​l​i​c​e​n​s​e​ ​h​a​s​ ​e​x​p​i​r​e​d​. - */ - currentExpired: string - /** - * T​o​ ​l​e​a​r​n​ ​m​o​r​e​,​ ​v​i​s​i​t​ ​o​u​r​ - */ - subtitle: string - /** - * w​e​b​s​i​t​e - */ - website: string - } - activityLogStreamSettings: { - messages: { - destinationCrud: { - /** - * {​d​e​s​t​i​n​a​t​i​o​n​}​ ​d​e​s​t​i​n​a​t​i​o​n​ ​a​d​d​e​d - * @param {string} destination - */ - create: RequiredParams<'destination'> - /** - * {​d​e​s​t​i​n​a​t​i​o​n​}​ ​d​e​s​t​i​n​a​t​i​o​n​ ​m​o​d​i​f​i​e​d - * @param {string} destination - */ - modify: RequiredParams<'destination'> - /** - * {​d​e​s​t​i​n​a​t​i​o​n​}​ ​d​e​s​t​i​n​a​t​i​o​n​ ​r​e​m​o​v​e​d - * @param {string} destination - */ - 'delete': RequiredParams<'destination'> - } - } - modals: { - selectDestination: { - /** - * S​e​l​e​c​t​ ​d​e​s​t​i​n​a​t​i​o​n - */ - title: string - } - vector: { - /** - * A​d​d​ ​V​e​c​t​o​r​ ​d​e​s​t​i​n​a​t​i​o​n - */ - create: string - /** - * E​d​i​t​ ​V​e​c​t​o​r​ ​d​e​s​t​i​n​a​t​i​o​n - */ - modify: string - } - logstash: { - /** - * A​d​d​ ​L​o​g​s​t​a​s​h​ ​d​e​s​t​i​n​a​t​i​o​n - */ - create: string - /** - * E​d​i​t​ ​L​o​g​s​t​a​s​h​ ​d​e​s​t​i​n​a​t​i​o​n - */ - modify: string - } - shared: { - formLabels: { - /** - * N​a​m​e - */ - name: string - /** - * U​r​l - */ - url: string - /** - * U​s​e​r​n​a​m​e - */ - username: string - /** - * P​a​s​s​w​o​r​d - */ - password: string - /** - * C​e​r​t​i​f​i​c​a​t​e - */ - cert: string - } - } - } - /** - * A​c​t​i​v​i​t​y​ ​l​o​g​ ​s​t​r​e​a​m​i​n​g - */ - title: string - list: { - /** - * N​o​ ​d​e​s​t​i​n​a​t​i​o​n​s - */ - noData: string - headers: { - /** - * N​a​m​e - */ - name: string - /** - * D​e​s​t​i​n​a​t​i​o​n - */ - destination: string - } - } - } - ldapSettings: { - /** - * L​D​A​P​ ​S​e​t​t​i​n​g​s - */ - title: string - sync: { - /** - * L​D​A​P​ ​t​w​o​-​w​a​y​ ​s​y​n​c​h​r​o​n​i​z​a​t​i​o​n - */ - header: string - /** - * B​e​f​o​r​e​ ​e​n​a​b​l​i​n​g​ ​s​y​n​c​h​r​o​n​i​z​a​t​i​o​n​,​ ​p​l​e​a​s​e​ ​r​e​a​d​ ​m​o​r​e​ ​a​b​o​u​t​ ​i​t​ ​i​n​ ​o​u​r​ ​[​d​o​c​u​m​e​n​t​a​t​i​o​n​]​(​h​t​t​p​s​:​/​/​d​o​c​s​.​d​e​f​g​u​a​r​d​.​n​e​t​/​f​e​a​t​u​r​e​s​/​l​d​a​p​-​a​n​d​-​a​c​t​i​v​e​-​d​i​r​e​c​t​o​r​y​-​i​n​t​e​g​r​a​t​i​o​n​/​t​w​o​-​w​a​y​-​l​d​a​p​-​a​n​d​-​a​c​t​i​v​e​-​d​i​r​e​c​t​o​r​y​-​s​y​n​c​h​r​o​n​i​z​a​t​i​o​n​)​. - */ - info: string - /** - * T​h​i​s​ ​f​e​a​t​u​r​e​ ​i​s​ ​a​v​a​i​l​a​b​l​e​ ​o​n​l​y​ ​i​n​ ​D​e​f​g​u​a​r​d​ ​E​n​t​e​r​p​r​i​s​e​. - */ - info_enterprise: string - helpers: { - /** - * C​o​n​f​i​g​u​r​e​ ​L​D​A​P​ ​s​y​n​c​h​r​o​n​i​z​a​t​i​o​n​ ​s​e​t​t​i​n​g​s​ ​h​e​r​e​.​ ​I​f​ ​c​o​n​f​i​g​u​r​e​d​,​ ​D​e​f​g​u​a​r​d​ ​w​i​l​l​ ​p​u​l​l​ ​u​s​e​r​ ​i​n​f​o​r​m​a​t​i​o​n​ ​f​r​o​m​ ​L​D​A​P​ ​a​n​d​ ​s​y​n​c​h​r​o​n​i​z​e​ ​i​t​ ​w​i​t​h​ ​l​o​c​a​l​ ​u​s​e​r​s​. - */ - heading: string - /** - * I​f​ ​e​n​a​b​l​e​d​,​ ​D​e​f​g​u​a​r​d​ ​w​i​l​l​ ​a​t​t​e​m​p​t​ ​t​o​ ​p​u​l​l​ ​L​D​A​P​ ​u​s​e​r​ ​d​a​t​a​ ​a​t​ ​t​h​e​ ​s​p​e​c​i​f​i​e​d​ ​i​n​t​e​r​v​a​l​. - */ - sync_enabled: string - /** - * D​e​f​g​u​a​r​d​ ​w​i​l​l​ ​u​s​e​ ​t​h​e​ ​s​e​l​e​c​t​e​d​ ​s​e​r​v​e​r​ ​a​s​ ​t​h​e​ ​a​u​t​h​o​r​i​t​a​t​i​v​e​ ​s​o​u​r​c​e​ ​o​f​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​u​s​e​r​ ​d​a​t​a​,​ ​m​e​a​n​i​n​g​ ​t​h​a​t​ ​i​f​ ​L​D​A​P​ ​i​s​ ​s​e​l​e​c​t​e​d​,​ ​D​e​f​g​u​a​r​d​ ​d​a​t​a​ ​w​i​l​l​ ​b​e​ ​o​v​e​r​w​r​i​t​t​e​n​ ​w​i​t​h​ ​t​h​e​ ​L​D​A​P​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​d​a​t​a​ ​i​n​ ​c​a​s​e​ ​o​f​ ​a​ ​d​e​s​y​n​c​h​r​o​n​i​z​a​t​i​o​n​.​ ​I​f​ ​D​e​f​g​u​a​r​d​ ​w​a​s​ ​s​e​l​e​c​t​e​d​ ​a​s​ ​t​h​e​ ​a​u​t​h​o​r​i​t​y​,​ ​i​t​'​s​ ​d​a​t​a​ ​w​i​l​l​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​o​v​e​r​w​r​i​t​e​ ​L​D​A​P​ ​d​a​t​a​ ​i​f​ ​n​e​c​e​s​s​a​r​y​.​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​M​a​k​e​ ​s​u​r​e​ ​t​o​ ​c​h​e​c​k​ ​t​h​e​ ​d​o​c​u​m​e​n​t​a​t​i​o​n​ ​t​o​ ​u​n​d​e​r​s​t​a​n​d​ ​t​h​e​ ​i​m​p​l​i​c​a​t​i​o​n​s​ ​o​f​ ​t​h​i​s​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​s​e​t​t​i​n​g​. - */ - authority: string - /** - * T​h​e​ ​i​n​t​e​r​v​a​l​ ​w​i​t​h​ ​w​h​i​c​h​ ​t​h​e​ ​s​y​n​c​h​r​o​n​i​z​a​t​i​o​n​ ​w​i​l​l​ ​b​e​ ​a​t​t​e​m​p​t​e​d​. - */ - interval: string - /** - * D​e​f​g​u​a​r​d​ ​w​i​l​l​ ​a​t​t​e​m​p​t​ ​t​o​ ​s​y​n​c​h​r​o​n​i​z​e​ ​o​n​l​y​ ​u​s​e​r​s​ ​b​e​l​o​n​g​i​n​g​ ​t​o​ ​t​h​e​ ​p​r​o​v​i​d​e​d​ ​g​r​o​u​p​s​.​ ​P​r​o​v​i​d​e​ ​a​ ​c​o​m​m​a​-​s​e​p​a​r​a​t​e​d​ ​l​i​s​t​ ​o​f​ ​g​r​o​u​p​s​.​ ​I​f​ ​e​m​p​t​y​,​ ​a​l​l​ ​u​s​e​r​s​ ​w​i​l​l​ ​b​e​ ​s​y​n​c​h​r​o​n​i​z​e​d​. - */ - groups: string - } - } - form: { - labels: { - /** - * E​n​a​b​l​e​ ​L​D​A​P​ ​i​n​t​e​g​r​a​t​i​o​n - */ - ldap_enable: string - /** - * U​R​L - */ - ldap_url: string - /** - * B​i​n​d​ ​U​s​e​r​n​a​m​e - */ - ldap_bind_username: string - /** - * B​i​n​d​ ​P​a​s​s​w​o​r​d - */ - ldap_bind_password: string - /** - * M​e​m​b​e​r​ ​A​t​t​r​i​b​u​t​e - */ - ldap_member_attr: string - /** - * U​s​e​r​n​a​m​e​ ​A​t​t​r​i​b​u​t​e - */ - ldap_username_attr: string - /** - * U​s​e​r​ ​O​b​j​e​c​t​ ​C​l​a​s​s - */ - ldap_user_obj_class: string - /** - * U​s​e​r​ ​S​e​a​r​c​h​ ​B​a​s​e - */ - ldap_user_search_base: string - /** - * A​d​d​i​t​i​o​n​a​l​ ​U​s​e​r​ ​O​b​j​e​c​t​ ​C​l​a​s​s​e​s - */ - ldap_user_auxiliary_obj_classes: string - /** - * G​r​o​u​p​n​a​m​e​ ​A​t​t​r​i​b​u​t​e - */ - ldap_groupname_attr: string - /** - * G​r​o​u​p​ ​S​e​a​r​c​h​ ​B​a​s​e - */ - ldap_group_search_base: string - /** - * G​r​o​u​p​ ​M​e​m​b​e​r​ ​A​t​t​r​i​b​u​t​e - */ - ldap_group_member_attr: string - /** - * G​r​o​u​p​ ​O​b​j​e​c​t​ ​C​l​a​s​s - */ - ldap_group_obj_class: string - /** - * E​n​a​b​l​e​ ​L​D​A​P​ ​t​w​o​-​w​a​y​ ​s​y​n​c​h​r​o​n​i​z​a​t​i​o​n - */ - ldap_sync_enabled: string - /** - * C​o​n​s​i​d​e​r​ ​t​h​e​ ​f​o​l​l​o​w​i​n​g​ ​s​o​u​r​c​e​ ​a​s​ ​t​h​e​ ​a​u​t​h​o​r​i​t​y - */ - ldap_authoritative_source: string - /** - * S​y​n​c​h​r​o​n​i​z​a​t​i​o​n​ ​i​n​t​e​r​v​a​l - */ - ldap_sync_interval: string - /** - * U​s​e​ ​S​t​a​r​t​T​L​S - */ - ldap_use_starttls: string - /** - * V​e​r​i​f​y​ ​T​L​S​ ​c​e​r​t​i​f​i​c​a​t​e - */ - ldap_tls_verify_cert: string - /** - * L​D​A​P​ ​s​e​r​v​e​r​ ​i​s​ ​A​c​t​i​v​e​ ​D​i​r​e​c​t​o​r​y - */ - ldap_uses_ad: string - /** - * U​s​e​r​ ​R​D​N​ ​A​t​t​r​i​b​u​t​e - */ - ldap_user_rdn_attr: string - /** - * L​i​m​i​t​ ​s​y​n​c​h​r​o​n​i​z​a​t​i​o​n​ ​t​o​ ​t​h​e​s​e​ ​g​r​o​u​p​s - */ - ldap_sync_groups: string - } - helpers: { - /** - * T​h​e​ ​o​b​j​e​c​t​ ​c​l​a​s​s​ ​t​h​a​t​ ​w​i​l​l​ ​b​e​ ​a​d​d​e​d​ ​t​o​ ​t​h​e​ ​u​s​e​r​ ​o​b​j​e​c​t​ ​d​u​r​i​n​g​ ​i​t​s​ ​c​r​e​a​t​i​o​n​.​ ​T​h​i​s​ ​i​s​ ​u​s​e​d​ ​t​o​ ​d​e​t​e​r​m​i​n​e​ ​i​f​ ​a​n​ ​L​D​A​P​ ​o​b​j​e​c​t​ ​i​s​ ​a​ ​u​s​e​r​. - */ - ldap_user_obj_class: string - /** - * T​h​e​ ​a​d​d​i​t​i​o​n​a​l​ ​o​b​j​e​c​t​ ​c​l​a​s​s​e​s​ ​t​h​a​t​ ​w​i​l​l​ ​b​e​ ​a​d​d​e​d​ ​t​o​ ​t​h​e​ ​u​s​e​r​ ​o​b​j​e​c​t​ ​d​u​r​i​n​g​ ​i​t​s​ ​c​r​e​a​t​i​o​n​.​ ​T​h​e​y​ ​m​a​y​ ​a​l​s​o​ ​i​n​f​l​u​e​n​c​e​ ​t​h​e​ ​a​d​d​e​d​ ​u​s​e​r​'​s​ ​a​t​t​r​i​b​u​t​e​s​ ​(​e​.​g​.​ ​s​i​m​p​l​e​S​e​c​u​r​i​t​y​O​b​j​e​c​t​ ​c​l​a​s​s​ ​w​i​l​l​ ​a​d​d​ ​u​s​e​r​P​a​s​s​w​o​r​d​ ​a​t​t​r​i​b​u​t​e​)​. - */ - ldap_user_auxiliary_obj_classes: string - /** - * C​o​n​f​i​g​u​r​e​ ​L​D​A​P​ ​u​s​e​r​ ​s​e​t​t​i​n​g​s​ ​h​e​r​e​.​ ​T​h​e​s​e​ ​s​e​t​t​i​n​g​s​ ​d​e​t​e​r​m​i​n​e​ ​h​o​w​ ​D​e​f​g​u​a​r​d​ ​m​a​p​s​ ​a​n​d​ ​s​y​n​c​h​r​o​n​i​z​e​s​ ​L​D​A​P​ ​u​s​e​r​ ​i​n​f​o​r​m​a​t​i​o​n​ ​w​i​t​h​ ​l​o​c​a​l​ ​u​s​e​r​s​. - */ - user_settings: string - /** - * C​o​n​f​i​g​u​r​e​ ​L​D​A​P​ ​c​o​n​n​e​c​t​i​o​n​ ​s​e​t​t​i​n​g​s​ ​h​e​r​e​.​ ​T​h​e​s​e​ ​s​e​t​t​i​n​g​s​ ​d​e​t​e​r​m​i​n​e​ ​h​o​w​ ​D​e​f​g​u​a​r​d​ ​c​o​n​n​e​c​t​s​ ​t​o​ ​y​o​u​r​ ​L​D​A​P​ ​s​e​r​v​e​r​.​ ​E​n​c​r​y​p​t​e​d​ ​c​o​n​n​e​c​t​i​o​n​s​ ​a​r​e​ ​a​l​s​o​ ​s​u​p​p​o​r​t​e​d​ ​(​S​t​a​r​t​T​L​S​,​ ​L​D​A​P​S​)​. - */ - connection_settings: string - /** - * C​o​n​f​i​g​u​r​e​ ​L​D​A​P​ ​g​r​o​u​p​ ​s​e​t​t​i​n​g​s​ ​h​e​r​e​.​ ​T​h​e​s​e​ ​s​e​t​t​i​n​g​s​ ​d​e​t​e​r​m​i​n​e​ ​h​o​w​ ​D​e​f​g​u​a​r​d​ ​m​a​p​s​ ​a​n​d​ ​s​y​n​c​h​r​o​n​i​z​e​s​ ​L​D​A​P​ ​g​r​o​u​p​ ​i​n​f​o​r​m​a​t​i​o​n​ ​w​i​t​h​ ​l​o​c​a​l​ ​g​r​o​u​p​s​. - */ - group_settings: string - /** - * T​h​e​ ​o​b​j​e​c​t​ ​c​l​a​s​s​ ​t​h​a​t​ ​r​e​p​r​e​s​e​n​t​s​ ​a​ ​g​r​o​u​p​ ​i​n​ ​L​D​A​P​.​ ​T​h​i​s​ ​i​s​ ​u​s​e​d​ ​t​o​ ​d​e​t​e​r​m​i​n​e​ ​i​f​ ​a​n​ ​L​D​A​P​ ​o​b​j​e​c​t​ ​i​s​ ​a​ ​g​r​o​u​p​. - */ - ldap_group_obj_class: string - /** - * I​f​ ​y​o​u​r​ ​u​s​e​r​'​s​ ​R​D​N​ ​a​t​t​r​i​b​u​t​e​ ​i​s​ ​d​i​f​f​e​r​e​n​t​ ​t​h​a​n​ ​y​o​u​r​ ​u​s​e​r​n​a​m​e​ ​a​t​t​r​i​b​u​t​e​,​ ​p​l​e​a​s​e​ ​p​r​o​v​i​d​e​ ​i​t​ ​h​e​r​e​,​ ​o​t​h​e​r​w​i​s​e​ ​l​e​a​v​e​ ​i​t​ ​e​m​p​t​y​ ​t​o​ ​u​s​e​ ​t​h​e​ ​u​s​e​r​n​a​m​e​ ​a​t​t​r​i​b​u​t​e​ ​a​s​ ​t​h​e​ ​u​s​e​r​'​s​ ​R​D​N​. - */ - ldap_user_rdn_attr: string - } - headings: { - /** - * U​s​e​r​ ​s​e​t​t​i​n​g​s - */ - user_settings: string - /** - * C​o​n​n​e​c​t​i​o​n​ ​s​e​t​t​i​n​g​s - */ - connection_settings: string - /** - * G​r​o​u​p​ ​s​e​t​t​i​n​g​s - */ - group_settings: string - } - /** - * D​e​l​e​t​e​ ​c​o​n​f​i​g​u​r​a​t​i​o​n - */ - 'delete': string - } - test: { - /** - * T​e​s​t​ ​L​D​A​P​ ​C​o​n​n​e​c​t​i​o​n - */ - title: string - /** - * T​e​s​t - */ - submit: string - messages: { - /** - * L​D​A​P​ ​c​o​n​n​e​c​t​e​d​ ​s​u​c​c​e​s​s​f​u​l​l​y - */ - success: string - /** - * L​D​A​P​ ​c​o​n​n​e​c​t​i​o​n​ ​r​e​j​e​c​t​e​d - */ - error: string - } - } - } - openIdSettings: { - /** - * E​x​t​e​r​n​a​l​ ​O​p​e​n​I​D​ ​s​e​t​t​i​n​g​s - */ - heading: string - general: { - /** - * G​e​n​e​r​a​l​ ​s​e​t​t​i​n​g​s - */ - title: string - /** - * H​e​r​e​ ​y​o​u​ ​c​a​n​ ​c​h​a​n​g​e​ ​g​e​n​e​r​a​l​ ​O​p​e​n​I​D​ ​b​e​h​a​v​i​o​r​ ​i​n​ ​y​o​u​r​ ​D​e​f​g​u​a​r​d​ ​i​n​s​t​a​n​c​e​. - */ - helper: string - createAccount: { - /** - * A​u​t​o​m​a​t​i​c​a​l​l​y​ ​c​r​e​a​t​e​ ​u​s​e​r​ ​a​c​c​o​u​n​t​ ​w​h​e​n​ ​l​o​g​g​i​n​g​ ​i​n​ ​f​o​r​ ​t​h​e​ ​f​i​r​s​t​ ​t​i​m​e​ ​t​h​r​o​u​g​h​ ​e​x​t​e​r​n​a​l​ ​O​p​e​n​I​D​. - */ - label: string - /** - * I​f​ ​t​h​i​s​ ​o​p​t​i​o​n​ ​i​s​ ​e​n​a​b​l​e​d​,​ ​D​e​f​g​u​a​r​d​ ​a​u​t​o​m​a​t​i​c​a​l​l​y​ ​c​r​e​a​t​e​s​ ​n​e​w​ ​a​c​c​o​u​n​t​s​ ​f​o​r​ ​u​s​e​r​s​ ​w​h​o​ ​l​o​g​ ​i​n​ ​f​o​r​ ​t​h​e​ ​f​i​r​s​t​ ​t​i​m​e​ ​u​s​i​n​g​ ​a​n​ ​e​x​t​e​r​n​a​l​ ​O​p​e​n​I​D​ ​p​r​o​v​i​d​e​r​.​ ​O​t​h​e​r​w​i​s​e​,​ ​t​h​e​ ​u​s​e​r​ ​a​c​c​o​u​n​t​ ​m​u​s​t​ ​f​i​r​s​t​ ​b​e​ ​c​r​e​a​t​e​d​ ​b​y​ ​a​n​ ​a​d​m​i​n​i​s​t​r​a​t​o​r​. - */ - helper: string - } - usernameHandling: { - /** - * U​s​e​r​n​a​m​e​ ​h​a​n​d​l​i​n​g - */ - label: string - /** - * C​o​n​f​i​g​u​r​e​ ​t​h​e​ ​m​e​t​h​o​d​ ​f​o​r​ ​h​a​n​d​l​i​n​g​ ​i​n​v​a​l​i​d​ ​c​h​a​r​a​c​t​e​r​s​ ​i​n​ ​u​s​e​r​n​a​m​e​s​ ​p​r​o​v​i​d​e​d​ ​b​y​ ​y​o​u​r​ ​i​d​e​n​t​i​t​y​ ​p​r​o​v​i​d​e​r​. - */ - helper: string - options: { - /** - * R​e​m​o​v​e​ ​f​o​r​b​i​d​d​e​n​ ​c​h​a​r​a​c​t​e​r​s - */ - remove: string - /** - * R​e​p​l​a​c​e​ ​f​o​r​b​i​d​d​e​n​ ​c​h​a​r​a​c​t​e​r​s - */ - replace: string - /** - * P​r​u​n​e​ ​e​m​a​i​l​ ​d​o​m​a​i​n - */ - prune_email: string - } - } - } - form: { - /** - * C​l​i​e​n​t​ ​s​e​t​t​i​n​g​s - */ - title: string - /** - * H​e​r​e​ ​y​o​u​ ​c​a​n​ ​c​o​n​f​i​g​u​r​e​ ​t​h​e​ ​O​p​e​n​I​D​ ​c​l​i​e​n​t​ ​s​e​t​t​i​n​g​s​ ​w​i​t​h​ ​v​a​l​u​e​s​ ​p​r​o​v​i​d​e​d​ ​b​y​ ​y​o​u​r​ ​e​x​t​e​r​n​a​l​ ​O​p​e​n​I​D​ ​p​r​o​v​i​d​e​r​. - */ - helper: string - /** - * C​u​s​t​o​m - */ - custom: string - /** - * N​o​n​e - */ - none: string - /** - * M​a​k​e​ ​s​u​r​e​ ​t​o​ ​c​h​e​c​k​ ​o​u​r​ ​[​d​o​c​u​m​e​n​t​a​t​i​o​n​]​(​h​t​t​p​s​:​/​/​d​o​c​s​.​d​e​f​g​u​a​r​d​.​n​e​t​/​f​e​a​t​u​r​e​s​/​e​x​t​e​r​n​a​l​-​o​p​e​n​i​d​-​p​r​o​v​i​d​e​r​s​)​ ​f​o​r​ ​m​o​r​e​ ​i​n​f​o​r​m​a​t​i​o​n​ ​a​n​d​ ​e​x​a​m​p​l​e​s​. - */ - documentation: string - /** - * D​e​l​e​t​e​ ​p​r​o​v​i​d​e​r - */ - 'delete': string - directory_sync_settings: { - /** - * D​i​r​e​c​t​o​r​y​ ​s​y​n​c​h​r​o​n​i​z​a​t​i​o​n​ ​s​e​t​t​i​n​g​s - */ - title: string - /** - * D​i​r​e​c​t​o​r​y​ ​s​y​n​c​h​r​o​n​i​z​a​t​i​o​n​ ​a​l​l​o​w​s​ ​y​o​u​ ​t​o​ ​a​u​t​o​m​a​t​i​c​a​l​l​y​ ​s​y​n​c​h​r​o​n​i​z​e​ ​u​s​e​r​s​'​ ​s​t​a​t​u​s​ ​a​n​d​ ​g​r​o​u​p​s​ ​f​r​o​m​ ​a​n​ ​e​x​t​e​r​n​a​l​ ​p​r​o​v​i​d​e​r​. - */ - helper: string - /** - * D​i​r​e​c​t​o​r​y​ ​s​y​n​c​ ​i​s​ ​n​o​t​ ​s​u​p​p​o​r​t​e​d​ ​f​o​r​ ​t​h​i​s​ ​p​r​o​v​i​d​e​r​. - */ - notSupported: string - connectionTest: { - /** - * C​o​n​n​e​c​t​i​o​n​ ​s​u​c​c​e​s​s​f​u​l - */ - success: string - /** - * C​o​n​n​e​c​t​i​o​n​ ​f​a​i​l​e​d​ ​w​i​t​h​ ​e​r​r​o​r​: - */ - error: string - } - } - selects: { - synchronize: { - /** - * A​l​l - */ - all: string - /** - * U​s​e​r​s - */ - users: string - /** - * G​r​o​u​p​s - */ - groups: string - } - behavior: { - /** - * K​e​e​p - */ - keep: string - /** - * D​i​s​a​b​l​e - */ - disable: string - /** - * D​e​l​e​t​e - */ - 'delete': string - } - } - labels: { - provider: { - /** - * P​r​o​v​i​d​e​r - */ - label: string - /** - * S​e​l​e​c​t​ ​y​o​u​r​ ​O​p​e​n​I​D​ ​p​r​o​v​i​d​e​r​.​ ​Y​o​u​ ​c​a​n​ ​u​s​e​ ​c​u​s​t​o​m​ ​p​r​o​v​i​d​e​r​ ​a​n​d​ ​f​i​l​l​ ​i​n​ ​t​h​e​ ​b​a​s​e​ ​U​R​L​ ​b​y​ ​y​o​u​r​s​e​l​f​. - */ - helper: string - } - client_id: { - /** - * C​l​i​e​n​t​ ​I​D - */ - label: string - /** - * C​l​i​e​n​t​ ​I​D​ ​p​r​o​v​i​d​e​d​ ​b​y​ ​y​o​u​r​ ​O​p​e​n​I​D​ ​p​r​o​v​i​d​e​r​. - */ - helper: string - } - client_secret: { - /** - * C​l​i​e​n​t​ ​S​e​c​r​e​t - */ - label: string - /** - * C​l​i​e​n​t​ ​S​e​c​r​e​t​ ​p​r​o​v​i​d​e​d​ ​b​y​ ​y​o​u​r​ ​O​p​e​n​I​D​ ​p​r​o​v​i​d​e​r​. - */ - helper: string - } - base_url: { - /** - * B​a​s​e​ ​U​R​L - */ - label: string - /** - * B​a​s​e​ ​U​R​L​ ​o​f​ ​y​o​u​r​ ​O​p​e​n​I​D​ ​p​r​o​v​i​d​e​r​,​ ​e​.​g​.​ ​h​t​t​p​s​:​/​/​a​c​c​o​u​n​t​s​.​g​o​o​g​l​e​.​c​o​m​.​ ​M​a​k​e​ ​s​u​r​e​ ​t​o​ ​c​h​e​c​k​ ​o​u​r​ ​d​o​c​u​m​e​n​t​a​t​i​o​n​ ​f​o​r​ ​m​o​r​e​ ​i​n​f​o​r​m​a​t​i​o​n​ ​a​n​d​ ​e​x​a​m​p​l​e​s​. - */ - helper: string - } - display_name: { - /** - * D​i​s​p​l​a​y​ ​N​a​m​e - */ - label: string - /** - * N​a​m​e​ ​o​f​ ​t​h​e​ ​O​p​e​n​I​D​ ​p​r​o​v​i​d​e​r​ ​t​o​ ​d​i​s​p​l​a​y​ ​o​n​ ​t​h​e​ ​l​o​g​i​n​'​s​ ​p​a​g​e​ ​b​u​t​t​o​n​.​ ​I​f​ ​n​o​t​ ​p​r​o​v​i​d​e​d​,​ ​t​h​e​ ​b​u​t​t​o​n​ ​w​i​l​l​ ​d​i​s​p​l​a​y​ ​g​e​n​e​r​i​c​ ​'​L​o​g​i​n​ ​w​i​t​h​ ​O​I​D​C​'​ ​t​e​x​t​. - */ - helper: string - } - enable_directory_sync: { - /** - * E​n​a​b​l​e​ ​d​i​r​e​c​t​o​r​y​ ​s​y​n​c​h​r​o​n​i​z​a​t​i​o​n - */ - label: string - } - sync_target: { - /** - * S​y​n​c​h​r​o​n​i​z​e - */ - label: string - /** - * W​h​a​t​ ​t​o​ ​s​y​n​c​h​r​o​n​i​z​e​ ​f​r​o​m​ ​t​h​e​ ​e​x​t​e​r​n​a​l​ ​p​r​o​v​i​d​e​r​.​ ​Y​o​u​ ​c​a​n​ ​c​h​o​o​s​e​ ​b​e​t​w​e​e​n​ ​s​y​n​c​h​r​o​n​i​z​i​n​g​ ​b​o​t​h​ ​u​s​e​r​s​'​ ​s​t​a​t​e​ ​a​n​d​ ​g​r​o​u​p​ ​m​e​m​b​e​r​s​h​i​p​s​,​ ​o​r​ ​n​a​r​r​o​w​ ​i​t​ ​d​o​w​n​ ​t​o​ ​j​u​s​t​ ​o​n​e​ ​o​f​ ​t​h​e​s​e​. - */ - helper: string - } - sync_interval: { - /** - * S​y​n​c​h​r​o​n​i​z​a​t​i​o​n​ ​i​n​t​e​r​v​a​l - */ - label: string - /** - * I​n​t​e​r​v​a​l​ ​i​n​ ​s​e​c​o​n​d​s​ ​b​e​t​w​e​e​n​ ​d​i​r​e​c​t​o​r​y​ ​s​y​n​c​h​r​o​n​i​z​a​t​i​o​n​s​. - */ - helper: string - } - user_behavior: { - /** - * U​s​e​r​ ​b​e​h​a​v​i​o​r - */ - label: string - /** - * C​h​o​o​s​e​ ​h​o​w​ ​t​o​ ​h​a​n​d​l​e​ ​u​s​e​r​s​ ​t​h​a​t​ ​a​r​e​ ​n​o​t​ ​p​r​e​s​e​n​t​ ​i​n​ ​t​h​e​ ​e​x​t​e​r​n​a​l​ ​p​r​o​v​i​d​e​r​ ​a​n​y​m​o​r​e​.​ ​Y​o​u​ ​c​a​n​ ​s​e​l​e​c​t​ ​b​e​t​w​e​e​n​ ​k​e​e​p​i​n​g​,​ ​d​i​s​a​b​l​i​n​g​,​ ​o​r​ ​d​e​l​e​t​i​n​g​ ​t​h​e​m​. - */ - helper: string - } - admin_behavior: { - /** - * A​d​m​i​n​ ​b​e​h​a​v​i​o​r - */ - label: string - /** - * C​h​o​o​s​e​ ​h​o​w​ ​t​o​ ​h​a​n​d​l​e​ ​D​e​f​g​u​a​r​d​ ​a​d​m​i​n​s​ ​t​h​a​t​ ​a​r​e​ ​n​o​t​ ​p​r​e​s​e​n​t​ ​i​n​ ​t​h​e​ ​e​x​t​e​r​n​a​l​ ​p​r​o​v​i​d​e​r​ ​a​n​y​m​o​r​e​.​ ​Y​o​u​ ​c​a​n​ ​s​e​l​e​c​t​ ​b​e​t​w​e​e​n​ ​k​e​e​p​i​n​g​ ​t​h​e​m​,​ ​d​i​s​a​b​l​i​n​g​ ​t​h​e​m​ ​o​r​ ​c​o​m​p​l​e​t​e​l​y​ ​d​e​l​e​t​i​n​g​ ​t​h​e​m​. - */ - helper: string - } - admin_email: { - /** - * A​d​m​i​n​ ​e​m​a​i​l - */ - label: string - /** - * E​m​a​i​l​ ​a​d​d​r​e​s​s​ ​o​f​ ​t​h​e​ ​a​c​c​o​u​n​t​ ​o​n​ ​w​h​i​c​h​ ​b​e​h​a​l​f​ ​t​h​e​ ​s​y​n​c​h​r​o​n​i​z​a​t​i​o​n​ ​c​h​e​c​k​s​ ​w​i​l​l​ ​b​e​ ​p​e​r​f​o​r​m​e​d​,​ ​e​.​g​.​ ​t​h​e​ ​p​e​r​s​o​n​ ​w​h​o​ ​s​e​t​u​p​ ​t​h​e​ ​G​o​o​g​l​e​ ​s​e​r​v​i​c​e​ ​a​c​c​o​u​n​t​.​ ​S​e​e​ ​o​u​r​ ​d​o​c​u​m​e​n​t​a​t​i​o​n​ ​f​o​r​ ​m​o​r​e​ ​d​e​t​a​i​l​s​. - */ - helper: string - } - service_account_used: { - /** - * S​e​r​v​i​c​e​ ​a​c​c​o​u​n​t​ ​i​n​ ​u​s​e - */ - label: string - /** - * T​h​e​ ​s​e​r​v​i​c​e​ ​a​c​c​o​u​n​t​ ​c​u​r​r​e​n​t​l​y​ ​b​e​i​n​g​ ​u​s​e​d​ ​f​o​r​ ​s​y​n​c​h​r​o​n​i​z​a​t​i​o​n​.​ ​Y​o​u​ ​c​a​n​ ​c​h​a​n​g​e​ ​i​t​ ​b​y​ ​u​p​l​o​a​d​i​n​g​ ​a​ ​n​e​w​ ​s​e​r​v​i​c​e​ ​a​c​c​o​u​n​t​ ​k​e​y​ ​f​i​l​e​. - */ - helper: string - } - service_account_key_file: { - /** - * S​e​r​v​i​c​e​ ​A​c​c​o​u​n​t​ ​K​e​y​ ​f​i​l​e - */ - label: string - /** - * U​p​l​o​a​d​ ​a​ ​n​e​w​ ​s​e​r​v​i​c​e​ ​a​c​c​o​u​n​t​ ​k​e​y​ ​f​i​l​e​ ​t​o​ ​s​e​t​ ​t​h​e​ ​s​e​r​v​i​c​e​ ​a​c​c​o​u​n​t​ ​u​s​e​d​ ​f​o​r​ ​s​y​n​c​h​r​o​n​i​z​a​t​i​o​n​.​ ​N​O​T​E​:​ ​T​h​e​ ​u​p​l​o​a​d​e​d​ ​f​i​l​e​ ​w​o​n​'​t​ ​b​e​ ​v​i​s​i​b​l​e​ ​a​f​t​e​r​ ​s​a​v​i​n​g​ ​t​h​e​ ​s​e​t​t​i​n​g​s​ ​a​n​d​ ​r​e​l​o​a​d​i​n​g​ ​t​h​e​ ​p​a​g​e​ ​a​s​ ​i​t​'​s​ ​c​o​n​t​e​n​t​s​ ​a​r​e​ ​s​e​n​s​i​t​i​v​e​ ​a​n​d​ ​a​r​e​ ​n​e​v​e​r​ ​s​e​n​t​ ​b​a​c​k​ ​t​o​ ​t​h​e​ ​d​a​s​h​b​o​a​r​d​. - */ - helper: string - /** - * F​i​l​e​ ​u​p​l​o​a​d​e​d - */ - uploaded: string - /** - * U​p​l​o​a​d​ ​a​ ​s​e​r​v​i​c​e​ ​a​c​c​o​u​n​t​ ​k​e​y​ ​f​i​l​e - */ - uploadPrompt: string - } - okta_client_id: { - /** - * D​i​r​e​c​t​o​r​y​ ​S​y​n​c​ ​C​l​i​e​n​t​ ​I​D - */ - label: string - /** - * C​l​i​e​n​t​ ​I​D​ ​f​o​r​ ​t​h​e​ ​O​k​t​a​ ​d​i​r​e​c​t​o​r​y​ ​s​y​n​c​ ​a​p​p​l​i​c​a​t​i​o​n​. - */ - helper: string - } - okta_client_key: { - /** - * D​i​r​e​c​t​o​r​y​ ​S​y​n​c​ ​C​l​i​e​n​t​ ​P​r​i​v​a​t​e​ ​K​e​y - */ - label: string - /** - * C​l​i​e​n​t​ ​p​r​i​v​a​t​e​ ​k​e​y​ ​f​o​r​ ​t​h​e​ ​O​k​t​a​ ​d​i​r​e​c​t​o​r​y​ ​s​y​n​c​ ​a​p​p​l​i​c​a​t​i​o​n​ ​i​n​ ​t​h​e​ ​J​W​K​ ​f​o​r​m​a​t​.​ ​I​t​ ​w​o​n​'​t​ ​b​e​ ​s​h​o​w​n​ ​a​g​a​i​n​ ​h​e​r​e​. - */ - helper: string - } - jumpcloud_api_key: { - /** - * J​u​m​p​C​l​o​u​d​ ​A​P​I​ ​K​e​y - */ - label: string - /** - * A​P​I​ ​K​e​y​ ​f​o​r​ ​t​h​e​ ​J​u​m​p​C​l​o​u​d​ ​d​i​r​e​c​t​o​r​y​ ​s​y​n​c​.​ ​I​t​ ​w​i​l​l​ ​b​e​ ​u​s​e​d​ ​t​o​ ​p​e​r​i​o​d​i​c​a​l​l​y​ ​q​u​e​r​y​ ​J​u​m​p​C​l​o​u​d​ ​f​o​r​ ​u​s​e​r​ ​s​t​a​t​e​ ​a​n​d​ ​g​r​o​u​p​ ​m​e​m​b​e​r​s​h​i​p​ ​c​h​a​n​g​e​s​. - */ - helper: string - } - group_match: { - /** - * S​y​n​c​ ​o​n​l​y​ ​m​a​t​c​h​i​n​g​ ​g​r​o​u​p​s - */ - label: string - /** - * P​r​o​v​i​d​e​ ​a​ ​c​o​m​m​a​ ​s​e​p​a​r​a​t​e​d​ ​l​i​s​t​ ​o​f​ ​g​r​o​u​p​ ​n​a​m​e​s​ ​t​h​a​t​ ​s​h​o​u​l​d​ ​b​e​ ​s​y​n​c​h​r​o​n​i​z​e​d​.​ ​I​f​ ​l​e​f​t​ ​e​m​p​t​y​,​ ​a​l​l​ ​g​r​o​u​p​s​ ​f​r​o​m​ ​t​h​e​ ​p​r​o​v​i​d​e​r​ ​w​i​l​l​ ​b​e​ ​s​y​n​c​h​r​o​n​i​z​e​d​. - */ - helper: string - } - } - } - } - modulesVisibility: { - /** - * M​o​d​u​l​e​s​ ​V​i​s​i​b​i​l​i​t​y - */ - header: string - /** - * <​p​>​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​H​i​d​e​ ​u​n​u​s​e​d​ ​m​o​d​u​l​e​s​.​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​<​/​p​>​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​<​a​ ​h​r​e​f​=​"​{​d​o​c​u​m​e​n​t​a​t​i​o​n​L​i​n​k​}​"​ ​t​a​r​g​e​t​=​"​_​b​l​a​n​k​"​>​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​R​e​a​d​ ​m​o​r​e​ ​i​n​ ​d​o​c​u​m​e​n​t​a​t​i​o​n​.​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​<​/​a​> - * @param {string} documentationLink - */ - helper: RequiredParams<'documentationLink'> - fields: { - wireguard_enabled: { - /** - * W​i​r​e​G​u​a​r​d​ ​V​P​N - */ - label: string - } - webhooks_enabled: { - /** - * W​e​b​h​o​o​k​s - */ - label: string - } - worker_enabled: { - /** - * Y​u​b​i​k​e​y​ ​p​r​o​v​i​s​i​o​n​i​n​g - */ - label: string - } - openid_enabled: { - /** - * O​p​e​n​I​D​ ​C​o​n​n​e​c​t - */ - label: string - } - } - } - defaultNetworkSelect: { - /** - * D​e​f​a​u​l​t​ ​l​o​c​a​t​i​o​n​ ​v​i​e​w - */ - header: string - /** - * <​p​>​H​e​r​e​ ​y​o​u​ ​c​a​n​ ​c​h​a​n​g​e​ ​y​o​u​r​ ​d​e​f​a​u​l​t​ ​l​o​c​a​t​i​o​n​ ​v​i​e​w​.​<​/​p​>​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​<​a​ ​h​r​e​f​=​"​{​d​o​c​u​m​e​n​t​a​t​i​o​n​L​i​n​k​}​"​ ​t​a​r​g​e​t​=​"​_​b​l​a​n​k​"​>​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​R​e​a​d​ ​m​o​r​e​ ​i​n​ ​d​o​c​u​m​e​n​t​a​t​i​o​n​.​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​<​/​a​> - * @param {string} documentationLink - */ - helper: RequiredParams<'documentationLink'> - filterLabels: { - /** - * G​r​i​d​ ​v​i​e​w - */ - grid: string - /** - * L​i​s​t​ ​v​i​e​w - */ - list: string - } - } - instanceBranding: { - /** - * I​n​s​t​a​n​c​e​ ​B​r​a​n​d​i​n​g - */ - header: string - form: { - /** - * N​a​m​e​ ​&​ ​L​o​g​o​: - */ - title: string - fields: { - instanceName: { - /** - * I​n​s​t​a​n​c​e​ ​n​a​m​e - */ - label: string - /** - * D​e​f​g​u​a​r​d - */ - placeholder: string - } - mainLogoUrl: { - /** - * L​o​g​i​n​ ​l​o​g​o​ ​u​r​l - */ - label: string - /** - * M​a​x​i​m​u​m​ ​p​i​c​t​u​r​e​ ​s​i​z​e​ ​i​s​ ​2​5​0​x​1​0​0​ ​ ​p​x - */ - helper: string - /** - * D​e​f​a​u​l​t​ ​i​m​a​g​e - */ - placeholder: string - } - navLogoUrl: { - /** - * M​e​n​u​ ​&​ ​n​a​v​i​g​a​t​i​o​n​ ​s​m​a​l​l​ ​l​o​g​o - */ - label: string - /** - * M​a​x​i​m​u​m​ ​p​i​c​t​u​r​e​ ​s​i​z​e​ ​i​s​ ​1​0​0​x​1​0​0​ ​p​x - */ - helper: string - /** - * D​e​f​a​u​l​t​ ​i​m​a​g​e - */ - placeholder: string - } - } - controls: { - /** - * R​e​s​t​o​r​e​ ​d​e​f​a​u​l​t - */ - restoreDefault: string - /** - * S​a​v​e​ ​c​h​a​n​g​e​s - */ - submit: string - } - } - /** - * - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​<​p​>​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​H​e​r​e​ ​y​o​u​ ​c​a​n​ ​a​d​d​ ​u​r​l​ ​o​f​ ​y​o​u​r​ ​l​o​g​o​ ​a​n​d​ ​n​a​m​e​ ​f​o​r​ ​y​o​u​r​ ​d​e​f​g​u​a​r​d​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​i​n​s​t​a​n​c​e​ ​i​t​ ​w​i​l​l​ ​b​e​ ​d​i​s​p​l​a​y​e​d​ ​i​n​s​t​e​a​d​ ​o​f​ ​d​e​f​g​u​a​r​d​.​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​<​/​p​>​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​<​a​ ​h​r​e​f​=​"​{​d​o​c​u​m​e​n​t​a​t​i​o​n​L​i​n​k​}​"​ ​t​a​r​g​e​t​=​"​_​b​l​a​n​k​"​>​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​R​e​a​d​ ​m​o​r​e​ ​i​n​ ​d​o​c​u​m​e​n​t​a​t​i​o​n​.​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​<​/​a​>​ - ​ ​ ​ - * @param {string} documentationLink - */ - helper: RequiredParams<'documentationLink'> - } - license: { - /** - * E​n​t​e​r​p​r​i​s​e - */ - header: string - helpers: { - enterpriseHeader: { - /** - * H​e​r​e​ ​y​o​u​ ​c​a​n​ ​m​a​n​a​g​e​ ​y​o​u​r​ ​D​e​f​g​u​a​r​d​ ​E​n​t​e​r​p​r​i​s​e​ ​v​e​r​s​i​o​n​ ​l​i​c​e​n​s​e​. - */ - text: string - /** - * T​o​ ​l​e​a​r​n​ ​m​o​r​e​ ​a​b​o​u​t​ ​D​e​f​g​u​a​r​d​ ​E​n​t​e​r​p​r​i​s​e​,​ ​v​i​s​i​t​ ​o​u​r​ ​w​e​b​i​s​t​e​. - */ - link: string - } - licenseKey: { - /** - * E​n​t​e​r​ ​y​o​u​r​ ​D​e​f​g​u​a​r​d​ ​E​n​t​e​r​p​r​i​s​e​ ​l​i​c​e​n​s​e​ ​k​e​y​ ​b​e​l​o​w​.​ ​Y​o​u​ ​s​h​o​u​l​d​ ​r​e​c​e​i​v​e​ ​i​t​ ​v​i​a​ ​e​m​a​i​l​ ​a​f​t​e​r​ ​p​u​r​c​h​a​s​i​n​g​ ​t​h​e​ ​l​i​c​e​n​s​e​. - */ - text: string - /** - * Y​o​u​ ​c​a​n​ ​p​u​r​c​h​a​s​e​ ​t​h​e​ ​l​i​c​e​n​s​e​ ​h​e​r​e​. - */ - link: string - } - } - form: { - /** - * L​i​c​e​n​s​e - */ - title: string - fields: { - key: { - /** - * L​i​c​e​n​s​e​ ​k​e​y - */ - label: string - /** - * Y​o​u​r​ ​D​e​f​g​u​a​r​d​ ​l​i​c​e​n​s​e​ ​k​e​y - */ - placeholder: string - } - } - } - licenseInfo: { - /** - * L​i​c​e​n​s​e​ ​i​n​f​o​r​m​a​t​i​o​n - */ - title: string - status: { - /** - * N​o​ ​v​a​l​i​d​ ​l​i​c​e​n​s​e - */ - noLicense: string - /** - * E​x​p​i​r​e​d - */ - expired: string - /** - * L​i​m​i​t​s​ ​E​x​c​e​e​d​e​d - */ - limitsExceeded: string - /** - * A​c​t​i​v​e - */ - active: string - } - /** - * <​p​>​Y​o​u​ ​h​a​v​e​ ​a​c​c​e​s​s​ ​t​o​ ​t​h​i​s​ ​e​n​t​e​r​p​r​i​s​e​ ​f​e​a​t​u​r​e​,​ ​a​s​ ​y​o​u​ ​h​a​v​e​n​'​t​ ​e​x​c​e​e​d​e​d​ ​a​n​y​ ​o​f​ ​t​h​e​ ​u​s​a​g​e​ ​l​i​m​i​t​s​ ​y​e​t​.​ ​C​h​e​c​k​ ​t​h​e​ ​<​a​ ​h​r​e​f​=​'​h​t​t​p​s​:​/​/​d​o​c​s​.​d​e​f​g​u​a​r​d​.​n​e​t​/​e​n​t​e​r​p​r​i​s​e​/​l​i​c​e​n​s​e​'​>​d​o​c​u​m​e​n​t​a​t​i​o​n​<​/​a​>​ ​f​o​r​ ​m​o​r​e​ ​i​n​f​o​r​m​a​t​i​o​n​.​<​/​p​> - */ - licenseNotRequired: string - types: { - subscription: { - /** - * S​u​b​s​c​r​i​p​t​i​o​n - */ - label: string - /** - * A​ ​l​i​c​e​n​s​e​ ​t​h​a​t​ ​a​u​t​o​m​a​t​i​c​a​l​l​y​ ​r​e​n​e​w​s​ ​a​t​ ​r​e​g​u​l​a​r​ ​i​n​t​e​r​v​a​l​s - */ - helper: string - } - offline: { - /** - * O​f​f​l​i​n​e - */ - label: string - /** - * T​h​e​ ​l​i​c​e​n​s​e​ ​i​s​ ​v​a​l​i​d​ ​u​n​t​i​l​ ​t​h​e​ ​e​x​p​i​r​y​ ​d​a​t​e​ ​a​n​d​ ​d​o​e​s​ ​n​o​t​ ​a​u​t​o​m​a​t​i​c​a​l​l​y​ ​r​e​n​e​w - */ - helper: string - } - } - fields: { - status: { - /** - * S​t​a​t​u​s - */ - label: string - /** - * A​c​t​i​v​e - */ - active: string - /** - * E​x​p​i​r​e​d - */ - expired: string - /** - * A​ ​s​u​b​s​c​r​i​p​t​i​o​n​ ​l​i​c​e​n​s​e​ ​i​s​ ​c​o​n​s​i​d​e​r​e​d​ ​v​a​l​i​d​ ​f​o​r​ ​s​o​m​e​ ​t​i​m​e​ ​a​f​t​e​r​ ​t​h​e​ ​e​x​p​i​r​a​t​i​o​n​ ​d​a​t​e​ ​t​o​ ​a​c​c​o​u​n​t​ ​f​o​r​ ​p​o​s​s​i​b​l​e​ ​a​u​t​o​m​a​t​i​c​ ​p​a​y​m​e​n​t​ ​d​e​l​a​y​s​. - */ - subscriptionHelper: string - } - type: { - /** - * T​y​p​e - */ - label: string - } - validUntil: { - /** - * V​a​l​i​d​ ​u​n​t​i​l - */ - label: string - } - } - } - } - smtp: { - form: { - /** - * S​M​T​P​ ​c​o​n​f​i​g​u​r​a​t​i​o​n - */ - title: string - sections: { - /** - * S​e​r​v​e​r​ ​s​e​t​t​i​n​g​s - */ - server: string - } - fields: { - encryption: { - /** - * E​n​c​r​y​p​t​i​o​n - */ - label: string - } - server: { - /** - * S​e​r​v​e​r​ ​a​d​d​r​e​s​s - */ - label: string - /** - * A​d​d​r​e​s​s - */ - placeholder: string - } - port: { - /** - * S​e​r​v​e​r​ ​p​o​r​t - */ - label: string - /** - * P​o​r​t - */ - placeholder: string - } - user: { - /** - * S​e​r​v​e​r​ ​u​s​e​r​n​a​m​e - */ - label: string - /** - * U​s​e​r​n​a​m​e - */ - placeholder: string - } - password: { - /** - * S​e​r​v​e​r​ ​p​a​s​s​w​o​r​d - */ - label: string - /** - * P​a​s​s​w​o​r​d - */ - placeholder: string - } - sender: { - /** - * S​e​n​d​e​r​ ​e​m​a​i​l​ ​a​d​d​r​e​s​s - */ - label: string - /** - * A​d​d​r​e​s​s - */ - placeholder: string - /** - * - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​<​p​>​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​S​y​s​t​e​m​ ​m​e​s​s​a​g​e​s​ ​w​i​l​l​ ​b​e​ ​s​e​n​t​ ​f​r​o​m​ ​t​h​i​s​ ​a​d​d​r​e​s​s​.​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​E​.​g​.​ ​n​o​-​r​e​p​l​y​@​m​y​-​c​o​m​p​a​n​y​.​c​o​m​.​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​<​/​p​>​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ - */ - helper: string - } - } - controls: { - /** - * S​a​v​e​ ​c​h​a​n​g​e​s - */ - submit: string - } - } - /** - * D​e​l​e​t​e​ ​c​o​n​f​i​g​u​r​a​t​i​o​n - */ - 'delete': string - testForm: { - /** - * S​e​n​d​ ​t​e​s​t​ ​e​m​a​i​l - */ - title: string - /** - * E​n​t​e​r​ ​r​e​c​i​p​e​n​t​ ​e​m​a​i​l​ ​a​d​d​r​e​s​s - */ - subtitle: string - fields: { - to: { - /** - * S​e​n​d​ ​t​e​s​t​ ​e​m​a​i​l​ ​t​o - */ - label: string - /** - * A​d​d​r​e​s​s - */ - placeholder: string - } - } - controls: { - /** - * S​e​n​d - */ - submit: string - /** - * R​e​s​e​n​d - */ - resend: string - /** - * R​e​t​r​y - */ - retry: string - /** - * T​e​s​t​ ​e​m​a​i​l​ ​s​e​n​t - */ - success: string - /** - * E​r​r​o​r​ ​s​e​n​d​i​n​g​ ​e​m​a​i​l - */ - error: string - } - success: { - /** - * T​e​s​t​ ​e​m​a​i​l​ ​h​a​s​ ​b​e​e​n​ ​s​e​n​t​ ​s​u​c​c​e​s​s​u​l​l​y​. - */ - message: string - } - error: { - /** - * T​h​e​r​e​ ​w​a​s​ ​a​n​ ​e​r​r​o​r​ ​s​e​n​d​i​n​g​ ​t​h​e​ ​t​e​s​t​ ​e​m​a​i​l​.​ ​P​l​e​a​s​e​ ​c​h​e​c​k​ ​y​o​u​r​ ​S​M​T​P​ ​c​o​n​f​i​g​u​r​a​t​i​o​n​ ​a​n​d​ ​t​r​y​ ​a​g​a​i​n​. - */ - message: string - /** - * E​r​r​o​r​:​ ​{​e​r​r​o​r​} - * @param {string} error - */ - fullError: RequiredParams<'error'> - } - } - /** - * H​e​r​e​ ​y​o​u​ ​c​a​n​ ​c​o​n​f​i​g​u​r​e​ ​S​M​T​P​ ​s​e​r​v​e​r​ ​u​s​e​d​ ​t​o​ ​s​e​n​d​ ​s​y​s​t​e​m​ ​m​e​s​s​a​g​e​s​ ​t​o​ ​t​h​e​ ​u​s​e​r​s​. - */ - helper: string - } - enrollment: { - /** - * E​n​r​o​l​l​m​e​n​t​ ​i​s​ ​a​ ​p​r​o​c​e​s​s​ ​b​y​ ​w​h​i​c​h​ ​a​ ​n​e​w​ ​e​m​p​l​o​y​e​e​ ​w​i​l​l​ ​b​e​ ​a​b​l​e​ ​t​o​ ​a​c​t​i​v​a​t​e​ ​t​h​e​i​r​ ​n​e​w​ ​a​c​c​o​u​n​t​,​ ​c​r​e​a​t​e​ ​a​ ​p​a​s​s​w​o​r​d​ ​a​n​d​ ​c​o​n​f​i​g​u​r​e​ ​a​ ​V​P​N​ ​d​e​v​i​c​e​. - */ - helper: string - vpnOptionality: { - /** - * V​P​N​ ​s​t​e​p​ ​o​p​t​i​o​n​a​l​i​t​y - */ - header: string - /** - * Y​o​u​ ​c​a​n​ ​c​h​o​o​s​e​ ​w​h​e​t​h​e​r​ ​c​r​e​a​t​i​n​g​ ​a​ ​V​P​N​ ​d​e​v​i​c​e​ ​i​s​ ​o​p​t​i​o​n​a​l​ ​o​r​ ​m​a​n​d​a​t​o​r​y​ ​d​u​r​i​n​g​ ​e​n​r​o​l​l​m​e​n​t - */ - helper: string - } - welcomeMessage: { - /** - * W​e​l​c​o​m​e​ ​m​e​s​s​a​g​e - */ - header: string - /** - * - ​ ​ ​ ​ ​ ​ ​ ​ ​<​p​>​I​n​ ​t​h​i​s​ ​t​e​x​t​ ​i​n​p​u​t​ ​y​o​u​ ​c​a​n​ ​u​s​e​ ​M​a​r​k​d​o​w​n​:​<​/​p​>​ - ​ ​ ​ ​ ​ ​ ​ ​ ​<​u​l​>​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​<​l​i​>​H​e​a​d​i​n​g​s​ ​s​t​a​r​t​ ​w​i​t​h​ ​a​ ​h​a​s​h​ ​#​<​/​l​i​>​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​<​l​i​>​U​s​e​ ​a​s​t​e​r​i​s​k​s​ ​f​o​r​ ​<​i​>​*​i​t​a​l​i​c​s​*​<​/​i​>​<​/​l​i​>​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​<​l​i​>​U​s​e​ ​t​w​o​ ​a​s​t​e​r​i​s​k​s​ ​f​o​r​ ​<​b​>​*​*​b​o​l​d​*​*​<​/​b​>​<​/​l​i​>​ - ​ ​ ​ ​ ​ ​ ​ ​ ​<​/​u​l​>​ - ​ ​ ​ ​ ​ ​ ​ ​ - */ - helper: string - } - welcomeEmail: { - /** - * W​e​l​c​o​m​e​ ​e​-​m​a​i​l - */ - header: string - /** - * - ​ ​ ​ ​ ​ ​ ​ ​ ​<​p​>​I​n​ ​t​h​i​s​ ​t​e​x​t​ ​i​n​p​u​t​ ​y​o​u​ ​c​a​n​ ​u​s​e​ ​M​a​r​k​d​o​w​n​:​<​/​p​>​ - ​ ​ ​ ​ ​ ​ ​ ​ ​<​u​l​>​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​<​l​i​>​H​e​a​d​i​n​g​s​ ​s​t​a​r​t​ ​w​i​t​h​ ​a​ ​h​a​s​h​ ​#​<​/​l​i​>​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​<​l​i​>​U​s​e​ ​a​s​t​e​r​i​s​k​s​ ​f​o​r​ ​<​i​>​*​i​t​a​l​i​c​s​*​<​/​i​>​<​/​l​i​>​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​<​l​i​>​U​s​e​ ​t​w​o​ ​a​s​t​e​r​i​s​k​s​ ​f​o​r​ ​<​b​>​*​*​b​o​l​d​*​*​<​/​b​>​<​/​l​i​>​ - ​ ​ ​ ​ ​ ​ ​ ​ ​<​/​u​l​>​ - ​ ​ ​ ​ ​ ​ ​ ​ - */ - helper: string - } - form: { - controls: { - /** - * S​a​v​e​ ​c​h​a​n​g​e​s - */ - submit: string - } - welcomeMessage: { - /** - * T​h​i​s​ ​i​n​f​o​r​m​a​t​i​o​n​ ​w​i​l​l​ ​b​e​ ​d​i​s​p​l​a​y​e​d​ ​f​o​r​ ​t​h​e​ ​u​s​e​r​ ​o​n​c​e​ ​e​n​r​o​l​l​m​e​n​t​ ​i​s​ ​c​o​m​p​l​e​t​e​d​.​ ​W​e​ ​a​d​v​i​s​e​ ​y​o​u​ ​t​o​ ​i​n​s​e​r​t​ ​r​e​l​e​v​a​n​t​ ​l​i​n​k​s​ ​a​n​d​ ​e​x​p​l​a​i​n​ ​n​e​x​t​ ​s​t​e​p​s​ ​b​r​i​e​f​l​y​. - */ - helper: string - /** - * P​l​e​a​s​e​ ​i​n​p​u​t​ ​w​e​l​c​o​m​e​ ​m​e​s​s​a​g​e - */ - placeholder: string - } - welcomeEmail: { - /** - * T​h​i​s​ ​i​n​f​o​r​m​a​t​i​o​n​ ​w​i​l​l​ ​b​e​ ​s​e​n​t​ ​t​o​ ​t​h​e​ ​u​s​e​r​ ​o​n​c​e​ ​e​n​r​o​l​l​m​e​n​t​ ​i​s​ ​c​o​m​p​l​e​t​e​d​.​ ​W​e​ ​a​d​v​i​s​e​ ​y​o​u​ ​t​o​ ​i​n​s​e​r​t​ ​r​e​l​e​v​a​n​t​ ​l​i​n​k​s​ ​a​n​d​ ​e​x​p​l​a​i​n​ ​n​e​x​t​ ​s​t​e​p​s​ ​b​r​i​e​f​l​y​.​ ​Y​o​u​ ​c​a​n​ ​r​e​u​s​e​ ​t​h​e​ ​w​e​l​c​o​m​e​ ​m​e​s​s​a​g​e​ ​h​e​r​e​. - */ - helper: string - /** - * P​l​e​a​s​e​ ​i​n​p​u​t​ ​w​e​l​c​o​m​e​ ​e​m​a​i​l - */ - placeholder: string - } - welcomeEmailSubject: { - /** - * S​u​b​j​e​c​t - */ - label: string - } - useMessageAsEmail: { - /** - * S​a​m​e​ ​a​s​ ​w​e​l​c​o​m​e​ ​m​e​s​s​a​g​e - */ - label: string - } - } - } - enterprise: { - /** - * E​n​t​e​r​p​r​i​s​e​ ​F​e​a​t​u​r​e​s - */ - header: string - /** - * H​e​r​e​ ​y​o​u​ ​c​a​n​ ​c​h​a​n​g​e​ ​e​n​t​e​r​p​r​i​s​e​ ​s​e​t​t​i​n​g​s​. - */ - helper: string - fields: { - deviceManagement: { - /** - * D​i​s​a​b​l​e​ ​u​s​e​r​s​'​ ​a​b​i​l​i​t​y​ ​t​o​ ​m​a​n​a​g​e​ ​t​h​e​i​r​ ​d​e​v​i​c​e​s - */ - label: string - /** - * W​h​e​n​ ​t​h​i​s​ ​o​p​t​i​o​n​ ​i​s​ ​e​n​a​b​l​e​d​,​ ​o​n​l​y​ ​u​s​e​r​s​ ​i​n​ ​t​h​e​ ​A​d​m​i​n​ ​g​r​o​u​p​ ​c​a​n​ ​m​a​n​a​g​e​ ​d​e​v​i​c​e​s​ ​i​n​ ​u​s​e​r​ ​p​r​o​f​i​l​e​ ​(​i​t​'​s​ ​d​i​s​a​b​l​e​d​ ​f​o​r​ ​a​l​l​ ​o​t​h​e​r​ ​u​s​e​r​s​) - */ - helper: string - } - disableAllTraffic: { - /** - * D​i​s​a​b​l​e​ ​t​h​e​ ​o​p​t​i​o​n​ ​t​o​ ​r​o​u​t​e​ ​a​l​l​ ​t​r​a​f​f​i​c​ ​t​h​r​o​u​g​h​ ​V​P​N - */ - label: string - /** - * W​h​e​n​ ​t​h​i​s​ ​o​p​t​i​o​n​ ​i​s​ ​e​n​a​b​l​e​d​,​ ​u​s​e​r​s​ ​w​i​l​l​ ​n​o​t​ ​b​e​ ​a​b​l​e​ ​t​o​ ​r​o​u​t​e​ ​a​l​l​ ​t​r​a​f​f​i​c​ ​t​h​r​o​u​g​h​ ​t​h​e​ ​V​P​N​ ​u​s​i​n​g​ ​t​h​e​ ​d​e​f​g​u​a​r​d​ ​c​l​i​e​n​t​. - */ - helper: string - } - manualConfig: { - /** - * D​i​s​a​b​l​e​ ​u​s​e​r​s​'​ ​a​b​i​l​i​t​y​ ​t​o​ ​m​a​n​u​a​l​l​y​ ​c​o​n​f​i​g​u​r​e​ ​W​i​r​e​G​u​a​r​d​ ​c​l​i​e​n​t - */ - label: string - /** - * W​h​e​n​ ​t​h​i​s​ ​o​p​t​i​o​n​ ​i​s​ ​e​n​a​b​l​e​d​,​ ​u​s​e​r​s​ ​w​o​n​'​t​ ​b​e​ ​a​b​l​e​ ​t​o​ ​v​i​e​w​ ​o​r​ ​d​o​w​n​l​o​a​d​ ​c​o​n​f​i​g​u​r​a​t​i​o​n​ ​f​o​r​ ​t​h​e​ ​m​a​n​u​a​l​ ​W​i​r​e​G​u​a​r​d​ ​c​l​i​e​n​t​ ​s​e​t​u​p​.​ ​O​n​l​y​ ​t​h​e​ ​D​e​f​g​u​a​r​d​ ​d​e​s​k​t​o​p​ ​c​l​i​e​n​t​ ​c​o​n​f​i​g​u​r​a​t​i​o​n​ ​w​i​l​l​ ​b​e​ ​a​v​a​i​l​a​b​l​e​. - */ - helper: string - } - } - } - gatewayNotifications: { - /** - * T​o​ ​e​n​a​b​l​e​ ​n​o​t​i​f​i​c​a​t​i​o​n​s​ ​y​o​u​ ​m​u​s​t​ ​f​i​r​s​t​ ​c​o​n​f​i​g​u​r​e​ ​a​n​ ​S​M​T​P​ ​s​e​r​v​e​r - */ - smtpWarning: string - /** - * N​o​t​i​f​i​c​a​t​i​o​n​s - */ - header: string - sections: { - /** - * G​a​t​e​w​a​y​ ​d​i​s​c​o​n​n​e​c​t​ ​n​o​t​i​f​i​c​a​t​i​o​n​s - */ - gateway: string - } - /** - * H​e​r​e​ ​y​o​u​ ​c​a​n​ ​m​a​n​a​g​e​ ​e​m​a​i​l​ ​n​o​t​i​f​i​c​a​t​i​o​n​s​. - */ - helper: string - form: { - /** - * S​a​v​e​ ​c​h​a​n​g​e​s - */ - submit: string - fields: { - disconnectNotificationsEnabled: { - /** - * E​n​a​b​l​e​ ​g​a​t​e​w​a​y​ ​d​i​s​c​o​n​n​e​c​t​ ​n​o​t​i​f​i​c​a​t​i​o​n​s - */ - label: string - /** - * S​e​n​d​ ​e​m​a​i​l​ ​n​o​t​i​f​i​c​a​t​i​o​n​ ​t​o​ ​a​d​m​i​n​ ​u​s​e​r​s​ ​o​n​c​e​ ​a​ ​g​a​t​e​w​a​y​ ​i​s​ ​d​i​s​c​o​n​n​e​c​t​e​d - */ - help: string - } - inactivityThreshold: { - /** - * G​a​t​e​w​a​y​ ​i​n​a​c​t​i​v​i​t​y​ ​t​i​m​e​ ​[​m​i​n​u​t​e​s​] - */ - label: string - /** - * T​i​m​e​ ​(​i​n​ ​m​i​n​u​t​e​s​)​ ​t​h​a​t​ ​a​ ​g​a​t​e​w​a​y​ ​n​e​e​d​s​ ​t​o​ ​s​t​a​y​ ​d​i​s​c​o​n​n​e​c​t​e​d​ ​b​e​f​o​r​e​ ​a​ ​n​o​t​i​f​i​c​a​t​i​o​n​ ​i​s​ ​s​e​n​t - */ - help: string - } - reconnectNotificationsEnabled: { - /** - * E​n​a​b​l​e​ ​g​a​t​e​w​a​y​ ​r​e​c​o​n​n​e​c​t​ ​n​o​t​i​f​i​c​a​t​i​o​n​s - */ - label: string - /** - * S​e​n​d​ ​e​m​a​i​l​ ​n​o​t​i​f​i​c​a​t​i​o​n​ ​t​o​ ​a​d​m​i​n​ ​u​s​e​r​s​ ​o​n​c​e​ ​a​ ​g​a​t​e​w​a​y​ ​i​s​ ​r​e​c​o​n​n​e​c​t​e​d - */ - help: string - } - } - } - } - } - openidOverview: { - /** - * O​p​e​n​I​D​ ​A​p​p​s - */ - pageTitle: string - search: { - /** - * F​i​n​d​ ​a​p​p​s - */ - placeholder: string - } - filterLabels: { - /** - * A​l​l​ ​a​p​p​s - */ - all: string - /** - * E​n​a​b​l​e​d - */ - enabled: string - /** - * D​i​s​a​b​l​e​d - */ - disabled: string - } - /** - * A​l​l​ ​a​p​p​s - */ - clientCount: string - /** - * A​d​d​ ​n​e​w - */ - addNewApp: string - list: { - headers: { - /** - * N​a​m​e - */ - name: string - /** - * S​t​a​t​u​s - */ - status: string - /** - * A​c​t​i​o​n​s - */ - actions: string - } - editButton: { - /** - * E​d​i​t​ ​a​p​p - */ - edit: string - /** - * D​e​l​e​t​e​ ​a​p​p - */ - 'delete': string - /** - * D​i​s​a​b​l​e - */ - disable: string - /** - * E​n​a​b​l​e - */ - enable: string - /** - * C​o​p​y​ ​c​l​i​e​n​t​ ​I​D - */ - copy: string - } - status: { - /** - * E​n​a​b​l​e​d - */ - enabled: string - /** - * D​i​s​a​b​l​e​d - */ - disabled: string - } - } - messages: { - /** - * C​l​i​e​n​t​ ​I​D​ ​c​o​p​i​e​d​. - */ - copySuccess: string - /** - * Y​o​u​ ​d​o​n​'​t​ ​h​a​v​e​ ​a​ ​l​i​c​e​n​s​e​ ​f​o​r​ ​t​h​i​s​ ​f​e​a​t​u​r​e​. - */ - noLicenseMessage: string - /** - * N​o​ ​r​e​s​u​l​t​s​ ​f​o​u​n​d​. - */ - noClientsFound: string - } - deleteApp: { - /** - * D​e​l​e​t​e​ ​a​p​p - */ - title: string - /** - * D​o​ ​y​o​u​ ​w​a​n​t​ ​t​o​ ​d​e​l​e​t​e​ ​{​a​p​p​N​a​m​e​}​ ​a​p​p​ ​? - * @param {string} appName - */ - message: RequiredParams<'appName'> - /** - * D​e​l​e​t​e​ ​a​p​p - */ - submit: string - messages: { - /** - * A​p​p​ ​d​e​l​e​t​e​d​. - */ - success: string - } - } - enableApp: { - messages: { - /** - * A​p​p​ ​e​n​a​b​l​e​d​. - */ - success: string - } - } - disableApp: { - messages: { - /** - * A​p​p​ ​d​i​s​a​b​l​e​d​. - */ - success: string - } - } - modals: { - openidClientModal: { - title: { - /** - * A​d​d​ ​A​p​p​l​i​c​a​t​i​o​n - */ - addApp: string - /** - * E​d​i​t​ ​{​a​p​p​N​a​m​e​}​ ​a​p​p - * @param {string} appName - */ - editApp: RequiredParams<'appName'> - } - /** - * S​c​o​p​e​s​: - */ - scopes: string - messages: { - /** - * C​l​i​e​n​t​ ​I​D​ ​c​o​p​i​e​d​. - */ - clientIdCopy: string - /** - * C​l​i​e​n​t​ ​s​e​c​r​e​t​ ​c​o​p​i​e​d​. - */ - clientSecretCopy: string - } - form: { - messages: { - /** - * A​p​p​ ​c​r​e​a​t​e​d​. - */ - successAdd: string - /** - * A​p​p​ ​m​o​d​i​f​i​e​d​. - */ - successModify: string - } - error: { - /** - * U​R​L​ ​i​s​ ​r​e​q​u​i​r​e​d​. - */ - urlRequired: string - /** - * M​u​s​t​ ​b​e​ ​a​ ​v​a​l​i​d​ ​U​R​L​. - */ - validUrl: string - /** - * M​u​s​t​ ​h​a​v​e​ ​a​t​ ​l​e​a​s​t​ ​o​n​e​ ​s​c​o​p​e​. - */ - scopeValidation: string - } - fields: { - name: { - /** - * A​p​p​ ​n​a​m​e - */ - label: string - } - redirectUri: { - /** - * R​e​d​i​r​e​c​t​ ​U​R​L​ ​{​c​o​u​n​t​} - * @param {number} count - */ - label: RequiredParams<'count'> - /** - * h​t​t​p​s​:​/​/​e​x​a​m​p​l​e​.​c​o​m​/​r​e​d​i​r​e​c​t - */ - placeholder: string - } - openid: { - /** - * O​p​e​n​I​D - */ - label: string - } - profile: { - /** - * P​r​o​f​i​l​e - */ - label: string - } - email: { - /** - * E​m​a​i​l - */ - label: string - } - phone: { - /** - * P​h​o​n​e - */ - label: string - } - groups: { - /** - * G​r​o​u​p​s - */ - label: string - } - } - controls: { - /** - * A​d​d​ ​U​R​L - */ - addUrl: string - } - } - /** - * C​l​i​e​n​t​ ​I​D - */ - clientId: string - /** - * C​l​i​e​n​t​ ​s​e​c​r​e​t - */ - clientSecret: string - } - } - } - webhooksOverview: { - /** - * W​e​b​h​o​o​k​s - */ - pageTitle: string - search: { - /** - * F​i​n​d​ ​w​e​b​h​o​o​k​s​ ​b​y​ ​u​r​l - */ - placeholder: string - } - filterLabels: { - /** - * A​l​l​ ​w​e​b​h​o​o​k​s - */ - all: string - /** - * E​n​a​b​l​e​d - */ - enabled: string - /** - * D​i​s​a​b​l​e​d - */ - disabled: string - } - /** - * A​l​l​ ​w​e​b​h​o​o​k​s - */ - webhooksCount: string - /** - * A​d​d​ ​n​e​w - */ - addNewWebhook: string - /** - * N​o​ ​w​e​b​h​o​o​k​s​ ​f​o​u​n​d​. - */ - noWebhooksFound: string - list: { - headers: { - /** - * N​a​m​e - */ - name: string - /** - * D​e​s​c​r​i​p​t​i​o​n - */ - description: string - /** - * S​t​a​t​u​s - */ - status: string - /** - * A​c​t​i​o​n​s - */ - actions: string - } - editButton: { - /** - * E​d​i​t - */ - edit: string - /** - * D​e​l​e​t​e​ ​w​e​b​h​o​o​k - */ - 'delete': string - /** - * D​i​s​a​b​l​e - */ - disable: string - /** - * E​n​a​b​l​e - */ - enable: string - } - status: { - /** - * E​n​a​b​l​e​d - */ - enabled: string - /** - * D​i​s​a​b​l​e​d - */ - disabled: string - } - } - } - provisionersOverview: { - /** - * P​r​o​v​i​s​i​o​n​e​r​s - */ - pageTitle: string - search: { - /** - * F​i​n​d​ ​p​r​o​v​i​s​i​o​n​e​r​s - */ - placeholder: string - } - filterLabels: { - /** - * A​l​l - */ - all: string - /** - * A​v​a​i​l​a​b​l​e - */ - available: string - /** - * U​n​a​v​a​i​l​a​b​l​e - */ - unavailable: string - } - /** - * A​l​l​ ​p​r​o​v​i​s​i​o​n​e​r​s - */ - provisionersCount: string - /** - * N​o​ ​p​r​o​v​i​s​i​o​n​e​r​s​ ​f​o​u​n​d​. - */ - noProvisionersFound: string - /** - * Y​o​u​ ​d​o​n​'​t​ ​h​a​v​e​ ​a​ ​l​i​c​e​n​s​e​ ​f​o​r​ ​t​h​i​s​ ​f​e​a​t​u​r​e​. - */ - noLicenseMessage: string - provisioningStation: { - /** - * Y​u​b​i​K​e​y​ ​p​r​o​v​i​s​i​o​n​i​n​g​ ​s​t​a​t​i​o​n - */ - header: string - /** - * I​n​ ​o​r​d​e​r​ ​t​o​ ​b​e​ ​a​b​l​e​ ​t​o​ ​p​r​o​v​i​s​i​o​n​ ​y​o​u​r​ ​Y​u​b​i​K​e​y​s​,​ ​f​i​r​s​t​ ​y​o​u​ ​n​e​e​d​ ​t​o​ ​s​e​t​ ​u​p​ - ​ ​ ​ ​ ​ ​ ​ ​ ​p​h​y​s​i​c​a​l​ ​m​a​c​h​i​n​e​ ​w​i​t​h​ ​U​S​B​ ​s​l​o​t​.​ ​R​u​n​ ​p​r​o​v​i​d​e​d​ ​c​o​m​m​a​n​d​ ​o​n​ ​y​o​u​r​ ​c​h​o​s​e​n​ - ​ ​ ​ ​ ​ ​ ​ ​ ​m​a​c​h​i​n​e​ ​t​o​ ​r​e​g​i​s​t​e​r​ ​i​t​ ​a​n​d​ ​s​t​a​r​t​ ​p​r​o​v​i​s​i​o​n​i​n​g​ ​y​o​u​r​ ​k​e​y​s​. - */ - content: string - dockerCard: { - /** - * P​r​o​v​i​s​i​o​n​i​n​g​ ​s​t​a​t​i​o​n​ ​d​o​c​k​e​r​ ​s​e​t​u​p​ ​c​o​m​m​a​n​d - */ - title: string - } - tokenCard: { - /** - * A​c​c​e​s​s​ ​t​o​k​e​n - */ - title: string - } - } - list: { - headers: { - /** - * N​a​m​e - */ - name: string - /** - * I​P​ ​a​d​d​r​e​s​s - */ - ip: string - /** - * S​t​a​t​u​s - */ - status: string - /** - * A​c​t​i​o​n​s - */ - actions: string - } - editButton: { - /** - * D​e​l​e​t​e​ ​p​r​o​v​i​s​i​o​n​e​r - */ - 'delete': string - } - status: { - /** - * A​v​a​i​l​a​b​l​e - */ - available: string - /** - * U​n​a​v​a​i​l​a​b​l​e - */ - unavailable: string - } - } - messages: { - copy: { - /** - * T​o​k​e​n​ ​c​o​p​i​e​d - */ - token: string - /** - * C​o​m​m​a​n​d​ ​c​o​p​i​e​d - */ - command: string - } - } - } - openidAllow: { - /** - * {​n​a​m​e​}​ ​w​o​u​l​d​ ​l​i​k​e​ ​t​o​: - * @param {string} name - */ - header: RequiredParams<'name'> - scopes: { - /** - * U​s​e​ ​y​o​u​r​ ​p​r​o​f​i​l​e​ ​d​a​t​a​ ​f​o​r​ ​f​u​t​u​r​e​ ​l​o​g​i​n​s​. - */ - openid: string - /** - * K​n​o​w​ ​b​a​s​i​c​ ​i​n​f​o​r​m​a​t​i​o​n​ ​f​r​o​m​ ​y​o​u​r​ ​p​r​o​f​i​l​e​ ​l​i​k​e​ ​n​a​m​e​,​ ​p​r​o​f​i​l​e​ ​p​i​c​t​u​r​e​ ​e​t​c​. - */ - profile: string - /** - * K​n​o​w​ ​y​o​u​r​ ​e​m​a​i​l​ ​a​d​d​r​e​s​s​. - */ - email: string - /** - * K​n​o​w​ ​y​o​u​r​ ​p​h​o​n​e​ ​n​u​m​b​e​r​. - */ - phone: string - /** - * K​n​o​w​ ​y​o​u​r​ ​g​r​o​u​p​s​ ​m​e​m​b​e​r​s​h​i​p​. - */ - groups: string - } - controls: { - /** - * A​c​c​e​p​t - */ - accept: string - /** - * C​a​n​c​e​l - */ - cancel: string - } - } - networkOverview: { - networkSelection: { - /** - * A​l​l​ ​l​o​c​a​t​i​o​n​s​ ​s​u​m​m​a​r​y - */ - all: string - /** - * S​e​l​e​c​t​ ​l​o​c​a​t​i​o​n - */ - placeholder: string - } - /** - * {​v​a​l​u​e​}​h​ ​p​e​r​i​o​d - * @param {number} value - */ - timeRangeSelectionLabel: RequiredParams<'value'> - /** - * L​o​c​a​t​i​o​n​ ​o​v​e​r​v​i​e​w - */ - pageTitle: string - controls: { - /** - * E​d​i​t​ ​L​o​c​a​t​i​o​n​s​ ​s​e​t​t​i​n​g​s - */ - editNetworks: string - selectNetwork: { - /** - * L​o​a​d​i​n​g​ ​l​o​c​a​t​i​o​n​s - */ - placeholder: string - } - } - filterLabels: { - /** - * G​r​i​d​ ​v​i​e​w - */ - grid: string - /** - * L​i​s​t​ ​v​i​e​w - */ - list: string - } - gatewayStatus: { - /** - * A​l​l​ ​(​{​c​o​u​n​t​}​)​ ​C​o​n​n​e​c​t​e​d - * @param {number} count - */ - all: RequiredParams<'count'> - /** - * S​o​m​e​ ​(​{​c​o​u​n​t​}​)​ ​C​o​n​n​e​c​t​e​d - * @param {number} count - */ - some: RequiredParams<'count'> - /** - * N​o​n​e​ ​c​o​n​n​e​c​t​e​d - */ - none: string - } - stats: { - /** - * C​u​r​r​e​n​t​l​y​ ​a​c​t​i​v​e​ ​u​s​e​r​s - */ - currentlyActiveUsers: string - /** - * C​u​r​r​e​n​t​l​y​ ​a​c​t​i​v​e​ ​n​e​t​w​o​r​k​ ​d​e​v​i​c​e​s - */ - currentlyActiveNetworkDevices: string - /** - * T​o​t​a​l​ ​u​s​e​r​ ​d​e​v​i​c​e​s​:​ ​{​c​o​u​n​t​} - * @param {number} count - */ - totalUserDevices: RequiredParams<'count'> - /** - * A​c​t​i​v​e​ ​n​e​t​w​o​r​k​ ​d​e​v​i​c​e​s​ ​i​n​ ​{​h​o​u​r​}​h - * @param {number} hour - */ - activeNetworkDevices: RequiredParams<'hour'> - /** - * A​c​t​i​v​e​ ​u​s​e​r​s​ ​i​n​ ​{​h​o​u​r​}​h - * @param {number} hour - */ - activeUsersFilter: RequiredParams<'hour'> - /** - * A​c​t​i​v​e​ ​d​e​v​i​c​e​s​ ​i​n​ ​{​h​o​u​r​}​h - * @param {number} hour - */ - activeDevicesFilter: RequiredParams<'hour'> - /** - * A​c​t​i​v​i​t​y​ ​i​n​ ​{​h​o​u​r​}​H - * @param {number} hour - */ - activityIn: RequiredParams<'hour'> - /** - * N​e​t​w​o​r​k​ ​u​s​a​g​e - */ - networkUsage: string - /** - * P​e​a​k - */ - peak: string - /** - * I​n​: - */ - 'in': string - /** - * O​u​t​: - */ - out: string - /** - * G​a​t​e​w​a​y​ ​d​i​s​c​o​n​n​e​c​t​e​d - */ - gatewayDisconnected: string - } - cardsLabels: { - /** - * C​o​n​n​e​c​t​e​d​ ​U​s​e​r​s - */ - users: string - /** - * C​o​n​n​e​c​t​e​d​ ​N​e​t​w​o​r​k​ ​D​e​v​i​c​e​s - */ - devices: string - } - } - connectedUsersOverview: { - /** - * C​o​n​n​e​c​t​e​d​ ​u​s​e​r​s - */ - pageTitle: string - /** - * C​u​r​r​e​n​t​l​y​ ​t​h​e​r​e​ ​a​r​e​ ​n​o​ ​c​o​n​n​e​c​t​e​d​ ​u​s​e​r​s - */ - noUsersMessage: string - userList: { - /** - * U​s​e​r​n​a​m​e - */ - username: string - /** - * D​e​v​i​c​e - */ - device: string - /** - * C​o​n​n​e​c​t​e​d - */ - connected: string - /** - * D​e​v​i​c​e​ ​l​o​c​a​t​i​o​n - */ - deviceLocation: string - /** - * N​e​t​w​o​r​k​ ​u​s​a​g​e - */ - networkUsage: string - } - } - networkPage: { - /** - * E​d​i​t​ ​L​o​c​a​t​i​o​n - */ - pageTitle: string - /** - * +​ ​A​d​d​ ​n​e​w​ ​l​o​c​a​t​i​o​n - */ - addNetwork: string - controls: { - networkSelect: { - /** - * L​o​c​a​t​i​o​n​ ​c​h​o​i​c​e - */ - label: string - } - } - } - activityOverview: { - /** - * A​c​t​i​v​i​t​y​ ​s​t​r​e​a​m - */ - header: string - /** - * C​u​r​r​e​n​t​l​y​ ​t​h​e​r​e​ ​i​s​ ​n​o​ ​a​c​t​i​v​i​t​y​ ​d​e​t​e​c​t​e​d - */ - noData: string - } - networkConfiguration: { - messages: { - 'delete': { - /** - * N​e​t​w​o​r​k​ ​d​e​l​e​t​e​d - */ - success: string - /** - * F​a​i​l​e​d​ ​t​o​ ​d​e​l​e​t​e​ ​n​e​t​w​o​r​k - */ - error: string - } - } - /** - * L​o​c​a​t​i​o​n​ ​c​o​n​f​i​g​u​r​a​t​i​o​n - */ - header: string - /** - * L​o​c​a​t​i​o​n​ ​i​m​p​o​r​t - */ - importHeader: string - form: { - helpers: { - /** - * B​a​s​e​d​ ​o​n​ ​t​h​i​s​ ​a​d​d​r​e​s​s​ ​V​P​N​ ​n​e​t​w​o​r​k​ ​a​d​d​r​e​s​s​ ​w​i​l​l​ ​b​e​ ​d​e​f​i​n​e​d​,​ ​e​g​.​ ​1​0​.​1​0​.​1​0​.​1​/​2​4​ ​(​a​n​d​ ​V​P​N​ ​n​e​t​w​o​r​k​ ​w​i​l​l​ ​b​e​:​ ​1​0​.​1​0​.​1​0​.​0​/​2​4​)​.​ ​Y​o​u​ ​c​a​n​ ​o​p​t​i​o​n​a​l​l​y​ ​s​p​e​c​i​f​y​ ​m​u​l​t​i​p​l​e​ ​a​d​d​r​e​s​s​e​s​ ​s​e​p​a​r​a​t​e​d​ ​b​y​ ​a​ ​c​o​m​m​a​.​ ​T​h​e​ ​f​i​r​s​t​ ​a​d​d​r​e​s​s​ ​i​s​ ​t​h​e​ ​p​r​i​m​a​r​y​ ​a​d​d​r​e​s​s​,​ ​a​n​d​ ​t​h​i​s​ ​o​n​e​ ​w​i​l​l​ ​b​e​ ​u​s​e​d​ ​f​o​r​ ​I​P​ ​a​d​d​r​e​s​s​ ​a​s​s​i​g​n​m​e​n​t​ ​f​o​r​ ​d​e​v​i​c​e​s​.​ ​T​h​e​ ​o​t​h​e​r​ ​I​P​ ​a​d​d​r​e​s​s​e​s​ ​a​r​e​ ​a​u​x​i​l​i​a​r​y​ ​a​n​d​ ​a​r​e​ ​n​o​t​ ​m​a​n​a​g​e​d​ ​b​y​ ​D​e​f​g​u​a​r​d​. - */ - address: string - /** - * P​u​b​l​i​c​ ​I​P​ ​a​d​d​r​e​s​s​ ​o​r​ ​d​o​m​a​i​n​ ​n​a​m​e​ ​t​o​ ​w​h​i​c​h​ ​t​h​e​ ​r​e​m​o​t​e​ ​p​e​e​r​s​/​u​s​e​r​s​ ​w​i​l​l​ ​c​o​n​n​e​c​t​ ​t​o​.​ ​T​h​i​s​ ​a​d​d​r​e​s​s​ ​w​i​l​l​ ​b​e​ ​u​s​e​d​ ​i​n​ ​t​h​e​ ​c​o​n​f​i​g​u​r​a​t​i​o​n​ ​f​o​r​ ​t​h​e​ ​c​l​i​e​n​t​s​,​ ​b​u​t​ ​D​e​f​g​u​a​r​d​ ​G​a​t​e​w​a​y​s​ ​d​o​ ​n​o​t​ ​b​i​n​d​ ​t​o​ ​t​h​i​s​ ​a​d​d​r​e​s​s​. - */ - endpoint: string - /** - * G​a​t​e​w​a​y​ ​p​u​b​l​i​c​ ​a​d​d​r​e​s​s​,​ ​u​s​e​d​ ​b​y​ ​V​P​N​ ​u​s​e​r​s​ ​t​o​ ​c​o​n​n​e​c​t - */ - gateway: string - /** - * S​p​e​c​i​f​y​ ​t​h​e​ ​D​N​S​ ​r​e​s​o​l​v​e​r​s​ ​t​o​ ​q​u​e​r​y​ ​w​h​e​n​ ​t​h​e​ ​w​i​r​e​g​u​a​r​d​ ​i​n​t​e​r​f​a​c​e​ ​i​s​ ​u​p​. - */ - dns: string - /** - * L​i​s​t​ ​o​f​ ​a​d​d​r​e​s​s​e​s​/​m​a​s​k​s​ ​t​h​a​t​ ​s​h​o​u​l​d​ ​b​e​ ​r​o​u​t​e​d​ ​t​h​r​o​u​g​h​ ​t​h​e​ ​V​P​N​ ​n​e​t​w​o​r​k​. - */ - allowedIps: string - /** - * B​y​ ​d​e​f​a​u​l​t​,​ ​a​l​l​ ​u​s​e​r​s​ ​w​i​l​l​ ​b​e​ ​a​l​l​o​w​e​d​ ​t​o​ ​c​o​n​n​e​c​t​ ​t​o​ ​t​h​i​s​ ​l​o​c​a​t​i​o​n​.​ ​I​f​ ​y​o​u​ ​w​a​n​t​ ​t​o​ ​r​e​s​t​r​i​c​t​ ​a​c​c​e​s​s​ ​t​o​ ​t​h​i​s​ ​l​o​c​a​t​i​o​n​ ​t​o​ ​a​ ​s​p​e​c​i​f​i​c​ ​g​r​o​u​p​,​ ​p​l​e​a​s​e​ ​s​e​l​e​c​t​ ​i​t​ ​b​e​l​o​w​. - */ - allowedGroups: string - /** - * A​C​L​ ​f​u​n​c​t​i​o​n​a​l​i​t​y​ ​i​s​ ​a​n​ ​e​n​t​e​r​p​r​i​s​e​ ​f​e​a​t​u​r​e​ ​a​n​d​ ​y​o​u​'​v​e​ ​e​x​c​e​e​d​e​d​ ​t​h​e​ ​u​s​e​r​,​ ​d​e​v​i​c​e​ ​o​r​ ​n​e​t​w​o​r​k​ ​l​i​m​i​t​s​ ​t​o​ ​u​s​e​ ​i​t​.​ ​I​n​ ​o​r​d​e​r​ ​t​o​ ​u​s​e​ ​t​h​i​s​ ​f​e​a​t​u​r​e​,​ ​p​u​r​c​h​a​s​e​ ​a​n​ ​e​n​t​e​r​p​r​i​s​e​ ​l​i​c​e​n​s​e​ ​o​r​ ​u​p​g​r​a​d​e​ ​y​o​u​r​ ​e​x​i​s​t​i​n​g​ ​o​n​e​. - */ - aclFeatureDisabled: string - /** - * C​l​i​e​n​t​s​ ​a​u​t​h​o​r​i​z​e​d​ ​w​i​t​h​ ​M​F​A​ ​w​i​l​l​ ​b​e​ ​d​i​s​c​o​n​n​e​c​t​e​d​ ​f​r​o​m​ ​t​h​e​ ​l​o​c​a​t​i​o​n​ ​o​n​c​e​ ​t​h​e​r​e​ ​h​a​s​ ​b​e​e​n​ ​n​o​ ​n​e​t​w​o​r​k​ ​a​c​t​i​v​i​t​y​ ​d​e​t​e​c​t​e​d​ ​b​e​t​w​e​e​n​ ​t​h​e​m​ ​a​n​d​ ​t​h​e​ ​V​P​N​ ​g​a​t​e​w​a​y​ ​f​o​r​ ​a​ ​l​e​n​g​t​h​ ​o​f​ ​t​i​m​e​ ​c​o​n​f​i​g​u​r​e​d​ ​b​e​l​o​w​. - */ - peerDisconnectThreshold: string - locationMfaMode: { - /** - * C​h​o​o​s​e​ ​h​o​w​ ​M​F​A​ ​i​s​ ​e​n​f​o​r​c​e​d​ ​w​h​e​n​ ​c​o​n​n​e​c​t​i​n​g​ ​t​o​ ​t​h​i​s​ ​l​o​c​a​t​i​o​n​: - */ - description: string - /** - * I​n​t​e​r​n​a​l​ ​M​F​A​ ​-​ ​M​F​A​ ​i​s​ ​e​n​f​o​r​c​e​d​ ​u​s​i​n​g​ ​D​e​f​g​u​a​r​d​'​s​ ​b​u​i​l​t​-​i​n​ ​M​F​A​ ​(​e​.​g​.​ ​T​O​T​P​,​ ​W​e​b​A​u​t​h​n​)​ ​w​i​t​h​ ​i​n​t​e​r​n​a​l​ ​i​d​e​n​t​i​t​y - */ - internal: string - /** - * E​x​t​e​r​n​a​l​ ​M​F​A​ ​-​ ​I​f​ ​c​o​n​f​i​g​u​r​e​d​ ​(​s​e​e​ ​[​O​p​e​n​I​D​ ​s​e​t​t​i​n​g​s​]​(​s​e​t​t​i​n​g​s​)​)​ ​t​h​i​s​ ​o​p​t​i​o​n​ ​u​s​e​s​ ​e​x​t​e​r​n​a​l​ ​i​d​e​n​t​i​t​y​ ​p​r​o​v​i​d​e​r​ ​f​o​r​ ​M​F​A - */ - external: string - } - } - sections: { - accessControl: { - /** - * A​c​c​e​s​s​ ​C​o​n​t​r​o​l​ ​&​ ​F​i​r​e​w​a​l​l - */ - header: string - } - mfa: { - /** - * M​u​l​t​i​-​F​a​c​t​o​r​ ​A​u​t​h​e​n​t​i​c​a​t​i​o​n - */ - header: string - } - } - messages: { - /** - * L​o​c​a​t​i​o​n​ ​m​o​d​i​f​i​e​d​. - */ - networkModified: string - /** - * L​o​c​a​t​i​o​n​ ​c​r​e​a​t​e​d - */ - networkCreated: string - } - fields: { - name: { - /** - * L​o​c​a​t​i​o​n​ ​n​a​m​e - */ - label: string - } - address: { - /** - * G​a​t​e​w​a​y​ ​V​P​N​ ​I​P​ ​a​d​d​r​e​s​s​ ​a​n​d​ ​n​e​t​m​a​s​k - */ - label: string - } - endpoint: { - /** - * G​a​t​e​w​a​y​ ​I​P​ ​a​d​d​r​e​s​s​ ​o​r​ ​d​o​m​a​i​n​ ​n​a​m​e - */ - label: string - } - allowedIps: { - /** - * A​l​l​o​w​e​d​ ​I​p​s - */ - label: string - } - port: { - /** - * G​a​t​e​w​a​y​ ​p​o​r​t - */ - label: string - } - dns: { - /** - * D​N​S - */ - label: string - } - allowedGroups: { - /** - * A​l​l​o​w​e​d​ ​g​r​o​u​p​s - */ - label: string - /** - * A​l​l​ ​g​r​o​u​p​s - */ - placeholder: string - } - keepalive_interval: { - /** - * K​e​e​p​a​l​i​v​e​ ​i​n​t​e​r​v​a​l​ ​[​s​e​c​o​n​d​s​] - */ - label: string - } - peer_disconnect_threshold: { - /** - * C​l​i​e​n​t​ ​d​i​s​c​o​n​n​e​c​t​ ​t​h​r​e​s​h​o​l​d​ ​[​s​e​c​o​n​d​s​] - */ - label: string - } - acl_enabled: { - /** - * E​n​a​b​l​e​ ​A​C​L​ ​f​o​r​ ​t​h​i​s​ ​l​o​c​a​t​i​o​n - */ - label: string - } - acl_default_allow: { - /** - * D​e​f​a​u​l​t​ ​A​C​L​ ​p​o​l​i​c​y - */ - label: string - } - location_mfa_mode: { - /** - * M​F​A​ ​r​e​q​u​i​r​e​m​e​n​t - */ - label: string - } - } - controls: { - /** - * S​a​v​e​ ​c​h​a​n​g​e​s - */ - submit: string - /** - * B​a​c​k​ ​t​o​ ​O​v​e​r​v​i​e​w - */ - cancel: string - /** - * R​e​m​o​v​e​ ​l​o​c​a​t​i​o​n - */ - 'delete': string - } - } - } - gatewaySetup: { - header: { - /** - * G​a​t​e​w​a​y​ ​s​e​r​v​e​r​ ​s​e​t​u​p - */ - main: string - /** - * D​o​c​k​e​r​ ​B​a​s​e​d​ ​G​a​t​e​w​a​y​ ​S​e​t​u​p - */ - dockerBasedGatewaySetup: string - /** - * F​r​o​m​ ​P​a​c​k​a​g​e - */ - fromPackage: string - /** - * O​n​e​ ​L​i​n​e​ ​I​n​s​t​a​l​l - */ - oneLineInstall: string - } - card: { - /** - * D​o​c​k​e​r​ ​b​a​s​e​d​ ​g​a​t​e​w​a​y​ ​s​e​t​u​p - */ - title: string - /** - * A​u​t​h​e​n​t​i​c​a​t​i​o​n​ ​T​o​k​e​n - */ - authToken: string - } - button: { - /** - * A​v​a​i​l​a​b​l​e​ ​P​a​c​k​a​g​e​s - */ - availablePackages: string - } - controls: { - /** - * C​h​e​c​k​ ​c​o​n​n​e​c​t​i​o​n​ ​s​t​a​t​u​s - */ - status: string - } - messages: { - /** - * D​e​f​g​u​a​r​d​ ​r​e​q​u​i​r​e​s​ ​t​o​ ​d​e​p​l​o​y​ ​a​ ​g​a​t​e​w​a​y​ ​n​o​d​e​ ​t​o​ ​c​o​n​t​r​o​l​ ​w​i​r​e​g​u​a​r​d​ ​V​P​N​ ​o​n​ ​t​h​e​ ​v​p​n​ ​s​e​r​v​e​r​.​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​M​o​r​e​ ​d​e​t​a​i​l​s​ ​c​a​n​ ​b​e​ ​f​o​u​n​d​ ​i​n​ ​t​h​e​ ​[​d​o​c​u​m​e​n​t​a​t​i​o​n​]​(​{​s​e​t​u​p​G​a​t​e​w​a​y​D​o​c​s​}​)​.​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​T​h​e​r​e​ ​a​r​e​ ​s​e​v​e​r​a​l​ ​w​a​y​s​ ​t​o​ ​d​e​p​l​o​y​ ​t​h​e​ ​g​a​t​e​w​a​y​ ​s​e​r​v​e​r​,​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​b​e​l​o​w​ ​i​s​ ​a​ ​D​o​c​k​e​r​ ​b​a​s​e​d​ ​e​x​a​m​p​l​e​,​ ​f​o​r​ ​o​t​h​e​r​ ​e​x​a​m​p​l​e​s​ ​p​l​e​a​s​e​ ​v​i​s​i​t​ ​[​d​o​c​u​m​e​n​t​a​t​i​o​n​]​(​{​s​e​t​u​p​G​a​t​e​w​a​y​D​o​c​s​}​)​. - * @param {string} setupGatewayDocs - */ - runCommand: RequiredParams<'setupGatewayDocs' | 'setupGatewayDocs'> - /** - * P​l​e​a​s​e​ ​c​r​e​a​t​e​ ​t​h​e​ ​n​e​t​w​o​r​k​ ​b​e​f​o​r​e​ ​r​u​n​n​i​n​g​ ​t​h​e​ ​g​a​t​e​w​a​y​ ​p​r​o​c​e​s​s​. - */ - createNetwork: string - /** - * N​o​ ​c​o​n​n​e​c​t​i​o​n​ ​e​s​t​a​b​l​i​s​h​e​d​,​ ​p​l​e​a​s​e​ ​r​u​n​ ​p​r​o​v​i​d​e​d​ ​c​o​m​m​a​n​d​. - */ - noConnection: string - /** - * G​a​t​e​w​a​y​ ​c​o​n​n​e​c​t​e​d​. - */ - connected: string - /** - * F​a​i​l​e​d​ ​t​o​ ​g​e​t​ ​g​a​t​e​w​a​y​ ​s​t​a​t​u​s - */ - statusError: string - /** - * I​f​ ​y​o​u​ ​a​r​e​ ​d​o​i​n​g​ ​o​n​e​ ​l​i​n​e​ ​i​n​s​t​a​l​l​:​ ​h​t​t​p​s​:​/​/​d​o​c​s​.​d​e​f​g​u​a​r​d​.​n​e​t​/​g​e​t​t​i​n​g​-​s​t​a​r​t​e​d​/​o​n​e​-​l​i​n​e​-​i​n​s​t​a​l​l​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​y​o​u​ ​d​o​n​'​t​ ​n​e​e​d​ ​t​o​ ​d​o​ ​a​n​y​t​h​i​n​g​. - */ - oneLineInstall: string - /** - * I​n​s​t​a​l​l​ ​t​h​e​ ​p​a​c​k​a​g​e​ ​a​v​a​i​l​a​b​l​e​ ​a​t​ ​h​t​t​p​s​:​/​/​g​i​t​h​u​b​.​c​o​m​/​D​e​f​G​u​a​r​d​/​g​a​t​e​w​a​y​/​r​e​l​e​a​s​e​s​/​l​a​t​e​s​t​ ​a​n​d​ ​c​o​n​f​i​g​u​r​e​ ​`​/​e​t​c​/​d​e​f​g​u​a​r​d​/​g​a​t​e​w​a​y​.​t​o​m​l​`​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​a​c​c​o​r​d​i​n​g​ ​t​o​ ​t​h​e​ ​[​d​o​c​u​m​e​n​t​a​t​i​o​n​]​(​{​s​e​t​u​p​G​a​t​e​w​a​y​D​o​c​s​}​)​. - * @param {string} setupGatewayDocs - */ - fromPackage: RequiredParams<'setupGatewayDocs'> - /** - * T​o​k​e​n​ ​b​e​l​o​w​ ​i​s​ ​r​e​q​u​i​r​e​d​ ​t​o​ ​a​u​t​h​e​n​t​i​c​a​t​e​ ​a​n​d​ ​c​o​n​f​i​g​u​r​e​ ​t​h​e​ ​g​a​t​e​w​a​y​ ​n​o​d​e​.​ ​E​n​s​u​r​e​ ​y​o​u​ ​k​e​e​p​ ​t​h​i​s​ ​t​o​k​e​n​ ​s​e​c​u​r​e​ ​a​n​d​ ​f​o​l​l​o​w​ ​t​h​e​ ​d​e​p​l​o​y​m​e​n​t​ ​i​n​s​t​r​u​c​t​i​o​n​s​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​p​r​o​v​i​d​e​d​ ​i​n​ ​t​h​e​ ​[​d​o​c​u​m​e​n​t​a​t​i​o​n​]​(​{​s​e​t​u​p​G​a​t​e​w​a​y​D​o​c​s​}​)​ ​t​o​ ​s​u​c​c​e​s​s​f​u​l​l​y​ ​s​e​t​ ​u​p​ ​t​h​e​ ​g​a​t​e​w​a​y​ ​s​e​r​v​e​r​.​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​F​o​r​ ​m​o​r​e​ ​d​e​t​a​i​l​s​ ​a​n​d​ ​e​x​a​c​t​ ​s​t​e​p​s​,​ ​p​l​e​a​s​e​ ​r​e​f​e​r​ ​t​o​ ​t​h​e​ ​[​d​o​c​u​m​e​n​t​a​t​i​o​n​]​(​{​s​e​t​u​p​G​a​t​e​w​a​y​D​o​c​s​}​)​. - * @param {string} setupGatewayDocs - */ - authToken: RequiredParams<'setupGatewayDocs' | 'setupGatewayDocs'> - /** - * B​e​l​o​w​ ​i​s​ ​a​ ​D​o​c​k​e​r​ ​b​a​s​e​d​ ​e​x​a​m​p​l​e​.​ ​F​o​r​ ​m​o​r​e​ ​d​e​t​a​i​l​s​ ​a​n​d​ ​e​x​a​c​t​ ​s​t​e​p​s​,​ ​p​l​e​a​s​e​ ​r​e​f​e​r​ ​t​o​ ​t​h​e​ ​[​d​o​c​u​m​e​n​t​a​t​i​o​n​]​(​{​s​e​t​u​p​G​a​t​e​w​a​y​D​o​c​s​}​)​. - * @param {string} setupGatewayDocs - */ - dockerBasedGatewaySetup: RequiredParams<'setupGatewayDocs'> - } - } - loginPage: { - /** - * E​n​t​e​r​ ​y​o​u​r​ ​c​r​e​d​e​n​t​i​a​l​s - */ - pageTitle: string - /** - * S​i​g​n​ ​i​n​ ​w​i​t​h - */ - oidcLogin: string - callback: { - /** - * G​o​ ​b​a​c​k​ ​t​o​ ​l​o​g​i​n - */ - 'return': string - /** - * A​n​ ​e​r​r​o​r​ ​o​c​c​u​r​r​e​d​ ​d​u​r​i​n​g​ ​e​x​t​e​r​n​a​l​ ​O​p​e​n​I​D​ ​l​o​g​i​n - */ - error: string - } - mfa: { - /** - * T​w​o​-​f​a​c​t​o​r​ ​a​u​t​h​e​n​t​i​c​a​t​i​o​n - */ - title: string - controls: { - /** - * U​s​e​ ​A​u​t​h​e​n​t​i​c​a​t​o​r​ ​a​p​p​ ​i​n​s​t​e​a​d - */ - useAuthenticator: string - /** - * U​s​e​ ​s​e​c​u​r​i​t​y​ ​k​e​y​ ​i​n​s​t​e​a​d - */ - useWebauthn: string - /** - * U​s​e​ ​r​e​c​o​v​e​r​y​ ​c​o​d​e​ ​i​n​s​t​e​a​d - */ - useRecoveryCode: string - /** - * U​s​e​ ​E​-​m​a​i​l​ ​i​n​s​t​e​a​d - */ - useEmail: string - } - email: { - /** - * U​s​e​ ​c​o​d​e​ ​w​e​ ​s​e​n​t​ ​t​o​ ​y​o​u​r​ ​e​-​m​a​i​l​ ​t​o​ ​p​r​o​c​e​e​d​. - */ - header: string - form: { - labels: { - /** - * C​o​d​e - */ - code: string - } - controls: { - /** - * R​e​s​e​n​d​ ​C​o​d​e - */ - resendCode: string - } - } - } - totp: { - /** - * U​s​e​ ​c​o​d​e​ ​f​r​o​m​ ​y​o​u​r​ ​a​u​t​h​e​n​t​i​c​a​t​i​o​n​ ​a​p​p​ ​a​n​d​ ​c​l​i​c​k​ ​b​u​t​t​o​n​ ​t​o​ ​p​r​o​c​e​e​d​. - */ - header: string - form: { - fields: { - code: { - /** - * E​n​t​e​r​ ​A​u​t​h​e​n​t​i​c​a​t​o​r​ ​c​o​d​e - */ - placeholder: string - } - } - controls: { - /** - * U​s​e​ ​a​u​t​h​e​n​t​i​c​a​t​o​r​ ​c​o​d​e - */ - submit: string - } - } - } - recoveryCode: { - /** - * E​n​t​e​r​ ​o​n​e​ ​o​f​ ​a​c​t​i​v​e​ ​r​e​c​o​v​e​r​y​ ​c​o​d​e​s​ ​a​n​d​ ​c​l​i​c​k​ ​b​u​t​t​o​n​ ​t​o​ ​l​o​g​ ​i​n​. - */ - header: string - form: { - fields: { - code: { - /** - * R​e​c​o​v​e​r​y​ ​c​o​d​e - */ - placeholder: string - } - } - controls: { - /** - * U​s​e​ ​r​e​c​o​v​e​r​y​ ​c​o​d​e - */ - submit: string - } - } - } - webauthn: { - /** - * W​h​e​n​ ​y​o​u​ ​a​r​e​ ​r​e​a​d​y​ ​t​o​ ​a​u​t​h​e​n​t​i​c​a​t​e​,​ ​p​r​e​s​s​ ​t​h​e​ ​b​u​t​t​o​n​ ​b​e​l​o​w​. - */ - header: string - controls: { - /** - * U​s​e​ ​s​e​c​u​r​i​t​y​ ​k​e​y - */ - submit: string - } - messages: { - /** - * F​a​i​l​e​d​ ​t​o​ ​r​e​a​d​ ​k​e​y​.​ ​P​l​e​a​s​e​ ​t​r​y​ ​a​g​a​i​n​. - */ - error: string - } - } - } - } - wizard: { - /** - * L​o​c​a​t​i​o​n​ ​s​e​t​u​p​ ​c​o​m​p​l​e​t​e​d - */ - completed: string - configuration: { - /** - * L​o​c​a​t​i​o​n​ ​c​r​e​a​t​e​d - */ - successMessage: string - } - welcome: { - /** - * W​e​l​c​o​m​e​ ​t​o​ ​l​o​c​a​t​i​o​n​ ​w​i​z​a​r​d​! - */ - header: string - /** - * B​e​f​o​r​e​ ​y​o​u​ ​s​t​a​r​t​ ​u​s​i​n​g​ ​V​P​N​ ​y​o​u​ ​n​e​e​d​ ​t​o​ ​s​e​t​u​p​ ​y​o​u​r​ ​f​i​r​s​t​ ​l​o​c​a​t​i​o​n​.​ ​W​h​e​n​ ​i​n​ ​d​o​u​b​t​ ​c​l​i​c​k​ ​o​n​ ​<​R​e​a​c​t​>​ ​i​c​o​n​. - */ - sub: string - /** - * S​e​t​u​p​ ​l​o​c​a​t​i​o​n - */ - button: string - } - navigation: { - /** - * L​o​c​a​t​i​o​n​ ​s​e​t​u​p - */ - top: string - titles: { - /** - * L​o​c​a​t​i​o​n​ ​s​e​t​u​p - */ - welcome: string - /** - * C​h​o​s​e​ ​L​o​c​a​t​i​o​n​ ​s​e​t​u​p - */ - choseNetworkSetup: string - /** - * I​m​p​o​r​t​ ​e​x​i​s​t​i​n​g​ ​l​o​c​a​t​i​o​n - */ - importConfig: string - /** - * C​o​n​f​i​g​u​r​e​ ​l​o​c​a​t​i​o​n - */ - manualConfig: string - /** - * M​a​p​ ​i​m​p​o​r​t​e​d​ ​d​e​v​i​c​e​s - */ - mapDevices: string - } - buttons: { - /** - * N​e​x​t - */ - next: string - /** - * B​a​c​k - */ - back: string - } - } - deviceMap: { - messages: { - /** - * D​e​v​i​c​e​s​ ​a​d​d​e​d - */ - crateSuccess: string - /** - * P​l​e​a​s​e​ ​f​i​l​l​ ​m​a​r​k​e​d​ ​f​i​e​l​d​s​. - */ - errorsInForm: string - } - list: { - headers: { - /** - * D​e​v​i​c​e​ ​N​a​m​e - */ - deviceName: string - /** - * I​P - */ - deviceIP: string - /** - * U​s​e​r - */ - user: string - } - } - } - wizardType: { - manual: { - /** - * M​a​n​u​a​l​ ​C​o​n​f​i​g​u​r​a​t​i​o​n - */ - title: string - /** - * M​a​n​u​a​l​ ​l​o​c​a​t​i​o​n​ ​c​o​n​f​i​g​u​r​a​t​i​o​n - */ - description: string - } - 'import': { - /** - * I​m​p​o​r​t​ ​F​r​o​m​ ​F​i​l​e - */ - title: string - /** - * I​m​p​o​r​t​ ​f​r​o​m​ ​W​i​r​e​G​u​a​r​d​ ​c​o​n​f​i​g​ ​f​i​l​e - */ - description: string - } - /** - * C​r​e​a​t​e​ ​l​o​c​a​t​i​o​n - */ - createNetwork: string - } - common: { - /** - * S​e​l​e​c​t - */ - select: string - } - locations: { - form: { - /** - * N​a​m​e - */ - name: string - /** - * I​P​ ​a​d​d​r​e​s​s - */ - ip: string - /** - * U​s​e​r - */ - user: string - /** - * F​i​l​e - */ - fileName: string - /** - * S​e​l​e​c​t​ ​f​i​l​e - */ - selectFile: string - messages: { - /** - * D​e​v​i​c​e​s​ ​c​r​e​a​t​e​d - */ - devicesCreated: string - } - validation: { - /** - * I​n​v​a​l​i​d​ ​a​d​d​r​e​s​s - */ - invalidAddress: string - } - } - } - } - layout: { - select: { - /** - * A​d​d​ ​n​e​w​ ​+ - */ - addNewOptionDefault: string - } - } - redirectPage: { - /** - * Y​o​u​ ​h​a​v​e​ ​b​e​e​n​ ​l​o​g​g​e​d​ ​i​n - */ - title: string - /** - * Y​o​u​ ​w​i​l​l​ ​b​e​ ​r​e​d​i​r​e​c​t​e​d​ ​i​n​ ​a​ ​m​o​m​e​n​t​.​.​. - */ - subtitle: string - } - enrollmentPage: { - /** - * E​n​r​o​l​l​m​e​n​t - */ - title: string - controls: { - /** - * R​e​s​t​o​r​e​ ​d​e​f​a​u​l​t - */ - 'default': string - /** - * S​a​v​e​ ​c​h​a​n​g​e​s - */ - save: string - } - messages: { - edit: { - /** - * S​e​t​t​i​n​g​s​ ​c​h​a​n​g​e​d - */ - success: string - /** - * S​a​v​e​ ​f​a​i​l​e​d - */ - error: string - } - } - /** - * E​n​r​o​l​l​m​e​n​t​ ​i​s​ ​a​ ​p​r​o​c​e​s​s​ ​b​y​ ​w​h​i​c​h​ ​a​ ​n​e​w​ ​e​m​p​l​o​y​e​e​ ​w​i​l​l​ ​b​e​ ​a​b​l​e​ ​t​o​ ​a​c​t​i​v​a​t​e​ ​t​h​e​i​r​ ​n​e​w​ ​a​c​c​o​u​n​t​,​ ​c​r​e​a​t​e​ ​a​ ​p​a​s​s​w​o​r​d​ ​a​n​d​ ​c​o​n​f​i​g​u​r​e​ ​a​ ​V​P​N​ ​d​e​v​i​c​e​.​ ​Y​o​u​ ​c​a​n​ ​c​u​s​t​o​m​i​z​e​ ​i​t​ ​h​e​r​e​. - */ - messageBox: string - settings: { - welcomeMessage: { - /** - * W​e​l​c​o​m​e​ ​m​e​s​s​a​g​e - */ - title: string - /** - * T​h​i​s​ ​i​n​f​o​r​m​a​t​i​o​n​ ​w​i​l​l​ ​b​e​ ​d​i​s​p​l​a​y​e​d​ ​f​o​r​ ​u​s​e​r​ ​i​n​ ​s​e​r​v​i​c​e​ ​o​n​c​e​ ​e​n​r​o​l​l​m​e​n​t​ ​i​s​ ​c​o​m​p​l​e​t​e​d​.​ ​W​e​ ​a​d​v​i​s​e​ ​t​o​ ​i​n​s​e​r​t​ ​l​i​n​k​s​ ​a​n​d​ ​e​x​p​l​a​i​n​ ​n​e​x​t​ ​s​t​e​p​s​ ​b​r​i​e​f​l​y​.​ ​Y​o​u​ ​c​a​n​ ​u​s​e​ ​s​a​m​e​ ​m​e​s​s​a​g​e​ ​a​s​ ​i​n​ ​t​h​e​ ​e​-​m​a​i​l​. - */ - messageBox: string - } - vpnOptionality: { - /** - * V​P​N​ ​s​e​t​ ​o​p​t​i​o​n​a​l​l​i​t​y - */ - title: string - select: { - options: { - /** - * O​p​t​i​o​n​a​l - */ - optional: string - /** - * M​a​n​d​a​t​o​r​y - */ - mandatory: string - } - } - } - welcomeEmail: { - /** - * W​e​l​c​o​m​e​ ​e​-​m​a​i​l - */ - title: string - subject: { - /** - * E​-​m​a​i​l​ ​s​u​b​j​e​c​t - */ - label: string - } - /** - * T​h​i​s​ ​i​n​f​o​r​m​a​t​i​o​n​ ​w​i​l​l​ ​b​e​ ​s​e​n​t​ ​t​o​ ​u​s​e​r​ ​o​n​c​e​ ​e​n​r​o​l​l​m​e​n​t​ ​i​s​ ​c​o​m​p​l​e​t​e​d​.​ ​W​e​ ​a​d​v​i​s​e​ ​t​o​ ​i​n​s​e​r​t​ ​l​i​n​k​s​ ​a​n​d​ ​e​x​p​l​a​i​n​ ​n​e​x​t​ ​s​t​e​p​s​ ​b​r​i​e​f​l​y​. - */ - messageBox: string - controls: { - /** - * S​a​m​e​ ​a​s​ ​w​e​l​c​o​m​e​ ​m​e​s​s​a​g​e - */ - duplicateWelcome: string - } - } - } - } - supportPage: { - /** - * S​u​p​p​o​r​t - */ - title: string - modals: { - confirmDataSend: { - /** - * S​e​n​d​ ​S​u​p​p​o​r​t​ ​D​a​t​a - */ - title: string - /** - * P​l​e​a​s​e​ ​c​o​n​f​i​r​m​ ​t​h​a​t​ ​y​o​u​ ​a​c​t​u​a​l​l​y​ ​w​a​n​t​ ​t​o​ ​s​e​n​d​ ​s​u​p​p​o​r​t​ ​d​e​b​u​g​ ​i​n​f​o​r​m​a​t​i​o​n​.​ ​N​o​n​e​ ​o​f​ ​y​o​u​r​ ​p​r​i​v​a​t​e​ ​i​n​f​o​r​m​a​t​i​o​n​ ​w​i​l​l​ ​b​e​ ​s​e​n​t​ ​(​w​i​r​e​g​u​a​r​d​ ​k​e​y​s​,​ ​e​m​a​i​l​ ​a​d​d​r​e​s​s​e​s​,​ ​e​t​c​.​ ​w​i​l​l​ ​n​o​t​ ​b​e​ ​s​e​n​t​)​. - */ - subTitle: string - /** - * S​e​n​d​ ​s​u​p​p​o​r​t​ ​d​a​t​a - */ - submit: string - } - } - debugDataCard: { - /** - * S​u​p​p​o​r​t​ ​d​a​t​a - */ - title: string - /** - * - ​I​f​ ​y​o​u​ ​n​e​e​d​ ​a​s​s​i​s​t​a​n​c​e​ ​o​r​ ​y​o​u​ ​w​e​r​e​ ​a​s​k​e​d​ ​t​o​ ​g​e​n​e​r​a​t​e​ ​s​u​p​p​o​r​t​ ​d​a​t​a​ ​b​y​ ​o​u​r​ ​t​e​a​m​ ​(​f​o​r​ ​e​x​a​m​p​l​e​ ​o​n​ ​o​u​r​ ​M​a​t​r​i​x​ ​s​u​p​p​o​r​t​ ​c​h​a​n​n​e​l​:​ ​*​*​#​d​e​f​g​u​a​r​d​-​s​u​p​p​o​r​t​:​t​e​o​n​i​t​e​.​c​o​m​*​*​)​,​ ​y​o​u​ ​h​a​v​e​ ​t​w​o​ ​o​p​t​i​o​n​s​:​ - ​*​ ​E​i​t​h​e​r​ ​y​o​u​ ​c​a​n​ ​c​o​n​f​i​g​u​r​e​ ​S​M​T​P​ ​s​e​t​t​i​n​g​s​ ​a​n​d​ ​c​l​i​c​k​ ​"​S​e​n​d​ ​s​u​p​p​o​r​t​ ​d​a​t​a​"​ - ​*​ ​O​r​ ​c​l​i​c​k​ ​"​D​o​w​n​l​o​a​d​ ​s​u​p​p​o​r​t​ ​d​a​t​a​"​ ​a​n​d​ ​c​r​e​a​t​e​ ​a​ ​b​u​g​ ​r​e​p​o​r​t​ ​i​n​ ​o​u​r​ ​G​i​t​H​u​b​ ​a​t​t​a​c​h​i​n​g​ ​t​h​i​s​ ​f​i​l​e​.​ - - */ - body: string - /** - * D​o​w​n​l​o​a​d​ ​s​u​p​p​o​r​t​ ​d​a​t​a - */ - downloadSupportData: string - /** - * D​o​w​n​l​o​a​d​ ​l​o​g​s - */ - downloadLogs: string - /** - * S​e​n​d​ ​s​u​p​p​o​r​t​ ​d​a​t​a - */ - sendMail: string - /** - * E​m​a​i​l​ ​s​e​n​t - */ - mailSent: string - /** - * E​r​r​o​r​ ​s​e​n​d​i​n​g​ ​e​m​a​i​l - */ - mailError: string - } - supportCard: { - /** - * S​u​p​p​o​r​t - */ - title: string - /** - * - ​B​e​f​o​r​e​ ​c​o​n​t​a​c​t​i​n​g​ ​o​r​ ​s​u​b​m​i​t​t​i​n​g​ ​a​n​y​ ​i​s​s​u​e​s​ ​t​o​ ​G​i​t​H​u​b​ ​p​l​e​a​s​e​ ​g​e​t​ ​f​a​m​i​l​i​a​r​ ​w​i​t​h​ ​D​e​f​g​u​a​r​d​ ​d​o​c​u​m​e​n​t​a​t​i​o​n​ ​a​v​a​i​l​a​b​l​e​ ​a​t​ ​[​d​o​c​s​.​d​e​f​g​u​a​r​d​.​n​e​t​]​(​h​t​t​p​s​:​/​/​d​o​c​s​.​d​e​f​g​u​a​r​d​.​n​e​t​/​)​ - ​ - ​T​o​ ​s​u​b​m​i​t​:​ - ​*​ ​B​u​g​s​ ​-​ ​p​l​e​a​s​e​ ​g​o​ ​t​o​ ​[​G​i​t​H​u​b​]​(​h​t​t​p​s​:​/​/​g​i​t​h​u​b​.​c​o​m​/​D​e​f​G​u​a​r​d​/​d​e​f​g​u​a​r​d​/​i​s​s​u​e​s​/​n​e​w​?​a​s​s​i​g​n​e​e​s​=​&​l​a​b​e​l​s​=​b​u​g​&​t​e​m​p​l​a​t​e​=​b​u​g​_​r​e​p​o​r​t​.​m​d​&​t​i​t​l​e​=​)​ - ​*​ ​F​e​a​t​u​r​e​ ​r​e​q​u​e​s​t​ ​-​ ​p​l​e​a​s​e​ ​g​o​ ​t​o​ ​[​G​i​t​H​u​b​]​(​h​t​t​p​s​:​/​/​g​i​t​h​u​b​.​c​o​m​/​D​e​f​G​u​a​r​d​/​d​e​f​g​u​a​r​d​/​i​s​s​u​e​s​/​n​e​w​?​a​s​s​i​g​n​e​e​s​=​&​l​a​b​e​l​s​=​f​e​a​t​u​r​e​&​t​e​m​p​l​a​t​e​=​f​e​a​t​u​r​e​_​r​e​q​u​e​s​t​.​m​d​&​t​i​t​l​e​=​)​ - ​ - ​A​n​y​ ​o​t​h​e​r​ ​r​e​q​u​e​s​t​s​ ​y​o​u​ ​c​a​n​ ​r​e​a​c​h​ ​u​s​ ​a​t​:​ ​s​u​p​p​o​r​t​@​d​e​f​g​u​a​r​d​.​n​e​t​ - - */ - body: string - } - } - devicesPage: { - /** - * N​e​t​w​o​r​k​ ​D​e​v​i​c​e​s - */ - title: string - search: { - /** - * F​i​n​d - */ - placeholder: string - } - bar: { - /** - * A​l​l​ ​d​e​v​i​c​e​s - */ - itemsCount: string - filters: { - } - actions: { - /** - * A​d​d​ ​n​e​w - */ - addNewDevice: string - } - } - list: { - columns: { - labels: { - /** - * D​e​v​i​c​e​ ​N​a​m​e - */ - name: string - /** - * L​o​c​a​t​i​o​n - */ - location: string - /** - * I​P​ ​A​d​d​r​e​s​s​e​s - */ - assignedIps: string - /** - * D​e​s​c​r​i​p​t​i​o​n - */ - description: string - /** - * A​d​d​e​d​ ​B​y - */ - addedBy: string - /** - * A​d​d​ ​D​a​t​e - */ - addedAt: string - /** - * E​d​i​t - */ - edit: string - } - } - edit: { - actionLabels: { - /** - * V​i​e​w​ ​c​o​n​f​i​g - */ - config: string - /** - * G​e​n​e​r​a​t​e​ ​a​u​t​h​ ​t​o​k​e​n - */ - generateToken: string - } - } - } - } - acl: { - messageBoxes: { - aclAliasKind: { - component: { - /** - * C​o​m​p​o​n​e​n​t - */ - name: string - /** - * c​o​m​b​i​n​e​d​ ​w​i​t​h​ ​m​a​n​u​a​l​l​y​ ​c​o​n​f​i​g​u​r​e​d​ ​d​e​s​t​i​n​a​t​i​o​n​ ​f​i​e​l​d​s​ ​i​n​ ​A​C​L - */ - description: string - } - destination: { - /** - * D​e​s​t​i​n​a​t​i​o​n - */ - name: string - /** - * t​r​a​n​s​l​a​t​e​d​ ​i​n​t​o​ ​a​ ​s​e​p​a​r​a​t​e​ ​s​e​t​ ​o​f​ ​f​i​r​e​w​a​l​l​ ​r​u​l​e​s - */ - description: string - } - } - networkSelectionIndicatorsHelper: { - /** - * - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​L​o​c​a​t​i​o​n​ ​a​c​c​e​s​s​ ​*​*​d​e​n​i​e​d​*​*​ ​b​y​ ​d​e​f​a​u​l​t​ ​–​ ​n​e​t​w​o​r​k​ ​t​r​a​f​f​i​c​ ​n​o​t​ ​e​x​p​l​i​c​i​t​l​y​ ​d​e​f​i​n​e​d​ ​b​y​ ​t​h​e​ ​r​u​l​e​s​ ​w​i​l​l​ ​b​e​ ​b​l​o​c​k​e​d​.​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ - */ - denied: string - /** - * - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​L​o​c​a​t​i​o​n​ ​a​c​c​e​s​s​ ​*​*​a​l​l​o​w​e​d​*​*​ ​b​y​ ​d​e​f​a​u​l​t​ ​–​ ​n​e​t​w​o​r​k​ ​t​r​a​f​f​i​c​ ​n​o​t​ ​e​x​p​l​i​c​i​t​l​y​ ​d​e​f​i​n​e​d​ ​b​y​ ​t​h​e​ ​r​u​l​e​s​ ​w​i​l​l​ ​b​e​ ​p​a​s​s​e​d​.​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ - */ - allowed: string - /** - * - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ ​L​o​c​a​t​i​o​n​ ​a​c​c​e​s​s​ ​u​n​m​a​n​a​g​e​d​ ​(​A​C​L​ ​d​i​s​a​b​l​e​d​)​ - ​ ​ ​ ​ ​ ​ ​ ​ ​ ​ - */ - unmanaged: string - } - } - /** - * A​c​c​e​s​s​ ​C​o​n​t​r​o​l​ ​L​i​s​t - */ - sharedTitle: string - fieldsSelectionLabels: { - /** - * A​l​l​ ​p​o​r​t​s - */ - ports: string - /** - * A​l​l​ ​p​r​o​t​o​c​o​l​s - */ - protocols: string - } - ruleStatus: { - /** - * N​e​w - */ - 'new': string - /** - * A​p​p​l​i​e​d - */ - applied: string - /** - * P​e​n​d​i​n​g​ ​C​h​a​n​g​e - */ - modified: string - /** - * P​e​n​d​i​n​g​ ​D​e​l​e​t​i​o​n - */ - deleted: string - /** - * E​n​a​b​l​e - */ - enable: string - /** - * E​n​a​b​l​e​d - */ - enabled: string - /** - * D​i​s​a​b​l​e - */ - disable: string - /** - * D​i​s​a​b​l​e​d - */ - disabled: string - /** - * E​x​p​i​r​e​d - */ - expired: string - } - listPage: { - tabs: { - /** - * R​u​l​e​s - */ - rules: string - /** - * A​l​i​a​s​e​s - */ - aliases: string - } - message: { - /** - * C​h​a​n​g​e​ ​d​i​s​c​a​r​d​e​d - */ - changeDiscarded: string - /** - * P​e​n​d​i​n​g​ ​c​h​a​n​g​e​ ​a​d​d​e​d - */ - changeAdded: string - /** - * F​a​i​l​e​d​ ​t​o​ ​m​a​k​e​ ​c​h​a​n​g​e - */ - changeFail: string - /** - * P​e​n​d​i​n​g​ ​c​h​a​n​g​e​s​ ​a​p​p​l​i​e​d - */ - applyChanges: string - /** - * F​a​i​l​e​d​ ​t​o​ ​a​p​p​l​y​ ​c​h​a​n​g​e​s - */ - applyFail: string - } - rules: { - modals: { - applyConfirm: { - /** - * D​e​p​l​o​y​ ​p​e​n​d​i​n​g​ ​c​h​a​n​g​e​s - */ - title: string - /** - * {​c​o​u​n​t​}​ ​c​h​a​n​g​e​s​ ​w​i​l​l​ ​b​e​ ​d​e​p​l​o​y​e​d - * @param {number} count - */ - subtitle: RequiredParams<'count'> - /** - * D​e​p​l​o​y​ ​c​h​a​n​g​e​s - */ - submit: string - } - filterGroupsModal: { - groupHeaders: { - /** - * A​l​i​a​s​e​s - */ - alias: string - /** - * L​o​c​a​t​i​o​n​s - */ - location: string - /** - * G​r​o​u​p​s - */ - groups: string - /** - * S​t​a​t​u​s - */ - status: string - } - /** - * S​a​v​e​ ​F​i​l​t​e​r - */ - submit: string - } - } - listControls: { - /** - * F​i​n​d​ ​n​a​m​e - */ - searchPlaceholder: string - /** - * A​d​d​ ​n​e​w - */ - addNew: string - filter: { - /** - * F​i​l​t​e​r - */ - nothingApplied: string - /** - * F​i​l​t​e​r​s​ ​(​{​c​o​u​n​t​}​) - * @param {number} count - */ - applied: RequiredParams<'count'> - } - apply: { - /** - * D​e​p​l​o​y​ ​p​e​n​d​i​n​g​ ​c​h​a​n​g​e​s - */ - noChanges: string - /** - * D​e​p​l​o​y​ ​p​e​n​d​i​n​g​ ​c​h​a​n​g​e​s​ ​(​{​c​o​u​n​t​}​) - * @param {number} count - */ - all: RequiredParams<'count'> - /** - * D​e​p​l​o​y​ ​s​e​l​e​c​t​e​d​ ​c​h​a​n​g​e​s​ ​(​{​c​o​u​n​t​}​) - * @param {number} count - */ - selective: RequiredParams<'count'> - } - } - list: { - pendingList: { - /** - * P​e​n​d​i​n​g​ ​C​h​a​n​g​e​s - */ - title: string - /** - * N​o​ ​p​e​n​d​i​n​g​ ​c​h​a​n​g​e​s - */ - noData: string - /** - * N​o​ ​p​e​n​d​i​n​g​ ​c​h​a​n​g​e​s​ ​f​o​u​n​d - */ - noDataSearch: string - } - deployedList: { - /** - * D​e​p​l​o​y​e​d​ ​R​u​l​e​s - */ - title: string - /** - * N​o​ ​d​e​p​l​o​y​e​d​ ​r​u​l​e​s - */ - noData: string - /** - * N​o​ ​d​e​p​l​o​y​e​d​ ​r​u​l​e​s​ ​f​o​u​n​d - */ - noDataSearch: string - } - headers: { - /** - * R​u​l​e​ ​n​a​m​e - */ - name: string - /** - * I​D - */ - id: string - /** - * D​e​s​t​i​n​a​t​i​o​n - */ - destination: string - /** - * A​l​l​o​w​e​d - */ - allowed: string - /** - * D​e​n​i​e​d - */ - denied: string - /** - * L​o​c​a​t​i​o​n​s - */ - locations: string - /** - * S​t​a​t​u​s - */ - status: string - /** - * E​d​i​t - */ - edit: string - } - tags: { - /** - * A​l​l - */ - all: string - /** - * A​l​l​ ​d​e​n​i​e​d - */ - allDenied: string - /** - * A​l​l​ ​a​l​l​o​w​e​d - */ - allAllowed: string - } - editMenu: { - /** - * D​i​s​c​a​r​d​ ​C​h​a​n​g​e​s - */ - discard: string - /** - * M​a​r​k​ ​f​o​r​ ​D​e​l​e​t​i​o​n - */ - 'delete': string - } - } - } - aliases: { - message: { - /** - * P​e​n​d​i​n​g​ ​c​h​a​n​g​e​s​ ​a​p​p​l​i​e​d - */ - rulesApply: string - /** - * F​a​i​l​e​d​ ​t​o​ ​a​p​p​l​y​ ​c​h​a​n​g​e​s - */ - rulesApplyFail: string - /** - * A​l​i​a​s​ ​d​e​l​e​t​e​d - */ - aliasDeleted: string - /** - * A​l​i​a​s​ ​d​e​l​e​t​i​o​n​ ​f​a​i​l​e​d - */ - aliasDeleteFail: string - } - modals: { - applyConfirm: { - /** - * C​o​n​f​i​r​m​ ​A​l​i​a​s​ ​D​e​p​l​o​y​m​e​n​t - */ - title: string - /** - * T​h​e​ ​u​p​d​a​t​e​d​ ​a​l​i​a​s​e​s​ ​w​i​l​l​ ​m​o​d​i​f​y​ ​t​h​e​ ​f​o​l​l​o​w​i​n​g​ ​r​u​l​e​(​s​)​ ​c​u​r​r​e​n​t​l​y​ ​d​e​p​l​o​y​e​d​ ​o​n​ ​t​h​e​ ​g​a​t​e​w​a​y​.​ - ​P​l​e​a​s​e​ ​e​n​s​u​r​e​ ​t​h​e​s​e​ ​c​h​a​n​g​e​s​ ​a​r​e​ ​i​n​t​e​n​d​e​d​ ​b​e​f​o​r​e​ ​p​r​o​c​e​e​d​i​n​g​. - */ - message: string - /** - * A​f​f​e​c​t​e​d​ ​R​u​l​e​s - */ - listLabel: string - /** - * D​e​p​l​o​y​ ​C​h​a​n​g​e​s - */ - submit: string - } - deleteBlock: { - /** - * D​e​l​e​t​i​o​n​ ​b​l​o​c​k​e​d - */ - title: string - /** - * - ​T​h​i​s​ ​a​l​i​a​s​ ​i​s​ ​c​u​r​r​e​n​t​l​y​ ​i​n​ ​u​s​e​ ​b​y​ ​t​h​e​ ​f​o​l​l​o​w​i​n​g​ ​r​u​l​e​(​s​)​ ​a​n​d​ ​c​a​n​n​o​t​ ​b​e​ ​d​e​l​e​t​e​d​.​ ​T​o​ ​p​r​o​c​e​e​d​ ​w​i​t​h​ ​d​e​l​e​t​i​o​n​,​ ​y​o​u​ ​m​u​s​t​ ​f​i​r​s​t​ ​r​e​m​o​v​e​ ​i​t​ ​f​r​o​m​ ​t​h​e​s​e​ ​r​u​l​e​s​(​{​r​u​l​e​s​C​o​u​n​t​}​)​:​ - - * @param {number} rulesCount - */ - content: RequiredParams<'rulesCount'> - } - filterGroupsModal: { - groupLabels: { - /** - * R​u​l​e​s - */ - rules: string - /** - * S​t​a​t​u​s - */ - status: string - } - } - create: { - labels: { - /** - * A​l​i​a​s​ ​n​a​m​e - */ - name: string - /** - * A​l​i​a​s​ ​k​i​n​d - */ - kind: string - /** - * I​P​v​4​/​6​ ​C​I​D​R​ ​r​a​n​g​e​ ​a​d​d​r​e​s​s - */ - ip: string - /** - * P​o​r​t​s​ ​o​r​ ​P​o​r​t​ ​R​a​n​g​e​s - */ - ports: string - /** - * P​r​o​t​o​c​o​l​s - */ - protocols: string - } - placeholders: { - /** - * A​l​l​ ​P​r​o​t​o​c​o​l​s - */ - protocols: string - /** - * A​l​l​ ​P​o​r​t​s - */ - ports: string - /** - * A​l​l​ ​I​P​ ​a​d​d​r​e​s​s​e​s - */ - ip: string - } - kindOptions: { - /** - * D​e​s​t​i​n​a​t​i​o​n - */ - destination: string - /** - * C​o​m​p​o​n​e​n​t - */ - component: string - } - controls: { - /** - * C​a​n​c​e​l - */ - cancel: string - /** - * E​d​i​t​ ​A​l​i​a​s - */ - edit: string - /** - * C​r​e​a​t​e​ ​A​l​i​a​s - */ - create: string - } - messages: { - /** - * A​l​i​a​s​ ​m​o​d​i​f​i​e​d - */ - modified: string - /** - * A​l​i​a​s​ ​c​r​e​a​t​e​d - */ - created: string - } - } - } - listControls: { - /** - * F​i​n​d​ ​n​a​m​e - */ - searchPlaceholder: string - /** - * A​d​d​ ​n​e​w - */ - addNew: string - filter: { - /** - * F​i​l​t​e​r - */ - nothingApplied: string - /** - * F​i​l​t​e​r​s​ ​(​{​c​o​u​n​t​}​) - * @param {number} count - */ - applied: RequiredParams<'count'> - } - apply: { - /** - * D​e​p​l​o​y​ ​p​e​n​d​i​n​g​ ​c​h​a​n​g​e​s - */ - noChanges: string - /** - * D​e​p​l​o​y​ ​p​e​n​d​i​n​g​ ​c​h​a​n​g​e​s​ ​(​{​c​o​u​n​t​}​) - * @param {number} count - */ - all: RequiredParams<'count'> - /** - * D​e​p​l​o​y​ ​s​e​l​e​c​t​e​d​ ​c​h​a​n​g​e​s​ ​(​{​c​o​u​n​t​}​) - * @param {number} count - */ - selective: RequiredParams<'count'> - } - } - list: { - pendingList: { - /** - * P​e​n​d​i​n​g​ ​C​h​a​n​g​e​s - */ - title: string - /** - * N​o​ ​p​e​n​d​i​n​g​ ​c​h​a​n​g​e​s - */ - noData: string - /** - * N​o​ ​p​e​n​d​i​n​g​ ​c​h​a​n​g​e​s​ ​f​o​u​n​d - */ - noDataSearch: string - } - deployedList: { - /** - * D​e​p​l​o​y​e​d​ ​A​l​i​a​s​e​s - */ - title: string - /** - * N​o​ ​d​e​p​l​o​y​e​d​ ​a​l​i​a​s​e​s - */ - noData: string - /** - * N​o​ ​d​e​p​l​o​y​e​d​ ​a​l​i​a​s​e​s​ ​f​o​u​n​d - */ - noDataSearch: string - } - headers: { - /** - * I​D - */ - id: string - /** - * A​l​i​a​s​ ​n​a​m​e - */ - name: string - /** - * A​l​i​a​s​ ​k​i​n​d - */ - kind: string - /** - * I​P​v​4​/​6​ ​C​I​D​R​ ​r​a​n​g​e​ ​a​d​d​r​e​s​s - */ - ip: string - /** - * P​o​r​t​s - */ - ports: string - /** - * P​r​o​t​o​c​o​l​s - */ - protocols: string - /** - * S​t​a​t​u​s - */ - status: string - /** - * E​d​i​t - */ - edit: string - /** - * R​u​l​e​s - */ - rules: string - } - status: { - /** - * A​p​p​l​i​e​d - */ - applied: string - /** - * M​o​d​i​f​i​e​d - */ - changed: string - } - tags: { - /** - * A​l​l​ ​d​e​n​i​e​d - */ - allDenied: string - /** - * A​l​l​ ​a​l​l​o​w​e​d - */ - allAllowed: string - } - editMenu: { - /** - * D​i​s​c​a​r​d​ ​c​h​a​n​g​e​s - */ - discardChanges: string - /** - * D​e​l​e​t​e​ ​a​l​i​a​s - */ - 'delete': string - } - } - } - } - createPage: { - formError: { - /** - * C​o​n​f​l​i​c​t​i​n​g​ ​m​e​m​b​e​r​s - */ - allowDenyConflict: string - /** - * M​u​s​t​ ​c​o​n​f​i​g​u​r​e​ ​s​o​m​e​ ​a​l​l​o​w​e​d​ ​u​s​e​r​s​,​ ​g​r​o​u​p​s​ ​o​r​ ​d​e​v​i​c​e​s - */ - allowNotConfigured: string - } - infoBox: { - /** - * - ​ ​ ​ ​ ​ ​ ​ ​ ​S​p​e​c​i​f​y​ ​o​n​e​ ​o​r​ ​m​o​r​e​ ​f​i​e​l​d​s​ ​(​U​s​e​r​s​,​ ​G​r​o​u​p​s​ ​o​r​ ​D​e​v​i​c​e​s​)​ ​t​o​ ​d​e​f​i​n​e​ ​t​h​i​s​ ​r​u​l​e​.​ ​T​h​e​ ​r​u​l​e​ ​w​i​l​l​ ​c​o​n​s​i​d​e​r​ ​a​l​l​ ​i​n​p​u​t​s​ ​p​r​o​v​i​d​e​d​ ​f​o​r​ ​m​a​t​c​h​i​n​g​ ​c​o​n​d​i​t​i​o​n​s​.​ ​L​e​a​v​e​ ​a​n​y​ ​f​i​e​l​d​s​ ​b​l​a​n​k​ ​i​f​ ​n​o​t​ ​n​e​e​d​e​d​. - */ - allowInstructions: string - /** - * - ​ ​ ​ ​ ​ ​ ​ ​ ​S​p​e​c​i​f​y​ ​o​n​e​ ​o​r​ ​m​o​r​e​ ​f​i​e​l​d​s​ ​(​I​P​ ​A​d​d​r​e​s​s​e​s​ ​o​r​ ​P​o​r​t​s​)​ ​t​o​ ​d​e​f​i​n​e​ ​t​h​i​s​ ​r​u​l​e​.​ ​T​h​e​ ​r​u​l​e​ ​w​i​l​l​ ​c​o​n​s​i​d​e​r​ ​a​l​l​ ​i​n​p​u​t​s​ ​p​r​o​v​i​d​e​d​ ​f​o​r​ ​m​a​t​c​h​i​n​g​ ​c​o​n​d​i​t​i​o​n​s​.​ ​L​e​a​v​e​ ​a​n​y​ ​f​i​e​l​d​s​ ​b​l​a​n​k​ ​i​f​ ​n​o​t​ ​n​e​e​d​e​d​. - */ - destinationInstructions: string - } - message: { - /** - * R​u​l​e​ ​c​r​e​a​t​e​d​ ​a​n​d​ ​a​d​d​e​d​ ​t​o​ ​p​e​n​d​i​n​g​ ​c​h​a​n​g​e​s​. - */ - create: string - /** - * R​u​l​e​ ​c​r​e​a​t​i​o​n​ ​f​a​i​l​e​d - */ - createFail: string - } - headers: { - /** - * R​u​l​e - */ - rule: string - /** - * C​r​e​a​t​e​ ​R​u​l​e - */ - createRule: string - /** - * A​l​l​o​w​e​d​ ​U​s​e​r​s​/​G​r​o​u​p​s​/​D​e​v​i​c​e​s - */ - allowed: string - /** - * D​e​n​i​e​d​ ​U​s​e​r​s​/​G​r​o​u​p​s​/​D​e​v​i​c​e​s - */ - denied: string - /** - * D​e​s​t​i​n​a​t​i​o​n - */ - destination: string - } - labels: { - /** - * R​u​l​e​ ​n​a​m​e - */ - name: string - /** - * P​r​i​o​r​i​t​y - */ - priority: string - /** - * S​t​a​t​u​s - */ - status: string - /** - * L​o​c​a​t​i​o​n​s - */ - locations: string - /** - * A​l​l​o​w​ ​a​l​l​ ​u​s​e​r​s - */ - allowAllUsers: string - /** - * I​n​c​l​u​d​e​ ​a​l​l​ ​l​o​c​a​t​i​o​n​s - */ - allowAllNetworks: string - /** - * A​l​l​o​w​ ​a​l​l​ ​n​e​t​w​o​r​k​ ​d​e​v​i​c​e​s - */ - allowAllNetworkDevices: string - /** - * D​e​n​y​ ​a​l​l​ ​u​s​e​r​s - */ - denyAllUsers: string - /** - * D​e​n​y​ ​a​l​l​ ​n​e​t​w​o​r​k​ ​d​e​v​i​c​e​s - */ - denyAllNetworkDevices: string - /** - * U​s​e​r​s - */ - users: string - /** - * G​r​o​u​p​s - */ - groups: string - /** - * N​e​t​w​o​r​k​ ​d​e​v​i​c​e​s - */ - devices: string - /** - * P​r​o​t​o​c​o​l​s - */ - protocols: string - /** - * I​P​v​4​/​6​ ​C​I​D​R​ ​r​a​n​g​e​ ​o​r​ ​a​d​d​r​e​s​s - */ - manualIp: string - /** - * P​o​r​t​s - */ - ports: string - /** - * A​l​i​a​s​e​s - */ - aliases: string - /** - * E​x​p​i​r​a​t​i​o​n​ ​D​a​t​e - */ - expires: string - /** - * M​a​n​u​a​l​ ​I​n​p​u​t - */ - manualInput: string - } - placeholders: { - /** - * A​l​l​ ​p​r​o​t​o​c​o​l​s - */ - allProtocols: string - /** - * A​l​l​ ​I​P​ ​a​d​d​r​e​s​s​e​s - */ - allIps: string - } - } - } - activity: { - /** - * A​c​t​i​v​i​t​y​ ​l​o​g - */ - title: string - modals: { - timeRange: { - /** - * A​c​t​i​v​i​t​y​ ​t​i​m​e - */ - title: string - } - } - list: { - /** - * A​l​l​ ​a​c​t​i​v​i​t​y - */ - allLabel: string - headers: { - /** - * D​a​t​e - */ - date: string - /** - * U​s​e​r - */ - user: string - /** - * I​P - */ - ip: string - /** - * L​o​c​a​t​i​o​n - */ - location: string - /** - * E​v​e​n​t - */ - event: string - /** - * M​o​d​u​l​e - */ - module: string - /** - * D​e​v​i​c​e - */ - device: string - /** - * D​e​s​c​r​i​p​t​i​o​n - */ - description: string - } - noData: { - /** - * N​o​ ​a​c​t​i​v​i​t​i​e​s​ ​p​r​e​s​e​n​t - */ - data: string - /** - * N​o​ ​a​c​t​i​v​i​t​i​e​s​ ​f​o​u​n​d - */ - search: string - } - } - } - enums: { - activityLogEventType: { - /** - * U​s​e​r​ ​l​o​g​i​n - */ - user_login: string - /** - * U​s​e​r​ ​l​o​g​i​n​ ​f​a​i​l​e​d - */ - user_login_failed: string - /** - * U​s​e​r​ ​M​F​A​ ​l​o​g​i​n - */ - user_mfa_login: string - /** - * U​s​e​r​ ​M​F​A​ ​l​o​g​i​n​ ​f​a​i​l​e​d - */ - user_mfa_login_failed: string - /** - * R​e​c​o​v​e​r​y​ ​c​o​d​e​ ​u​s​e​d - */ - recovery_code_used: string - /** - * U​s​e​r​ ​l​o​g​o​u​t - */ - user_logout: string - /** - * U​s​e​r​ ​a​d​d​e​d - */ - user_added: string - /** - * U​s​e​r​ ​r​e​m​o​v​e​d - */ - user_removed: string - /** - * U​s​e​r​ ​m​o​d​i​f​i​e​d - */ - user_modified: string - /** - * U​s​e​r​ ​g​r​o​u​p​s​ ​m​o​d​i​f​i​e​d - */ - user_groups_modified: string - /** - * M​F​A​ ​e​n​a​b​l​e​d - */ - mfa_enabled: string - /** - * M​F​A​ ​d​i​s​a​b​l​e​d - */ - mfa_disabled: string - /** - * U​s​e​r​ ​M​F​A​ ​d​i​s​a​b​l​e​d - */ - user_mfa_disabled: string - /** - * M​F​A​ ​T​O​T​P​ ​e​n​a​b​l​e​d - */ - mfa_totp_enabled: string - /** - * M​F​A​ ​T​O​T​P​ ​d​i​s​a​b​l​e​d - */ - mfa_totp_disabled: string - /** - * M​F​A​ ​e​m​a​i​l​ ​e​n​a​b​l​e​d - */ - mfa_email_enabled: string - /** - * M​F​A​ ​e​m​a​i​l​ ​d​i​s​a​b​l​e​d - */ - mfa_email_disabled: string - /** - * M​F​A​ ​s​e​c​u​r​i​t​y​ ​k​e​y​ ​a​d​d​e​d - */ - mfa_security_key_added: string - /** - * M​F​A​ ​s​e​c​u​r​i​t​y​ ​k​e​y​ ​r​e​m​o​v​e​d - */ - mfa_security_key_removed: string - /** - * D​e​v​i​c​e​ ​a​d​d​e​d - */ - device_added: string - /** - * D​e​v​i​c​e​ ​r​e​m​o​v​e​d - */ - device_removed: string - /** - * D​e​v​i​c​e​ ​m​o​d​i​f​i​e​d - */ - device_modified: string - /** - * N​e​t​w​o​r​k​ ​d​e​v​i​c​e​ ​a​d​d​e​d - */ - network_device_added: string - /** - * N​e​t​w​o​r​k​ ​d​e​v​i​c​e​ ​r​e​m​o​v​e​d - */ - network_device_removed: string - /** - * N​e​t​w​o​r​k​ ​d​e​v​i​c​e​ ​m​o​d​i​f​i​e​d - */ - network_device_modified: string - /** - * A​c​t​i​v​i​t​y​ ​l​o​g​ ​s​t​r​e​a​m​ ​c​r​e​a​t​e​d - */ - activity_log_stream_created: string - /** - * A​c​t​i​v​i​t​y​ ​l​o​g​ ​s​t​r​e​a​m​ ​m​o​d​i​f​i​e​d - */ - activity_log_stream_modified: string - /** - * A​c​t​i​v​i​t​y​ ​l​o​g​ ​s​t​r​e​a​m​ ​r​e​m​o​v​e​d - */ - activity_log_stream_removed: string - /** - * V​P​N​ ​c​l​i​e​n​t​ ​c​o​n​n​e​c​t​e​d - */ - vpn_client_connected: string - /** - * V​P​N​ ​c​l​i​e​n​t​ ​d​i​s​c​o​n​n​e​c​t​e​d - */ - vpn_client_disconnected: string - /** - * V​P​N​ ​c​l​i​e​n​t​ ​c​o​n​n​e​c​t​e​d​ ​t​o​ ​M​F​A​ ​l​o​c​a​t​i​o​n - */ - vpn_client_connected_mfa: string - /** - * V​P​N​ ​c​l​i​e​n​t​ ​d​i​s​c​o​n​n​e​c​t​e​d​ ​f​r​o​m​ ​M​F​A​ ​l​o​c​a​t​i​o​n - */ - vpn_client_disconnected_mfa: string - /** - * V​P​N​ ​c​l​i​e​n​t​ ​f​a​i​l​e​d​ ​M​F​A​ ​a​u​t​h​e​n​t​i​c​a​t​i​o​n - */ - vpn_client_mfa_failed: string - /** - * E​n​r​o​l​l​m​e​n​t​ ​t​o​k​e​n​ ​a​d​d​e​d - */ - enrollment_token_added: string - /** - * E​n​r​o​l​l​m​e​n​t​ ​s​t​a​r​t​e​d - */ - enrollment_started: string - /** - * D​e​v​i​c​e​ ​a​d​d​e​d - */ - enrollment_device_added: string - /** - * E​n​r​o​l​l​m​e​n​t​ ​c​o​m​p​l​e​t​e​d - */ - enrollment_completed: string - /** - * P​a​s​s​w​o​r​d​ ​r​e​s​e​t​ ​r​e​q​u​e​s​t​e​d - */ - password_reset_requested: string - /** - * P​a​s​s​w​o​r​d​ ​r​e​s​e​t​ ​s​t​a​r​t​e​d - */ - password_reset_started: string - /** - * P​a​s​s​w​o​r​d​ ​r​e​s​e​t​ ​c​o​m​p​l​e​t​e​d - */ - password_reset_completed: string - /** - * V​P​N​ ​l​o​c​a​t​i​o​n​ ​a​d​d​e​d - */ - vpn_location_added: string - /** - * V​P​N​ ​l​o​c​a​t​i​o​n​ ​r​e​m​o​v​e​d - */ - vpn_location_removed: string - /** - * V​P​N​ ​l​o​c​a​t​i​o​n​ ​m​o​d​i​f​i​e​d - */ - vpn_location_modified: string - /** - * A​P​I​ ​t​o​k​e​n​ ​a​d​d​e​d - */ - api_token_added: string - /** - * A​P​I​ ​t​o​k​e​n​ ​r​e​m​o​v​e​d - */ - api_token_removed: string - /** - * A​P​I​ ​t​o​k​e​n​ ​r​e​n​a​m​e​d - */ - api_token_renamed: string - /** - * O​p​e​n​I​D​ ​a​p​p​ ​a​d​d​e​d - */ - open_id_app_added: string - /** - * O​p​e​n​I​D​ ​a​p​p​ ​r​e​m​o​v​e​d - */ - open_id_app_removed: string - /** - * O​p​e​n​I​D​ ​a​p​p​ ​m​o​d​i​f​i​e​d - */ - open_id_app_modified: string - /** - * O​p​e​n​I​D​ ​a​p​p​ ​s​t​a​t​e​ ​c​h​a​n​g​e​d - */ - open_id_app_state_changed: string - /** - * O​p​e​n​I​D​ ​p​r​o​v​i​d​e​r​ ​r​e​m​o​v​e​d - */ - open_id_provider_removed: string - /** - * O​p​e​n​I​D​ ​p​r​o​v​i​d​e​r​ ​m​o​d​i​f​i​e​d - */ - open_id_provider_modified: string - /** - * S​e​t​t​i​n​g​s​ ​u​p​d​a​t​e​d - */ - settings_updated: string - /** - * S​e​t​t​i​n​g​s​ ​p​a​r​t​i​a​l​l​y​ ​u​p​d​a​t​e​d - */ - settings_updated_partial: string - /** - * D​e​f​a​u​l​t​ ​b​r​a​n​d​i​n​g​ ​r​e​s​t​o​r​e​d - */ - settings_default_branding_restored: string - /** - * G​r​o​u​p​s​ ​b​u​l​k​ ​a​s​s​i​g​n​e​d - */ - groups_bulk_assigned: string - /** - * G​r​o​u​p​ ​a​d​d​e​d - */ - group_added: string - /** - * G​r​o​u​p​ ​m​o​d​i​f​i​e​d - */ - group_modified: string - /** - * G​r​o​u​p​ ​r​e​m​o​v​e​d - */ - group_removed: string - /** - * G​r​o​u​p​ ​m​e​m​b​e​r​ ​a​d​d​e​d - */ - group_member_added: string - /** - * G​r​o​u​p​ ​m​e​m​b​e​r​ ​r​e​m​o​v​e​d - */ - group_member_removed: string - /** - * G​r​o​u​p​ ​m​e​m​b​e​r​s​ ​m​o​d​i​f​i​e​d - */ - group_members_modified: string - /** - * W​e​b​h​o​o​k​ ​a​d​d​e​d - */ - web_hook_added: string - /** - * W​e​b​h​o​o​k​ ​m​o​d​i​f​i​e​d - */ - web_hook_modified: string - /** - * W​e​b​h​o​o​k​ ​r​e​m​o​v​e​d - */ - web_hook_removed: string - /** - * W​e​b​h​o​o​k​ ​s​t​a​t​e​ ​c​h​a​n​g​e​d - */ - web_hook_state_changed: string - /** - * A​u​t​h​e​n​t​i​c​a​t​i​o​n​ ​k​e​y​ ​a​d​d​e​d - */ - authentication_key_added: string - /** - * A​u​t​h​e​n​t​i​c​a​t​i​o​n​ ​k​e​y​ ​r​e​m​o​v​e​d - */ - authentication_key_removed: string - /** - * A​u​t​h​e​n​t​i​c​a​t​i​o​n​ ​k​e​y​ ​r​e​n​a​m​e​d - */ - authentication_key_renamed: string - /** - * P​a​s​s​w​o​r​d​ ​c​h​a​n​g​e​d - */ - password_changed: string - /** - * P​a​s​s​w​o​r​d​ ​c​h​a​n​g​e​d​ ​b​y​ ​a​d​m​i​n - */ - password_changed_by_admin: string - /** - * P​a​s​s​w​o​r​d​ ​r​e​s​e​t - */ - password_reset: string - /** - * C​l​i​e​n​t​ ​c​o​n​f​i​g​u​r​a​t​i​o​n​ ​t​o​k​e​n​ ​a​d​d​e​d - */ - client_configuration_token_added: string - /** - * U​s​e​r​ ​S​N​A​T​ ​b​i​n​d​i​n​g​ ​a​d​d​e​d - */ - user_snat_binding_added: string - /** - * U​s​e​r​ ​S​N​A​T​ ​b​i​n​d​i​n​g​ ​m​o​d​i​f​i​e​d - */ - user_snat_binding_modified: string - /** - * U​s​e​r​ ​S​N​A​T​ ​b​i​n​d​i​n​g​ ​r​e​m​o​v​e​d - */ - user_snat_binding_removed: string - } - activityLogModule: { - /** - * D​e​f​g​u​a​r​d - */ - defguard: string - /** - * C​l​i​e​n​t - */ - client: string - /** - * E​n​r​o​l​l​m​e​n​t - */ - enrollment: string - /** - * V​P​N - */ - vpn: string - } - } -} - -export type TranslationFunctions = { - common: { - conditions: { - /** - * or - */ - or: () => LocalizedString - /** - * and - */ - and: () => LocalizedString - /** - * equal - */ - equal: () => LocalizedString - } - controls: { - /** - * Time range - */ - timeRange: () => LocalizedString - /** - * Add new - */ - addNew: () => LocalizedString - /** - * Add - */ - add: () => LocalizedString - /** - * Accept - */ - accept: () => LocalizedString - /** - * Next - */ - next: () => LocalizedString - /** - * Back - */ - back: () => LocalizedString - /** - * Cancel - */ - cancel: () => LocalizedString - /** - * Confirm - */ - confirm: () => LocalizedString - /** - * Submit - */ - submit: () => LocalizedString - /** - * Close - */ - close: () => LocalizedString - /** - * Select - */ - select: () => LocalizedString - /** - * Finish - */ - finish: () => LocalizedString - /** - * Save changes - */ - saveChanges: () => LocalizedString - /** - * Save - */ - save: () => LocalizedString - /** - * Restore default - */ - RestoreDefault: () => LocalizedString - /** - * Delete - */ - 'delete': () => LocalizedString - /** - * Rename - */ - rename: () => LocalizedString - /** - * Copy - */ - copy: () => LocalizedString - /** - * Edit - */ - edit: () => LocalizedString - /** - * Dismiss - */ - dismiss: () => LocalizedString - /** - * Show - */ - show: () => LocalizedString - /** - * Enable - */ - enable: () => LocalizedString - /** - * Enabled - */ - enabled: () => LocalizedString - /** - * Disable - */ - disable: () => LocalizedString - /** - * Disabled - */ - disabled: () => LocalizedString - /** - * Select all - */ - selectAll: () => LocalizedString - /** - * Clear - */ - clear: () => LocalizedString - /** - * Clear all - */ - clearAll: () => LocalizedString - /** - * Filter - */ - filter: () => LocalizedString - /** - * Filters - */ - filters: () => LocalizedString - } - /** - * Key - */ - key: () => LocalizedString - /** - * Name - */ - name: () => LocalizedString - /** - * No data - */ - noData: () => LocalizedString - /** - * Unavailable - */ - unavailable: () => LocalizedString - /** - * Not set - */ - notSet: () => LocalizedString - /** - * Search - */ - search: () => LocalizedString - /** - * Time - */ - time: () => LocalizedString - /** - * From - */ - from: () => LocalizedString - /** - * Until - */ - until: () => LocalizedString - } - messages: { - /** - * Error has occurred. - */ - error: () => LocalizedString - /** - * Operation succeeded - */ - success: () => LocalizedString - /** - * Failed to get application version. - */ - errorVersion: () => LocalizedString - /** - * Context is not secure. - */ - insecureContext: () => LocalizedString - /** - * Details: - */ - details: () => LocalizedString - clipboard: { - /** - * Clipboard is not accessible. - */ - error: () => LocalizedString - /** - * Content copied to clipboard. - */ - success: () => LocalizedString - } - } - modals: { - outdatedComponentsModal: { - /** - * Version mismatch - */ - title: () => LocalizedString - /** - * Defguard detected unsupported version in some components. - */ - subtitle: () => LocalizedString - content: { - /** - * Incompatible components: - */ - title: () => LocalizedString - /** - * Unknown version - */ - unknownVersion: () => LocalizedString - /** - * Unknown hostname - */ - unknownHostname: () => LocalizedString - } - } - upgradeLicenseModal: { - enterprise: { - /** - * Upgrade to Enterprise - */ - title: () => LocalizedString - /** - * This functionality is an **enterprise feature** and you've exceeded the user, device or network limits to use it. In order to use this feature, purchase an enterprise license or upgrade your existing one. - */ - subTitle: () => LocalizedString - } - limit: { - /** - * Upgrade - */ - title: () => LocalizedString - /** - * - You have **reached the limit** of this functionality. To **[ manage more locations/users/devices ]** purchase of the Enterprise license is required. - - */ - subTitle: () => LocalizedString - } - /** - * - You can find out more about features like: - - Real time and automatic client synchronization - - External SSO - - Controlling VPN clients behavior - - Full enterprise feature list: [https://docs.defguard.net/enterprise/enterprise-features](https://docs.defguard.net/enterprise/enterprise-features)
- Licensing information: [https://docs.defguard.net/enterprise/license](https://docs.defguard.net/enterprise/license) - - */ - content: () => LocalizedString - controls: { - /** - * Maybe later - */ - cancel: () => LocalizedString - /** - * See all Enterprise plans - */ - confirm: () => LocalizedString - } - } - standaloneDeviceEnrollmentModal: { - /** - * Network device token - */ - title: () => LocalizedString - toasters: { - /** - * Token generation failed. - */ - error: () => LocalizedString - } - } - standaloneDeviceConfigModal: { - /** - * Network device config - */ - title: () => LocalizedString - /** - * Config - */ - cardTitle: () => LocalizedString - toasters: { - getConfig: { - /** - * Failed to get device config. - */ - error: () => LocalizedString - } - } - } - editStandaloneModal: { - /** - * Edit network device - */ - title: () => LocalizedString - toasts: { - /** - * Device modified - */ - success: () => LocalizedString - /** - * Modifying the device failed - */ - failure: () => LocalizedString - } - } - deleteStandaloneDevice: { - /** - * Delete network device - */ - title: () => LocalizedString - /** - * Device {name} will be deleted. - */ - content: (arg: { name: string }) => LocalizedString - messages: { - /** - * Device deleted - */ - success: () => LocalizedString - /** - * Failed to remove device. - */ - error: () => LocalizedString - } - } - addStandaloneDevice: { - toasts: { - /** - * Device added - */ - deviceCreated: () => LocalizedString - /** - * Device could not be added. - */ - creationFailed: () => LocalizedString - } - infoBox: { - /** - * Here you can add definitions or generate configurations for devices that can connect to your VPN. Only locations without Multi-Factor Authentication are available here, as MFA is only supported in Defguard Desktop Client for now. - */ - setup: () => LocalizedString - } - form: { - /** - * Add Device - */ - submit: () => LocalizedString - labels: { - /** - * Device Name - */ - deviceName: () => LocalizedString - /** - * Location - */ - location: () => LocalizedString - /** - * Assigned IP Address - */ - assignedAddress: () => LocalizedString - /** - * Description - */ - description: () => LocalizedString - generation: { - /** - * Generate key pair - */ - auto: () => LocalizedString - /** - * Use my own public key - */ - manual: () => LocalizedString - } - /** - * Provide Your Public Key - */ - publicKey: () => LocalizedString - } - } - steps: { - method: { - /** - * Choose a preferred method - */ - title: () => LocalizedString - cards: { - cli: { - /** - * Defguard Command Line Client - */ - title: () => LocalizedString - /** - * When using defguard-cli your device will be automatically configured. - */ - subtitle: () => LocalizedString - /** - * Defguard CLI download and documentation - */ - docs: () => LocalizedString - } - manual: { - /** - * Manual WireGuard Client - */ - title: () => LocalizedString - /** - * If your device does not support our CLI binaries you can always generate a WireGuard configuration file and configure it manually - but any updates to the VPN location configuration will require manual changes in device configuration. - */ - subtitle: () => LocalizedString - } - } - } - manual: { - /** - * Add new VPN device using WireGuard Client - */ - title: () => LocalizedString - finish: { - /** - * Download the provided configuration file to your device and import it into your VPN client to complete the setup. - */ - messageTop: () => LocalizedString - /** - * Use provided configuration file below by scanning QR code or importing it as file on your device's WireGuard app. - */ - ctaInstruction: () => LocalizedString - /** - * - Please remember that Defguard **doesn't store private keys**. We will securely generate the public and private key pair in your browser, but only store the public key in Defguard database. Please download the configuration generated with the private key for the device, as it will not be accessible later. - - */ - warningMessage: () => LocalizedString - actionCard: { - /** - * Config - */ - title: () => LocalizedString - } - } - } - cli: { - /** - * Add device using Defguard Command Line Client - */ - title: () => LocalizedString - finish: { - /** - * First download Defguard command line client binary and install it on your server. - */ - topMessage: () => LocalizedString - /** - * Download Defguard CLI Client - */ - downloadButton: () => LocalizedString - /** - * Copy and paste this command in your terminal on the device - */ - commandCopy: () => LocalizedString - } - setup: { - /** - * Here you can add definitions or generate configurations for devices that can connect to your VPN. Only locations without Multi-Factor Authentication are available here, as MFA is only supported in Defguard Desktop Client for now. - */ - stepMessage: () => LocalizedString - form: { - /** - * Add Device - */ - submit: () => LocalizedString - } - } - } - } - } - updatesNotificationToaster: { - /** - * New version available {version} - */ - title: (arg: { version: string }) => LocalizedString - controls: { - /** - * See what's new - */ - more: () => LocalizedString - } - } - enterpriseUpgradeToaster: { - /** - * You've reached the enterprise functionality limit. - */ - title: () => LocalizedString - /** - * You've exceeded the limit of your current Defguard plan and the enterprise - features will be disabled. Purchase an enterprise license or upgrade your - existing one to continue using these features. - */ - message: () => LocalizedString - /** - * See all enterprise plans - */ - link: () => LocalizedString - } - updatesNotification: { - header: { - /** - * Update Available - */ - title: () => LocalizedString - /** - * new version {version} - */ - newVersion: (arg: { version: string }) => LocalizedString - /** - * critical update - */ - criticalBadge: () => LocalizedString - } - controls: { - /** - * Visit release page - */ - visitRelease: () => LocalizedString - } - } - addGroup: { - /** - * Add group - */ - title: () => LocalizedString - /** - * Select all users - */ - selectAll: () => LocalizedString - /** - * Group name - */ - groupName: () => LocalizedString - /** - * Filter/Search - */ - searchPlaceholder: () => LocalizedString - /** - * Create group - */ - submit: () => LocalizedString - /** - * Group settings - */ - groupSettings: () => LocalizedString - /** - * Admin group - */ - adminGroup: () => LocalizedString - } - editGroup: { - /** - * Edit group - */ - title: () => LocalizedString - /** - * Select all users - */ - selectAll: () => LocalizedString - /** - * Group name - */ - groupName: () => LocalizedString - /** - * Filter/Search - */ - searchPlaceholder: () => LocalizedString - /** - * Update group - */ - submit: () => LocalizedString - /** - * Group settings - */ - groupSettings: () => LocalizedString - /** - * Admin group - */ - adminGroup: () => LocalizedString - } - deleteGroup: { - /** - * Delete group {name} - */ - title: (arg: { name: string }) => LocalizedString - /** - * This action will permanently delete this group. - */ - subTitle: () => LocalizedString - /** - * This group is currently assigned to following VPN Locations: - */ - locationListHeader: () => LocalizedString - /** - * If this is the only allowed group for a given location, the location will become accessible to all users. - */ - locationListFooter: () => LocalizedString - /** - * Delete group - */ - submit: () => LocalizedString - /** - * Cancel - */ - cancel: () => LocalizedString - } - deviceConfig: { - /** - * Device VPN configurations - */ - title: () => LocalizedString - } - changePasswordSelf: { - /** - * Change password - */ - title: () => LocalizedString - messages: { - /** - * Password has been changed - */ - success: () => LocalizedString - /** - * Failed to changed password - */ - error: () => LocalizedString - } - form: { - labels: { - /** - * New password - */ - newPassword: () => LocalizedString - /** - * Current password - */ - oldPassword: () => LocalizedString - /** - * Confirm new password - */ - repeat: () => LocalizedString - } - } - controls: { - /** - * Change password - */ - submit: () => LocalizedString - /** - * Cancel - */ - cancel: () => LocalizedString - } - } - disableMfa: { - /** - * Disable MFA - */ - title: () => LocalizedString - /** - * Do you want to disable MFA for user {username}? - */ - message: (arg: { username: string }) => LocalizedString - messages: { - /** - * MFA for user {username} has been disabled - */ - success: (arg: { username: string }) => LocalizedString - /** - * Failed to disable MFA for user {username} - */ - error: (arg: { username: string }) => LocalizedString - } - controls: { - /** - * Disable MFA - */ - submit: () => LocalizedString - /** - * Cancel - */ - cancel: () => LocalizedString - } - } - startEnrollment: { - /** - * Start enrollment - */ - title: () => LocalizedString - /** - * Desktop activation - */ - desktopTitle: () => LocalizedString - messages: { - /** - * User enrollment started - */ - success: () => LocalizedString - /** - * Desktop configuration started - */ - successDesktop: () => LocalizedString - /** - * Failed to start user enrollment - */ - error: () => LocalizedString - /** - * Failed to start desktop activation - */ - errorDesktop: () => LocalizedString - } - messageBox: { - /** - * You can share the following URL and token with the user to configure their Defguard desktop or mobile client. - */ - clientForm: () => LocalizedString - /** - * You can share this QR code for easy Defguard mobile client configuration. - */ - clientQr: () => LocalizedString - } - form: { - email: { - /** - * Email - */ - label: () => LocalizedString - } - mode: { - options: { - /** - * Send token by email - */ - email: () => LocalizedString - /** - * Deliver token yourself - */ - manual: () => LocalizedString - } - } - /** - * Start enrollment - */ - submit: () => LocalizedString - /** - * Activate desktop - */ - submitDesktop: () => LocalizedString - /** - * Configure SMTP to send token by email. Go to Settings -> SMTP. - */ - smtpDisabled: () => LocalizedString - } - tokenCard: { - /** - * Activation token - */ - title: () => LocalizedString - } - urlCard: { - /** - * Defguard Instance URL - */ - title: () => LocalizedString - } - } - deleteNetwork: { - /** - * Delete {name} location - */ - title: (arg: { name: string }) => LocalizedString - /** - * This action will permanently delete this location. - */ - subTitle: () => LocalizedString - /** - * Delete location - */ - submit: () => LocalizedString - /** - * Cancel - */ - cancel: () => LocalizedString - } - changeWebhook: { - messages: { - /** - * Webhook changed. - */ - success: () => LocalizedString - } - } - manageWebAuthNKeys: { - /** - * Security keys - */ - title: () => LocalizedString - messages: { - /** - * WebAuthN key has been deleted. - */ - deleted: () => LocalizedString - /** - * Key is already registered - */ - duplicateKeyError: () => LocalizedString - } - /** - * -

- Security keys can be used as your second factor of authentication - instead of a verification code. Learn more about configuring a - security key. -

- - */ - infoMessage: () => LocalizedString - form: { - messages: { - /** - * Security key added. - */ - success: () => LocalizedString - } - fields: { - name: { - /** - * New key name - */ - label: () => LocalizedString - } - } - controls: { - /** - * Add new Key - */ - submit: () => LocalizedString - } - } - } - recoveryCodes: { - /** - * Recovery codes - */ - title: () => LocalizedString - /** - * I have saved my codes - */ - submit: () => LocalizedString - messages: { - /** - * Codes copied. - */ - copied: () => LocalizedString - } - /** - * -

- Treat your recovery codes with the same level of attention as you - would your password! We recommend saving them with a password manager - such as Lastpass, bitwarden or Keeper. -

- - */ - infoMessage: () => LocalizedString - } - registerTOTP: { - /** - * Authenticator App Setup - */ - title: () => LocalizedString - /** - * -

- To setup your MFA, scan this QR code with your authenticator app, then - enter the code in the field below: -

- - */ - infoMessage: () => LocalizedString - messages: { - /** - * TOTP path copied. - */ - totpCopied: () => LocalizedString - /** - * TOTP Enabled - */ - success: () => LocalizedString - } - /** - * Copy TOTP path - */ - copyPath: () => LocalizedString - form: { - fields: { - code: { - /** - * Authenticator code - */ - label: () => LocalizedString - /** - * Code is invalid - */ - error: () => LocalizedString - } - } - controls: { - /** - * Verify code - */ - submit: () => LocalizedString - } - } - } - registerEmailMFA: { - /** - * Email MFA Setup - */ - title: () => LocalizedString - /** - * -

- To setup your MFA enter the code that was sent to your account email: {email} -

- - */ - infoMessage: (arg: { email: string }) => LocalizedString - messages: { - /** - * Email MFA Enabled - */ - success: () => LocalizedString - /** - * Verification code resent - */ - resend: () => LocalizedString - } - form: { - fields: { - code: { - /** - * Email code - */ - label: () => LocalizedString - /** - * Code is invalid - */ - error: () => LocalizedString - } - } - controls: { - /** - * Verify code - */ - submit: () => LocalizedString - /** - * Resend email - */ - resend: () => LocalizedString - } - } - } - editDevice: { - /** - * Edit device - */ - title: () => LocalizedString - messages: { - /** - * Device has been updated. - */ - success: () => LocalizedString - } - form: { - fields: { - name: { - /** - * Device Name - */ - label: () => LocalizedString - } - publicKey: { - /** - * Device Public Key (WireGuard) - */ - label: () => LocalizedString - } - } - controls: { - /** - * Edit device - */ - submit: () => LocalizedString - } - } - } - deleteDevice: { - /** - * Delete device - */ - title: () => LocalizedString - /** - * Do you want to delete {deviceName} device ? - */ - message: (arg: { deviceName: unknown }) => LocalizedString - /** - * Delete device - */ - submit: () => LocalizedString - messages: { - /** - * Device has been deleted. - */ - success: () => LocalizedString - } - } - keyDetails: { - /** - * YubiKey details - */ - title: () => LocalizedString - /** - * Download all keys - */ - downloadAll: () => LocalizedString - } - deleteUser: { - /** - * Delete account - */ - title: () => LocalizedString - controls: { - /** - * Delete account - */ - submit: () => LocalizedString - } - /** - * Do you want to delete {username} account permanently? - */ - message: (arg: { username: string }) => LocalizedString - messages: { - /** - * {username} deleted. - */ - success: (arg: { username: string }) => LocalizedString - } - } - disableUser: { - /** - * Disable account - */ - title: () => LocalizedString - controls: { - /** - * Disable account - */ - submit: () => LocalizedString - } - /** - * Do you want to disable {username} account? - */ - message: (arg: { username: string }) => LocalizedString - messages: { - /** - * {username} disabled. - */ - success: (arg: { username: string }) => LocalizedString - } - } - enableUser: { - /** - * Enable account - */ - title: () => LocalizedString - controls: { - /** - * Enable account - */ - submit: () => LocalizedString - } - /** - * Do you want to enable {username} account? - */ - message: (arg: { username: string }) => LocalizedString - messages: { - /** - * {username} enabled. - */ - success: (arg: { username: string }) => LocalizedString - } - } - deleteProvisioner: { - /** - * Delete provisioner - */ - title: () => LocalizedString - controls: { - /** - * Delete provisioner - */ - submit: () => LocalizedString - } - /** - * Do you want to delete {id} provisioner? - */ - message: (arg: { id: string }) => LocalizedString - messages: { - /** - * {provisioner} deleted. - */ - success: (arg: { provisioner: string }) => LocalizedString - } - } - changeUserPassword: { - messages: { - /** - * Password changed. - */ - success: () => LocalizedString - } - /** - * Change user password - */ - title: () => LocalizedString - form: { - controls: { - /** - * Save new password - */ - submit: () => LocalizedString - } - fields: { - newPassword: { - /** - * New password - */ - label: () => LocalizedString - } - confirmPassword: { - /** - * Repeat password - */ - label: () => LocalizedString - } - } - } - } - provisionKeys: { - /** - * Yubikey provisioning: - */ - title: () => LocalizedString - /** - * Please be advised that this operation wll wipe openpgp application on yubikey and reconfigure it. - */ - warning: () => LocalizedString - /** - * The selected provisioner must have a clean YubiKey - plugged in be provisioned. To clean a used YubiKey - gpg --card-edit before provisioning. - */ - infoBox: () => LocalizedString - /** - * Select one of the following provisioners to provision a YubiKey: - */ - selectionLabel: () => LocalizedString - noData: { - /** - * No workers found, waiting... - */ - workers: () => LocalizedString - } - controls: { - /** - * Provision YubiKey - */ - submit: () => LocalizedString - } - messages: { - /** - * Keys provisioned - */ - success: () => LocalizedString - /** - * Error while getting worker status. - */ - errorStatus: () => LocalizedString - } - } - addUser: { - /** - * Add new user - */ - title: () => LocalizedString - messages: { - /** - * User added - */ - userAdded: () => LocalizedString - } - form: { - /** - * Add user - */ - submit: () => LocalizedString - error: { - /** - * Email already taken - */ - emailReserved: () => LocalizedString - } - fields: { - username: { - /** - * login - */ - placeholder: () => LocalizedString - /** - * Login - */ - label: () => LocalizedString - } - password: { - /** - * Password - */ - placeholder: () => LocalizedString - /** - * Password - */ - label: () => LocalizedString - } - email: { - /** - * User e-mail - */ - placeholder: () => LocalizedString - /** - * User e-mail - */ - label: () => LocalizedString - } - firstName: { - /** - * First name - */ - placeholder: () => LocalizedString - /** - * First name - */ - label: () => LocalizedString - } - lastName: { - /** - * Last name - */ - placeholder: () => LocalizedString - /** - * Last name - */ - label: () => LocalizedString - } - phone: { - /** - * Phone - */ - placeholder: () => LocalizedString - /** - * Phone - */ - label: () => LocalizedString - } - enableEnrollment: { - /** - * Use user self-enrollment process - */ - label: () => LocalizedString - /** - * more information here - */ - link: () => LocalizedString - } - } - } - } - webhookModal: { - title: { - /** - * Add webhook. - */ - addWebhook: () => LocalizedString - /** - * Edit webhook - */ - editWebhook: () => LocalizedString - } - messages: { - /** - * Client ID copied. - */ - clientIdCopy: () => LocalizedString - /** - * Client secret copied. - */ - clientSecretCopy: () => LocalizedString - } - form: { - /** - * Trigger events: - */ - triggers: () => LocalizedString - messages: { - /** - * Webhook created. - */ - successAdd: () => LocalizedString - /** - * Webhook modified. - */ - successModify: () => LocalizedString - } - error: { - /** - * URL is required. - */ - urlRequired: () => LocalizedString - /** - * Must be a valid URL. - */ - validUrl: () => LocalizedString - /** - * Must have at least one trigger. - */ - scopeValidation: () => LocalizedString - /** - * Token is required. - */ - tokenRequired: () => LocalizedString - } - fields: { - description: { - /** - * Description - */ - label: () => LocalizedString - /** - * Webhook to create gmail account on new user - */ - placeholder: () => LocalizedString - } - token: { - /** - * Secret token - */ - label: () => LocalizedString - /** - * Authorization token - */ - placeholder: () => LocalizedString - } - url: { - /** - * Webhook URL - */ - label: () => LocalizedString - /** - * https://example.com/webhook - */ - placeholder: () => LocalizedString - } - userCreated: { - /** - * New user Created - */ - label: () => LocalizedString - } - userDeleted: { - /** - * User deleted - */ - label: () => LocalizedString - } - userModified: { - /** - * User modified - */ - label: () => LocalizedString - } - hwkeyProvision: { - /** - * User Yubikey provision - */ - label: () => LocalizedString - } - } - } - } - deleteWebhook: { - /** - * Delete webhook - */ - title: () => LocalizedString - /** - * Do you want to delete {name} webhook ? - */ - message: (arg: { name: string }) => LocalizedString - /** - * Delete - */ - submit: () => LocalizedString - messages: { - /** - * Webhook deleted. - */ - success: () => LocalizedString - } - } - } - addDevicePage: { - /** - * Add device - */ - title: () => LocalizedString - helpers: { - /** - * You can add a device using this wizard. Opt for our native application "defguard" or any other WireGuard client. If you're unsure, we recommend using defguard for simplicity. - */ - setupOpt: () => LocalizedString - /** - * Please download defguard desktop client here and then follow this guide. - */ - client: () => LocalizedString - } - messages: { - /** - * Device added - */ - deviceAdded: () => LocalizedString - } - steps: { - setupMethod: { - /** - * Choose Your Connection Method - */ - title: () => LocalizedString - /** - * You can add a device using this wizard. To proceed, you'll need to install the defguard Client on the device you're adding. You can also use any standard WireGuard® client, but for the best experience and ease of setup, we recommend using our native defguard Client. - */ - message: () => LocalizedString - methods: { - client: { - /** - * Remote Device Activation - */ - title: () => LocalizedString - /** - * Use the Defguard Client to set up your device. Easily configure it with a single token or by scanning a QR code. - */ - description: () => LocalizedString - } - wg: { - /** - * Manual WireGuard Client - */ - title: () => LocalizedString - /** - * For advanced users, get a unique config via download or QR code. Download any WireGuard® client and take control of your VPN setup. - */ - description: () => LocalizedString - } - } - } - client: { - /** - * Client Activation - */ - title: () => LocalizedString - /** - * If you want to configure your Defguard desktop client, please install the client (links below), open it and just press the One-Click Desktop Configuration button - */ - desktopDeepLinkHelp: () => LocalizedString - /** - * If you are having trouble with the One-Click configuration you can do it manually by clicking *Add Instance* in the desktop client, and entering the following URL and Token: - */ - message: () => LocalizedString - /** - * Scan the QR code with your installed Defguard app. If you haven't installed it yet, use your device's app store or the link below. - */ - qrDescription: () => LocalizedString - /** - * If you want to configure your Mobile Defguard Client, please just scan this QR code in the app: - */ - qrHelp: () => LocalizedString - /** - * Download for Desktop - */ - desktopDownload: () => LocalizedString - /** - * Token copied to clipboard - */ - tokenCopy: () => LocalizedString - /** - * Failed to prepare client setup - */ - tokenFailure: () => LocalizedString - labels: { - /** - * Defguard Instance Token (new) - */ - mergedToken: () => LocalizedString - /** - * Authentication Token - */ - token: () => LocalizedString - /** - * URL - */ - url: () => LocalizedString - } - } - configDevice: { - /** - * Configure device - */ - title: () => LocalizedString - messages: { - /** - * Configuration has been copied to the clipboard - */ - copyConfig: () => LocalizedString - } - helpers: { - /** - * -

- Please be advised that you have to download the configuration now, - since we do not store your private key. After this - page is closed, you will not be able to get your - full configuration file (with private keys, only blank template). -

- - */ - warningAutoMode: () => LocalizedString - /** - * -

- Please be advised that configuration provided here does not include private key and uses public key to fill it's place you will need to replace it on your own for configuration to work properly. -

- - */ - warningManualMode: () => LocalizedString - /** - * You don't have access to any network. - */ - warningNoNetworks: () => LocalizedString - /** - * -

- You can setup your device faster with wireguard application by scanning this QR code. -

- */ - qrHelper: () => LocalizedString - } - /** - * Use provided configuration file below by scanning QR Code or importing it as file on your devices WireGuard instance. - */ - qrInfo: () => LocalizedString - /** - * Device Name - */ - inputNameLabel: () => LocalizedString - /** - * WireGuard Config File - */ - qrLabel: () => LocalizedString - } - setupDevice: { - /** - * Create VPN device - */ - title: () => LocalizedString - /** - * -

- You need to configure WireGuard® VPN on your device, please visit  - documentation if you don't know how to do it. -

- - */ - infoMessage: (arg: { addDevicesDocs: string }) => LocalizedString - options: { - /** - * Generate key pair - */ - auto: () => LocalizedString - /** - * Use my own public key - */ - manual: () => LocalizedString - } - form: { - fields: { - name: { - /** - * Device Name - */ - label: () => LocalizedString - } - publicKey: { - /** - * Provide Your Public Key - */ - label: () => LocalizedString - } - } - errors: { - name: { - /** - * Device with this name already exists - */ - duplicatedName: () => LocalizedString - } - } - } - } - copyToken: { - /** - * Client activation - */ - title: () => LocalizedString - /** - * Activation token - */ - tokenCardTitle: () => LocalizedString - /** - * Defguard Instance URL - */ - urlCardTitle: () => LocalizedString - } - } - } - userPage: { - title: { - /** - * User Profile - */ - view: () => LocalizedString - /** - * Edit User Profile - */ - edit: () => LocalizedString - } - messages: { - /** - * User updated. - */ - editSuccess: () => LocalizedString - /** - * Could not get user information. - */ - failedToFetchUserData: () => LocalizedString - /** - * Password reset email has been sent. - */ - passwordResetEmailSent: () => LocalizedString - } - userDetails: { - /** - * Profile Details - */ - header: () => LocalizedString - messages: { - /** - * App and all tokens deleted. - */ - deleteApp: () => LocalizedString - } - warningModals: { - /** - * Warning - */ - title: () => LocalizedString - content: { - /** - * Changing the username has a significant impact on services the user has logged into using Defguard. After changing it, the user may lose access to applications (since they will not recognize them). Are you sure you want to proceed? - */ - usernameChange: () => LocalizedString - /** - * If you are using external OpenID Connect (OIDC) providers to authenticate users, changing a user's email address may have a significant impact on their ability to log in to Defguard. Are you sure you want to proceed? - */ - emailChange: () => LocalizedString - } - buttons: { - /** - * Proceed - */ - proceed: () => LocalizedString - /** - * Cancel - */ - cancel: () => LocalizedString - } - } - fields: { - username: { - /** - * Username - */ - label: () => LocalizedString - } - firstName: { - /** - * First name - */ - label: () => LocalizedString - } - lastName: { - /** - * Last name - */ - label: () => LocalizedString - } - phone: { - /** - * Phone number - */ - label: () => LocalizedString - } - email: { - /** - * E-mail - */ - label: () => LocalizedString - } - status: { - /** - * Status - */ - label: () => LocalizedString - /** - * Active - */ - active: () => LocalizedString - /** - * Disabled - */ - disabled: () => LocalizedString - } - groups: { - /** - * User groups - */ - label: () => LocalizedString - /** - * No groups - */ - noData: () => LocalizedString - } - apps: { - /** - * Authorized apps - */ - label: () => LocalizedString - /** - * No authorized apps - */ - noData: () => LocalizedString - } - } - } - userAuthInfo: { - /** - * Password and authentication - */ - header: () => LocalizedString - password: { - /** - * Password settings - */ - header: () => LocalizedString - /** - * Change password - */ - changePassword: () => LocalizedString - /** - * {ldapName} password update required - */ - ldap_change_heading: (arg: { ldapName: string }) => LocalizedString - /** - * Defguard doesn't store your password in plain text, so we can’t retrieve it for automatic synchronization with your {ldapName} credentials. To enable {ldapName} login to other services, please update your Defguard password for your {ldapName} password to be set — you can re-enter your current password if you wish. This step is necessary to ensure consistent and secure authentication across both systems. - */ - ldap_change_message: (arg: { ldapName: string }) => LocalizedString - } - recovery: { - /** - * Recovery options - */ - header: () => LocalizedString - codes: { - /** - * Recovery Codes - */ - label: () => LocalizedString - /** - * Viewed - */ - viewed: () => LocalizedString - } - } - mfa: { - /** - * Two-factor methods - */ - header: () => LocalizedString - edit: { - /** - * Disable MFA - */ - disable: () => LocalizedString - } - messages: { - /** - * MFA disabled. - */ - mfaDisabled: () => LocalizedString - /** - * One time password disabled. - */ - OTPDisabled: () => LocalizedString - /** - * Email MFA disabled. - */ - EmailMFADisabled: () => LocalizedString - /** - * MFA method changed - */ - changeMFAMethod: () => LocalizedString - } - securityKey: { - /** - * security key - */ - singular: () => LocalizedString - /** - * security keys - */ - plural: () => LocalizedString - } - /** - * default - */ - 'default': () => LocalizedString - /** - * Enabled - */ - enabled: () => LocalizedString - /** - * Disabled - */ - disabled: () => LocalizedString - labels: { - /** - * Time based one time passwords - */ - totp: () => LocalizedString - /** - * Email - */ - email: () => LocalizedString - /** - * Security keys - */ - webauth: () => LocalizedString - } - editMode: { - /** - * Enable - */ - enable: () => LocalizedString - /** - * Disable - */ - disable: () => LocalizedString - /** - * Make default - */ - makeDefault: () => LocalizedString - webauth: { - /** - * Manage security keys - */ - manage: () => LocalizedString - } - } - } - } - controls: { - /** - * Edit profile - */ - editButton: () => LocalizedString - /** - * Delete account - */ - deleteAccount: () => LocalizedString - } - devices: { - /** - * User devices - */ - header: () => LocalizedString - addDevice: { - /** - * Add new device - */ - web: () => LocalizedString - /** - * Add this device - */ - desktop: () => LocalizedString - } - card: { - labels: { - /** - * Public IP - */ - publicIP: () => LocalizedString - /** - * Connected through - */ - connectedThrough: () => LocalizedString - /** - * Connected date - */ - connectionDate: () => LocalizedString - /** - * Last connected from - */ - lastLocation: () => LocalizedString - /** - * Last connected - */ - lastConnected: () => LocalizedString - /** - * Assigned IP - */ - assignedIp: () => LocalizedString - /** - * active - */ - active: () => LocalizedString - /** - * Never connected - */ - noData: () => LocalizedString - } - edit: { - /** - * Edit device - */ - edit: () => LocalizedString - /** - * Delete device - */ - 'delete': () => LocalizedString - /** - * Show configuration - */ - showConfigurations: () => LocalizedString - } - } - } - yubiKey: { - /** - * User YubiKey - */ - header: () => LocalizedString - /** - * Provision a YubiKey - */ - provision: () => LocalizedString - keys: { - /** - * PGP key - */ - pgp: () => LocalizedString - /** - * SSH key - */ - ssh: () => LocalizedString - } - noLicense: { - /** - * YubiKey module - */ - moduleName: () => LocalizedString - /** - * This is enterprise module for YubiKey - */ - line1: () => LocalizedString - /** - * management and provisioning. - */ - line2: () => LocalizedString - } - } - authenticationKeys: { - /** - * User Authentication Keys - */ - header: () => LocalizedString - /** - * Add new Key - */ - addKey: () => LocalizedString - keysList: { - common: { - /** - * Rename - */ - rename: () => LocalizedString - /** - * Key - */ - key: () => LocalizedString - /** - * Download - */ - download: () => LocalizedString - /** - * Copy - */ - copy: () => LocalizedString - /** - * Serial Number - */ - serialNumber: () => LocalizedString - /** - * Delete - */ - 'delete': () => LocalizedString - } - } - deleteModal: { - /** - * Delete Authentication Key - */ - title: () => LocalizedString - /** - * Key {name} will be deleted permanently. - */ - confirmMessage: (arg: { name: string }) => LocalizedString - } - addModal: { - /** - * Add new Authentication Key - */ - header: () => LocalizedString - /** - * Key Type - */ - keyType: () => LocalizedString - keyForm: { - placeholders: { - /** - * Key Name - */ - title: () => LocalizedString - key: { - /** - * Begins with ssh-rsa, ecdsa-sha2-nistp256, ... - */ - ssh: () => LocalizedString - /** - * Begins with -----BEGIN PGP PUBLIC KEY BLOCK----- - */ - gpg: () => LocalizedString - } - } - labels: { - /** - * Name - */ - title: () => LocalizedString - /** - * Key - */ - key: () => LocalizedString - } - /** - * Add {name} key - */ - submit: (arg: { name: string }) => LocalizedString - } - yubikeyForm: { - selectWorker: { - /** - * Please be advised that this operation will wipe openpgp application on YubiKey and reconfigure it. - */ - info: () => LocalizedString - /** - * Select on of the following provisioners to provision a YubiKey - */ - selectLabel: () => LocalizedString - /** - * No workers are registered right now. - */ - noData: () => LocalizedString - /** - * Available - */ - available: () => LocalizedString - /** - * Unavailable - */ - unavailable: () => LocalizedString - } - provisioning: { - /** - * Provisioning in progress, please wait. - */ - inProgress: () => LocalizedString - /** - * Provisioning failed ! - */ - error: () => LocalizedString - /** - * Yubikey provisioned successfully - */ - success: () => LocalizedString - } - /** - * Provision Yubikey - */ - submit: () => LocalizedString - } - messages: { - /** - * Key added. - */ - keyAdded: () => LocalizedString - /** - * Key has already been added. - */ - keyExists: () => LocalizedString - /** - * Unsupported key format. - */ - unsupportedKeyFormat: () => LocalizedString - /** - * Could not add the key. Please try again later. - */ - genericError: () => LocalizedString - } - } - } - apiTokens: { - /** - * User API Tokens - */ - header: () => LocalizedString - /** - * Add new API Token - */ - addToken: () => LocalizedString - tokensList: { - common: { - /** - * Rename - */ - rename: () => LocalizedString - /** - * Token - */ - token: () => LocalizedString - /** - * Copy - */ - copy: () => LocalizedString - /** - * Delete - */ - 'delete': () => LocalizedString - /** - * Created at - */ - createdAt: () => LocalizedString - } - } - deleteModal: { - /** - * Delete API Token - */ - title: () => LocalizedString - /** - * API token {name} will be deleted permanently. - */ - confirmMessage: (arg: { name: string }) => LocalizedString - } - addModal: { - /** - * Add new API Token - */ - header: () => LocalizedString - tokenForm: { - placeholders: { - /** - * API Token Name - */ - name: () => LocalizedString - } - labels: { - /** - * Name - */ - name: () => LocalizedString - } - /** - * Add API token - */ - submit: () => LocalizedString - } - copyToken: { - /** - * Please copy the API token below now. You won't be able to see it again. - */ - warningMessage: () => LocalizedString - /** - * Copy new API Token - */ - header: () => LocalizedString - } - messages: { - /** - * API token added. - */ - tokenAdded: () => LocalizedString - /** - * Could not add API token. Please try again later. - */ - genericError: () => LocalizedString - } - } - } - } - usersOverview: { - /** - * Users - */ - pageTitle: () => LocalizedString - grid: { - /** - * Connected Users - */ - usersTitle: () => LocalizedString - /** - * Connected Network Devices - */ - devicesTitle: () => LocalizedString - } - search: { - /** - * Find users - */ - placeholder: () => LocalizedString - } - filterLabels: { - /** - * All users - */ - all: () => LocalizedString - /** - * Admins only - */ - admin: () => LocalizedString - /** - * Users only - */ - users: () => LocalizedString - } - /** - * All users - */ - usersCount: () => LocalizedString - /** - * Add new - */ - addNewUser: () => LocalizedString - list: { - headers: { - /** - * User name - */ - name: () => LocalizedString - /** - * Login - */ - username: () => LocalizedString - /** - * Phone - */ - phone: () => LocalizedString - /** - * Actions - */ - actions: () => LocalizedString - } - editButton: { - /** - * Change password - */ - changePassword: () => LocalizedString - /** - * Edit account - */ - edit: () => LocalizedString - /** - * Add YubiKey - */ - addYubikey: () => LocalizedString - /** - * Add SSH Key - */ - addSSH: () => LocalizedString - /** - * Add GPG Key - */ - addGPG: () => LocalizedString - /** - * Delete account - */ - 'delete': () => LocalizedString - /** - * Start enrollment - */ - startEnrollment: () => LocalizedString - /** - * Configure Desktop Client - */ - activateDesktop: () => LocalizedString - /** - * Reset password - */ - resetPassword: () => LocalizedString - /** - * Disable MFA - */ - disableMfa: () => LocalizedString - } - } - } - navigation: { - bar: { - /** - * VPN Overview - */ - overview: () => LocalizedString - /** - * Users - */ - users: () => LocalizedString - /** - * YubiKeys - */ - provisioners: () => LocalizedString - /** - * Webhooks - */ - webhooks: () => LocalizedString - /** - * OpenID Apps - */ - openId: () => LocalizedString - /** - * My Profile - */ - myProfile: () => LocalizedString - /** - * Settings - */ - settings: () => LocalizedString - /** - * Log out - */ - logOut: () => LocalizedString - /** - * Enrollment - */ - enrollment: () => LocalizedString - /** - * Support - */ - support: () => LocalizedString - /** - * Groups - */ - groups: () => LocalizedString - /** - * Network Devices - */ - devices: () => LocalizedString - /** - * Access Control - */ - acl: () => LocalizedString - /** - * Activity log - */ - activity: () => LocalizedString - } - mobileTitles: { - /** - * Activity log - */ - activity: () => LocalizedString - /** - * Groups - */ - groups: () => LocalizedString - /** - * Create location - */ - wizard: () => LocalizedString - /** - * Users - */ - users: () => LocalizedString - /** - * Settings - */ - settings: () => LocalizedString - /** - * User Profile - */ - user: () => LocalizedString - /** - * Yubikey - */ - provisioners: () => LocalizedString - /** - * Webhooks - */ - webhooks: () => LocalizedString - /** - * OpenId Apps - */ - openId: () => LocalizedString - /** - * Location Overview - */ - overview: () => LocalizedString - /** - * Edit Location - */ - networkSettings: () => LocalizedString - /** - * Enrollment - */ - enrollment: () => LocalizedString - /** - * Support - */ - support: () => LocalizedString - /** - * Network Devices - */ - devices: () => LocalizedString - } - /** - * Copyright ©2023-2025 - */ - copyright: () => LocalizedString - version: { - /** - * Application version: {version} - */ - open: (arg: { version: string }) => LocalizedString - /** - * v{version} - */ - closed: (arg: { version: string }) => LocalizedString - } - } - form: { - /** - * Download - */ - download: () => LocalizedString - /** - * Copy - */ - copy: () => LocalizedString - /** - * Save changes - */ - saveChanges: () => LocalizedString - /** - * Submit - */ - submit: () => LocalizedString - /** - * Sign in - */ - login: () => LocalizedString - /** - * Cancel - */ - cancel: () => LocalizedString - /** - * Close - */ - close: () => LocalizedString - placeholders: { - /** - * Password - */ - password: () => LocalizedString - /** - * Username - */ - username: () => LocalizedString - /** - * Username or email - */ - username_or_email: () => LocalizedString - } - error: { - /** - * Enter valid URL - */ - urlInvalid: () => LocalizedString - /** - * Name is already taken. - */ - reservedName: () => LocalizedString - /** - * IP is invalid. - */ - invalidIp: () => LocalizedString - /** - * IP is already in use. - */ - reservedIp: () => LocalizedString - /** - * Field contains forbidden characters. - */ - forbiddenCharacter: () => LocalizedString - /** - * Username is already in use. - */ - usernameTaken: () => LocalizedString - /** - * Key is invalid. - */ - invalidKey: () => LocalizedString - /** - * Field is invalid. - */ - invalid: () => LocalizedString - /** - * Field is required. - */ - required: () => LocalizedString - /** - * Submitted code is invalid. - */ - invalidCode: () => LocalizedString - /** - * Maximum length exceeded. - */ - maximumLength: () => LocalizedString - /** - * Field length cannot exceed {length} - */ - maximumLengthOf: (arg: { length: number }) => LocalizedString - /** - * Minimum length not reached. - */ - minimumLength: () => LocalizedString - /** - * Minimum length of {length} not reached. - */ - minimumLengthOf: (arg: { length: number }) => LocalizedString - /** - * No special characters are allowed. - */ - noSpecialChars: () => LocalizedString - /** - * One digit required. - */ - oneDigit: () => LocalizedString - /** - * Special character required. - */ - oneSpecial: () => LocalizedString - /** - * One uppercase character required. - */ - oneUppercase: () => LocalizedString - /** - * One lowercase character required. - */ - oneLowercase: () => LocalizedString - /** - * Maximum port is 65535. - */ - portMax: () => LocalizedString - /** - * Enter a valid endpoint. - */ - endpoint: () => LocalizedString - /** - * Enter a valid address. - */ - address: () => LocalizedString - /** - * Enter a valid address with a netmask. - */ - addressNetmask: () => LocalizedString - /** - * Enter a valid port. - */ - validPort: () => LocalizedString - /** - * Code should have 6 digits. - */ - validCode: () => LocalizedString - /** - * Only valid IP or domain is allowed. - */ - allowedIps: () => LocalizedString - /** - * Cannot start from number. - */ - startFromNumber: () => LocalizedString - /** - * Fields don't match. - */ - repeat: () => LocalizedString - /** - * Expected a valid number. - */ - number: () => LocalizedString - /** - * Minimum value of {value} not reached. - */ - minimumValue: (arg: { value: number }) => LocalizedString - /** - * Maximum value of {value} exceeded. - */ - maximumValue: (arg: { value: number }) => LocalizedString - /** - * Too many bad login attempts. Please try again in a few minutes. - */ - tooManyBadLoginAttempts: () => LocalizedString - } - floatingErrors: { - /** - * Please correct the following: - */ - title: () => LocalizedString - } - } - components: { - /** - * One-Click Desktop Configuration - */ - openClientDeepLink: () => LocalizedString - aclDefaultPolicySelect: { - /** - * Default ACL Policy - */ - label: () => LocalizedString - options: { - /** - * Allow - */ - allow: () => LocalizedString - /** - * Deny - */ - deny: () => LocalizedString - } - } - standaloneDeviceTokenModalContent: { - /** - * First download defguard command line client binaries and install them on your server. - */ - headerMessage: () => LocalizedString - /** - * Download Defguard CLI Client - */ - downloadButton: () => LocalizedString - expandableCard: { - /** - * Copy and paste this command in your terminal on the device - */ - title: () => LocalizedString - } - } - deviceConfigsCard: { - /** - * WireGuard Config for location: - */ - cardTitle: () => LocalizedString - messages: { - /** - * Configuration copied to the clipboard - */ - copyConfig: () => LocalizedString - } - } - gatewaysStatus: { - /** - * Gateways - */ - label: () => LocalizedString - states: { - /** - * All ({count}) Connected - */ - all: (arg: { count: number }) => LocalizedString - /** - * Some ({count}) Connected - */ - some: (arg: { count: number }) => LocalizedString - /** - * None connected - */ - none: () => LocalizedString - /** - * Status check failed - */ - error: () => LocalizedString - } - messages: { - /** - * Failed to get gateways status - */ - error: () => LocalizedString - /** - * Failed to delete gateway - */ - deleteError: () => LocalizedString - } - } - noLicenseBox: { - footer: { - /** - * Get an enterprise license - */ - get: () => LocalizedString - /** - * by contacting: - */ - contact: () => LocalizedString - } - } - locationMfaModeSelect: { - /** - * MFA Requirement - */ - label: () => LocalizedString - options: { - /** - * Do not enforce MFA - */ - disabled: () => LocalizedString - /** - * Internal MFA - */ - internal: () => LocalizedString - /** - * External MFA - */ - external: () => LocalizedString - } - } - } - settingsPage: { - /** - * Settings - */ - title: () => LocalizedString - tabs: { - /** - * SMTP - */ - smtp: () => LocalizedString - /** - * Global settings - */ - global: () => LocalizedString - /** - * LDAP - */ - ldap: () => LocalizedString - /** - * OpenID - */ - openid: () => LocalizedString - /** - * Enterprise features - */ - enterprise: () => LocalizedString - /** - * Gateway notifications - */ - gatewayNotifications: () => LocalizedString - /** - * Activity log streaming - */ - activityLogStream: () => LocalizedString - } - messages: { - /** - * Settings updated - */ - editSuccess: () => LocalizedString - /** - * Challenge message changed - */ - challengeSuccess: () => LocalizedString - } - enterpriseOnly: { - /** - * This feature is available only in Defguard Enterprise. - */ - title: () => LocalizedString - /** - * Your current license has expired. - */ - currentExpired: () => LocalizedString - /** - * To learn more, visit our - */ - subtitle: () => LocalizedString - /** - * website - */ - website: () => LocalizedString - } - activityLogStreamSettings: { - messages: { - destinationCrud: { - /** - * {destination} destination added - */ - create: (arg: { destination: string }) => LocalizedString - /** - * {destination} destination modified - */ - modify: (arg: { destination: string }) => LocalizedString - /** - * {destination} destination removed - */ - 'delete': (arg: { destination: string }) => LocalizedString - } - } - modals: { - selectDestination: { - /** - * Select destination - */ - title: () => LocalizedString - } - vector: { - /** - * Add Vector destination - */ - create: () => LocalizedString - /** - * Edit Vector destination - */ - modify: () => LocalizedString - } - logstash: { - /** - * Add Logstash destination - */ - create: () => LocalizedString - /** - * Edit Logstash destination - */ - modify: () => LocalizedString - } - shared: { - formLabels: { - /** - * Name - */ - name: () => LocalizedString - /** - * Url - */ - url: () => LocalizedString - /** - * Username - */ - username: () => LocalizedString - /** - * Password - */ - password: () => LocalizedString - /** - * Certificate - */ - cert: () => LocalizedString - } - } - } - /** - * Activity log streaming - */ - title: () => LocalizedString - list: { - /** - * No destinations - */ - noData: () => LocalizedString - headers: { - /** - * Name - */ - name: () => LocalizedString - /** - * Destination - */ - destination: () => LocalizedString - } - } - } - ldapSettings: { - /** - * LDAP Settings - */ - title: () => LocalizedString - sync: { - /** - * LDAP two-way synchronization - */ - header: () => LocalizedString - /** - * Before enabling synchronization, please read more about it in our [documentation](https://docs.defguard.net/features/ldap-and-active-directory-integration/two-way-ldap-and-active-directory-synchronization). - */ - info: () => LocalizedString - /** - * This feature is available only in Defguard Enterprise. - */ - info_enterprise: () => LocalizedString - helpers: { - /** - * Configure LDAP synchronization settings here. If configured, Defguard will pull user information from LDAP and synchronize it with local users. - */ - heading: () => LocalizedString - /** - * If enabled, Defguard will attempt to pull LDAP user data at the specified interval. - */ - sync_enabled: () => LocalizedString - /** - * Defguard will use the selected server as the authoritative source of - user data, meaning that if LDAP is selected, Defguard data will be overwritten with the LDAP - data in case of a desynchronization. If Defguard was selected as the authority, it's data will - overwrite LDAP data if necessary. - Make sure to check the documentation to understand the implications of this - setting. - */ - authority: () => LocalizedString - /** - * The interval with which the synchronization will be attempted. - */ - interval: () => LocalizedString - /** - * Defguard will attempt to synchronize only users belonging to the provided groups. Provide a comma-separated list of groups. If empty, all users will be synchronized. - */ - groups: () => LocalizedString - } - } - form: { - labels: { - /** - * Enable LDAP integration - */ - ldap_enable: () => LocalizedString - /** - * URL - */ - ldap_url: () => LocalizedString - /** - * Bind Username - */ - ldap_bind_username: () => LocalizedString - /** - * Bind Password - */ - ldap_bind_password: () => LocalizedString - /** - * Member Attribute - */ - ldap_member_attr: () => LocalizedString - /** - * Username Attribute - */ - ldap_username_attr: () => LocalizedString - /** - * User Object Class - */ - ldap_user_obj_class: () => LocalizedString - /** - * User Search Base - */ - ldap_user_search_base: () => LocalizedString - /** - * Additional User Object Classes - */ - ldap_user_auxiliary_obj_classes: () => LocalizedString - /** - * Groupname Attribute - */ - ldap_groupname_attr: () => LocalizedString - /** - * Group Search Base - */ - ldap_group_search_base: () => LocalizedString - /** - * Group Member Attribute - */ - ldap_group_member_attr: () => LocalizedString - /** - * Group Object Class - */ - ldap_group_obj_class: () => LocalizedString - /** - * Enable LDAP two-way synchronization - */ - ldap_sync_enabled: () => LocalizedString - /** - * Consider the following source as the authority - */ - ldap_authoritative_source: () => LocalizedString - /** - * Synchronization interval - */ - ldap_sync_interval: () => LocalizedString - /** - * Use StartTLS - */ - ldap_use_starttls: () => LocalizedString - /** - * Verify TLS certificate - */ - ldap_tls_verify_cert: () => LocalizedString - /** - * LDAP server is Active Directory - */ - ldap_uses_ad: () => LocalizedString - /** - * User RDN Attribute - */ - ldap_user_rdn_attr: () => LocalizedString - /** - * Limit synchronization to these groups - */ - ldap_sync_groups: () => LocalizedString - } - helpers: { - /** - * The object class that will be added to the user object during its creation. This is used to determine if an LDAP object is a user. - */ - ldap_user_obj_class: () => LocalizedString - /** - * The additional object classes that will be added to the user object during its creation. They may also influence the added user's attributes (e.g. simpleSecurityObject class will add userPassword attribute). - */ - ldap_user_auxiliary_obj_classes: () => LocalizedString - /** - * Configure LDAP user settings here. These settings determine how Defguard maps and synchronizes LDAP user information with local users. - */ - user_settings: () => LocalizedString - /** - * Configure LDAP connection settings here. These settings determine how Defguard connects to your LDAP server. Encrypted connections are also supported (StartTLS, LDAPS). - */ - connection_settings: () => LocalizedString - /** - * Configure LDAP group settings here. These settings determine how Defguard maps and synchronizes LDAP group information with local groups. - */ - group_settings: () => LocalizedString - /** - * The object class that represents a group in LDAP. This is used to determine if an LDAP object is a group. - */ - ldap_group_obj_class: () => LocalizedString - /** - * If your user's RDN attribute is different than your username attribute, please provide it here, otherwise leave it empty to use the username attribute as the user's RDN. - */ - ldap_user_rdn_attr: () => LocalizedString - } - headings: { - /** - * User settings - */ - user_settings: () => LocalizedString - /** - * Connection settings - */ - connection_settings: () => LocalizedString - /** - * Group settings - */ - group_settings: () => LocalizedString - } - /** - * Delete configuration - */ - 'delete': () => LocalizedString - } - test: { - /** - * Test LDAP Connection - */ - title: () => LocalizedString - /** - * Test - */ - submit: () => LocalizedString - messages: { - /** - * LDAP connected successfully - */ - success: () => LocalizedString - /** - * LDAP connection rejected - */ - error: () => LocalizedString - } - } - } - openIdSettings: { - /** - * External OpenID settings - */ - heading: () => LocalizedString - general: { - /** - * General settings - */ - title: () => LocalizedString - /** - * Here you can change general OpenID behavior in your Defguard instance. - */ - helper: () => LocalizedString - createAccount: { - /** - * Automatically create user account when logging in for the first time through external OpenID. - */ - label: () => LocalizedString - /** - * If this option is enabled, Defguard automatically creates new accounts for users who log in for the first time using an external OpenID provider. Otherwise, the user account must first be created by an administrator. - */ - helper: () => LocalizedString - } - usernameHandling: { - /** - * Username handling - */ - label: () => LocalizedString - /** - * Configure the method for handling invalid characters in usernames provided by your identity provider. - */ - helper: () => LocalizedString - options: { - /** - * Remove forbidden characters - */ - remove: () => LocalizedString - /** - * Replace forbidden characters - */ - replace: () => LocalizedString - /** - * Prune email domain - */ - prune_email: () => LocalizedString - } - } - } - form: { - /** - * Client settings - */ - title: () => LocalizedString - /** - * Here you can configure the OpenID client settings with values provided by your external OpenID provider. - */ - helper: () => LocalizedString - /** - * Custom - */ - custom: () => LocalizedString - /** - * None - */ - none: () => LocalizedString - /** - * Make sure to check our [documentation](https://docs.defguard.net/features/external-openid-providers) for more information and examples. - */ - documentation: () => LocalizedString - /** - * Delete provider - */ - 'delete': () => LocalizedString - directory_sync_settings: { - /** - * Directory synchronization settings - */ - title: () => LocalizedString - /** - * Directory synchronization allows you to automatically synchronize users' status and groups from an external provider. - */ - helper: () => LocalizedString - /** - * Directory sync is not supported for this provider. - */ - notSupported: () => LocalizedString - connectionTest: { - /** - * Connection successful - */ - success: () => LocalizedString - /** - * Connection failed with error: - */ - error: () => LocalizedString - } - } - selects: { - synchronize: { - /** - * All - */ - all: () => LocalizedString - /** - * Users - */ - users: () => LocalizedString - /** - * Groups - */ - groups: () => LocalizedString - } - behavior: { - /** - * Keep - */ - keep: () => LocalizedString - /** - * Disable - */ - disable: () => LocalizedString - /** - * Delete - */ - 'delete': () => LocalizedString - } - } - labels: { - provider: { - /** - * Provider - */ - label: () => LocalizedString - /** - * Select your OpenID provider. You can use custom provider and fill in the base URL by yourself. - */ - helper: () => LocalizedString - } - client_id: { - /** - * Client ID - */ - label: () => LocalizedString - /** - * Client ID provided by your OpenID provider. - */ - helper: () => LocalizedString - } - client_secret: { - /** - * Client Secret - */ - label: () => LocalizedString - /** - * Client Secret provided by your OpenID provider. - */ - helper: () => LocalizedString - } - base_url: { - /** - * Base URL - */ - label: () => LocalizedString - /** - * Base URL of your OpenID provider, e.g. https://accounts.google.com. Make sure to check our documentation for more information and examples. - */ - helper: () => LocalizedString - } - display_name: { - /** - * Display Name - */ - label: () => LocalizedString - /** - * Name of the OpenID provider to display on the login's page button. If not provided, the button will display generic 'Login with OIDC' text. - */ - helper: () => LocalizedString - } - enable_directory_sync: { - /** - * Enable directory synchronization - */ - label: () => LocalizedString - } - sync_target: { - /** - * Synchronize - */ - label: () => LocalizedString - /** - * What to synchronize from the external provider. You can choose between synchronizing both users' state and group memberships, or narrow it down to just one of these. - */ - helper: () => LocalizedString - } - sync_interval: { - /** - * Synchronization interval - */ - label: () => LocalizedString - /** - * Interval in seconds between directory synchronizations. - */ - helper: () => LocalizedString - } - user_behavior: { - /** - * User behavior - */ - label: () => LocalizedString - /** - * Choose how to handle users that are not present in the external provider anymore. You can select between keeping, disabling, or deleting them. - */ - helper: () => LocalizedString - } - admin_behavior: { - /** - * Admin behavior - */ - label: () => LocalizedString - /** - * Choose how to handle Defguard admins that are not present in the external provider anymore. You can select between keeping them, disabling them or completely deleting them. - */ - helper: () => LocalizedString - } - admin_email: { - /** - * Admin email - */ - label: () => LocalizedString - /** - * Email address of the account on which behalf the synchronization checks will be performed, e.g. the person who setup the Google service account. See our documentation for more details. - */ - helper: () => LocalizedString - } - service_account_used: { - /** - * Service account in use - */ - label: () => LocalizedString - /** - * The service account currently being used for synchronization. You can change it by uploading a new service account key file. - */ - helper: () => LocalizedString - } - service_account_key_file: { - /** - * Service Account Key file - */ - label: () => LocalizedString - /** - * Upload a new service account key file to set the service account used for synchronization. NOTE: The uploaded file won't be visible after saving the settings and reloading the page as it's contents are sensitive and are never sent back to the dashboard. - */ - helper: () => LocalizedString - /** - * File uploaded - */ - uploaded: () => LocalizedString - /** - * Upload a service account key file - */ - uploadPrompt: () => LocalizedString - } - okta_client_id: { - /** - * Directory Sync Client ID - */ - label: () => LocalizedString - /** - * Client ID for the Okta directory sync application. - */ - helper: () => LocalizedString - } - okta_client_key: { - /** - * Directory Sync Client Private Key - */ - label: () => LocalizedString - /** - * Client private key for the Okta directory sync application in the JWK format. It won't be shown again here. - */ - helper: () => LocalizedString - } - jumpcloud_api_key: { - /** - * JumpCloud API Key - */ - label: () => LocalizedString - /** - * API Key for the JumpCloud directory sync. It will be used to periodically query JumpCloud for user state and group membership changes. - */ - helper: () => LocalizedString - } - group_match: { - /** - * Sync only matching groups - */ - label: () => LocalizedString - /** - * Provide a comma separated list of group names that should be synchronized. If left empty, all groups from the provider will be synchronized. - */ - helper: () => LocalizedString - } - } - } - } - modulesVisibility: { - /** - * Modules Visibility - */ - header: () => LocalizedString - /** - *

- Hide unused modules. -

- - Read more in documentation. - - */ - helper: (arg: { documentationLink: string }) => LocalizedString - fields: { - wireguard_enabled: { - /** - * WireGuard VPN - */ - label: () => LocalizedString - } - webhooks_enabled: { - /** - * Webhooks - */ - label: () => LocalizedString - } - worker_enabled: { - /** - * Yubikey provisioning - */ - label: () => LocalizedString - } - openid_enabled: { - /** - * OpenID Connect - */ - label: () => LocalizedString - } - } - } - defaultNetworkSelect: { - /** - * Default location view - */ - header: () => LocalizedString - /** - *

Here you can change your default location view.

- - Read more in documentation. - - */ - helper: (arg: { documentationLink: string }) => LocalizedString - filterLabels: { - /** - * Grid view - */ - grid: () => LocalizedString - /** - * List view - */ - list: () => LocalizedString - } - } - instanceBranding: { - /** - * Instance Branding - */ - header: () => LocalizedString - form: { - /** - * Name & Logo: - */ - title: () => LocalizedString - fields: { - instanceName: { - /** - * Instance name - */ - label: () => LocalizedString - /** - * Defguard - */ - placeholder: () => LocalizedString - } - mainLogoUrl: { - /** - * Login logo url - */ - label: () => LocalizedString - /** - * Maximum picture size is 250x100 px - */ - helper: () => LocalizedString - /** - * Default image - */ - placeholder: () => LocalizedString - } - navLogoUrl: { - /** - * Menu & navigation small logo - */ - label: () => LocalizedString - /** - * Maximum picture size is 100x100 px - */ - helper: () => LocalizedString - /** - * Default image - */ - placeholder: () => LocalizedString - } - } - controls: { - /** - * Restore default - */ - restoreDefault: () => LocalizedString - /** - * Save changes - */ - submit: () => LocalizedString - } - } - /** - * -

- Here you can add url of your logo and name for your defguard - instance it will be displayed instead of defguard. -

- - Read more in documentation. - - - */ - helper: (arg: { documentationLink: string }) => LocalizedString - } - license: { - /** - * Enterprise - */ - header: () => LocalizedString - helpers: { - enterpriseHeader: { - /** - * Here you can manage your Defguard Enterprise version license. - */ - text: () => LocalizedString - /** - * To learn more about Defguard Enterprise, visit our webiste. - */ - link: () => LocalizedString - } - licenseKey: { - /** - * Enter your Defguard Enterprise license key below. You should receive it via email after purchasing the license. - */ - text: () => LocalizedString - /** - * You can purchase the license here. - */ - link: () => LocalizedString - } - } - form: { - /** - * License - */ - title: () => LocalizedString - fields: { - key: { - /** - * License key - */ - label: () => LocalizedString - /** - * Your Defguard license key - */ - placeholder: () => LocalizedString - } - } - } - licenseInfo: { - /** - * License information - */ - title: () => LocalizedString - status: { - /** - * No valid license - */ - noLicense: () => LocalizedString - /** - * Expired - */ - expired: () => LocalizedString - /** - * Limits Exceeded - */ - limitsExceeded: () => LocalizedString - /** - * Active - */ - active: () => LocalizedString - } - /** - *

You have access to this enterprise feature, as you haven't exceeded any of the usage limits yet. Check the documentation for more information.

- */ - licenseNotRequired: () => LocalizedString - types: { - subscription: { - /** - * Subscription - */ - label: () => LocalizedString - /** - * A license that automatically renews at regular intervals - */ - helper: () => LocalizedString - } - offline: { - /** - * Offline - */ - label: () => LocalizedString - /** - * The license is valid until the expiry date and does not automatically renew - */ - helper: () => LocalizedString - } - } - fields: { - status: { - /** - * Status - */ - label: () => LocalizedString - /** - * Active - */ - active: () => LocalizedString - /** - * Expired - */ - expired: () => LocalizedString - /** - * A subscription license is considered valid for some time after the expiration date to account for possible automatic payment delays. - */ - subscriptionHelper: () => LocalizedString - } - type: { - /** - * Type - */ - label: () => LocalizedString - } - validUntil: { - /** - * Valid until - */ - label: () => LocalizedString - } - } - } - } - smtp: { - form: { - /** - * SMTP configuration - */ - title: () => LocalizedString - sections: { - /** - * Server settings - */ - server: () => LocalizedString - } - fields: { - encryption: { - /** - * Encryption - */ - label: () => LocalizedString - } - server: { - /** - * Server address - */ - label: () => LocalizedString - /** - * Address - */ - placeholder: () => LocalizedString - } - port: { - /** - * Server port - */ - label: () => LocalizedString - /** - * Port - */ - placeholder: () => LocalizedString - } - user: { - /** - * Server username - */ - label: () => LocalizedString - /** - * Username - */ - placeholder: () => LocalizedString - } - password: { - /** - * Server password - */ - label: () => LocalizedString - /** - * Password - */ - placeholder: () => LocalizedString - } - sender: { - /** - * Sender email address - */ - label: () => LocalizedString - /** - * Address - */ - placeholder: () => LocalizedString - /** - * -

- System messages will be sent from this address. - E.g. no-reply@my-company.com. -

- - */ - helper: () => LocalizedString - } - } - controls: { - /** - * Save changes - */ - submit: () => LocalizedString - } - } - /** - * Delete configuration - */ - 'delete': () => LocalizedString - testForm: { - /** - * Send test email - */ - title: () => LocalizedString - /** - * Enter recipent email address - */ - subtitle: () => LocalizedString - fields: { - to: { - /** - * Send test email to - */ - label: () => LocalizedString - /** - * Address - */ - placeholder: () => LocalizedString - } - } - controls: { - /** - * Send - */ - submit: () => LocalizedString - /** - * Resend - */ - resend: () => LocalizedString - /** - * Retry - */ - retry: () => LocalizedString - /** - * Test email sent - */ - success: () => LocalizedString - /** - * Error sending email - */ - error: () => LocalizedString - } - success: { - /** - * Test email has been sent successully. - */ - message: () => LocalizedString - } - error: { - /** - * There was an error sending the test email. Please check your SMTP configuration and try again. - */ - message: () => LocalizedString - /** - * Error: {error} - */ - fullError: (arg: { error: string }) => LocalizedString - } - } - /** - * Here you can configure SMTP server used to send system messages to the users. - */ - helper: () => LocalizedString - } - enrollment: { - /** - * Enrollment is a process by which a new employee will be able to activate their new account, create a password and configure a VPN device. - */ - helper: () => LocalizedString - vpnOptionality: { - /** - * VPN step optionality - */ - header: () => LocalizedString - /** - * You can choose whether creating a VPN device is optional or mandatory during enrollment - */ - helper: () => LocalizedString - } - welcomeMessage: { - /** - * Welcome message - */ - header: () => LocalizedString - /** - * -

In this text input you can use Markdown:

-
    -
  • Headings start with a hash #
  • -
  • Use asterisks for *italics*
  • -
  • Use two asterisks for **bold**
  • -
- - */ - helper: () => LocalizedString - } - welcomeEmail: { - /** - * Welcome e-mail - */ - header: () => LocalizedString - /** - * -

In this text input you can use Markdown:

-
    -
  • Headings start with a hash #
  • -
  • Use asterisks for *italics*
  • -
  • Use two asterisks for **bold**
  • -
- - */ - helper: () => LocalizedString - } - form: { - controls: { - /** - * Save changes - */ - submit: () => LocalizedString - } - welcomeMessage: { - /** - * This information will be displayed for the user once enrollment is completed. We advise you to insert relevant links and explain next steps briefly. - */ - helper: () => LocalizedString - /** - * Please input welcome message - */ - placeholder: () => LocalizedString - } - welcomeEmail: { - /** - * This information will be sent to the user once enrollment is completed. We advise you to insert relevant links and explain next steps briefly. You can reuse the welcome message here. - */ - helper: () => LocalizedString - /** - * Please input welcome email - */ - placeholder: () => LocalizedString - } - welcomeEmailSubject: { - /** - * Subject - */ - label: () => LocalizedString - } - useMessageAsEmail: { - /** - * Same as welcome message - */ - label: () => LocalizedString - } - } - } - enterprise: { - /** - * Enterprise Features - */ - header: () => LocalizedString - /** - * Here you can change enterprise settings. - */ - helper: () => LocalizedString - fields: { - deviceManagement: { - /** - * Disable users' ability to manage their devices - */ - label: () => LocalizedString - /** - * When this option is enabled, only users in the Admin group can manage devices in user profile (it's disabled for all other users) - */ - helper: () => LocalizedString - } - disableAllTraffic: { - /** - * Disable the option to route all traffic through VPN - */ - label: () => LocalizedString - /** - * When this option is enabled, users will not be able to route all traffic through the VPN using the defguard client. - */ - helper: () => LocalizedString - } - manualConfig: { - /** - * Disable users' ability to manually configure WireGuard client - */ - label: () => LocalizedString - /** - * When this option is enabled, users won't be able to view or download configuration for the manual WireGuard client setup. Only the Defguard desktop client configuration will be available. - */ - helper: () => LocalizedString - } - } - } - gatewayNotifications: { - /** - * To enable notifications you must first configure an SMTP server - */ - smtpWarning: () => LocalizedString - /** - * Notifications - */ - header: () => LocalizedString - sections: { - /** - * Gateway disconnect notifications - */ - gateway: () => LocalizedString - } - /** - * Here you can manage email notifications. - */ - helper: () => LocalizedString - form: { - /** - * Save changes - */ - submit: () => LocalizedString - fields: { - disconnectNotificationsEnabled: { - /** - * Enable gateway disconnect notifications - */ - label: () => LocalizedString - /** - * Send email notification to admin users once a gateway is disconnected - */ - help: () => LocalizedString - } - inactivityThreshold: { - /** - * Gateway inactivity time [minutes] - */ - label: () => LocalizedString - /** - * Time (in minutes) that a gateway needs to stay disconnected before a notification is sent - */ - help: () => LocalizedString - } - reconnectNotificationsEnabled: { - /** - * Enable gateway reconnect notifications - */ - label: () => LocalizedString - /** - * Send email notification to admin users once a gateway is reconnected - */ - help: () => LocalizedString - } - } - } - } - } - openidOverview: { - /** - * OpenID Apps - */ - pageTitle: () => LocalizedString - search: { - /** - * Find apps - */ - placeholder: () => LocalizedString - } - filterLabels: { - /** - * All apps - */ - all: () => LocalizedString - /** - * Enabled - */ - enabled: () => LocalizedString - /** - * Disabled - */ - disabled: () => LocalizedString - } - /** - * All apps - */ - clientCount: () => LocalizedString - /** - * Add new - */ - addNewApp: () => LocalizedString - list: { - headers: { - /** - * Name - */ - name: () => LocalizedString - /** - * Status - */ - status: () => LocalizedString - /** - * Actions - */ - actions: () => LocalizedString - } - editButton: { - /** - * Edit app - */ - edit: () => LocalizedString - /** - * Delete app - */ - 'delete': () => LocalizedString - /** - * Disable - */ - disable: () => LocalizedString - /** - * Enable - */ - enable: () => LocalizedString - /** - * Copy client ID - */ - copy: () => LocalizedString - } - status: { - /** - * Enabled - */ - enabled: () => LocalizedString - /** - * Disabled - */ - disabled: () => LocalizedString - } - } - messages: { - /** - * Client ID copied. - */ - copySuccess: () => LocalizedString - /** - * You don't have a license for this feature. - */ - noLicenseMessage: () => LocalizedString - /** - * No results found. - */ - noClientsFound: () => LocalizedString - } - deleteApp: { - /** - * Delete app - */ - title: () => LocalizedString - /** - * Do you want to delete {appName} app ? - */ - message: (arg: { appName: string }) => LocalizedString - /** - * Delete app - */ - submit: () => LocalizedString - messages: { - /** - * App deleted. - */ - success: () => LocalizedString - } - } - enableApp: { - messages: { - /** - * App enabled. - */ - success: () => LocalizedString - } - } - disableApp: { - messages: { - /** - * App disabled. - */ - success: () => LocalizedString - } - } - modals: { - openidClientModal: { - title: { - /** - * Add Application - */ - addApp: () => LocalizedString - /** - * Edit {appName} app - */ - editApp: (arg: { appName: string }) => LocalizedString - } - /** - * Scopes: - */ - scopes: () => LocalizedString - messages: { - /** - * Client ID copied. - */ - clientIdCopy: () => LocalizedString - /** - * Client secret copied. - */ - clientSecretCopy: () => LocalizedString - } - form: { - messages: { - /** - * App created. - */ - successAdd: () => LocalizedString - /** - * App modified. - */ - successModify: () => LocalizedString - } - error: { - /** - * URL is required. - */ - urlRequired: () => LocalizedString - /** - * Must be a valid URL. - */ - validUrl: () => LocalizedString - /** - * Must have at least one scope. - */ - scopeValidation: () => LocalizedString - } - fields: { - name: { - /** - * App name - */ - label: () => LocalizedString - } - redirectUri: { - /** - * Redirect URL {count} - */ - label: (arg: { count: number }) => LocalizedString - /** - * https://example.com/redirect - */ - placeholder: () => LocalizedString - } - openid: { - /** - * OpenID - */ - label: () => LocalizedString - } - profile: { - /** - * Profile - */ - label: () => LocalizedString - } - email: { - /** - * Email - */ - label: () => LocalizedString - } - phone: { - /** - * Phone - */ - label: () => LocalizedString - } - groups: { - /** - * Groups - */ - label: () => LocalizedString - } - } - controls: { - /** - * Add URL - */ - addUrl: () => LocalizedString - } - } - /** - * Client ID - */ - clientId: () => LocalizedString - /** - * Client secret - */ - clientSecret: () => LocalizedString - } - } - } - webhooksOverview: { - /** - * Webhooks - */ - pageTitle: () => LocalizedString - search: { - /** - * Find webhooks by url - */ - placeholder: () => LocalizedString - } - filterLabels: { - /** - * All webhooks - */ - all: () => LocalizedString - /** - * Enabled - */ - enabled: () => LocalizedString - /** - * Disabled - */ - disabled: () => LocalizedString - } - /** - * All webhooks - */ - webhooksCount: () => LocalizedString - /** - * Add new - */ - addNewWebhook: () => LocalizedString - /** - * No webhooks found. - */ - noWebhooksFound: () => LocalizedString - list: { - headers: { - /** - * Name - */ - name: () => LocalizedString - /** - * Description - */ - description: () => LocalizedString - /** - * Status - */ - status: () => LocalizedString - /** - * Actions - */ - actions: () => LocalizedString - } - editButton: { - /** - * Edit - */ - edit: () => LocalizedString - /** - * Delete webhook - */ - 'delete': () => LocalizedString - /** - * Disable - */ - disable: () => LocalizedString - /** - * Enable - */ - enable: () => LocalizedString - } - status: { - /** - * Enabled - */ - enabled: () => LocalizedString - /** - * Disabled - */ - disabled: () => LocalizedString - } - } - } - provisionersOverview: { - /** - * Provisioners - */ - pageTitle: () => LocalizedString - search: { - /** - * Find provisioners - */ - placeholder: () => LocalizedString - } - filterLabels: { - /** - * All - */ - all: () => LocalizedString - /** - * Available - */ - available: () => LocalizedString - /** - * Unavailable - */ - unavailable: () => LocalizedString - } - /** - * All provisioners - */ - provisionersCount: () => LocalizedString - /** - * No provisioners found. - */ - noProvisionersFound: () => LocalizedString - /** - * You don't have a license for this feature. - */ - noLicenseMessage: () => LocalizedString - provisioningStation: { - /** - * YubiKey provisioning station - */ - header: () => LocalizedString - /** - * In order to be able to provision your YubiKeys, first you need to set up - physical machine with USB slot. Run provided command on your chosen - machine to register it and start provisioning your keys. - */ - content: () => LocalizedString - dockerCard: { - /** - * Provisioning station docker setup command - */ - title: () => LocalizedString - } - tokenCard: { - /** - * Access token - */ - title: () => LocalizedString - } - } - list: { - headers: { - /** - * Name - */ - name: () => LocalizedString - /** - * IP address - */ - ip: () => LocalizedString - /** - * Status - */ - status: () => LocalizedString - /** - * Actions - */ - actions: () => LocalizedString - } - editButton: { - /** - * Delete provisioner - */ - 'delete': () => LocalizedString - } - status: { - /** - * Available - */ - available: () => LocalizedString - /** - * Unavailable - */ - unavailable: () => LocalizedString - } - } - messages: { - copy: { - /** - * Token copied - */ - token: () => LocalizedString - /** - * Command copied - */ - command: () => LocalizedString - } - } - } - openidAllow: { - /** - * {name} would like to: - */ - header: (arg: { name: string }) => LocalizedString - scopes: { - /** - * Use your profile data for future logins. - */ - openid: () => LocalizedString - /** - * Know basic information from your profile like name, profile picture etc. - */ - profile: () => LocalizedString - /** - * Know your email address. - */ - email: () => LocalizedString - /** - * Know your phone number. - */ - phone: () => LocalizedString - /** - * Know your groups membership. - */ - groups: () => LocalizedString - } - controls: { - /** - * Accept - */ - accept: () => LocalizedString - /** - * Cancel - */ - cancel: () => LocalizedString - } - } - networkOverview: { - networkSelection: { - /** - * All locations summary - */ - all: () => LocalizedString - /** - * Select location - */ - placeholder: () => LocalizedString - } - /** - * {value}h period - */ - timeRangeSelectionLabel: (arg: { value: number }) => LocalizedString - /** - * Location overview - */ - pageTitle: () => LocalizedString - controls: { - /** - * Edit Locations settings - */ - editNetworks: () => LocalizedString - selectNetwork: { - /** - * Loading locations - */ - placeholder: () => LocalizedString - } - } - filterLabels: { - /** - * Grid view - */ - grid: () => LocalizedString - /** - * List view - */ - list: () => LocalizedString - } - gatewayStatus: { - /** - * All ({count}) Connected - */ - all: (arg: { count: number }) => LocalizedString - /** - * Some ({count}) Connected - */ - some: (arg: { count: number }) => LocalizedString - /** - * None connected - */ - none: () => LocalizedString - } - stats: { - /** - * Currently active users - */ - currentlyActiveUsers: () => LocalizedString - /** - * Currently active network devices - */ - currentlyActiveNetworkDevices: () => LocalizedString - /** - * Total user devices: {count} - */ - totalUserDevices: (arg: { count: number }) => LocalizedString - /** - * Active network devices in {hour}h - */ - activeNetworkDevices: (arg: { hour: number }) => LocalizedString - /** - * Active users in {hour}h - */ - activeUsersFilter: (arg: { hour: number }) => LocalizedString - /** - * Active devices in {hour}h - */ - activeDevicesFilter: (arg: { hour: number }) => LocalizedString - /** - * Activity in {hour}H - */ - activityIn: (arg: { hour: number }) => LocalizedString - /** - * Network usage - */ - networkUsage: () => LocalizedString - /** - * Peak - */ - peak: () => LocalizedString - /** - * In: - */ - 'in': () => LocalizedString - /** - * Out: - */ - out: () => LocalizedString - /** - * Gateway disconnected - */ - gatewayDisconnected: () => LocalizedString - } - cardsLabels: { - /** - * Connected Users - */ - users: () => LocalizedString - /** - * Connected Network Devices - */ - devices: () => LocalizedString - } - } - connectedUsersOverview: { - /** - * Connected users - */ - pageTitle: () => LocalizedString - /** - * Currently there are no connected users - */ - noUsersMessage: () => LocalizedString - userList: { - /** - * Username - */ - username: () => LocalizedString - /** - * Device - */ - device: () => LocalizedString - /** - * Connected - */ - connected: () => LocalizedString - /** - * Device location - */ - deviceLocation: () => LocalizedString - /** - * Network usage - */ - networkUsage: () => LocalizedString - } - } - networkPage: { - /** - * Edit Location - */ - pageTitle: () => LocalizedString - /** - * + Add new location - */ - addNetwork: () => LocalizedString - controls: { - networkSelect: { - /** - * Location choice - */ - label: () => LocalizedString - } - } - } - activityOverview: { - /** - * Activity stream - */ - header: () => LocalizedString - /** - * Currently there is no activity detected - */ - noData: () => LocalizedString - } - networkConfiguration: { - messages: { - 'delete': { - /** - * Network deleted - */ - success: () => LocalizedString - /** - * Failed to delete network - */ - error: () => LocalizedString - } - } - /** - * Location configuration - */ - header: () => LocalizedString - /** - * Location import - */ - importHeader: () => LocalizedString - form: { - helpers: { - /** - * Based on this address VPN network address will be defined, eg. 10.10.10.1/24 (and VPN network will be: 10.10.10.0/24). You can optionally specify multiple addresses separated by a comma. The first address is the primary address, and this one will be used for IP address assignment for devices. The other IP addresses are auxiliary and are not managed by Defguard. - */ - address: () => LocalizedString - /** - * Public IP address or domain name to which the remote peers/users will connect to. This address will be used in the configuration for the clients, but Defguard Gateways do not bind to this address. - */ - endpoint: () => LocalizedString - /** - * Gateway public address, used by VPN users to connect - */ - gateway: () => LocalizedString - /** - * Specify the DNS resolvers to query when the wireguard interface is up. - */ - dns: () => LocalizedString - /** - * List of addresses/masks that should be routed through the VPN network. - */ - allowedIps: () => LocalizedString - /** - * By default, all users will be allowed to connect to this location. If you want to restrict access to this location to a specific group, please select it below. - */ - allowedGroups: () => LocalizedString - /** - * ACL functionality is an enterprise feature and you've exceeded the user, device or network limits to use it. In order to use this feature, purchase an enterprise license or upgrade your existing one. - */ - aclFeatureDisabled: () => LocalizedString - /** - * Clients authorized with MFA will be disconnected from the location once there has been no network activity detected between them and the VPN gateway for a length of time configured below. - */ - peerDisconnectThreshold: () => LocalizedString - locationMfaMode: { - /** - * Choose how MFA is enforced when connecting to this location: - */ - description: () => LocalizedString - /** - * Internal MFA - MFA is enforced using Defguard's built-in MFA (e.g. TOTP, WebAuthn) with internal identity - */ - internal: () => LocalizedString - /** - * External MFA - If configured (see [OpenID settings](settings)) this option uses external identity provider for MFA - */ - external: () => LocalizedString - } - } - sections: { - accessControl: { - /** - * Access Control & Firewall - */ - header: () => LocalizedString - } - mfa: { - /** - * Multi-Factor Authentication - */ - header: () => LocalizedString - } - } - messages: { - /** - * Location modified. - */ - networkModified: () => LocalizedString - /** - * Location created - */ - networkCreated: () => LocalizedString - } - fields: { - name: { - /** - * Location name - */ - label: () => LocalizedString - } - address: { - /** - * Gateway VPN IP address and netmask - */ - label: () => LocalizedString - } - endpoint: { - /** - * Gateway IP address or domain name - */ - label: () => LocalizedString - } - allowedIps: { - /** - * Allowed Ips - */ - label: () => LocalizedString - } - port: { - /** - * Gateway port - */ - label: () => LocalizedString - } - dns: { - /** - * DNS - */ - label: () => LocalizedString - } - allowedGroups: { - /** - * Allowed groups - */ - label: () => LocalizedString - /** - * All groups - */ - placeholder: () => LocalizedString - } - keepalive_interval: { - /** - * Keepalive interval [seconds] - */ - label: () => LocalizedString - } - peer_disconnect_threshold: { - /** - * Client disconnect threshold [seconds] - */ - label: () => LocalizedString - } - acl_enabled: { - /** - * Enable ACL for this location - */ - label: () => LocalizedString - } - acl_default_allow: { - /** - * Default ACL policy - */ - label: () => LocalizedString - } - location_mfa_mode: { - /** - * MFA requirement - */ - label: () => LocalizedString - } - } - controls: { - /** - * Save changes - */ - submit: () => LocalizedString - /** - * Back to Overview - */ - cancel: () => LocalizedString - /** - * Remove location - */ - 'delete': () => LocalizedString - } - } - } - gatewaySetup: { - header: { - /** - * Gateway server setup - */ - main: () => LocalizedString - /** - * Docker Based Gateway Setup - */ - dockerBasedGatewaySetup: () => LocalizedString - /** - * From Package - */ - fromPackage: () => LocalizedString - /** - * One Line Install - */ - oneLineInstall: () => LocalizedString - } - card: { - /** - * Docker based gateway setup - */ - title: () => LocalizedString - /** - * Authentication Token - */ - authToken: () => LocalizedString - } - button: { - /** - * Available Packages - */ - availablePackages: () => LocalizedString - } - controls: { - /** - * Check connection status - */ - status: () => LocalizedString - } - messages: { - /** - * Defguard requires to deploy a gateway node to control wireguard VPN on the vpn server. - More details can be found in the [documentation]({setupGatewayDocs}). - There are several ways to deploy the gateway server, - below is a Docker based example, for other examples please visit [documentation]({setupGatewayDocs}). - */ - runCommand: (arg: { setupGatewayDocs: string }) => LocalizedString - /** - * Please create the network before running the gateway process. - */ - createNetwork: () => LocalizedString - /** - * No connection established, please run provided command. - */ - noConnection: () => LocalizedString - /** - * Gateway connected. - */ - connected: () => LocalizedString - /** - * Failed to get gateway status - */ - statusError: () => LocalizedString - /** - * If you are doing one line install: https://docs.defguard.net/getting-started/one-line-install - you don't need to do anything. - */ - oneLineInstall: () => LocalizedString - /** - * Install the package available at https://github.com/DefGuard/gateway/releases/latest and configure `/etc/defguard/gateway.toml` - according to the [documentation]({setupGatewayDocs}). - */ - fromPackage: (arg: { setupGatewayDocs: string }) => LocalizedString - /** - * Token below is required to authenticate and configure the gateway node. Ensure you keep this token secure and follow the deployment instructions - provided in the [documentation]({setupGatewayDocs}) to successfully set up the gateway server. - For more details and exact steps, please refer to the [documentation]({setupGatewayDocs}). - */ - authToken: (arg: { setupGatewayDocs: string }) => LocalizedString - /** - * Below is a Docker based example. For more details and exact steps, please refer to the [documentation]({setupGatewayDocs}). - */ - dockerBasedGatewaySetup: (arg: { setupGatewayDocs: string }) => LocalizedString - } - } - loginPage: { - /** - * Enter your credentials - */ - pageTitle: () => LocalizedString - /** - * Sign in with - */ - oidcLogin: () => LocalizedString - callback: { - /** - * Go back to login - */ - 'return': () => LocalizedString - /** - * An error occurred during external OpenID login - */ - error: () => LocalizedString - } - mfa: { - /** - * Two-factor authentication - */ - title: () => LocalizedString - controls: { - /** - * Use Authenticator app instead - */ - useAuthenticator: () => LocalizedString - /** - * Use security key instead - */ - useWebauthn: () => LocalizedString - /** - * Use recovery code instead - */ - useRecoveryCode: () => LocalizedString - /** - * Use E-mail instead - */ - useEmail: () => LocalizedString - } - email: { - /** - * Use code we sent to your e-mail to proceed. - */ - header: () => LocalizedString - form: { - labels: { - /** - * Code - */ - code: () => LocalizedString - } - controls: { - /** - * Resend Code - */ - resendCode: () => LocalizedString - } - } - } - totp: { - /** - * Use code from your authentication app and click button to proceed. - */ - header: () => LocalizedString - form: { - fields: { - code: { - /** - * Enter Authenticator code - */ - placeholder: () => LocalizedString - } - } - controls: { - /** - * Use authenticator code - */ - submit: () => LocalizedString - } - } - } - recoveryCode: { - /** - * Enter one of active recovery codes and click button to log in. - */ - header: () => LocalizedString - form: { - fields: { - code: { - /** - * Recovery code - */ - placeholder: () => LocalizedString - } - } - controls: { - /** - * Use recovery code - */ - submit: () => LocalizedString - } - } - } - webauthn: { - /** - * When you are ready to authenticate, press the button below. - */ - header: () => LocalizedString - controls: { - /** - * Use security key - */ - submit: () => LocalizedString - } - messages: { - /** - * Failed to read key. Please try again. - */ - error: () => LocalizedString - } - } - } - } - wizard: { - /** - * Location setup completed - */ - completed: () => LocalizedString - configuration: { - /** - * Location created - */ - successMessage: () => LocalizedString - } - welcome: { - /** - * Welcome to location wizard! - */ - header: () => LocalizedString - /** - * Before you start using VPN you need to setup your first location. When in doubt click on icon. - */ - sub: () => LocalizedString - /** - * Setup location - */ - button: () => LocalizedString - } - navigation: { - /** - * Location setup - */ - top: () => LocalizedString - titles: { - /** - * Location setup - */ - welcome: () => LocalizedString - /** - * Chose Location setup - */ - choseNetworkSetup: () => LocalizedString - /** - * Import existing location - */ - importConfig: () => LocalizedString - /** - * Configure location - */ - manualConfig: () => LocalizedString - /** - * Map imported devices - */ - mapDevices: () => LocalizedString - } - buttons: { - /** - * Next - */ - next: () => LocalizedString - /** - * Back - */ - back: () => LocalizedString - } - } - deviceMap: { - messages: { - /** - * Devices added - */ - crateSuccess: () => LocalizedString - /** - * Please fill marked fields. - */ - errorsInForm: () => LocalizedString - } - list: { - headers: { - /** - * Device Name - */ - deviceName: () => LocalizedString - /** - * IP - */ - deviceIP: () => LocalizedString - /** - * User - */ - user: () => LocalizedString - } - } - } - wizardType: { - manual: { - /** - * Manual Configuration - */ - title: () => LocalizedString - /** - * Manual location configuration - */ - description: () => LocalizedString - } - 'import': { - /** - * Import From File - */ - title: () => LocalizedString - /** - * Import from WireGuard config file - */ - description: () => LocalizedString - } - /** - * Create location - */ - createNetwork: () => LocalizedString - } - common: { - /** - * Select - */ - select: () => LocalizedString - } - locations: { - form: { - /** - * Name - */ - name: () => LocalizedString - /** - * IP address - */ - ip: () => LocalizedString - /** - * User - */ - user: () => LocalizedString - /** - * File - */ - fileName: () => LocalizedString - /** - * Select file - */ - selectFile: () => LocalizedString - messages: { - /** - * Devices created - */ - devicesCreated: () => LocalizedString - } - validation: { - /** - * Invalid address - */ - invalidAddress: () => LocalizedString - } - } - } - } - layout: { - select: { - /** - * Add new + - */ - addNewOptionDefault: () => LocalizedString - } - } - redirectPage: { - /** - * You have been logged in - */ - title: () => LocalizedString - /** - * You will be redirected in a moment... - */ - subtitle: () => LocalizedString - } - enrollmentPage: { - /** - * Enrollment - */ - title: () => LocalizedString - controls: { - /** - * Restore default - */ - 'default': () => LocalizedString - /** - * Save changes - */ - save: () => LocalizedString - } - messages: { - edit: { - /** - * Settings changed - */ - success: () => LocalizedString - /** - * Save failed - */ - error: () => LocalizedString - } - } - /** - * Enrollment is a process by which a new employee will be able to activate their new account, create a password and configure a VPN device. You can customize it here. - */ - messageBox: () => LocalizedString - settings: { - welcomeMessage: { - /** - * Welcome message - */ - title: () => LocalizedString - /** - * This information will be displayed for user in service once enrollment is completed. We advise to insert links and explain next steps briefly. You can use same message as in the e-mail. - */ - messageBox: () => LocalizedString - } - vpnOptionality: { - /** - * VPN set optionallity - */ - title: () => LocalizedString - select: { - options: { - /** - * Optional - */ - optional: () => LocalizedString - /** - * Mandatory - */ - mandatory: () => LocalizedString - } - } - } - welcomeEmail: { - /** - * Welcome e-mail - */ - title: () => LocalizedString - subject: { - /** - * E-mail subject - */ - label: () => LocalizedString - } - /** - * This information will be sent to user once enrollment is completed. We advise to insert links and explain next steps briefly. - */ - messageBox: () => LocalizedString - controls: { - /** - * Same as welcome message - */ - duplicateWelcome: () => LocalizedString - } - } - } - } - supportPage: { - /** - * Support - */ - title: () => LocalizedString - modals: { - confirmDataSend: { - /** - * Send Support Data - */ - title: () => LocalizedString - /** - * Please confirm that you actually want to send support debug information. None of your private information will be sent (wireguard keys, email addresses, etc. will not be sent). - */ - subTitle: () => LocalizedString - /** - * Send support data - */ - submit: () => LocalizedString - } - } - debugDataCard: { - /** - * Support data - */ - title: () => LocalizedString - /** - * - If you need assistance or you were asked to generate support data by our team (for example on our Matrix support channel: **#defguard-support:teonite.com**), you have two options: - * Either you can configure SMTP settings and click "Send support data" - * Or click "Download support data" and create a bug report in our GitHub attaching this file. - - */ - body: () => LocalizedString - /** - * Download support data - */ - downloadSupportData: () => LocalizedString - /** - * Download logs - */ - downloadLogs: () => LocalizedString - /** - * Send support data - */ - sendMail: () => LocalizedString - /** - * Email sent - */ - mailSent: () => LocalizedString - /** - * Error sending email - */ - mailError: () => LocalizedString - } - supportCard: { - /** - * Support - */ - title: () => LocalizedString - /** - * - Before contacting or submitting any issues to GitHub please get familiar with Defguard documentation available at [docs.defguard.net](https://docs.defguard.net/) - - To submit: - * Bugs - please go to [GitHub](https://github.com/DefGuard/defguard/issues/new?assignees=&labels=bug&template=bug_report.md&title=) - * Feature request - please go to [GitHub](https://github.com/DefGuard/defguard/issues/new?assignees=&labels=feature&template=feature_request.md&title=) - - Any other requests you can reach us at: support@defguard.net - - */ - body: () => LocalizedString - } - } - devicesPage: { - /** - * Network Devices - */ - title: () => LocalizedString - search: { - /** - * Find - */ - placeholder: () => LocalizedString - } - bar: { - /** - * All devices - */ - itemsCount: () => LocalizedString - filters: { - } - actions: { - /** - * Add new - */ - addNewDevice: () => LocalizedString - } - } - list: { - columns: { - labels: { - /** - * Device Name - */ - name: () => LocalizedString - /** - * Location - */ - location: () => LocalizedString - /** - * IP Addresses - */ - assignedIps: () => LocalizedString - /** - * Description - */ - description: () => LocalizedString - /** - * Added By - */ - addedBy: () => LocalizedString - /** - * Add Date - */ - addedAt: () => LocalizedString - /** - * Edit - */ - edit: () => LocalizedString - } - } - edit: { - actionLabels: { - /** - * View config - */ - config: () => LocalizedString - /** - * Generate auth token - */ - generateToken: () => LocalizedString - } - } - } - } - acl: { - messageBoxes: { - aclAliasKind: { - component: { - /** - * Component - */ - name: () => LocalizedString - /** - * combined with manually configured destination fields in ACL - */ - description: () => LocalizedString - } - destination: { - /** - * Destination - */ - name: () => LocalizedString - /** - * translated into a separate set of firewall rules - */ - description: () => LocalizedString - } - } - networkSelectionIndicatorsHelper: { - /** - * - Location access **denied** by default – network traffic not explicitly defined by the rules will be blocked. - - */ - denied: () => LocalizedString - /** - * - Location access **allowed** by default – network traffic not explicitly defined by the rules will be passed. - - */ - allowed: () => LocalizedString - /** - * - Location access unmanaged (ACL disabled) - - */ - unmanaged: () => LocalizedString - } - } - /** - * Access Control List - */ - sharedTitle: () => LocalizedString - fieldsSelectionLabels: { - /** - * All ports - */ - ports: () => LocalizedString - /** - * All protocols - */ - protocols: () => LocalizedString - } - ruleStatus: { - /** - * New - */ - 'new': () => LocalizedString - /** - * Applied - */ - applied: () => LocalizedString - /** - * Pending Change - */ - modified: () => LocalizedString - /** - * Pending Deletion - */ - deleted: () => LocalizedString - /** - * Enable - */ - enable: () => LocalizedString - /** - * Enabled - */ - enabled: () => LocalizedString - /** - * Disable - */ - disable: () => LocalizedString - /** - * Disabled - */ - disabled: () => LocalizedString - /** - * Expired - */ - expired: () => LocalizedString - } - listPage: { - tabs: { - /** - * Rules - */ - rules: () => LocalizedString - /** - * Aliases - */ - aliases: () => LocalizedString - } - message: { - /** - * Change discarded - */ - changeDiscarded: () => LocalizedString - /** - * Pending change added - */ - changeAdded: () => LocalizedString - /** - * Failed to make change - */ - changeFail: () => LocalizedString - /** - * Pending changes applied - */ - applyChanges: () => LocalizedString - /** - * Failed to apply changes - */ - applyFail: () => LocalizedString - } - rules: { - modals: { - applyConfirm: { - /** - * Deploy pending changes - */ - title: () => LocalizedString - /** - * {count} changes will be deployed - */ - subtitle: (arg: { count: number }) => LocalizedString - /** - * Deploy changes - */ - submit: () => LocalizedString - } - filterGroupsModal: { - groupHeaders: { - /** - * Aliases - */ - alias: () => LocalizedString - /** - * Locations - */ - location: () => LocalizedString - /** - * Groups - */ - groups: () => LocalizedString - /** - * Status - */ - status: () => LocalizedString - } - /** - * Save Filter - */ - submit: () => LocalizedString - } - } - listControls: { - /** - * Find name - */ - searchPlaceholder: () => LocalizedString - /** - * Add new - */ - addNew: () => LocalizedString - filter: { - /** - * Filter - */ - nothingApplied: () => LocalizedString - /** - * Filters ({count}) - */ - applied: (arg: { count: number }) => LocalizedString - } - apply: { - /** - * Deploy pending changes - */ - noChanges: () => LocalizedString - /** - * Deploy pending changes ({count}) - */ - all: (arg: { count: number }) => LocalizedString - /** - * Deploy selected changes ({count}) - */ - selective: (arg: { count: number }) => LocalizedString - } - } - list: { - pendingList: { - /** - * Pending Changes - */ - title: () => LocalizedString - /** - * No pending changes - */ - noData: () => LocalizedString - /** - * No pending changes found - */ - noDataSearch: () => LocalizedString - } - deployedList: { - /** - * Deployed Rules - */ - title: () => LocalizedString - /** - * No deployed rules - */ - noData: () => LocalizedString - /** - * No deployed rules found - */ - noDataSearch: () => LocalizedString - } - headers: { - /** - * Rule name - */ - name: () => LocalizedString - /** - * ID - */ - id: () => LocalizedString - /** - * Destination - */ - destination: () => LocalizedString - /** - * Allowed - */ - allowed: () => LocalizedString - /** - * Denied - */ - denied: () => LocalizedString - /** - * Locations - */ - locations: () => LocalizedString - /** - * Status - */ - status: () => LocalizedString - /** - * Edit - */ - edit: () => LocalizedString - } - tags: { - /** - * All - */ - all: () => LocalizedString - /** - * All denied - */ - allDenied: () => LocalizedString - /** - * All allowed - */ - allAllowed: () => LocalizedString - } - editMenu: { - /** - * Discard Changes - */ - discard: () => LocalizedString - /** - * Mark for Deletion - */ - 'delete': () => LocalizedString - } - } - } - aliases: { - message: { - /** - * Pending changes applied - */ - rulesApply: () => LocalizedString - /** - * Failed to apply changes - */ - rulesApplyFail: () => LocalizedString - /** - * Alias deleted - */ - aliasDeleted: () => LocalizedString - /** - * Alias deletion failed - */ - aliasDeleteFail: () => LocalizedString - } - modals: { - applyConfirm: { - /** - * Confirm Alias Deployment - */ - title: () => LocalizedString - /** - * The updated aliases will modify the following rule(s) currently deployed on the gateway. - Please ensure these changes are intended before proceeding. - */ - message: () => LocalizedString - /** - * Affected Rules - */ - listLabel: () => LocalizedString - /** - * Deploy Changes - */ - submit: () => LocalizedString - } - deleteBlock: { - /** - * Deletion blocked - */ - title: () => LocalizedString - /** - * - This alias is currently in use by the following rule(s) and cannot be deleted. To proceed with deletion, you must first remove it from these rules({rulesCount}): - - */ - content: (arg: { rulesCount: number }) => LocalizedString - } - filterGroupsModal: { - groupLabels: { - /** - * Rules - */ - rules: () => LocalizedString - /** - * Status - */ - status: () => LocalizedString - } - } - create: { - labels: { - /** - * Alias name - */ - name: () => LocalizedString - /** - * Alias kind - */ - kind: () => LocalizedString - /** - * IPv4/6 CIDR range address - */ - ip: () => LocalizedString - /** - * Ports or Port Ranges - */ - ports: () => LocalizedString - /** - * Protocols - */ - protocols: () => LocalizedString - } - placeholders: { - /** - * All Protocols - */ - protocols: () => LocalizedString - /** - * All Ports - */ - ports: () => LocalizedString - /** - * All IP addresses - */ - ip: () => LocalizedString - } - kindOptions: { - /** - * Destination - */ - destination: () => LocalizedString - /** - * Component - */ - component: () => LocalizedString - } - controls: { - /** - * Cancel - */ - cancel: () => LocalizedString - /** - * Edit Alias - */ - edit: () => LocalizedString - /** - * Create Alias - */ - create: () => LocalizedString - } - messages: { - /** - * Alias modified - */ - modified: () => LocalizedString - /** - * Alias created - */ - created: () => LocalizedString - } - } - } - listControls: { - /** - * Find name - */ - searchPlaceholder: () => LocalizedString - /** - * Add new - */ - addNew: () => LocalizedString - filter: { - /** - * Filter - */ - nothingApplied: () => LocalizedString - /** - * Filters ({count}) - */ - applied: (arg: { count: number }) => LocalizedString - } - apply: { - /** - * Deploy pending changes - */ - noChanges: () => LocalizedString - /** - * Deploy pending changes ({count}) - */ - all: (arg: { count: number }) => LocalizedString - /** - * Deploy selected changes ({count}) - */ - selective: (arg: { count: number }) => LocalizedString - } - } - list: { - pendingList: { - /** - * Pending Changes - */ - title: () => LocalizedString - /** - * No pending changes - */ - noData: () => LocalizedString - /** - * No pending changes found - */ - noDataSearch: () => LocalizedString - } - deployedList: { - /** - * Deployed Aliases - */ - title: () => LocalizedString - /** - * No deployed aliases - */ - noData: () => LocalizedString - /** - * No deployed aliases found - */ - noDataSearch: () => LocalizedString - } - headers: { - /** - * ID - */ - id: () => LocalizedString - /** - * Alias name - */ - name: () => LocalizedString - /** - * Alias kind - */ - kind: () => LocalizedString - /** - * IPv4/6 CIDR range address - */ - ip: () => LocalizedString - /** - * Ports - */ - ports: () => LocalizedString - /** - * Protocols - */ - protocols: () => LocalizedString - /** - * Status - */ - status: () => LocalizedString - /** - * Edit - */ - edit: () => LocalizedString - /** - * Rules - */ - rules: () => LocalizedString - } - status: { - /** - * Applied - */ - applied: () => LocalizedString - /** - * Modified - */ - changed: () => LocalizedString - } - tags: { - /** - * All denied - */ - allDenied: () => LocalizedString - /** - * All allowed - */ - allAllowed: () => LocalizedString - } - editMenu: { - /** - * Discard changes - */ - discardChanges: () => LocalizedString - /** - * Delete alias - */ - 'delete': () => LocalizedString - } - } - } - } - createPage: { - formError: { - /** - * Conflicting members - */ - allowDenyConflict: () => LocalizedString - /** - * Must configure some allowed users, groups or devices - */ - allowNotConfigured: () => LocalizedString - } - infoBox: { - /** - * - Specify one or more fields (Users, Groups or Devices) to define this rule. The rule will consider all inputs provided for matching conditions. Leave any fields blank if not needed. - */ - allowInstructions: () => LocalizedString - /** - * - Specify one or more fields (IP Addresses or Ports) to define this rule. The rule will consider all inputs provided for matching conditions. Leave any fields blank if not needed. - */ - destinationInstructions: () => LocalizedString - } - message: { - /** - * Rule created and added to pending changes. - */ - create: () => LocalizedString - /** - * Rule creation failed - */ - createFail: () => LocalizedString - } - headers: { - /** - * Rule - */ - rule: () => LocalizedString - /** - * Create Rule - */ - createRule: () => LocalizedString - /** - * Allowed Users/Groups/Devices - */ - allowed: () => LocalizedString - /** - * Denied Users/Groups/Devices - */ - denied: () => LocalizedString - /** - * Destination - */ - destination: () => LocalizedString - } - labels: { - /** - * Rule name - */ - name: () => LocalizedString - /** - * Priority - */ - priority: () => LocalizedString - /** - * Status - */ - status: () => LocalizedString - /** - * Locations - */ - locations: () => LocalizedString - /** - * Allow all users - */ - allowAllUsers: () => LocalizedString - /** - * Include all locations - */ - allowAllNetworks: () => LocalizedString - /** - * Allow all network devices - */ - allowAllNetworkDevices: () => LocalizedString - /** - * Deny all users - */ - denyAllUsers: () => LocalizedString - /** - * Deny all network devices - */ - denyAllNetworkDevices: () => LocalizedString - /** - * Users - */ - users: () => LocalizedString - /** - * Groups - */ - groups: () => LocalizedString - /** - * Network devices - */ - devices: () => LocalizedString - /** - * Protocols - */ - protocols: () => LocalizedString - /** - * IPv4/6 CIDR range or address - */ - manualIp: () => LocalizedString - /** - * Ports - */ - ports: () => LocalizedString - /** - * Aliases - */ - aliases: () => LocalizedString - /** - * Expiration Date - */ - expires: () => LocalizedString - /** - * Manual Input - */ - manualInput: () => LocalizedString - } - placeholders: { - /** - * All protocols - */ - allProtocols: () => LocalizedString - /** - * All IP addresses - */ - allIps: () => LocalizedString - } - } - } - activity: { - /** - * Activity log - */ - title: () => LocalizedString - modals: { - timeRange: { - /** - * Activity time - */ - title: () => LocalizedString - } - } - list: { - /** - * All activity - */ - allLabel: () => LocalizedString - headers: { - /** - * Date - */ - date: () => LocalizedString - /** - * User - */ - user: () => LocalizedString - /** - * IP - */ - ip: () => LocalizedString - /** - * Location - */ - location: () => LocalizedString - /** - * Event - */ - event: () => LocalizedString - /** - * Module - */ - module: () => LocalizedString - /** - * Device - */ - device: () => LocalizedString - /** - * Description - */ - description: () => LocalizedString - } - noData: { - /** - * No activities present - */ - data: () => LocalizedString - /** - * No activities found - */ - search: () => LocalizedString - } - } - } - enums: { - activityLogEventType: { - /** - * User login - */ - user_login: () => LocalizedString - /** - * User login failed - */ - user_login_failed: () => LocalizedString - /** - * User MFA login - */ - user_mfa_login: () => LocalizedString - /** - * User MFA login failed - */ - user_mfa_login_failed: () => LocalizedString - /** - * Recovery code used - */ - recovery_code_used: () => LocalizedString - /** - * User logout - */ - user_logout: () => LocalizedString - /** - * User added - */ - user_added: () => LocalizedString - /** - * User removed - */ - user_removed: () => LocalizedString - /** - * User modified - */ - user_modified: () => LocalizedString - /** - * User groups modified - */ - user_groups_modified: () => LocalizedString - /** - * MFA enabled - */ - mfa_enabled: () => LocalizedString - /** - * MFA disabled - */ - mfa_disabled: () => LocalizedString - /** - * User MFA disabled - */ - user_mfa_disabled: () => LocalizedString - /** - * MFA TOTP enabled - */ - mfa_totp_enabled: () => LocalizedString - /** - * MFA TOTP disabled - */ - mfa_totp_disabled: () => LocalizedString - /** - * MFA email enabled - */ - mfa_email_enabled: () => LocalizedString - /** - * MFA email disabled - */ - mfa_email_disabled: () => LocalizedString - /** - * MFA security key added - */ - mfa_security_key_added: () => LocalizedString - /** - * MFA security key removed - */ - mfa_security_key_removed: () => LocalizedString - /** - * Device added - */ - device_added: () => LocalizedString - /** - * Device removed - */ - device_removed: () => LocalizedString - /** - * Device modified - */ - device_modified: () => LocalizedString - /** - * Network device added - */ - network_device_added: () => LocalizedString - /** - * Network device removed - */ - network_device_removed: () => LocalizedString - /** - * Network device modified - */ - network_device_modified: () => LocalizedString - /** - * Activity log stream created - */ - activity_log_stream_created: () => LocalizedString - /** - * Activity log stream modified - */ - activity_log_stream_modified: () => LocalizedString - /** - * Activity log stream removed - */ - activity_log_stream_removed: () => LocalizedString - /** - * VPN client connected - */ - vpn_client_connected: () => LocalizedString - /** - * VPN client disconnected - */ - vpn_client_disconnected: () => LocalizedString - /** - * VPN client connected to MFA location - */ - vpn_client_connected_mfa: () => LocalizedString - /** - * VPN client disconnected from MFA location - */ - vpn_client_disconnected_mfa: () => LocalizedString - /** - * VPN client failed MFA authentication - */ - vpn_client_mfa_failed: () => LocalizedString - /** - * Enrollment token added - */ - enrollment_token_added: () => LocalizedString - /** - * Enrollment started - */ - enrollment_started: () => LocalizedString - /** - * Device added - */ - enrollment_device_added: () => LocalizedString - /** - * Enrollment completed - */ - enrollment_completed: () => LocalizedString - /** - * Password reset requested - */ - password_reset_requested: () => LocalizedString - /** - * Password reset started - */ - password_reset_started: () => LocalizedString - /** - * Password reset completed - */ - password_reset_completed: () => LocalizedString - /** - * VPN location added - */ - vpn_location_added: () => LocalizedString - /** - * VPN location removed - */ - vpn_location_removed: () => LocalizedString - /** - * VPN location modified - */ - vpn_location_modified: () => LocalizedString - /** - * API token added - */ - api_token_added: () => LocalizedString - /** - * API token removed - */ - api_token_removed: () => LocalizedString - /** - * API token renamed - */ - api_token_renamed: () => LocalizedString - /** - * OpenID app added - */ - open_id_app_added: () => LocalizedString - /** - * OpenID app removed - */ - open_id_app_removed: () => LocalizedString - /** - * OpenID app modified - */ - open_id_app_modified: () => LocalizedString - /** - * OpenID app state changed - */ - open_id_app_state_changed: () => LocalizedString - /** - * OpenID provider removed - */ - open_id_provider_removed: () => LocalizedString - /** - * OpenID provider modified - */ - open_id_provider_modified: () => LocalizedString - /** - * Settings updated - */ - settings_updated: () => LocalizedString - /** - * Settings partially updated - */ - settings_updated_partial: () => LocalizedString - /** - * Default branding restored - */ - settings_default_branding_restored: () => LocalizedString - /** - * Groups bulk assigned - */ - groups_bulk_assigned: () => LocalizedString - /** - * Group added - */ - group_added: () => LocalizedString - /** - * Group modified - */ - group_modified: () => LocalizedString - /** - * Group removed - */ - group_removed: () => LocalizedString - /** - * Group member added - */ - group_member_added: () => LocalizedString - /** - * Group member removed - */ - group_member_removed: () => LocalizedString - /** - * Group members modified - */ - group_members_modified: () => LocalizedString - /** - * Webhook added - */ - web_hook_added: () => LocalizedString - /** - * Webhook modified - */ - web_hook_modified: () => LocalizedString - /** - * Webhook removed - */ - web_hook_removed: () => LocalizedString - /** - * Webhook state changed - */ - web_hook_state_changed: () => LocalizedString - /** - * Authentication key added - */ - authentication_key_added: () => LocalizedString - /** - * Authentication key removed - */ - authentication_key_removed: () => LocalizedString - /** - * Authentication key renamed - */ - authentication_key_renamed: () => LocalizedString - /** - * Password changed - */ - password_changed: () => LocalizedString - /** - * Password changed by admin - */ - password_changed_by_admin: () => LocalizedString - /** - * Password reset - */ - password_reset: () => LocalizedString - /** - * Client configuration token added - */ - client_configuration_token_added: () => LocalizedString - /** - * User SNAT binding added - */ - user_snat_binding_added: () => LocalizedString - /** - * User SNAT binding modified - */ - user_snat_binding_modified: () => LocalizedString - /** - * User SNAT binding removed - */ - user_snat_binding_removed: () => LocalizedString - } - activityLogModule: { - /** - * Defguard - */ - defguard: () => LocalizedString - /** - * Client - */ - client: () => LocalizedString - /** - * Enrollment - */ - enrollment: () => LocalizedString - /** - * VPN - */ - vpn: () => LocalizedString - } - } -} - -export type Formatters = {} diff --git a/web/src/i18n/i18n-util.async.ts b/web/src/i18n/i18n-util.async.ts deleted file mode 100644 index 25819ad3e..000000000 --- a/web/src/i18n/i18n-util.async.ts +++ /dev/null @@ -1,28 +0,0 @@ -// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. -/* eslint-disable */ - -import { initFormatters } from './formatters' -import type { Locales, Translations } from './i18n-types' -import { loadedFormatters, loadedLocales, locales } from './i18n-util' - -const localeTranslationLoaders = { - en: () => import('./en'), - ko: () => import('./ko'), - pl: () => import('./pl'), -} - -const updateDictionary = (locale: Locales, dictionary: Partial): Translations => - loadedLocales[locale] = { ...loadedLocales[locale], ...dictionary } - -export const importLocaleAsync = async (locale: Locales): Promise => - (await localeTranslationLoaders[locale]()).default as unknown as Translations - -export const loadLocaleAsync = async (locale: Locales): Promise => { - updateDictionary(locale, await importLocaleAsync(locale)) - loadFormatters(locale) -} - -export const loadAllLocalesAsync = (): Promise => Promise.all(locales.map(loadLocaleAsync)) - -export const loadFormatters = (locale: Locales): void => - void (loadedFormatters[locale] = initFormatters(locale)) diff --git a/web/src/i18n/i18n-util.sync.ts b/web/src/i18n/i18n-util.sync.ts deleted file mode 100644 index e3a5b51c6..000000000 --- a/web/src/i18n/i18n-util.sync.ts +++ /dev/null @@ -1,28 +0,0 @@ -// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. -/* eslint-disable */ - -import { initFormatters } from './formatters' -import type { Locales, Translations } from './i18n-types' -import { loadedFormatters, loadedLocales, locales } from './i18n-util' - -import en from './en' -import ko from './ko' -import pl from './pl' - -const localeTranslations = { - en, - ko, - pl, -} - -export const loadLocale = (locale: Locales): void => { - if (loadedLocales[locale]) return - - loadedLocales[locale] = localeTranslations[locale] as unknown as Translations - loadFormatters(locale) -} - -export const loadAllLocales = (): void => locales.forEach(loadLocale) - -export const loadFormatters = (locale: Locales): void => - void (loadedFormatters[locale] = initFormatters(locale)) diff --git a/web/src/i18n/i18n-util.ts b/web/src/i18n/i18n-util.ts deleted file mode 100644 index 65ae07091..000000000 --- a/web/src/i18n/i18n-util.ts +++ /dev/null @@ -1,39 +0,0 @@ -// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. -/* eslint-disable */ - -import { i18n as initI18n, i18nObject as initI18nObject, i18nString as initI18nString } from 'typesafe-i18n' -import type { LocaleDetector } from 'typesafe-i18n/detectors' -import type { LocaleTranslationFunctions, TranslateByString } from 'typesafe-i18n' -import { detectLocale as detectLocaleFn } from 'typesafe-i18n/detectors' -import { initExtendDictionary } from 'typesafe-i18n/utils' -import type { Formatters, Locales, Translations, TranslationFunctions } from './i18n-types' - -export const baseLocale: Locales = 'en' - -export const locales: Locales[] = [ - 'en', - 'ko', - 'pl' -] - -export const isLocale = (locale: string): locale is Locales => locales.includes(locale as Locales) - -export const loadedLocales: Record = {} as Record - -export const loadedFormatters: Record = {} as Record - -export const extendDictionary = initExtendDictionary() - -export const i18nString = (locale: Locales): TranslateByString => initI18nString(locale, loadedFormatters[locale]) - -export const i18nObject = (locale: Locales): TranslationFunctions => - initI18nObject( - locale, - loadedLocales[locale], - loadedFormatters[locale] - ) - -export const i18n = (): LocaleTranslationFunctions => - initI18n(loadedLocales, loadedFormatters) - -export const detectLocale = (...detectors: LocaleDetector[]): Locales => detectLocaleFn(baseLocale, locales, ...detectors) diff --git a/web/src/i18n/ko/index.ts b/web/src/i18n/ko/index.ts deleted file mode 100644 index 62ee06aff..000000000 --- a/web/src/i18n/ko/index.ts +++ /dev/null @@ -1,1725 +0,0 @@ -import { deepmerge } from 'deepmerge-ts'; -import { PartialDeep } from 'type-fest'; - -import en from '../en'; -import { Translation } from '../i18n-types'; - -const translation: PartialDeep = { - common: { - conditions: { - or: '또는', - and: '그리고', - equal: '같음', - }, - controls: { - next: '다음', - back: '뒤로', - cancel: '취소', - confirm: '확인', - submit: '제출', - close: '닫기', - select: '선택', - finish: '완료', - saveChanges: '변경 사항 저장', - save: '저장', - RestoreDefault: '기본값 복원', - delete: '삭제', - rename: '이름 변경', - copy: '복사', - edit: '편집', - }, - key: '키', - name: '이름', - }, - messages: { - error: '오류가 발생했습니다.', - success: '작업이 성공했습니다', - errorVersion: '애플리케이션 버전을 가져오지 못했습니다.', - insecureContext: '컨텍스트가 안전하지 않습니다.', - details: '상세내용:', - clipboard: { - error: '클립보드에 액세스할 수 없습니다.', - success: '클립보드에 복사되었습니다.', - }, - }, - modals: { - addGroup: { - title: '그룹 추가', - selectAll: '모든 사용자 선택', - groupName: '그룹 이름', - searchPlaceholder: '필터/검색', - submit: '그룹 생성', - }, - editGroup: { - title: '그룹 편집', - selectAll: '모든 사용자 선택', - groupName: '그룹 이름', - searchPlaceholder: '필터/검색', - submit: '그룹 업데이트', - }, - deleteGroup: { - title: '{name} 그룹 삭제', - subTitle: '이 작업은 이 그룹을 영구적으로 삭제합니다.', - locationListHeader: '이 그룹은 현재 다음 VPN 위치에 할당되어 있습니다:', - locationListFooter: `이것이 주어진 위치에 허용된 유일한 그룹인 경우, 해당 위치는 모든 사용자가 액세스할 수 있게 됩니다.`, - submit: '그룹 삭제', - cancel: '취소', - }, - deviceConfig: { - title: '장치 VPN 구성', - }, - changePasswordSelf: { - title: '비밀번호 변경', - messages: { - success: '비밀번호가 변경되었습니다', - error: '비밀번호 변경에 실패했습니다', - }, - form: { - labels: { - newPassword: '새 비밀번호', - oldPassword: '현재 비밀번호', - repeat: '새 비밀번호 확인', - }, - }, - controls: { - submit: '비밀번호 변경', - cancel: '취소', - }, - }, - startEnrollment: { - title: '등록 시작', - desktopTitle: '데스크톱 활성화', - messages: { - success: '사용자 등록이 시작되었습니다', - successDesktop: '데스크톱 구성이 시작되었습니다', - error: '사용자 등록을 시작하지 못했습니다', - errorDesktop: '데스크톱 활성화를 시작하지 못했습니다', - }, - form: { - email: { - label: '이메일', - }, - mode: { - options: { - email: '이메일로 토큰 보내기', - manual: '직접 토큰 전달', - }, - }, - submit: '등록 시작', - submitDesktop: '데스크톱 활성화', - smtpDisabled: - '이메일로 토큰을 보내려면 SMTP를 구성하십시오. 설정 -> SMTP로 이동하십시오.', - }, - tokenCard: { - title: '활성화 토큰', - }, - urlCard: { - title: 'Defguard 인스턴스 URL', - }, - }, - deleteNetwork: { - title: '{name} 위치 삭제', - subTitle: '이 작업은 이 위치를 영구적으로 삭제합니다.', - submit: '위치 삭제', - cancel: '취소', - }, - changeWebhook: { - messages: { - success: 'Webhook이 변경되었습니다.', - }, - }, - manageWebAuthNKeys: { - title: '보안 키', - messages: { - deleted: 'WebAuthN 키가 삭제되었습니다.', - duplicateKeyError: '키가 이미 등록되어 있습니다', - }, - infoMessage: ` -

- 보안 키는 인증 코드 대신 2단계 인증으로 사용될 수 있습니다. - - 보안 키 구성에 대해 자세히 알아보세요. -

-`, - form: { - messages: { - success: '보안 키가 추가되었습니다.', - }, - fields: { - name: { - label: '새 키 이름', - }, - }, - controls: { - submit: '새 키 추가', - }, - }, - }, - recoveryCodes: { - title: '복구 코드', - submit: '코드를 저장했습니다', - messages: { - copied: '코드가 복사되었습니다.', - }, - infoMessage: ` -

- 복구 코드는 비밀번호와 동일한 수준의 주의를 기울여 취급하십시오! - - Lastpass, bitwarden 또는 Keeper와 같은 비밀번호 관리자를 사용하여 저장하는 것을 권장합니다. -

-`, - }, - registerTOTP: { - title: 'Authenticator 앱 설정', - infoMessage: ` -

- MFA를 설정하려면, 이 QR 코드를 인증 앱으로 스캔한 다음, - 아래 필드에 코드를 입력하세요: -

-`, - messages: { - totpCopied: 'TOTP 경로가 복사되었습니다.', - success: 'TOTP가 활성화되었습니다', - }, - copyPath: 'TOTP 경로 복사', - form: { - fields: { - code: { - label: 'Authenticator 코드', - error: '코드가 유효하지 않습니다', - }, - }, - controls: { - submit: '코드 확인', - }, - }, - }, - registerEmailMFA: { - title: '이메일 MFA 설정', - infoMessage: ` -

- MFA를 설정하려면 계정 이메일: {email}로 전송된 코드를 입력하세요 -

-`, - messages: { - success: '이메일 MFA가 활성화되었습니다', - resend: '인증 코드가 재전송되었습니다', - }, - form: { - fields: { - code: { - label: '이메일 코드', - error: '코드가 유효하지 않습니다', - }, - }, - controls: { - submit: '코드 확인', - resend: '이메일 재전송', - }, - }, - }, - editDevice: { - title: '장치 편집', - messages: { - success: '장치가 업데이트되었습니다.', - }, - form: { - fields: { - name: { - label: '장치 이름', - }, - publicKey: { - label: '장치 공개 키 (WireGuard)', - }, - }, - controls: { - submit: '장치 편집', - }, - }, - }, - deleteDevice: { - title: '장치 삭제', - message: '{deviceName} 장치를 삭제하시겠습니까?', - submit: '장치 삭제', - messages: { - success: '장치가 삭제되었습니다.', - }, - }, - keyDetails: { - title: 'YubiKey 세부 정보', - downloadAll: '모든 키 다운로드', - }, - deleteUser: { - title: '계정 삭제', - controls: { - submit: '계정 삭제', - }, - message: '{username} 계정을 영구적으로 삭제하시겠습니까?', - messages: { - success: '{username}이(가) 삭제되었습니다.', - }, - }, - disableUser: { - title: '계정 비활성화', - controls: { - submit: '계정 비활성화', - }, - message: '{username} 계정을 비활성화하시겠습니까?', - messages: { - success: '{username}이(가) 비활성화되었습니다.', - }, - }, - enableUser: { - title: '계정 활성화', - controls: { - submit: '계정 활성화', - }, - message: '{username} 계정을 활성화하시겠습니까?', - messages: { - success: '{username}이(가) 활성화되었습니다.', - }, - }, - deleteProvisioner: { - title: '프로비저너 삭제', - controls: { - submit: '프로비저너 삭제', - }, - message: '{id} 프로비저너를 삭제하시겠습니까?', - messages: { - success: '{provisioner}이(가) 삭제되었습니다.', - }, - }, - changeUserPassword: { - messages: { - success: '비밀번호가 변경되었습니다.', - }, - title: '사용자 비밀번호 변경', - form: { - controls: { - submit: '새 비밀번호 저장', - }, - fields: { - newPassword: { - label: '새 비밀번호', - }, - confirmPassword: { - label: '비밀번호 다시 입력', - }, - }, - }, - }, - provisionKeys: { - title: 'Yubikey 프로비저닝:', - warning: '이 작업은 yubikey의 openpgp 애플리케이션을 삭제하고 재구성합니다.', - infoBox: `선택한 프로비저너에는 프로비저닝할 깨끗한 YubiKey가 - 연결되어 있어야 합니다. 사용된 YubiKey를 청소하려면 프로비저닝하기 전에 - gpg --card-edit 를 실행하십시오.`, - selectionLabel: '다음 프로비저너 중 하나를 선택하여 YubiKey를 프로비저닝하십시오:', - noData: { - workers: '작업자를 찾을 수 없습니다. 대기 중...', - }, - controls: { - submit: 'YubiKey 프로비저닝', - }, - messages: { - success: '키가 프로비저닝되었습니다', - errorStatus: '작업자 상태를 가져오는 중 오류가 발생했습니다.', - }, - }, - addUser: { - title: '새 사용자 추가', - messages: { - userAdded: '사용자가 추가되었습니다', - }, - form: { - submit: '사용자 추가', - fields: { - username: { - placeholder: '로그인', - label: '로그인', - }, - password: { - placeholder: '비밀번호', - label: '비밀번호', - }, - email: { - placeholder: '사용자 이메일', - label: '사용자 이메일', - }, - firstName: { - placeholder: '이름', - label: '이름', - }, - lastName: { - placeholder: '성', - label: '성', - }, - phone: { - placeholder: '전화번호', - label: '전화번호', - }, - enableEnrollment: { - label: '등록 프로세스 사용', - link: '자세한 정보는 여기를 참고하세요', - }, - }, - }, - }, - webhookModal: { - title: { - addWebhook: '웹훅 추가.', - editWebhook: '웹훅 편집', - }, - messages: { - clientIdCopy: '클라이언트 ID가 복사되었습니다.', - clientSecretCopy: '클라이언트 암호가 복사되었습니다.', - }, - form: { - triggers: '트리거 이벤트:', - messages: { - successAdd: '웹훅이 생성되었습니다.', - successModify: '웹훅이 수정되었습니다.', - }, - error: { - urlRequired: 'URL이 필요합니다.', - validUrl: '유효한 URL이어야 합니다.', - scopeValidation: '최소 하나의 트리거가 있어야 합니다.', - tokenRequired: '토큰이 필요합니다.', - }, - fields: { - description: { - label: '설명', - placeholder: '새 사용자 생성 시 gmail 계정을 생성하는 웹훅', - }, - token: { - label: '비밀 토큰', - placeholder: '인증 토큰', - }, - url: { - label: '웹훅 URL', - placeholder: 'https://example.com/webhook', - }, - userCreated: { - label: '새 사용자 생성됨', - }, - userDeleted: { - label: '사용자 삭제됨', - }, - userModified: { - label: '사용자 수정됨', - }, - hwkeyProvision: { - label: '사용자 Yubikey 프로비저닝', - }, - }, - }, - }, - deleteWebhook: { - title: '웹훅 삭제', - message: '{name} 웹훅을 삭제하시겠습니까?', - submit: '삭제', - messages: { - success: '웹훅이 삭제되었습니다.', - }, - }, - }, - addDevicePage: { - title: '장치 추가', - helpers: { - setupOpt: `이 마법사를 사용하여 장치를 추가할 수 있습니다. 당사의 기본 애플리케이션인 "defguard" 또는 다른 WireGuard 클라이언트를 선택하세요. 잘 모르시겠다면 간편하게 defguard를 사용하는 것을 권장합니다.`, - client: `defguard 데스크톱 클라이언트는 여기에서 다운로드하고 이 가이드를 따르세요.`, - }, - messages: { - deviceAdded: '장치가 추가되었습니다', - }, - steps: { - configDevice: { - title: '장치 구성', - messages: { - copyConfig: '구성이 클립보드에 복사되었습니다', - }, - helpers: { - warningAutoMode: ` -

- 개인 키를 저장하지 않으므로 - 지금 구성을 다운로드해야 합니다. - 이 페이지가 닫히면 전체 구성 파일(개인 키 포함, 빈 템플릿만)을 - 가져올 수 없습니다. -

-`, - warningManualMode: ` -

- 여기에 제공된 구성에는 개인 키가 포함되어 있지 않으며 공개 키를 사용하여 채워져 있습니다. 구성이 제대로 작동하려면 직접 교체해야 합니다. -

-`, - warningNoNetworks: '액세스할 수 있는 네트워크가 없습니다.', - qrHelper: ` -

- 이 QR 코드를 스캔하여 wireguard 애플리케이션으로 장치를 더 빠르게 설정할 수 있습니다. -

`, - }, - qrInfo: - '아래 제공된 구성 파일을 QR 코드를 스캔하거나 장치의 WireGuard 인스턴스에 파일로 가져와서 사용하세요.', - inputNameLabel: '장치 이름', - qrLabel: 'WireGuard 구성 파일', - }, - setupDevice: { - title: 'VPN 장치 생성', - infoMessage: ` -

- 장치에서 WireGuardVPN을 구성해야 합니다. 방법을 모르는 경우  - 문서를 참조하세요. -

-`, - options: { - auto: '키 쌍 생성', - manual: '내 공개 키 사용', - }, - form: { - fields: { - name: { - label: '장치 이름', - }, - publicKey: { - label: '공개 키 제공', - }, - }, - errors: { - name: { - duplicatedName: '이 이름을 가진 장치가 이미 존재합니다', - }, - }, - }, - }, - copyToken: { - title: '클라이언트 활성화', - tokenCardTitle: '활성화 토큰', - urlCardTitle: 'Defguard 인스턴스 URL', - }, - }, - }, - userPage: { - title: { - view: '사용자 프로필', - edit: '사용자 프로필 편집', - }, - messages: { - editSuccess: '사용자가 업데이트되었습니다.', - failedToFetchUserData: '사용자 정보를 가져올 수 없습니다.', - passwordResetEmailSent: '비밀번호 재설정 이메일이 전송되었습니다.', - }, - userDetails: { - header: '프로필 세부 정보', - messages: { - deleteApp: '앱 및 모든 토큰이 삭제되었습니다.', - }, - warningModals: { - title: '경고', - content: { - usernameChange: `사용자 이름을 변경하면 Defguard를 사용하여 로그인한 서비스에 큰 영향을 미칩니다. 사용자 이름을 변경하면 사용자가 애플리케이션에 대한 액세스 권한을 잃을 수 있습니다(애플리케이션에서 해당 사용자를 인식하지 못하기 때문에). 계속 진행하시겠습니까?`, - emailChange: `외부 OpenID Connect(OIDC) 공급자를 사용하여 사용자를 인증하는 경우 사용자의 이메일 주소를 변경하면 Defguard에 로그인하는 기능에 큰 영향을 미칠 수 있습니다. 계속 진행하시겠습니까?`, - }, - buttons: { - proceed: '진행', - cancel: '취소', - }, - }, - fields: { - username: { - label: '사용자 이름', - }, - firstName: { - label: '이름', - }, - lastName: { - label: '성', - }, - phone: { - label: '전화번호', - }, - email: { - label: '이메일', - }, - status: { - label: '상태', - active: '활성', - disabled: '비활성', - }, - groups: { - label: '사용자 그룹', - noData: '그룹 없음', - }, - apps: { - label: '승인된 앱', - noData: '승인된 앱 없음', - }, - }, - }, - userAuthInfo: { - header: '비밀번호 및 인증', - password: { - header: '비밀번호 설정', - changePassword: '비밀번호 변경', - }, - recovery: { - header: '복구 옵션', - codes: { - label: '복구 코드', - viewed: '조회됨', - }, - }, - mfa: { - header: '이중 인증 방법', - edit: { - disable: 'MFA 비활성화', - }, - messages: { - mfaDisabled: 'MFA가 비활성화되었습니다.', - OTPDisabled: '일회용 비밀번호가 비활성화되었습니다.', - EmailMFADisabled: '이메일 MFA가 비활성화되었습니다.', - changeMFAMethod: 'MFA 방법이 변경되었습니다', - }, - securityKey: { - singular: '보안 키', - plural: '보안 키', - }, - default: '기본값', - enabled: '활성화됨', - disabled: '비활성화됨', - labels: { - totp: '시간 기반 일회용 비밀번호', - email: '이메일', - webauth: '보안 키', - }, - editMode: { - enable: '활성화', - disable: '비활성화', - makeDefault: '기본값으로 설정', - webauth: { - manage: '보안 키 관리', - }, - }, - }, - }, - controls: { - editButton: '프로필 편집', - deleteAccount: '계정 삭제', - }, - devices: { - header: '사용자 장치', - addDevice: { - web: '새 장치 추가', - desktop: '이 장치 추가', - }, - card: { - labels: { - publicIP: '공개 IP', - connectedThrough: '연결 방식', - connectionDate: '연결 날짜', - lastLocation: '마지막 연결 위치', - lastConnected: '마지막 연결', - assignedIp: '할당된 IP', - active: '활성', - noData: '연결된 적 없음', - }, - edit: { - edit: '장치 편집', - delete: '장치 삭제', - showConfigurations: '구성 보기', - }, - }, - }, - yubiKey: { - header: '사용자 YubiKey', - provision: 'YubiKey 프로비저닝', - keys: { - pgp: 'PGP 키', - ssh: 'SSH 키', - }, - noLicense: { - moduleName: 'YubiKey 모듈', - line1: 'YubiKey 관리 및 프로비저닝을 위한 엔터프라이즈 모듈입니다.', - line2: '', - }, - }, - authenticationKeys: { - header: '사용자 인증 키', - addKey: '새 키 추가', - keysList: { - common: { - rename: '이름 변경', - key: '키', - download: '다운로드', - copy: '복사', - serialNumber: '시리얼 번호', - delete: '삭제', - }, - }, - deleteModal: { - title: '인증 키 삭제', - confirmMessage: '{name} 키가 영구적으로 삭제됩니다.', - }, - addModal: { - header: '새 인증 키 추가', - keyType: '키 유형', - keyForm: { - placeholders: { - title: '키 이름', - key: { - ssh: 'ssh-rsa, ecdsa-sha2-nistp256, ... 로 시작', - gpg: '-----BEGIN PGP PUBLIC KEY BLOCK----- 로 시작', - }, - }, - labels: { - title: '이름', - key: '키', - }, - submit: '{name} 키 추가', - }, - yubikeyForm: { - selectWorker: { - info: '이 작업은 YubiKey의 openpgp 애플리케이션을 삭제하고 재구성합니다.', - selectLabel: - '다음 프로비저너 중 하나를 선택하여 YubiKey를 프로비저닝하십시오', - noData: '현재 등록된 작업자가 없습니다.', - available: '사용 가능', - unavailable: '사용 불가', - }, - provisioning: { - inProgress: '프로비저닝 진행 중, 잠시 기다려 주세요.', - error: '프로비저닝 실패!', - success: 'Yubikey가 성공적으로 프로비저닝되었습니다', - }, - submit: 'Yubikey 프로비저닝', - }, - messages: { - keyAdded: '키가 추가되었습니다.', - keyExists: '키가 이미 추가되었습니다.', - unsupportedKeyFormat: '지원되지 않는 키 형식입니다.', - genericError: '키를 추가할 수 없습니다. 나중에 다시 시도하십시오.', - }, - }, - }, - }, - usersOverview: { - pageTitle: '사용자', - search: { - placeholder: '사용자 찾기', - }, - filterLabels: { - all: '모든 사용자', - admin: '관리자만', - users: '사용자만', - }, - usersCount: '모든 사용자', - addNewUser: '새 추가', - list: { - headers: { - name: '사용자 이름', - username: '로그인', - phone: '전화', - actions: '작업', - }, - editButton: { - changePassword: '비밀번호 변경', - edit: '계정 편집', - addYubikey: 'YubiKey 추가', - addSSH: 'SSH 키 추가', - addGPG: 'GPG 키 추가', - delete: '계정 삭제', - startEnrollment: '등록 시작', - activateDesktop: '데스크톱 클라이언트 구성', - resetPassword: '비밀번호 재설정', - }, - }, - }, - navigation: { - bar: { - overview: 'VPN 개요', - users: '사용자', - provisioners: 'YubiKeys', - webhooks: 'Webhooks', - openId: 'OpenID 앱', - myProfile: '내 프로필', - settings: '설정', - logOut: '로그아웃', - enrollment: '등록', - support: '지원', - groups: '그룹', - }, - mobileTitles: { - groups: '그룹', - wizard: 'Location 생성', - users: '사용자', - settings: '설정', - user: '사용자 프로필', - provisioners: 'Yubikey', - webhooks: 'Webhooks', - openId: 'OpenId 앱', - overview: '위치 개요', - networkSettings: '위치 편집', - enrollment: '등록', - support: '지원', - }, - copyright: 'Copyright ©2023-2025', - version: { - open: '애플리케이션 버전: {version}', - closed: 'v{version}', - }, - }, - form: { - download: '다운로드', - copy: '복사', - saveChanges: '변경 사항 저장', - submit: '제출', - login: '로그인', - cancel: '취소', - close: '닫기', - placeholders: { - password: '비밀번호', - username: '사용자 이름', - }, - error: { - forbiddenCharacter: '필드에 금지된 문자가 포함되어 있습니다.', - usernameTaken: '사용자 이름이 이미 사용 중입니다.', - invalidKey: '키가 유효하지 않습니다.', - invalid: '필드가 유효하지 않습니다.', - required: '필드는 필수입니다.', - invalidCode: '제출된 코드가 유효하지 않습니다.', - maximumLength: '최대 길이를 초과했습니다.', - minimumLength: '최소 길이에 도달하지 않았습니다.', - noSpecialChars: '특수 문자는 허용되지 않습니다.', - oneDigit: '숫자 하나가 필요합니다.', - oneSpecial: '특수 문자가 필요합니다.', - oneUppercase: '대문자 하나가 필요합니다.', - oneLowercase: '소문자 하나가 필요합니다.', - portMax: '최대 포트는 65535입니다.', - endpoint: '유효한 엔드포인트를 입력하세요.', - address: '유효한 주소를 입력하세요.', - validPort: '유효한 포트를 입력하세요.', - validCode: '코드는 6자리여야 합니다.', - allowedIps: '유효한 IP 또는 도메인만 허용됩니다.', - startFromNumber: '숫자로 시작할 수 없습니다.', - repeat: `필드가 일치하지 않습니다.`, - number: '유효한 숫자를 입력해야 합니다.', - minimumValue: `{value}의 최솟값에 도달하지 않았습니다.`, - maximumValue: '{value}의 최댓값을 초과했습니다.', - tooManyBadLoginAttempts: `잘못된 로그인 시도가 너무 많습니다. 몇 분 후에 다시 시도하십시오.`, - }, - floatingErrors: { - title: '다음을 수정하십시오:', - }, - }, - components: { - deviceConfigsCard: { - cardTitle: '위치에 대한 WireGuard 구성:', - messages: { - copyConfig: '클립보드에 구성이 복사되었습니다.', - }, - }, - gatewaysStatus: { - label: '게이트웨이', - states: { - error: '연결 정보를 가져오는 데 실패했습니다.', - }, - messages: { - error: '게이트웨이 상태를 가져오지 못했습니다', - deleteError: '게이트웨이를 삭제하지 못했습니다', - }, - }, - noLicenseBox: { - footer: { - get: '엔터프라이즈 라이선스 받기', - contact: '연락처:', - }, - }, - }, - settingsPage: { - title: '설정', - tabs: { - smtp: 'SMTP', - global: '전역 설정', - ldap: 'LDAP', - openid: 'OpenID', - enterprise: '엔터프라이즈 기능', - }, - messages: { - editSuccess: '설정이 업데이트되었습니다', - challengeSuccess: '챌린지 메시지가 변경되었습니다', - }, - enterpriseOnly: { - title: '이 기능은 Defguard Enterprise에서만 사용할 수 있습니다.', - subtitle: '자세한 내용은 ', - website: '웹사이트', - }, - ldapSettings: { - title: 'LDAP 설정', - form: { - labels: { - ldap_url: 'URL', - ldap_bind_username: '바인드 사용자 이름', - ldap_bind_password: '바인드 비밀번호', - ldap_member_attr: '멤버 속성', - ldap_username_attr: '사용자 이름 속성', - ldap_user_obj_class: '사용자 객체 클래스', - ldap_user_search_base: '사용자 검색 기준', - ldap_groupname_attr: '그룹 이름 속성', - ldap_group_search_base: '그룹 검색 기준', - ldap_group_member_attr: '그룹 멤버 속성', - ldap_group_obj_class: '그룹 객체 클래스', - }, - delete: '구성 삭제', - }, - test: { - title: 'LDAP 연결 테스트', - submit: '테스트', - messages: { - success: 'LDAP 연결 성공', - error: 'LDAP 연결 거부됨', - }, - }, - }, - openIdSettings: { - general: { - title: '외부 OpenID 설정', - helper: '여기에서 Defguard 인스턴스의 일반 OpenID 동작을 변경할 수 있습니다.', - createAccount: { - label: '외부 OpenID를 통해 처음 로그인할 때 사용자 계정을 자동으로 생성합니다.', - helper: - '이 옵션을 활성화하면 Defguard는 외부 OpenID 공급자를 사용하여 처음 로그인하는 사용자에 대한 새 계정을 자동으로 생성합니다. 그렇지 않으면 관리자가 먼저 사용자 계정을 생성해야 합니다.', - }, - }, - form: { - title: '외부 OpenID 클라이언트 설정', - helper: - '여기에서 외부 OpenID 공급자가 제공한 값으로 OpenID 클라이언트 설정을 구성할 수 있습니다.', - custom: '사용자 정의', - documentation: '설명서', - delete: '공급자 삭제', - labels: { - provider: { - label: '공급자', - helper: - 'OpenID 공급자를 선택하세요. 사용자 정의 공급자를 사용하고 직접 기본 URL을 입력할 수 있습니다.', - }, - client_id: { - label: '클라이언트 ID', - helper: 'OpenID 공급자가 제공한 클라이언트 ID입니다.', - }, - client_secret: { - label: '클라이언트 보안 비밀', - helper: 'OpenID 공급자가 제공한 클라이언트 보안 비밀입니다.', - }, - base_url: { - label: '기본 URL', - helper: - 'OpenID 공급자의 기본 URL입니다(예: https://accounts.google.com). 자세한 정보 및 예는 설명서를 확인하십시오.', - }, - }, - }, - }, - modulesVisibility: { - header: '모듈 가시성', - helper: `

- 사용하지 않는 모듈이 있는 경우 해당 모듈의 가시성을 비활성화할 수 있습니다. -

- - 자세한 내용은 설명서를 참조하십시오. - `, - fields: { - wireguard_enabled: { - label: 'WireGuard VPN', - }, - webhooks_enabled: { - label: '웹훅', - }, - worker_enabled: { - label: 'Yubikey 프로비저닝', - }, - openid_enabled: { - label: 'OpenID Connect', - }, - }, - }, - defaultNetworkSelect: { - header: '기본 위치 보기', - helper: `

여기에서 기본 위치 보기를 변경할 수 있습니다.

- - 자세한 내용은 설명서를 참조하십시오. - `, - filterLabels: { - grid: '그리드 보기', - list: '목록 보기', - }, - }, - instanceBranding: { - header: '인스턴스 브랜딩', - form: { - title: '이름 및 로고:', - fields: { - instanceName: { - label: '인스턴스 이름', - placeholder: 'Defguard', - }, - mainLogoUrl: { - label: '로그인 로고 url', - helper: '

최대 사진 크기는 250x100 px입니다

', - placeholder: '기본 이미지', - }, - navLogoUrl: { - label: '메뉴 및 탐색 작은 로고', - helper: '

최대 사진 크기는 100x100 px입니다

', - placeholder: '기본 이미지', - }, - }, - controls: { - restoreDefault: '기본값 복원', - submit: '변경 사항 저장', - }, - }, - helper: ` -

- 여기에서 defguard 인스턴스의 로고 및 이름 url을 - 추가할 수 있습니다. defguard 대신 표시됩니다. -

- - 자세한 내용은 설명서를 참조하십시오. - - `, - }, - license: { - header: '엔터프라이즈', - helpers: { - enterpriseHeader: { - text: '여기에서 Defguard Enterprise 버전 라이선스를 관리할 수 있습니다.', - link: 'Defguard Enterprise에 대한 자세한 내용은 웹사이트를 방문하십시오.', - }, - licenseKey: { - text: '아래에 Defguard Enterprise 라이선스 키를 입력하세요. 라이선스 구매 후 이메일을 통해 받아야 합니다.', - link: '라이선스는 여기에서 구입할 수 있습니다.', - }, - }, - form: { - title: '라이선스', - fields: { - key: { - label: '라이선스 키', - placeholder: 'Defguard 라이선스 키', - }, - }, - }, - licenseInfo: { - title: '라이선스 정보', - types: { - subscription: { - label: '구독', - helper: '정기적으로 자동 갱신되는 라이선스', - }, - offline: { - label: '오프라인', - helper: '라이선스는 만료 날짜까지 유효하며 자동으로 갱신되지 않습니다', - }, - }, - fields: { - type: { - label: '유형', - }, - validUntil: { - label: '유효 기간', - }, - }, - }, - }, - smtp: { - form: { - title: 'SMTP 구성', - fields: { - encryption: { - label: '암호화', - }, - server: { - label: '서버 주소', - placeholder: '주소', - }, - port: { - label: '서버 포트', - placeholder: '포트', - }, - user: { - label: '서버 사용자 이름', - placeholder: '사용자 이름', - }, - password: { - label: '서버 비밀번호', - placeholder: '비밀번호', - }, - sender: { - label: '보내는 사람 이메일 주소', - placeholder: '주소', - helper: ` -

- 시스템 메시지는 이 주소에서 발송됩니다. - 예: no-reply@my-company.com. -

- `, - }, - }, - controls: { - submit: '변경 사항 저장', - }, - }, - delete: '구성 삭제', - testForm: { - title: '테스트 이메일 보내기', - fields: { - to: { - label: '주소', - placeholder: '주소', - }, - }, - controls: { - submit: '보내기', - success: '테스트 이메일 전송됨', - error: '이메일 전송 오류', - }, - }, - helper: ` -

- 여기에서 사용자에게 시스템 메시지를 보내는 데 사용되는 SMTP 서버를 구성할 수 있습니다. -

- `, - }, - enrollment: { - helper: - '등록은 신규 직원이 새 계정을 활성화 및 비밀번호를 생성하고, VPN 장치를 구성할 수 있도록 하는 프로세스입니다.', - vpnOptionality: { - header: 'VPN 단계 선택 사항', - helper: - '등록 중 VPN 장치 생성을 선택 사항 또는 필수 사항으로 선택할 수 있습니다.', - }, - welcomeMessage: { - header: '환영 메시지', - helper: ` -

이 텍스트 입력란에서는 Markdown을 사용할 수 있습니다:

-
    -
  • 제목은 해시 #로 시작합니다
  • -
  • 별표를 사용하여 *이탤릭체*를 만듭니다
  • -
  • 별표 두 개를 사용하여 **굵게** 만듭니다
  • -
- `, - }, - welcomeEmail: { - header: '환영 이메일', - helper: ` -

이 텍스트 입력란에서는 Markdown을 사용할 수 있습니다:

-
    -
  • 제목은 해시 #로 시작합니다
  • -
  • 별표를 사용하여 *이탤릭체*를 만듭니다
  • -
  • 별표 두 개를 사용하여 **굵게** 만듭니다
  • -
- `, - }, - form: { - controls: { - submit: '변경 사항 저장', - }, - welcomeMessage: { - helper: - '등록이 완료되면 사용자에게 이 정보가 표시됩니다. 관련 링크를 삽입하고 다음 단계를 간략하게 설명하는 것이 좋습니다.', - placeholder: '환영 메시지를 입력하세요', - }, - welcomeEmail: { - helper: - '등록이 완료되면 사용자에게 이 정보가 전송됩니다. 관련 링크를 삽입하고 다음 단계를 간략하게 설명하는 것이 좋습니다. 환영 메시지를 여기에서 다시 사용할 수 있습니다.', - placeholder: '환영 이메일을 입력하세요', - }, - welcomeEmailSubject: { - label: '제목', - }, - useMessageAsEmail: { - label: '환영 메시지와 동일하게', - }, - }, - }, - enterprise: { - header: '엔터프라이즈 기능', - helper: '

여기에서 엔터프라이즈 설정을 변경할 수 있습니다.

', - fields: { - deviceManagement: { - label: '사용자가 자신의 장치를 관리하는 기능 비활성화', - helper: - '이 옵션을 활성화하면 관리자 그룹의 사용자만 사용자 프로필에서 장치를 관리할 수 있습니다(다른 모든 사용자는 비활성화됨)', - }, - manualConfig: { - label: '사용자가 수동 WireGuard 구성을 다운로드하는 기능 비활성화', - helper: - '이 옵션을 활성화하면 사용자에게 수동 클라이언트 설정을 위한 WireGuard 구성이 표시되지 않습니다.', - }, - }, - }, - }, - openidOverview: { - pageTitle: 'OpenID 앱', - search: { - placeholder: '앱 찾기', - }, - filterLabels: { - all: '모든 앱', - enabled: '활성화됨', - disabled: '비활성화됨', - }, - clientCount: '모든 앱', - addNewApp: '새 추가', - list: { - headers: { - name: '이름', - status: '상태', - actions: '작업', - }, - editButton: { - edit: '앱 편집', - delete: '앱 삭제', - disable: '비활성화', - enable: '활성화', - copy: '클라이언트 ID 복사', - }, - status: { - enabled: '활성화됨', - disabled: '비활성화됨', - }, - }, - messages: { - copySuccess: '클라이언트 ID가 복사되었습니다.', - noLicenseMessage: '이 기능에 대한 라이선스가 없습니다.', - noClientsFound: '결과를 찾을 수 없습니다.', - }, - deleteApp: { - title: '앱 삭제', - message: '{appName} 앱을 삭제하시겠습니까?', - submit: '앱 삭제', - messages: { - success: '앱이 삭제되었습니다.', - }, - }, - enableApp: { - messages: { - success: '앱이 활성화되었습니다.', - }, - }, - disableApp: { - messages: { - success: '앱이 비활성화되었습니다.', - }, - }, - modals: { - openidClientModal: { - title: { - addApp: '애플리케이션 추가', - editApp: '{appName} 앱 편집', - }, - scopes: '범위:', - messages: { - clientIdCopy: '클라이언트 ID 복사됨.', - clientSecretCopy: '클라이언트 암호 복사됨.', - }, - form: { - messages: { - successAdd: '앱 생성됨.', - successModify: '앱 수정됨.', - }, - error: { - urlRequired: 'URL이 필요합니다.', - validUrl: '유효한 URL이어야 합니다.', - scopeValidation: '최소 하나의 범위가 있어야 합니다.', - }, - fields: { - name: { - label: '앱 이름', - }, - redirectUri: { - label: '리디렉션 URL {count}', - placeholder: 'https://example.com/redirect', - }, - openid: { - label: 'OpenID', - }, - profile: { - label: '프로필', - }, - email: { - label: '이메일', - }, - phone: { - label: '전화', - }, - groups: { - label: '그룹', - }, - }, - controls: { - addUrl: 'URL 추가', - }, - }, - clientId: '클라이언트 ID', - clientSecret: '클라이언트 암호', - }, - }, - }, - webhooksOverview: { - pageTitle: 'Webhooks', - search: { - placeholder: 'URL로 웹훅 찾기', - }, - filterLabels: { - all: '모든 웹훅', - enabled: '활성화됨', - disabled: '비활성화됨', - }, - webhooksCount: '모든 웹훅', - addNewWebhook: '새 추가', - noWebhooksFound: '웹훅을 찾을 수 없습니다.', - list: { - headers: { - name: '이름', - description: '설명', - status: '상태', - actions: '작업', - }, - editButton: { - edit: '편집', - delete: '웹훅 삭제', - disable: '비활성화', - enable: '활성화', - }, - status: { - enabled: '활성화됨', - disabled: '비활성화됨', - }, - }, - }, - provisionersOverview: { - pageTitle: '프로비저너', - search: { - placeholder: '프로비저너 찾기', - }, - filterLabels: { - all: '전체', - available: '사용 가능', - unavailable: '사용 불가', - }, - provisionersCount: '모든 프로비저너', - noProvisionersFound: '프로비저너를 찾을 수 없습니다.', - noLicenseMessage: '이 기능에 대한 라이선스가 없습니다.', - provisioningStation: { - header: 'YubiKey 프로비저닝 스테이션', - content: `YubiKeys를 프로비저닝하려면 먼저 USB 슬롯이 있는 물리적 시스템을 - 설정해야 합니다. 선택한 시스템에서 제공된 명령을 실행하여 등록하고 - 키 프로비저닝을 시작하세요.`, - dockerCard: { - title: '프로비저닝 스테이션 도커 설정 명령', - }, - tokenCard: { - title: '액세스 토큰', - }, - }, - list: { - headers: { - name: '이름', - ip: 'IP 주소', - status: '상태', - actions: '작업', - }, - editButton: { - delete: '프로비저너 삭제', - }, - status: { - available: '사용 가능', - unavailable: '사용 불가', - }, - }, - messages: { - copy: { - token: '토큰 복사됨', - command: '명령 복사됨', - }, - }, - }, - openidAllow: { - header: '{name}이(가) 다음을 원합니다:', - scopes: { - openid: '향후 로그인을 위해 프로필 데이터를 사용합니다.', - profile: '이름, 프로필 사진 등 프로필의 기본 정보를 알고 있습니다.', - email: '이메일 주소를 알고 있습니다.', - phone: '전화번호를 알고 있습니다.', - groups: '그룹 멤버십을 알고 있습니다.', - }, - controls: { - accept: '수락', - cancel: '취소', - }, - }, - networkOverview: { - pageTitle: '위치 개요', - controls: { - editNetworks: '위치 설정 편집', - selectNetwork: { - placeholder: '위치 로드 중', - }, - }, - filterLabels: { - grid: '그리드 보기', - list: '목록 보기', - }, - stats: { - currentlyActiveUsers: '현재 활성 사용자', - activeUsersFilter: '{hour}시간 내 활성 사용자', - activeDevicesFilter: '{hour}시간 내 활성 장치', - activityIn: '{hour}시간 내 활동', - in: '들어오는 트래픽:', - out: '나가는 트래픽:', - gatewayDisconnected: '게이트웨이 연결 끊김', - }, - }, - connectedUsersOverview: { - pageTitle: '연결된 사용자', - noUsersMessage: '현재 연결된 사용자가 없습니다', - userList: { - username: '사용자 이름', - device: '장치', - connected: '연결됨', - deviceLocation: '장치 위치', - networkUsage: '네트워크 사용량', - }, - }, - networkPage: { - pageTitle: '위치 편집', - addNetwork: '+ 새 위치 추가', - controls: { - networkSelect: { - label: '위치 선택', - }, - }, - }, - activityOverview: { - header: '활동 스트림', - noData: '현재 감지된 활동이 없습니다', - }, - networkConfiguration: { - messages: { - delete: { - success: '네트워크 삭제됨', - error: '네트워크 삭제 실패', - }, - }, - header: '위치 구성', - importHeader: '위치 가져오기', - form: { - helpers: { - address: - '이 주소를 기반으로 VPN 네트워크 주소가 정의됩니다. 예: 10.10.10.1/24 (VPN 네트워크는 10.10.10.0/24가 됩니다)', - gateway: 'VPN 사용자가 연결하는 데 사용되는 게이트웨이 공개 주소', - dns: 'wireguard 인터페이스가 활성화될 때 쿼리할 DNS 확인자를 지정합니다.', - allowedIps: 'VPN 네트워크를 통해 라우팅되어야 하는 주소/마스크 목록입니다.', - allowedGroups: - '기본적으로 모든 사용자가 이 위치에 연결할 수 있습니다. 특정 그룹으로 이 위치에 대한 액세스를 제한하려면 아래에서 선택하십시오.', - }, - messages: { - networkModified: '위치가 수정되었습니다.', - networkCreated: '위치가 생성되었습니다', - }, - fields: { - name: { - label: '위치 이름', - }, - address: { - label: '게이트웨이 VPN IP 주소 및 넷마스크', - }, - endpoint: { - label: '게이트웨이 주소', - }, - allowedIps: { - label: '허용된 IP', - }, - port: { - label: '게이트웨이 포트', - }, - dns: { - label: 'DNS', - }, - allowedGroups: { - label: '허용된 그룹', - placeholder: '모든 그룹', - }, - keepalive_interval: { - label: 'Keepalive 간격 [초]', - }, - peer_disconnect_threshold: { - label: '피어 연결 끊김 임계값 [초]', - }, - acl_enabled: { - label: '이 위치에 대한 ACL 활성화', - }, - acl_default_allow: { - label: '기본 ACL 정책', - }, - }, - controls: { - submit: '변경 사항 저장', - cancel: '개요로 돌아가기', - delete: '위치 제거', - }, - }, - }, - gatewaySetup: { - header: { - main: '게이트웨이 서버 설정', - dockerBasedGatewaySetup: `Docker 기반 게이트웨이 설정`, - fromPackage: `패키지로부터`, - oneLineInstall: `한 줄 설치`, - }, - card: { - title: 'Docker 기반 게이트웨이 설정', - authToken: `인증 토큰`, - }, - button: { - availablePackages: `사용 가능한 패키지`, - }, - controls: { - status: '연결 상태 확인', - }, - messages: { - runCommand: `Defguard는 vpn 서버에서 wireguard VPN을 제어하기 위해 게이트웨이 노드를 배포해야 합니다. - 자세한 내용은 [문서]({setupGatewayDocs})를 참조하십시오. - 게이트웨이 서버를 배포하는 방법에는 여러 가지가 있으며, - 아래는 Docker 기반 예시입니다. 다른 예시는 [문서]({setupGatewayDocs})를 참조하십시오.`, - createNetwork: `게이트웨이 프로세스를 실행하기 전에 네트워크를 생성하십시오.`, - noConnection: `연결이 설정되지 않았습니다. 제공된 명령을 실행하십시오.`, - connected: `게이트웨이가 연결되었습니다.`, - statusError: '게이트웨이 상태를 가져오지 못했습니다', - oneLineInstall: `한 줄 설치를 수행하는 경우: https://docs.defguard.net/getting-started/one-line-install - 아무 것도 할 필요가 없습니다.`, - fromPackage: `https://github.com/DefGuard/gateway/releases/latest에서 사용 가능한 패키지를 설치하고 [문서]({setupGatewayDocs})에 따라 \`/etc/defguard/gateway.toml\`을 구성하십시오. - `, - authToken: `아래 토큰은 게이트웨이 노드를 인증하고 구성하는 데 필요합니다. 이 토큰을 안전하게 보관하고 - [문서]({setupGatewayDocs})에 제공된 배포 지침에 따라 게이트웨이 서버를 성공적으로 설정하십시오. - 자세한 내용 및 정확한 단계는 [문서]({setupGatewayDocs})를 참조하십시오.`, - dockerBasedGatewaySetup: `아래는 Docker 기반 예시입니다. 자세한 내용 및 정확한 단계는 [문서]({setupGatewayDocs})를 참조하십시오.`, - }, - }, - loginPage: { - pageTitle: '자격 증명을 입력하세요', - callback: { - return: '로그인으로 돌아가기', - error: '외부 OpenID 로그인 중 오류가 발생했습니다', - }, - mfa: { - title: '이중 인증', - controls: { - useAuthenticator: '대신 인증 앱 사용', - useWebauthn: '대신 보안 키 사용', - useRecoveryCode: '대신 복구 코드 사용', - useEmail: '대신 이메일 사용', - }, - email: { - header: '이메일로 전송된 코드를 사용하여 진행하십시오.', - form: { - labels: { - code: '코드', - }, - controls: { - resendCode: '코드 재전송', - }, - }, - }, - totp: { - header: '인증 앱의 코드를 사용하고 버튼을 클릭하여 진행하십시오.', - form: { - fields: { - code: { - placeholder: '인증 코드 입력', - }, - }, - controls: { - submit: '인증 코드 사용', - }, - }, - }, - recoveryCode: { - header: '활성 복구 코드 중 하나를 입력하고 버튼을 클릭하여 로그인하십시오.', - form: { - fields: { - code: { - placeholder: '복구 코드', - }, - }, - controls: { - submit: '복구 코드 사용', - }, - }, - }, - webauthn: { - header: '인증할 준비가 되면 아래 버튼을 누르십시오.', - controls: { - submit: '보안 키 사용', - }, - messages: { - error: '키를 읽지 못했습니다. 다시 시도하십시오.', - }, - }, - }, - }, - wizard: { - completed: '위치 설정 완료', - configuration: { - successMessage: '위치 생성됨', - }, - welcome: { - header: '위치 마법사에 오신 것을 환영합니다!', - sub: 'VPN을 사용하기 전에 먼저 위치를 설정해야 합니다. 확실하지 않은 경우 아이콘을 클릭하십시오.', - button: '위치 설정', - }, - navigation: { - top: '위치 설정', - titles: { - welcome: '위치 설정', - choseNetworkSetup: '위치 설정 선택', - importConfig: '기존 위치 가져오기', - manualConfig: '위치 구성', - mapDevices: '가져온 장치 매핑', - }, - buttons: { - next: '다음', - back: '뒤로', - }, - }, - deviceMap: { - messages: { - crateSuccess: '장치 추가됨', - errorsInForm: '표시된 필드를 채워주세요.', - }, - list: { - headers: { - deviceName: '장치 이름', - deviceIP: 'IP', - user: '사용자', - }, - }, - }, - wizardType: { - manual: { - title: '수동 구성', - description: '수동 위치 구성', - }, - import: { - title: '파일에서 가져오기', - description: 'WireGuard 구성 파일에서 가져오기', - }, - createNetwork: '위치 생성', - }, - common: { - select: '선택', - }, - locations: { - form: { - name: '이름', - ip: 'IP 주소', - user: '사용자', - fileName: '파일', - selectFile: '파일 선택', - messages: { devicesCreated: '장치 생성됨' }, - validation: { invalidAddress: '잘못된 주소' }, - }, - }, - }, - layout: { - select: { - addNewOptionDefault: '새 추가 +', - }, - }, - redirectPage: { - title: '로그인되었습니다', - subtitle: '잠시 후 리디렉션됩니다...', - }, - enrollmentPage: { - title: '등록', - controls: { - default: '기본값 복원', - save: '변경 사항 저장', - }, - messages: { - edit: { - success: '설정이 변경되었습니다', - error: '저장 실패', - }, - }, - messageBox: - '등록은 신입 직원이 새 계정을 확인하고, 비밀번호를 생성하고, VPN 장치를 구성할 수 있도록 하는 프로세스입니다. 이 패널에서 관련 메시지를 사용자 지정할 수 있습니다.', - settings: { - welcomeMessage: { - title: '환영 메시지', - messageBox: - '이 정보는 등록이 완료되면 서비스 내 사용자에게 표시됩니다. 링크를 삽입하고 다음 단계를 간략하게 설명하는 것이 좋습니다. 이메일에 있는 것과 동일한 메시지를 사용할 수 있습니다.', - }, - vpnOptionality: { - title: 'VPN 설정 선택 사항', - select: { - options: { - optional: '선택 사항', - mandatory: '필수', - }, - }, - }, - welcomeEmail: { - title: '환영 이메일', - subject: { - label: '이메일 제목', - }, - messageBox: - '등록이 완료되면 사용자에게 이 정보가 전송됩니다. 관련 링크를 삽입하고 다음 단계를 간략하게 설명하는 것이 좋습니다.', - controls: { - duplicateWelcome: '환영 메시지와 동일', - }, - }, - }, - }, - supportPage: { - title: '지원', - modals: { - confirmDataSend: { - title: '지원 데이터 보내기', - subTitle: - '실제로 지원 디버그 정보를 보내려는 것인지 확인하십시오. 개인 정보는 전송되지 않습니다(wireguard 키, 이메일 주소 등은 전송되지 않음).', - submit: '지원 데이터 보내기', - }, - }, - debugDataCard: { - title: '지원 데이터', - body: ` -지원이 필요하거나 저희 팀(예: Matrix 지원 채널: **#defguard-support:teonite.com**)에서 지원 데이터 생성을 요청받은 경우 다음 두 가지 옵션이 있습니다. -* SMTP 설정을 구성하고 "지원 데이터 보내기"를 클릭합니다. -* 또는 "지원 데이터 다운로드"를 클릭하고 이 파일을 첨부하여 GitHub에 버그 보고서를 생성합니다. -`, - downloadSupportData: '지원 데이터 다운로드', - downloadLogs: '로그 다운로드', - sendMail: '지원 데이터 보내기', - mailSent: '이메일 전송됨', - mailError: '이메일 전송 오류', - }, - supportCard: { - title: '지원', - body: ` -GitHub에 문의하거나 문제를 제출하기 전에 [docs.defguard.net](https://docs.defguard.net/)에서 제공되는 Defguard 문서를 숙지하십시오. - -제출하려면: -* 버그 - [GitHub](https://github.com/DefGuard/defguard/issues/new?assignees=&labels=bug&template=bug_report.md&title=)로 이동하십시오. -* 기능 요청 - [GitHub](https://github.com/DefGuard/defguard/issues/new?assignees=&labels=feature&template=feature_request.md&title=)로 이동하십시오. - -기타 요청은 support@defguard.net으로 문의하십시오. -`, - }, - }, -}; - -const ko = deepmerge(en, translation); - -export default ko; diff --git a/web/src/i18n/pl/index.ts b/web/src/i18n/pl/index.ts deleted file mode 100644 index 588a4a60e..000000000 --- a/web/src/i18n/pl/index.ts +++ /dev/null @@ -1,2394 +0,0 @@ -import { deepmerge } from 'deepmerge-ts'; -import { PartialDeep } from 'type-fest'; - -import en from '../en'; -import { Translation } from '../i18n-types'; - -const translation: PartialDeep = { - common: { - conditions: { - and: 'I', - equal: 'Równy', - or: 'Albo', - }, - controls: { - accept: 'Akceptuj', - next: 'Następny', - back: 'Wróć', - cancel: 'Anuluj', - confirm: 'Potwierdź', - submit: 'Wyślij', - close: 'Zamknij', - select: 'Wybierz', - finish: 'Zakończ', - saveChanges: 'Zapisz zmiany', - save: 'Zapisz', - RestoreDefault: 'Przywróć domyślne', - delete: 'Usuń', - rename: 'Zmień nazwę', - copy: 'Skopiuj', - edit: 'Edytuj', - dismiss: 'Odrzuć', - show: 'Pokaż', - enable: 'Włącz', - enabled: 'Włączony', - disable: 'Wyłącz', - disabled: 'Wyłączony', - selectAll: 'Zaznacz wszystko', - clear: 'Wyczyść', - clearAll: 'Wyczyść wszystko', - }, - key: 'Klucz', - name: 'Nazwa', - noData: 'Brak danych', - unavailable: 'Niedostępne', - notSet: 'Nieustawione', - search: 'Szukaj', - }, - messages: { - error: 'Wystąpił błąd.', - success: 'Operacja zakończyła się sukcesem', - errorVersion: 'Nie udało się uzyskać wersji aplikacji.', - details: 'Szczegóły:', - clipboard: { - success: 'Skopiowano do schowka', - error: 'Schowek nie jest dostępny', - }, - insecureContext: 'Kontekst nie jest bezpieczny', - }, - modals: { - upgradeLicenseModal: { - enterprise: { - title: 'Podnieś do Enterprise', - //md - subTitle: `Został przekroczony limit użytkowników, urządzeń lub sieci, a ta funkcjonalność jest dostępna tylko w wersji **enterprise**. Aby użyć tej funkcjonalności, należy zakupić lub podnieść obecną licencję enterprise.`, - }, - limit: { - title: 'Podnieś', - //md - subTitle: ` - **Osiągnięto limit** funkcjonalności. Aby **[ zarządzać większą liczbą lokalizacji/użytkowników/urządzeń ]** wymagany jest zakup licencji Enterprise. - `, - }, - //md - content: ` -Aby dowiedzieć się więcej o: -- Automatyczniej synchronizacji klientów w czasie rzeczywistym -- Zewnętrznym SSO -- Kontrolowaniu działania klientów VPN - -Pełna lista funkcjonalności enterprise: [https://docs.defguard.net/enterprise/enterprise-features](https://docs.defguard.net/enterprise/enterprise-features)
-Informacja o licencjonowaniu: [https://docs.defguard.net/enterprise/license](https://docs.defguard.net/enterprise/license) - `, - controls: { - cancel: 'Może później', - confirm: 'Wszystkie plany Enterprise', - }, - }, - standaloneDeviceEnrollmentModal: { - title: 'Network device token', - toasters: { - error: 'Token generation failed.', - }, - }, - standaloneDeviceConfigModal: { - title: 'Konfiguracja urządzenia sieciowego', - cardTitle: 'Konfiguracja', - toasters: { - getConfig: { - error: 'Nie udało się pobrać konfiguracji urządzenia.', - }, - }, - }, - editStandaloneModal: { - title: 'Edycja urządzenia sieciowego', - toasts: { - success: 'Urządzenia zostało zmienione', - failure: 'Nie udało się zmienić urządzenia.', - }, - }, - deleteStandaloneDevice: { - title: 'Usuń urządzenie sieciowe', - content: 'Urządzenie {name} zostanie usunięte.', - messages: { - success: 'Urządzenie zostało usunięte', - error: 'Nie udało się usunąć urządzenia.', - }, - }, - addStandaloneDevice: { - toasts: { - deviceCreated: 'Urządzenie zostało dodane', - creationFailed: 'Urządzenie nie mogło być dodane.', - }, - infoBox: { - setup: - 'Tu można dodać definicje lub utworzyć konfiguracje dla urządzeń, które można podłączyć do sieci VPN. Dostępne są jedynie lokalizacje bez uwierzytelniania wieloskładnikowego (MFA), ponieważ póki co ta funkcjonalność jest dostępna tylko w kliencie Defguard Desktop.', - }, - form: { - submit: 'Dodaj urządzenie', - labels: { - deviceName: 'Nazwa urządzenia', - location: 'Położenie', - assignedAddress: 'Przydzielony adres IP', - description: 'Opis', - generation: { - auto: 'Utwórz parę kluczy', - manual: 'Własny klucz publiczny', - }, - publicKey: 'Podaj swój klucz publiczny', - }, - }, - steps: { - method: { - title: 'Wybierz preferowaną metodę', - cards: { - cli: { - title: 'Klient Defguard CLI', - subtitle: - 'Używając defguard-cli urządznie zostanie automatycznie skonfigurowane.', - docs: 'Pobieranie i dokumentacja klienta Defguard CLI', - }, - manual: { - title: 'Ręczny klient WireGuard', - subtitle: - 'Jeżeli Twoje urządzenie nie wspiera naszych programów CLI, zawsze można utworzyć plik konfiguracyjny WireGuard i skonfigurowć je ręcznie - ale w takim przypadku uaktualnienia lokalizacji VPN będą wymagały ręcznych zmian w urządzeniu.', - }, - }, - }, - manual: { - title: 'Dodaj nowe urządzenie VPN używając klienta WireGuard', - finish: { - messageTop: - 'Pobierz podany plik konfiguracyjny na urządzeniu i zaimportuj go do klienta VPN żeby zakończyć jego konfigurowanie.', - ctaInstruction: - 'Użyj podanego niżej pliku konfiguracyjnego skanując kod QR lub importując go jako plik w aplikacji WireGuard na urządzeniu.', - // MD - warningMessage: ` - Należy pamiętać, że Defguard **nie przechowuje kluczy prywatnych**. Para kluczy (publiczny i prywatny) zostanie bezpiecznie utworzona w przeglądarce, ale jedynie klucz publiczny zostanie zapisany w bazie danych Defguard. Proszę pobrać utworzoną konfigurację zawierającą klucz prywatny dla urządzenia, gdyż nie będzie ona później dostępna. - `, - actionCard: { - title: 'Konfiguracja', - }, - }, - }, - cli: { - title: 'Dodaj urządzenie używając klienta Defguard CLI', - finish: { - topMessage: - 'Najpierw pobierz klienta Defguard CLI i zainstaluj go na serwerze.', - downloadButton: 'Pobierz klienta Defguard CLI', - commandCopy: 'Skopiuj i wklej to polecenie w terminalu na urządzeniu', - }, - setup: { - stepMessage: - 'Tu można dodać definicje lub utworzyć konfiguracje dla urządzeń, które mogą łączyć się do sieci VPN. Tutaj dostępne są jedynie lokalizacje bez uwierzytelniania wieloskładnikowego (MFA) ponieważ póki co MFA jest wspierane jedynie w kliencie Defguard Desktop.', - form: { - submit: 'Dodaj urządzenie', - }, - }, - }, - }, - }, - updatesNotification: { - header: { - criticalBadge: 'Aktualizacja krytyczna', - newVersion: 'Nowa wersja {version}', - title: 'Aktualizacja dostępna', - }, - controls: { - visitRelease: 'Zobacz stronę aktualizacji', - }, - }, - updatesNotificationToaster: { - title: 'Nowa wersja dostępna {version}', - controls: { - more: 'Zobacz co nowego', - }, - }, - addGroup: { - groupName: 'Nazwa grupy', - searchPlaceholder: 'Szukaj', - selectAll: 'Zaznacz wszystkich', - submit: 'Stwórz grupę', - title: 'Dodaj grupę', - groupSettings: 'Ustawienia grupy', - adminGroup: 'Grupa administratorska', - }, - editGroup: { - groupName: 'Nazwa grupy', - searchPlaceholder: 'Szukaj', - selectAll: 'Zaznacz wszystkich', - submit: 'Zmień grupę', - title: 'Edytuj grupę', - groupSettings: 'Ustawienia grupy', - adminGroup: 'Grupa administratorska', - }, - deleteGroup: { - title: 'Usuń grupę {name}', - subTitle: 'Grupa zostanie nieodwołalnie usunięta.', - locationListHeader: - 'Ta grupa jest obecnie przypisana do następujących lokalizacji:', - locationListFooter: `Jeżeli to jedyna dozwolona grupa dla danej lokalizacji, stanie się ona dostępna dla wszystkich użytkowników.`, - submit: 'Usuń grupę', - cancel: 'Wróć', - }, - registerEmailMFA: { - title: 'Skonfiguruj e-mail MFA', - form: { - controls: { - resend: 'Wyślij kod ponownie', - submit: 'Zweryfikuj kod', - }, - fields: { - code: { - error: 'Podany kod jest nieprawidłowy', - label: 'Kod', - }, - }, - }, - infoMessage: ` -

- Aby zakończyć konfigurację, wpisz kod, który został wysłany na adres: {email} -

- `, - messages: { - resend: 'Kod wysłany ponownie', - success: 'Metoda MFA e-mail włączona', - }, - }, - deviceConfig: { - title: 'Konfiguracje VPN urządzenia', - }, - changePasswordSelf: { - title: 'Zmień hasło', - messages: { - success: 'Hasło zostało zmienione', - error: 'Błąd zmiany hasła', - }, - form: { - labels: { - repeat: 'Powtórz hasło', - newPassword: 'Nowe hasło', - oldPassword: 'Obecne hasło', - }, - }, - controls: { - cancel: 'Wróć', - submit: 'Zmień hasło', - }, - }, - startEnrollment: { - title: 'Rozpocznij rejestrację', - desktopTitle: 'Konfiguracja klienta desktop', - messages: { - success: 'Rejestracja użytkownika rozpoczęta', - successDesktop: 'Konfiguracja klienta rozpoczęta', - errorDesktop: 'Błąd konfiguracji klienta desktop', - error: 'Błąd rejestracji użytkownika', - }, - form: { - email: { - label: 'E-mail', - }, - mode: { - options: { - email: 'Wyślij token przez e-mail', - manual: 'Przekaż token ręcznie', - }, - }, - submit: 'Rozpocznij rejestrację', - submitDesktop: 'Aktywacja desktop', - smtpDisabled: - 'Skonfiguruj SMTP, żeby wysłać token przez e-mail. Przejdź do Ustawienia -> SMTP.', - }, - tokenCard: { - title: 'Token aktywacji', - }, - urlCard: { - title: 'URL instancji Defguard', - }, - }, - deleteNetwork: { - cancel: 'Wróć', - submit: 'Usuń lokalizację', - subTitle: 'Lokalizacja zostanie nieodwołalnie usunięta.', - title: 'Usuń lokalizację {name}', - }, - changeWebhook: { - messages: { - success: 'Webhook zmieniony.', - }, - }, - manageWebAuthNKeys: { - title: 'Klucze bezpieczeństwa', - messages: { - deleted: 'Klucz WebAuthN został usunięty.', - duplicateKeyError: 'Klucz jest już zarejestrowany', - }, - infoMessage: ` -

- Klucze bezpieczeństwa mogą być używane jako drugi czynnik uwierzytelniający - zamiast kodu weryfikacyjnego. Dowiedz się więcej o konfiguracji - klucza bezpieczeństwa. -

-`, - form: { - messages: { - success: 'Klucz bezpieczeństwa dodany.', - }, - fields: { - name: { - label: 'Nazwa nowego klucza', - }, - }, - controls: { - submit: 'Dodaj nowy klucz', - }, - }, - }, - recoveryCodes: { - title: 'Kody odzysku', - submit: 'Zapisałem swoje kody', - messages: { - copied: 'Kody skopiowane.', - }, - infoMessage: ` -

- Traktuj swoje kody odzyskiwania z takim samym poziomem uwagi jak - jak swoje hasło! Zalecamy zapisywanie ich za pomocą menedżera haseł - takich jak Lastpass, bitwarden czy Keeper. -

-`, - }, - registerTOTP: { - title: 'Authenticator App Setup', - infoMessage: ` -

- Aby skonfigurować MFA, zeskanuj ten kod QR za pomocą aplikacji uwierzytelniającej, a następnie - wprowadź kod w polu poniżej: -

-`, - messages: { - totpCopied: 'Ścieżka TOTP skopiowana.', - success: 'TOTP Enabled', - }, - copyPath: 'Kopiuj ścieżkę TOTP', - form: { - fields: { - code: { - label: 'Kod uwierzytelniający', - error: 'Kod jest nieprawidłowy', - }, - }, - controls: { - submit: 'Weryfikuj kod', - }, - }, - }, - editDevice: { - title: 'Edytuj urządzenie', - messages: { - success: 'Urządzenie zostało zaktualizowane.', - }, - form: { - fields: { - name: { - label: 'Nazwa urządzenia', - }, - publicKey: { - label: 'Klucz publiczny urządzenia (WireGuard)', - }, - }, - controls: { - submit: 'Edytuj urządzenie', - }, - }, - }, - deleteDevice: { - title: 'Usuń urządzenie', - message: 'Czy chcesz usunąć urządzenie {deviceName} ?', - submit: 'Usuń urządzenie', - messages: { - success: 'Urządzenie zostało usunięte.', - }, - }, - keyDetails: { - title: 'Szczegóły YubiKey', - downloadAll: 'Pobierz wszystkie klucze', - }, - deleteUser: { - title: 'Usuń użytkownika', - controls: { - submit: 'Usuń użytkownika', - }, - message: 'Czy chcesz trwale usunąć konto {username} ?', - messages: { - success: '{username} usunięte.', - }, - }, - disableUser: { - title: 'Dezaktywuj użytkownika', - controls: { - submit: 'Dezaktywuj użytkownika', - }, - message: 'Czy chcesz dezaktywować użytkownika {username}?', - messages: { - success: 'Użytkownik {username} został dezaktywowany.', - }, - }, - enableUser: { - title: 'Aktywuj użytkownika', - controls: { - submit: 'Aktywuj użytkownika', - }, - message: 'Czy chcesz aktywować użytkownika {username}?', - messages: { - success: 'Użytkownik {username} został aktywowany.', - }, - }, - deleteProvisioner: { - title: 'Usuń provisionera', - controls: { - submit: 'Usuń provisionera', - }, - message: 'Czy chcesz usunąć {id} provisionera?', - messages: { - success: '{provisioner} usunięty.', - }, - }, - changeUserPassword: { - messages: { - success: 'Hasło zmienione.', - }, - title: 'Zmiana hasła użytkownika', - form: { - controls: { - submit: 'Zapisz nowe hasło', - }, - fields: { - newPassword: { - label: 'Nowe hasło', - }, - confirmPassword: { - label: 'Powtórz hasło', - }, - }, - }, - }, - provisionKeys: { - warning: 'Ta operacja bezpowrotnie usunie dane z aplikacji OpenPGP klucza.', - title: 'Provisionowanie klucza YubiKey:', - infoBox: `Wybrany provisioner musi mieć podłączony pusty YubiKey. - Aby zresetować YubiKey uruchom - gpg --card-edit przed generowaniem kluczy.`, - selectionLabel: - 'Wybierz jeden z następujących provisionerów, aby wygenrować klucze na YubiKey:', - noData: { - workers: 'Nie znaleziono workerów...', - }, - controls: { - submit: 'Wygeneruj klucze dla YubiKey', - }, - messages: { - success: 'Klucze zostały przetransferowane na YubiKey', - errorStatus: 'Wystapił błąd podczas pobierania statusu.', - }, - }, - addUser: { - messages: { - userAdded: 'Stworzono użytkownika', - }, - title: 'Dodaj nowego użytkownika', - form: { - submit: 'Dodaj użytkownika', - fields: { - username: { - placeholder: 'login', - label: 'Login', - }, - password: { - placeholder: 'Hasło', - label: 'Hasło', - }, - email: { - placeholder: 'E-mail użytkownika', - label: 'E-mail użytkownika', - }, - firstName: { - placeholder: 'Imię', - label: 'Imię', - }, - lastName: { - placeholder: 'Nazwisko', - label: 'Nazwisko', - }, - phone: { - placeholder: 'Telefon', - label: 'Telefon', - }, - enableEnrollment: { - label: 'Użyj zdalnej rejestracji', - link: 'więcej informacji tutaj', - }, - }, - }, - }, - webhookModal: { - title: { - addWebhook: 'Dodaj webhook', - editWebhook: 'Edytuj webhook', - }, - messages: { - clientIdCopy: 'Skopiowano identyfikator klienta', - clientSecretCopy: 'Sekret klienta skopiowany.', - }, - form: { - triggers: 'Zdarzenia wyzwalające:', - messages: { - successAdd: 'Webhook utworzony.', - successModify: 'Webhook zmodyfikowany.', - }, - error: { - urlRequired: 'URL jest wymagany.', - validUrl: 'Musi być poprawnym adresem URL.', - scopeValidation: 'Musi mieć co najmniej jeden wyzwalacz.', - tokenRequired: 'Token jest wymagany.', - }, - fields: { - description: { - label: 'Opis', - placeholder: 'Webhook do tworzenia konta gmail na nowym użytkowniku', - }, - token: { - label: 'Secret token', - placeholder: 'Token autoryzacyjny', - }, - url: { - label: 'Webhook URL', - placeholder: 'https://example.com/webhook', - }, - userCreated: { - label: 'Stworzenie nowego użytkownika', - }, - userDeleted: { - label: 'Użytkownik usunięty', - }, - userModified: { - label: 'Użytkownik zmodyfikowany', - }, - hwkeyProvision: { - label: 'Stworzenie kluczy na YubiKey dla użytkownika', - }, - }, - }, - }, - deleteWebhook: { - title: 'Usuń webhook', - message: 'Czy chcesz usunąć {name} webhook ?', - submit: 'Usuń', - messages: { - success: 'Webhook usunięty.', - }, - }, - }, - addDevicePage: { - title: 'Dodaj urządzenie', - messages: { - deviceAdded: 'Urządzenie dodane', - }, - helpers: { - setupOpt: `Możesz dodać urządzenie używając naszego klienta lub samemu skonfigurwać urządzenie.`, - client: `Pobierz klienta defguard tutaj, a następnie postępuj zgodnie z instrukcją w celu jego konfiguracji.`, - }, - - steps: { - setupDevice: { - title: 'Dodaj urządzenie', - form: { - errors: { - name: { - duplicatedName: 'Nazwa jest już zajęta', - }, - }, - fields: { - name: { - label: 'Nazwa', - }, - publicKey: { - label: 'Klucz publiczny', - }, - }, - }, - options: { - auto: 'Generuj klucze', - manual: 'Użyj własnych', - }, - infoMessage: `

W razie problemów możesz odwiedzić dokumentacje.

`, - }, - configDevice: { - title: 'Skonfiguruj urządzenie', - messages: { - copyConfig: 'Konfiguracja skopiowa', - }, - qrInfo: - 'Użyj poniższych konfiguracji aby połączyć się z wybranymi lokalizacjami.', - helpers: { - warningNoNetworks: 'Nie posiadasz dostępu do żadnej sieci.', - qrHelper: `

Możesz skonfigurować WireGuard na telefonie skanując QR kod używając aplikacji WireGuard.

`, - warningAutoMode: ` -

Uwaga, Defguard nie przechowuje twojego klucza prywatnego. Gdy opuścisz obecną stronę nie będziesz mógł pobrać ponownie konfiguracji z kluczem prywatnym.

-`, - warningManualMode: `

-Uwaga, podane tutaj konfiguracje nie posiadają klucza prywatnego. Musisz uzupełnić pobraną konfigurację o swój klucz prywatny. -

`, - }, - qrLabel: 'Konfiguracja WireGuard', - inputNameLabel: 'Nazwa urządzenia', - }, - copyToken: { - title: 'Autoryzacja klienta', - urlCardTitle: 'Url', - tokenCardTitle: 'Token', - }, - }, - }, - userPage: { - title: { - view: 'Profil użytkownika', - edit: 'Edycja profilu użytkownika', - }, - messages: { - editSuccess: 'Użytkownik zaktualizowany.', - failedToFetchUserData: 'Błąd pobierania informacji o użytkowniku.', - passwordResetEmailSent: 'E-mail zerowania hasła został wysłany.', - }, - userDetails: { - header: 'Szczegóły profilu', - messages: { - deleteApp: 'Aplikacja i wszystkie tokeny usunięte.', - }, - warningModals: { - title: 'Ostrzeżenie', - content: { - usernameChange: `Zmiana nazwy użytkownika ma znaczący wpływ na usługi, do których użytkownik zalogował się za pomocą Defguard. Po zmianie nazwy użytkownika użytkownik może stracić do nich dostęp (ponieważ nie będą go rozpoznawać). Czy na pewno chcesz kontynuować?`, - emailChange: `Jeśli korzystasz z zewnętrznych dostawców OpenID Connect (OIDC) do uwierzytelniania użytkowników, zmiana adresu e-mail użytkownika może mieć wpływ na jego możliwość zalogowania się do Defguarda. Czy na pewno chcesz kontynuować?`, - }, - buttons: { - proceed: 'Proceed', - cancel: 'Cancel', - }, - }, - fields: { - username: { - label: 'Nazwa użytkownika', - }, - firstName: { - label: 'Imię', - }, - lastName: { - label: 'Nazwisko', - }, - phone: { - label: 'Numer telefonu', - }, - email: { - label: 'E-mail', - }, - status: { - label: 'Status', - active: 'Aktywny', - disabled: 'Nieaktywny', - }, - groups: { - label: 'Grupy użytkowników', - noData: 'Brak grup', - }, - apps: { - label: 'Autoryzowane aplikacje', - noData: 'Brak autoryzowanych aplikacji', - }, - }, - }, - userAuthInfo: { - header: 'Hasło i uwierzytelnienie', - password: { - header: 'Ustawienia hasła', - changePassword: 'Zmiana hasła', - ldap_change_heading: 'Wymagana aktualizacja hasła {ldapName}', - ldap_change_message: - 'Defguard nie ma możliwości odczytania twojego hasła, więc nie możemy go pobrać do automatycznej synchronizacji z danymi logowania {ldapName}. Aby umożliwić logowanie do innych usług za pomocą {ldapName}, zaktualizuj swoje hasło Defguard, aby jednocześnie ustawić hasło {ldapName} — możesz ponownie wpisać swoje obecne hasło, jeśli chcesz. Ten krok jest konieczny, aby zapewnić spójną i bezpieczną autoryzację w obu systemach.', - }, - recovery: { - header: 'Opcje odzyskiwania danych', - codes: { - label: 'Kody odzyskiwania', - viewed: 'Obejrzane', - }, - }, - mfa: { - header: 'Metody dwuskładnikowe', - edit: { - disable: 'Wyłącz MFA', - }, - messages: { - mfaDisabled: 'MFA wyłączone.', - OTPDisabled: 'Hasło jednorazowe wyłączone.', - changeMFAMethod: 'Metoda MFA zmieniona.', - EmailMFADisabled: 'Metoda e-mail wyłączona.', - }, - securityKey: { - singular: 'klucz bezpieczeństwa', - plural: 'klucze bezpieczeństwa', - }, - default: 'domyślny', - enabled: 'Włączony', - disabled: 'Wyłączony', - labels: { - totp: 'Hasła jednorazowe oparte na czasie', - webauth: 'Klucze bezpieczeństwa', - email: 'E-mail', - }, - editMode: { - enable: 'Włącz', - disable: 'Wyłącz', - makeDefault: 'Uczyń domyślnym', - webauth: { - manage: 'Zarządzaj kluczami bezpieczeństwa', - }, - }, - }, - }, - controls: { - editButton: 'Edytuj profil', - deleteAccount: 'Usuń konto', - }, - devices: { - header: 'Urządzenia użytkownika', - addDevice: { - web: 'Dodaj nowe urządzenie', - desktop: 'Dodaj to urządzenie', - }, - card: { - labels: { - noData: 'Nie połączono', - connectedThrough: 'Połączone przez', - publicIP: 'Publiczny adres IP', - connectionDate: 'Data połączenia', - lastLocation: 'Ostatnie połączenie z', - active: 'aktywne', - assignedIp: 'Przydzielony adres IP', - lastConnected: 'Ostatnio połączone', - }, - edit: { - edit: 'Edycja urządzenia', - delete: 'Usuń urządzenie', - showConfigurations: 'Pokaż konfiguracje', - }, - }, - }, - yubiKey: { - header: 'YubiKey użytkownika', - provision: 'Sprovisionuj YubiKey', - keys: { - pgp: 'Klucz PGP', - ssh: 'Klucz SSH', - }, - noLicense: { - moduleName: 'Moduł YubiKey', - line1: 'To jest płatny moduł dla YubiKey', - line2: 'zarządzanie i provisioning.', - }, - }, - authenticationKeys: { - header: 'Klucze autoryzacyjne użytkownika', - addKey: 'Dodaj nowy klucz', - keysList: { - common: { - copy: 'Skopiuj', - delete: 'Usuń', - download: 'Pobierz', - key: 'Klucz', - rename: 'Zmień nazwę', - serialNumber: 'Numer seryjny', - }, - }, - deleteModal: { - confirmMessage: 'Klucz {name} zostanie trwale usunięty.', - title: 'Usuń klucz autoryzacyjny', - }, - addModal: { - header: 'Dodaj nowy klucz autoryzacyjny', - keyType: 'Typ Klucza', - keyForm: { - labels: { - key: 'Klucz', - title: 'Nazwa', - }, - placeholders: { - title: 'Nazwa Klucza', - key: { - ssh: 'Rozpoczyna się z ‘ssh-rsa’, ‘ecdsa-sha2-nistp256’, ...', - gpg: 'Rozpoczyna się z ‘-----BEGIN PGP PUBLIC KEY BLOCK-----‘', - }, - }, - submit: 'Dodaj klucz {name}', - }, - messages: { - keyAdded: 'Klucz dodany.', - keyExists: 'Klucz już został dodany.', - unsupportedKeyFormat: 'Format klucza nie jest wspierany.', - genericError: 'Nie udało się dodać klucza. Proszę spróbować ponownie później.', - }, - yubikeyForm: { - selectWorker: { - info: 'Ta operacja wyzeruje moduł GPG do ustawień fabrycznych po czym ponownie go skonfiguruje. Ta operacja jest nieodwracalna.', - selectLabel: 'Wybierz jedną stację do konfiguracji klucza.', - noData: 'Obecnie nie ma dostępnych stacji.', - available: 'Dostępny', - unavailable: 'Niedostępny', - }, - provisioning: { - inProgress: 'Klucz jest konfigurowany, proszę czekać.', - error: 'Konfiguracja klucza zakończyła się niepowodzeniem.', - success: 'Klucz skonfigurowany pomyślnie.', - }, - submit: 'Skonfiguruj klucz', - }, - }, - }, - apiTokens: { - header: 'API Tokeny użytkownika', - addToken: 'Dodaj nowy API Token', - tokensList: { - common: { - rename: 'Zmień nazwę', - token: 'Token', - copy: 'Skopiuj', - delete: 'Usuń', - createdAt: 'Utworzono', - }, - }, - deleteModal: { - title: 'Usuń API Token', - confirmMessage: 'API token {name} zostanie trwale usunięty.', - }, - addModal: { - header: 'Dodaj nowy API Token', - tokenForm: { - placeholders: { - name: 'Nazwa API Tokena', - }, - labels: { - name: 'Nazwa', - }, - submit: 'Dodaj API token', - }, - copyToken: { - warningMessage: - 'Skopiuj poniższy API token teraz. Nie będzie on dostępny w późniejszym czasie.', - header: 'Skopiuj nowy API Token', - }, - messages: { - tokenAdded: 'API token dodany.', - genericError: 'Nie udało się dodać API tokena. Spróbuj ponownie później.', - }, - }, - }, - }, - usersOverview: { - pageTitle: 'Użytkownicy', - search: { - placeholder: 'Znajdź użytkowników', - }, - filterLabels: { - all: 'Wszyscy użytkownicy', - admin: 'Tylko administratorzy', - users: 'Tylko użytkownicy', - }, - usersCount: 'Wszyscy użytkownicy', - addNewUser: 'Dodaj użytkownika', - list: { - headers: { - name: 'Nazwa użytkownika', - username: 'Login', - phone: 'Telefon', - actions: 'Akcje', - }, - editButton: { - activateDesktop: 'Aktywacja klienta desktop', - changePassword: 'Zmień hasło', - edit: 'Edytuj konto', - delete: 'Usuń konto', - startEnrollment: 'Rozpocznij rejestrację', - resetPassword: 'Resetuj hasło', - addGPG: 'Dodaj klucz GPG', - addSSH: 'Dodaj klucz SSH', - addYubikey: 'Dodaj YubiKey', - }, - }, - }, - navigation: { - bar: { - overview: 'Przegląd sieci', - users: 'Użytkownicy', - provisioners: 'YubiKey Provisioners', - webhooks: 'Webhooki', - openId: 'Aplikacje OpenID', - myProfile: 'Mój profil', - settings: 'Ustawienia', - logOut: 'Wyloguj się', - enrollment: 'Rejestracja', - support: 'Wsparcie', - groups: 'Grupy', - devices: 'Urządzenia sieciowe', - acl: 'Kontrola dostępu', - }, - mobileTitles: { - wizard: 'Konfiguracja VPN', - users: 'Użytkownicy', - settings: 'Ustawienia globalne Defguard', - user: 'Profil użytkownika', - provisioners: 'YubiKey Provisioners', - webhooks: 'Webhooki', - openId: 'Aplikacje OpenID', - overview: 'Przegląd lokalizacji', - networkSettings: 'Edycja lokalizacji', - enrollment: 'Rejestracja', - support: 'Wsparcie', - groups: 'Grupy', - devices: 'Urządzenia sieciowe', - }, - copyright: 'Copyright ©2023-2025', - version: { - open: 'Wersja aplikacji: {version}', - closed: 'v{version}', - }, - }, - form: { - download: 'Pobierz', - copy: 'Kopiuj', - saveChanges: 'Zapisz zmiany', - submit: 'Zapisz', - login: 'Zaloguj się', - cancel: 'Anuluj', - close: 'Zamknij', - placeholders: { - password: 'Hasło', - username: 'Nazwa użytkownika', - }, - error: { - invalidCode: 'Podany kod jest niewłaściwy.', - forbiddenCharacter: 'Pole zawiera niedozwolone znaki.', - usernameTaken: 'Nazwa użytkownika jest już w użyciu.', - invalidKey: 'Klucz jest nieprawidłowy.', - invalid: 'Pole jest nieprawidłowe.', - required: 'Pole jest wymagane.', - maximumLength: 'Maksymalna długość przekroczona.', - minimumLength: 'Minimalna długość nie została osiągnięta.', - noSpecialChars: 'Nie wolno używać znaków specjalnych.', - oneDigit: 'Wymagana jedna cyfra.', - oneSpecial: 'Wymagany jest znak specjalny.', - oneUppercase: 'Wymagana jedna duża litera.', - oneLowercase: 'Wymagana jedna mała litera.', - portMax: 'Maksymalny numer portu to 65535.', - endpoint: 'Wpisz poprawny adres.', - address: 'Wprowadź poprawny adres.', - addressNetmask: 'Wprowadź poprawny adres IP oraz maskę sieci.', - validPort: 'Wprowadź prawidłowy port.', - validCode: 'Kod powinien mieć 6 cyfr.', - allowedIps: 'Tylko poprawne adresy IP oraz domeny.', - startFromNumber: 'Nie może zaczynać się od liczby.', - repeat: 'Wartości się nie pokrywają.', - maximumValue: 'Maksymalna wartość {value} przekroczona.', - minimumValue: 'Minimalna wartość {value} nie osiągnięta.', - tooManyBadLoginAttempts: - 'Zbyt duża ilość nieprawidłowego logowania. Spróbuj ponownie za kilka minut.', - number: 'Wartość musi być liczbą.', - }, - floatingErrors: { - title: 'Popraw następujące błędy:', - }, - }, - components: { - deviceConfigsCard: { - cardTitle: 'Konfiguracja lokalizacji', - messages: { - copyConfig: 'Konfiguracja skopiowana', - }, - }, - gatewaysStatus: { - label: 'Gateways', - states: { - error: 'Błąd pobierania statusu', - }, - messages: { - error: 'Błąd pobierania statusu połączeń gateway', - deleteError: 'Błąd usuwania gateway', - }, - }, - noLicenseBox: { - footer: { - get: 'Uzyskaj licencję enterprise', - contact: 'poprzez kontakt:', - }, - }, - }, - settingsPage: { - title: 'Ustawienia', - tabs: { - smtp: 'SMTP', - global: 'Globalne', - ldap: 'LDAP', - openid: 'OpenID', - enterprise: 'Funkcjonalności enterprise', - }, - messages: { - editSuccess: 'Ustawienia zaktualizowane.', - challengeSuccess: 'Zmieniono wiadomość do podpisu.', - }, - enterpriseOnly: { - title: 'Ta funkcja jest dostępna tylko w wersji Defguard Enterprise', - currentExpired: 'Twoja obecna licencja wygasła.', - subtitle: 'Aby uzyskać więcej informacji, odwiedź naszą ', - website: 'stronę internetową', - }, - ldapSettings: { - title: 'Ustawienia LDAP', - sync: { - header: 'Obustronna synchronizacja LDAP', - info: 'Przed włączeniem synchronizacji, zapoznaj się z [dokumentacją](https://docs.defguard.net/enterprise/all-enteprise-features).', - }, - form: { - labels: { - ldap_enable: 'Włącz integrację z LDAP', - ldap_url: 'URL', - ldap_bind_username: 'Bind Username', - ldap_bind_password: 'Bind Password', - ldap_member_attr: 'Member Attribute', - ldap_username_attr: 'Username Attribute', - ldap_user_obj_class: 'User Object Class', - ldap_user_search_base: 'User Search Base', - ldap_user_auxiliary_obj_classes: 'Additional User Object Classes', - ldap_groupname_attr: 'Groupname Attribute', - ldap_group_search_base: 'Group Search Base', - ldap_group_member_attr: 'Group Member Attribute', - ldap_group_obj_class: 'Group Object Class', - ldap_sync_enabled: 'Włącz synchronizację w dwie strony', - ldap_authoritative_source: 'Użyj autorytatywne źródło danych', - ldap_sync_interval: 'Interwał synchronizacji', - ldap_use_starttls: 'Użyj StartTLS', - ldap_tls_verify_cert: 'Sprawdzaj certyfikat TLS', - ldap_uses_ad: 'Serwer LDAP jest serwerem Active Directory', - }, - delete: 'Usuń konfigurację', - }, - test: { - title: 'Test połączenia LDAP', - messages: { - error: 'Brak połączenia', - success: 'Połączono z LDAP', - }, - submit: 'Test', - }, - }, - openIdSettings: { - heading: 'Ustawienia zewnętrznego OpenID', - general: { - title: 'Ogólne ustawienia', - helper: - 'Możesz tu zmienić ogólną mechanikę działania zewnętrznego OpenID w twojej instancji Defguarda.', - createAccount: { - label: - 'Automatycznie twórz konta w momencie logowania przez zewnętrznego dostawcę OpenID', - helper: - 'Jeśli ta opcja jest włączona, Defguard automatycznie tworzy nowe konta dla użytkowników, którzy logują się po raz pierwszy za pomocą zewnętrznego dostawcy OpenID. W innym przypadku konto użytkownika musi zostać najpierw utworzone przez administratora.', - }, - useOpenIdForMfa: { - label: 'Używaj zewnętrznego OpenID dla MFA klienta', - helper: - 'Gdy zewnętrzny proces Multi-Factor Authentication (MFA) OpenID SSO jest włączony, użytkownicy łączący się z lokalizacjami VPN wymagającymi MFA będą musieli uwierzytelniać się przez swoją przeglądarkę używając skonfigurowanego dostawcy dla każdego połączenia. Jeśli to ustawienie jest wyłączone, MFA dla tych lokalizacji VPN będzie obsługiwane przez wewnętrzny system SSO Defguard. W takim przypadku użytkownicy muszą mieć skonfigurowane TOTP lub MFA oparte na e-mailu.', - }, - usernameHandling: { - label: 'Obsługa nazw użytkowników', - helper: - 'Skonfiguruj metodę obsługi nieprawidłowych znaków w nazwach użytkowników twojego dostawcy tożsamości.', - options: { - remove: 'Usuń niedozwolone znaki', - replace: 'Zamień niedozwolone znaki', - prune_email: 'Przytnij adres e-mail', - }, - }, - }, - form: { - title: 'Ustawienia klienta', - helper: - 'Tutaj możesz skonfigurować ustawienia klienta OpenID z wartościami dostarczonymi przez zewnętrznego dostawcę OpenID.', - custom: 'Niestandardowy', - none: 'Brak', - documentation: - 'Przeczytaj więcej o tej funkcji w naszej [dokumentacji](https://docs.defguard.net/enterprise/enterprise-features).', - delete: 'Usuń dostawcę', - directory_sync_settings: { - title: 'Ustawienia synchronizacji katalogu', - helper: - 'Synchronizacja katalogu pozwala na automatyczną synchronizację grup użytkowników i ich statusu na podstawie zewnętrznego dostawcy.', - notSupported: 'Synchronizacja katalogu nie jest obsługiwana dla tego dostawcy.', - connectionTest: { - success: 'Połączenie zakończone sukcesem.', - error: 'Wystąpił błąd podczas próby połączenia:', - }, - }, - selects: { - synchronize: { - all: 'Wszystko', - users: 'Użytkownicy', - groups: 'Grupy', - }, - behavior: { - keep: 'Zachowaj', - disable: 'Dezaktywuj', - delete: 'Usuń', - }, - }, - labels: { - provider: { - label: 'Dostawca', - helper: - 'Wybierz swojego dostawcę OpenID. Możesz użyć dostawcy niestandardowego i samodzielnie wypełnić pole URL bazowego.', - }, - client_id: { - label: 'ID klienta', - helper: 'ID klienta dostarczone przez dostawcę OpenID.', - }, - client_secret: { - label: 'Sekret klienta', - helper: 'Sekret klienta dostarczony przez dostawcę OpenID.', - }, - base_url: { - label: 'URL bazowy', - helper: - 'Podstawowy adres URL twojego dostawcy OpenID, np. https://accounts.google.com. Sprawdź naszą dokumentację, aby uzyskać więcej informacji i zobaczyć przykłady.', - }, - display_name: { - label: 'Wyświetlana nazwa', - helper: - 'Nazwa dostawcy OpenID, która będzie wyświetlana na przycisku logowania. Jeśli zostawisz to pole puste, przycisk będzie miał tekst "Zaloguj przez OIDC".', - }, - enable_directory_sync: { - label: 'Włącz synchronizację katalogu', - }, - sync_target: { - label: 'Synchronizuj', - helper: - 'Co będzie synchronizowane z zewnętrznym dostawcą OpenID. Możesz wybrać pomiędzy synchronizacją statusu użytkowników, ich przynależności do grup lub synchronizacją obu.', - }, - sync_interval: { - label: 'Interwał synchronizacji', - helper: 'Odstęp czasu w sekundach pomiędzy synchronizacjami katalogu.', - }, - user_behavior: { - label: 'Zachowanie kont użytkowników', - helper: - 'Wybierz jak postępować z kontami użytkowników, które nie znajdują się w katalogu zewnętrznego dostawcy. Możesz wybrać między zachowaniem ich, dezaktywacją lub całkowitym usunięciem.', - }, - admin_behavior: { - label: 'Zachowanie kont administratorów', - helper: - 'Wybierz, jak postępować z kontami administratorów Defguard, które nie znajdują się w katalogu zewnętrznego dostawcy. Możesz wybrać między zachowaniem ich, dezaktywacją lub całkowitym usunięciem.', - }, - admin_email: { - label: 'E-mail administratora', - helper: - 'Adres e-mail konta, za pośrednictwem którego będzię odbywać się synchronizacja, np. e-mail konta osoby, która skonfigurowała konto usługi Google. Więcej szczegółów możesz znaleźć w naszej dokumentacji.', - }, - service_account_used: { - label: 'Używane konto usługi', - helper: - 'Obecnie używane konto usługi Google do synchronizacji. Możesz je zmienić, przesyłając nowy plik klucza konta usługi.', - }, - service_account_key_file: { - label: 'Plik klucza konta usługi', - helper: - 'Prześlij nowy plik klucza konta usługi, aby ustawić konto usługi używane do synchronizacji. UWAGA: Przesłany plik nie będzie widoczny po zapisaniu ustawień i ponownym załadowaniu strony, ponieważ jego zawartość jest poufna i nie jest przesyłana z powrotem do panelu.', - uploaded: 'Przesłany plik', - uploadPrompt: 'Prześlij plik klucza konta usługi', - }, - okta_client_id: { - label: 'ID klienta synchronizacji Okta', - helper: 'ID klienta dla aplikacji synchronizacji Okta.', - }, - okta_client_key: { - label: 'Klucz prywatny klienta synchronizacji Okta', - helper: - 'Klucz prywatny dla aplikacji synchronizacji Okta w formacie JWK. Klucz nie jest wyświetlany ponownie po wgraniu.', - }, - jumpcloud_api_key: { - label: 'Klucz API JumpCloud', - helper: - 'Klucz API JumpCloud używany do synchronizacji stanu użytkowników i grup.', - }, - group_match: { - label: 'Synchronizuj tylko pasujące grupy', - helper: - 'Podaj listę nazw grup oddzielonych przecinkami, które powinny być synchronizowane. Jeśli pole zostanie puste, wszystkie grupy dostawcy zostaną zsynchronizowane.', - }, - }, - }, - }, - modulesVisibility: { - header: 'Widoczność modułów', - helper: `

- Jeśli nie używasz niektórych modułów, możesz zmienić ich widoczność -

- - Przeczytaj więcej w dokumentacji. - `, - fields: { - wireguard_enabled: { - label: 'WireGuard VPN', - }, - webhooks_enabled: { - label: 'Webhooks', - }, - worker_enabled: { - label: 'YubiBridge', - }, - openid_enabled: { - label: 'OpenID connect', - }, - }, - }, - defaultNetworkSelect: { - header: 'Domyślny widok sieci', - helper: `

Tutaj możesz zmienić domyślny widok sieci.

- - Przeczytaj więcej w dokumentacji. - `, - filterLabels: { - grid: 'Widok siatki', - list: 'Widok listy', - }, - }, - instanceBranding: { - header: 'Brandowanie instancji', - form: { - title: 'Nazwa i logo', - fields: { - instanceName: { - label: 'Nazwa instancji', - placeholder: 'Defguard', - }, - mainLogoUrl: { - label: 'URL logo na stronie logowania', - helper: 'Maksymalna wielkość zdjęcia to 250x100 px.', - placeholder: 'Domyślny obrazek', - }, - navLogoUrl: { - label: 'Menu i nawigacja - małe logo', - helper: 'Maksymalna wielkość zdjęcia to 100x100 px.', - placeholder: 'Domyślny obrazek', - }, - }, - controls: { - restoreDefault: 'Przywróć domyślne', - submit: 'Zapisz zmiany', - }, - }, - helper: ` -

- Tutaj możesz dodać URL swojego logo i nazwę dla swojej instancji defguard; - będzie ona wyświetlana zamiast defguard. -

- - Przeczytaj więcej w dokumentacji. - - `, - }, - license: { - header: 'Funkcje enterprise', - helpers: { - enterpriseHeader: { - text: 'Tutaj możesz zarządzać swoją licencją Defguard Enterprise.', - link: 'By dowiedzieć się więcej, odwiedź naszą stronę.', - }, - licenseKey: { - text: 'Wprowadź poniżej klucz licencyjny Defguard Enterprise. Powinieneś otrzymać go na swoją skrzynkę e-mailową po zakupie licencji.', - link: 'Licencję możesz zakupić tutaj.', - }, - }, - form: { - title: 'Licencja', - fields: { - key: { - label: 'Klucz licencji', - placeholder: 'Klucz licencji dla twojej instancji Defguard', - }, - }, - }, - licenseInfo: { - title: 'Informacje o licencji', - licenseNotRequired: - "

Posiadasz dostęp do tej funkcji enterprise, ponieważ nie przekroczyłeś jeszcze żadnych limitów. Sprawdź dokumentację, aby uzyskać więcej informacji.

", - types: { - subscription: { - label: 'Subskrypcja', - helper: 'Subskrypcja automatycznie odnawiana cyklicznie', - }, - offline: { - label: 'Offline', - helper: 'Licencja ważna do daty wygaśnięcia, odnawiana ręcznie', - }, - }, - fields: { - status: { - label: 'Status', - active: 'Aktywna', - expired: 'Wygasła', - subscriptionHelper: - 'Licencja w formie subskrypcji jest ważna przez pewien czas po dacie wygaśnięcia, by uwzględnić możliwe opóźnienia w automatycznej płatności.', - }, - type: { - label: 'Typ', - }, - validUntil: { - label: 'Ważna do', - }, - }, - }, - }, - smtp: { - form: { - title: 'Ustawienia', - sections: { - server: 'Ustawienia serwera', - }, - fields: { - server: { - label: 'Adres serwera', - placeholder: 'Adres', - }, - port: { - label: 'Port', - placeholder: 'Port', - }, - encryption: { - label: 'Szyfrowanie', - }, - user: { - label: 'Użytkownik', - placeholder: 'Użytkownik', - }, - password: { - label: 'Hasło', - placeholder: 'Hasło', - }, - sender: { - label: 'Adres wysyłającego', - placeholder: 'Adres', - helper: ` -

- Systemowe wiadomości będą wysyłane z tego adresu, np. no-reply@my-company.com. -

- `, - }, - }, - controls: { - submit: 'Zapisz zmiany', - }, - }, - delete: 'Usuń konfigurację', - testForm: { - title: 'Wyślij testowy e-mail', - subtitle: 'Wprowadź adres e-mail odbiorcy', - fields: { - to: { - label: 'Adres odbiorcy', - placeholder: 'Adres', - }, - }, - controls: { - submit: 'Wyślij', - resend: 'Wyślij ponownie', - retry: 'Spróbuj ponownie', - success: 'E-mail wysłany pomyślnie', - error: 'Błąd wysyłania e-maila', - }, - }, - helper: - 'Skonfiguruj serwer SMTP do wysyłania wiadomości systemowych do użytkowników.', - }, - enrollment: { - helper: - 'Rejestracja to proces, w ramach którego nowy użytkownik może samodzielnie aktywować swoje konto, ustawić hasło i skonfigurować urządzenie VPN.', - vpnOptionality: { - header: 'Opcjonalność kroku VPN', - helper: - 'Możesz zdecydować czy dodawanie urządzenia VPN jest obowiązkowym czy opcjonalnym krokiem rejestracji', - }, - welcomeMessage: { - header: 'Wiadomość powitalna', - helper: ` -

W tym polu możesz używać Markdown:

-
    -
  • Nagłówki zaczynają się od #
  • -
  • Użyj asterysków aby uzyskać *kursywę*
  • -
  • Użyj dwóch asterysków aby uzyskać **pogrubienie**
  • -
- `, - }, - welcomeEmail: { - header: 'E-mail powitalny', - helper: ` -

W tym polu możesz używać Markdown:

-
    -
  • Nagłówki zaczynają się od #
  • -
  • Użyj asterysków aby uzyskać *kursywę*
  • -
  • Użyj dwóch asterysków aby uzyskać **pogrubienie**
  • -
- `, - }, - form: { - controls: { - submit: 'Zapisz zmiany', - }, - welcomeMessage: { - helper: - 'Ta wiadomość będzie pokazywana użytkownikom po zakończeniu rejestracji. Sugerujemy wymienienie w niej istotnych linków oraz krótkie wyjaśnienie kolejnych kroków.', - placeholder: 'Wpisz wiadomość powitalną', - }, - welcomeEmail: { - helper: - 'Ta wiadomość zostanie wysłana do użytkowników po zakończeniu rejestracji. Sugerujemy wymienienie w niej istotnych linków oraz krótkie wyjaśnienie kolejnych kroków. Możesz użyć tej samej treści co w wiadomości powitalnej.', - placeholder: 'Wpisz e-mail powitalny', - }, - welcomeEmailSubject: { - label: 'Temat', - }, - useMessageAsEmail: { - label: 'Taki sam jak wiadomość powitalna', - }, - }, - }, - enterprise: { - header: 'Funkcjonalności Enterprise', - helper: 'Tutaj możesz zmienić ustawienia enterprise.', - fields: { - deviceManagement: { - label: 'Zablokuj możliwość zarządzania urządzeniami przez użytkowników', - helper: - 'Kiedy ta opcja jest włączona, tylko użytkownicy w grupie "Admin" mogą zarządzać urządzeniami w profilu użytkownika', - }, - disableAllTraffic: { - label: 'Zablokuj możliwość przekierowania całego ruchu przez VPN', - helper: - 'Kiedy ta opcja jest włączona, użytkownicy nie będą mogli przekierować całego ruchu przez VPN za pomocą klienta Defguard.', - }, - manualConfig: { - label: 'Wyłącz manualną konfigurację WireGuard', - helper: - 'Kiedy ta opcja jest włączona, użytkownicy nie będą mogli pobrać ani wyświetlić danych do manualnej konfiguracji WireGuard. Możliwe będzie wyłącznie skonfigurowanie klienta Defguard.', - }, - }, - }, - gatewayNotifications: { - smtpWarning: 'Aby włączyć powiadomienia należy najpierw skonfigurować serwer SMTP', - header: 'Powiadomienia', - helper: 'Tutaj możesz włączyć powiadomienia e-mail.', - sections: { - gateway: 'Powiadomienia o rozłączeniu Gatewaya', - }, - form: { - submit: 'Zapisz zmiany', - fields: { - disconnectNotificationsEnabled: { - label: 'Włącz powiadomienia o rozłączeniu', - help: "Wyślij powiadomienie do administratorów po rozłączeniu się Gateway'a", - }, - inactivityThreshold: { - label: 'Czas nieaktywności [minuty]', - help: 'Czas (w minutach), który musi upłynąć od rozłączenia zanim zostanie wysłane powiadomienie', - }, - reconnectNotificationsEnabled: { - label: 'Włącz powiadomienia o ponownym połączeniu', - help: "Wyślij powiadomienie do administratorów po ponownym nawiązaniu połączenia z Gateway'em", - }, - }, - }, - }, - }, - openidOverview: { - pageTitle: 'Aplikacje OpenID', - search: { - placeholder: 'Znajdź aplikacje', - }, - filterLabels: { - all: 'Wszystkie aplikacje', - enabled: 'Włączone', - disabled: 'Wyłączone', - }, - clientCount: 'Wszystkie aplikacje', - addNewApp: 'Dodaj aplikację', - list: { - headers: { - name: 'Nazwa', - status: 'Status', - actions: 'Akcję', - }, - editButton: { - edit: 'Edytuj aplikację', - delete: 'Usuń aplikację', - disable: 'Wyłącz', - enable: 'Włącz', - copy: 'Skopuj ID', - }, - status: { - enabled: 'Włączona', - disabled: 'Wyłączona', - }, - }, - messages: { - noLicenseMessage: 'Nie masz licencji dla tej funkcjonalności.', - noClientsFound: 'Nie znaleziono żadnych wyników.', - copySuccess: 'ID skopiowane', - }, - deleteApp: { - title: 'Usuń aplikację', - message: 'Czy chcesz usunąć aplikację {appName} ?', - submit: 'Usuń aplikację', - messages: { - success: 'Aplikacja usunięta.', - }, - }, - enableApp: { - messages: { - success: 'Aplikacja włączona', - }, - }, - disableApp: { - messages: { - success: 'Aplikacja wyłączona', - }, - }, - modals: { - openidClientModal: { - title: { - addApp: 'Dodaj aplikację', - editApp: 'Edytuj aplikację: {appName}', - }, - scopes: 'Zakresy:', - messages: { - clientIdCopy: 'Client ID zostało skopiowane.', - clientSecretCopy: 'Client secret zostało skopiowane.', - }, - form: { - messages: { - successAdd: 'Aplikacja utworzona.', - successModify: 'Aplikacja zmodyfikowana.', - }, - error: { - urlRequired: 'URL jest wymagany.', - validUrl: 'URL musi być poprawny.', - scopeValidation: 'Musi mieć co najmniej jeden zakres.', - }, - fields: { - name: { - label: 'Nazwa aplikacji', - }, - redirectUri: { - label: 'Przekierowujący URL {count}', - placeholder: 'https://example.com/redirect', - }, - openid: { - label: 'OpenID', - }, - profile: { - label: 'Profil', - }, - email: { - label: 'E-mail', - }, - phone: { - label: 'Telefon', - }, - groups: { - label: 'Grupy', - }, - }, - controls: { - addUrl: 'Dodaj URL', - }, - }, - clientId: 'Client ID', - clientSecret: 'Client secret', - }, - }, - }, - webhooksOverview: { - pageTitle: 'Webhooki', - search: { - placeholder: 'Znajdź webhooki po adresie URL', - }, - filterLabels: { - all: 'Wszystkie webhooki', - enabled: 'Włączone', - disabled: 'Wyłączone', - }, - webhooksCount: 'Wszystkie webhooki', - addNewWebhook: 'Dodaj webhook', - noWebhooksFound: 'Nie znaleziono żadnych webhooków', - list: { - headers: { - name: 'Nazwa', - description: 'Opis', - status: 'Status', - actions: 'Akcję', - }, - editButton: { - edit: 'Edytuj', - delete: 'Usuń webhook', - disable: 'Wyłącz', - enable: 'Włącz', - }, - status: { - enabled: 'Włączony', - disabled: 'Wyłączony', - }, - }, - }, - provisionersOverview: { - pageTitle: 'Provisionery', - search: { - placeholder: 'Wyszukaj provisionera', - }, - filterLabels: { - all: 'Wszystkie', - available: 'Dostępne', - unavailable: 'Niedostępne', - }, - provisionersCount: 'Wszystkie provisionery', - noProvisionersFound: 'Nie znaleziono provisionerów.', - noLicenseMessage: 'Nie masz licencji na tę funkcję.', - provisioningStation: { - header: 'Stacja provisionująca YubiKey', - content: `Aby móc sprovisionować YubiKeya, należy najpierw skonfigurować - fizyczną maszynę z gniazdem USB. Uruchom podane polecenie na wybranej maszynie - aby zarejestrować maszynę i rozpocząć generowanie kluczy.`, - tokenCard: { - title: 'Token autoryzacyjny', - }, - dockerCard: { - title: 'Przykład Docker', - }, - }, - list: { - headers: { - name: 'Nazwa', - ip: 'Adres IP', - status: 'Status', - actions: 'Akcję', - }, - editButton: { - delete: 'Usuń provisionera', - }, - status: { - available: 'Dostępny', - unavailable: 'Niedostępny', - }, - }, - messages: { - copy: { - command: 'Komenda skopiowa', - token: 'Token skopiowany', - }, - }, - }, - openidAllow: { - header: '{name} chciałby:', - scopes: { - openid: 'Użyć danych z twojego profilu do przyszłych logowań.', - profile: - 'Poznać podstawowe informacje z twojego profilu, takie jak login, imię itp', - email: 'Poznać twój adres e-mail.', - phone: 'Poznać twój numer telefonu.', - groups: 'Poznać twoje grupy.', - }, - controls: { - accept: 'Akceptuj', - cancel: 'Anuluj', - }, - }, - networkOverview: { - pageTitle: 'Przegląd lokalizacji', - controls: { - editNetworks: 'Edycja lokalizacji', - selectNetwork: { - placeholder: 'Oczekiwanie na lokalizacje', - }, - }, - filterLabels: { - grid: 'Widok siatki', - list: 'Widok listy', - }, - stats: { - currentlyActiveUsers: 'Obecnie aktywni użytkownicy', - activeUsersFilter: 'Aktywni użytkownicy w {hour}H', - activeDevicesFilter: 'Aktywne urządzenia w {hour}H', - activityIn: 'Aktywność w {hour}H', - in: 'Przychodzący:', - out: 'Wychodzący:', - gatewayDisconnected: 'Gateway rozłączony', - }, - }, - connectedUsersOverview: { - pageTitle: 'Podłączeni użytkownicy', - noUsersMessage: 'Obecnie nie ma żadnych podłączonych użytkowników', - userList: { - username: 'Nazwa użytkownika', - device: 'Urządzenia:', - connected: 'Połączony:', - deviceLocation: 'Lokacja urządzenia', - networkUsage: 'Użycie sieci', - }, - }, - networkPage: { - pageTitle: 'Edycja lokalizacji', - addNetwork: '+ Dodaj lokalizację', - controls: { - networkSelect: { - label: 'Wybór lokalizacji', - }, - }, - }, - activityOverview: { - header: 'Strumień aktywności', - noData: 'Obecnie nie wykryto żadnej aktywności', - }, - networkConfiguration: { - messages: { - delete: { - error: 'Błąd podczas próby usunięcia lokalizacji', - success: 'Lokalizacja usunięta', - }, - }, - header: 'Konfiguracja lokalizacji', - importHeader: 'Import lokalizacji', - form: { - helpers: { - address: - 'Na podstawie tego adresu będzie stworzona sieć VPN, np. 10.10.10.1/24 (sieć VPN: 10.10.10.0/24). Opcjonalnie możesz podać wiele adresów, oddzielając je przecinkiem. Pierwszy adres będzie adresem głównym i zostanie użyty do przypisywania adresów IP urządzeniom. Pozostałe adresy są dodatkowe i nie będą zarządzane przez Defguarda.', - endpoint: - 'Publiczny adres IP lub domena internetowa, do której będą łączyć się użytkownicy/urządzenia. Ten adres zostanie użyty w konfiguracji klientów, ale Gatewaye Defguard nie wiążą się z tym adresem.', - gateway: - 'Adres publiczny Gatewaya, używany przez użytkowników VPN do łączenia się.', - dns: 'Określ resolwery DNS, które mają odpytywać, gdy interfejs WireGuard jest aktywny.', - allowedIps: 'Lista adresów/masek, które powinny być routowane przez sieć VPN.', - allowedGroups: - 'Domyślnie wszyscy użytkownicy będą mogli połączyć się z tą lokalizacją. Jeżeli chcesz ogranicznyć dostęp do tej lokalizacji do wybranej grupy użytkowników, wybierz ją poniżej.', - }, - messages: { - networkModified: 'Lokalizacja zmodyfikowana', - networkCreated: 'Lokalizacja utworzona', - }, - fields: { - name: { - label: 'Nazwa lokalizacji', - }, - address: { - label: 'Adres i maska sieci VPN', - }, - endpoint: { - label: 'Adres IP lub domena internetowa Gatewaya', - }, - allowedIps: { - label: 'Dozwolone adresy IP', - }, - port: { - label: 'Port Gatewaya', - }, - dns: { - label: 'DNS', - }, - allowedGroups: { - label: 'Dozwolone grupy', - placeholder: 'Wszystkie grupy', - }, - mfa_enabled: { - label: 'Wymagaj MFA dla tej lokalizacji', - }, - keepalive_interval: { - label: 'Utrzymanie połączenia [sekundy]', - }, - peer_disconnect_threshold: { - label: 'Próg rozłączania [sekundy]', - }, - acl_enabled: { - label: 'Włącz ACL dla tej lokacji', - }, - acl_default_allow: { - label: 'Domyślna polityka ACL', - }, - }, - controls: { - submit: 'Zapisz zmiany', - cancel: 'Wróć', - delete: 'Usuń lokalizację', - }, - }, - }, - gatewaySetup: { - header: { - main: 'Uruchomienie serwera gateway', - dockerBasedGatewaySetup: `Konfiguracja gateway za pomocą narzędzia docker`, - fromPackage: `Z pakietu`, - oneLineInstall: `Instalacja za pomocą jednej linii`, - }, - card: { - title: 'Komenda Dockera uruchamiająca serwer gateway', - authToken: 'Token Autoryzacyjny', - }, - button: { - availablePackages: `Dostępne pakiety`, - }, - controls: { - status: 'Sprawdź status połączenia', - }, - messages: { - runCommand: `Defguard wymaga uruchomienia serwera gateway w celu kontrolowania VPN. - Szczegóły znajdziesz w [dokumentacji]({setupGatewayDocs}). - Istnieje wiele sposobów na uruchomienie serwera gateway, poniższy przykład używa technologii Docker, - więcej przykładów znajdziesz w [dokumentacji]({setupGatewayDocs}).`, - createNetwork: `Utwórz sieć przed uruchomieniem procesu gateway.`, - noConnection: `Brak połączenia proszę uruchom poniższą komendę.`, - connected: `Gateway połączony.`, - statusError: 'Nie udało się uzyskać statusu', - oneLineInstall: `Jeśli wykonujesz instalację w jednej linii: https://docs.defguard.net/getting-started/one-line-install - nie ma potrzeby wykonywania dalszych kroków.`, - fromPackage: `Zainstaluj pakiet dostępny na https://github.com/DefGuard/gateway/releases/latest i skonfiguruj \`/etc/defguard/gateway.toml\` - na podstawie [dokumentacji]({setupGatewayDocs}).`, - authToken: `Poniższy token jest wymagany do autoryzacji i konfiguracji węzła gateway. Upewnij się, że zachowasz ten token w bezpiecznym miejscu, - a następnie podążaj za instrukcją wdrażania usługi znajdującej się w [dokumentacji]({setupGatewayDocs}), aby pomyślnie skonfigurować serwer gateway. - Po więcej szczegółów i dokładnych kroków, proszę zapoznaj się z [dokumentacją](setupGatewayDocs).`, - dockerBasedGatewaySetup: `Poniżej znajduje się przykład oparty na Dockerze. - Więcej szczegółów i dokładnych kroków można znaleźć w [dokumentacji]({setupGatewayDocs}).`, - }, - }, - loginPage: { - pageTitle: 'Wprowadź swoje dane logowania', - callback: { - return: 'Powrót do logowania', - error: 'Wystąpił błąd podczas logowania przez zewnętrznego dostawcę OpenID', - }, - oidcLogin: 'Zaloguj się przez', - mfa: { - title: 'Autoryzacja dwuetapowa.', - controls: { - useAuthenticator: 'Zamiast tego użyj aplikacji Authenticator', - useWebauthn: 'Zamiast tego użyj klucza bezpieczeństwa', - useRecoveryCode: 'Zamiast tego użyj kodu odzyskiwania', - useEmail: 'Zamiast tego użyj e-mail', - }, - email: { - header: 'Użyj kodu wysłanego na e-mail aby kontynuować', - form: { - controls: { - resendCode: 'Wyślij kod ponownie', - }, - labels: { - code: 'Kod', - }, - }, - }, - totp: { - header: - 'Użyj kodu z aplikacji uwierzytelniającej i kliknij przycisk, aby kontynuować', - form: { - fields: { - code: { - placeholder: 'Wprowadź kod uwierzytelniający', - }, - }, - controls: { - submit: 'Użyj kodu uwierzytelniającego', - }, - }, - }, - recoveryCode: { - header: - 'Wpisz jeden z aktywnych kodów odzyskiwania i kliknij przycisk, aby się zalogować.', - form: { - fields: { - code: { - placeholder: 'Kod odzyskiwania', - }, - }, - controls: { - submit: 'Użyj kodu odzyskiwania', - }, - }, - }, - webauthn: { - header: 'Gdy jesteś gotowy do uwierzytelnienia, naciśnij przycisk poniżej.', - controls: { - submit: 'Użyj klucza bezpieczeństwa', - }, - messages: { - error: 'Nie udało się odczytać klucza. Proszę spróbować ponownie.', - }, - }, - }, - }, - wizard: { - completed: 'Sieć skonfigurowana', - configuration: { - successMessage: 'Sieć utworzona', - }, - navigation: { - top: 'Konfiguracja sieci', - titles: { - welcome: 'Konfiguracja sieci', - choseNetworkSetup: 'Wybierz tryb konfiguracji', - importConfig: 'Importuj istnijącą sieć', - manualConfig: 'Konfiguracja sieci', - mapDevices: 'Mapowanie importowanych urządzeń', - }, - buttons: { - next: 'Dalej', - back: 'Wróć', - }, - }, - welcome: { - header: 'Witaj w asystencie konfiguracji lokalizacji!', - sub: 'Zanim zaczniesz, musisz wybrać tryb konfiguracji. Ikony zawierają przydane informacje.', - button: 'Zacznij konfigurację', - }, - deviceMap: { - messages: { - crateSuccess: 'Urządzenie dodane', - errorsInForm: 'Uzupełnij oznaczone pola', - }, - list: { - headers: { - deviceName: 'Nazwa', - deviceIP: 'IP', - user: 'Użytkownik', - }, - }, - }, - wizardType: { - manual: { - title: 'Manualny', - description: 'Ręczna konfiguracja sieci WireGuard', - }, - import: { - title: 'Import', - description: 'Import z pliku konfiguracyjnego WireGuard', - }, - createNetwork: 'Utwórz sieć WireGuard', - }, - common: { - select: 'Wybierz', - }, - locations: { - form: { - name: 'Nazwa', - ip: 'Adres IP', - user: 'Użytkownik', - fileName: 'Plik', - selectFile: 'Wybierz plik', - messages: { devicesCreated: 'Urządzenia utworzone.' }, - validation: { invalidAddress: 'Nieprawidłowy adres.' }, - }, - }, - }, - layout: { - select: { - addNewOptionDefault: 'Dodaj +', - }, - }, - redirectPage: { - title: 'Zostałeś zalogowany', - subtitle: 'Wkrótce nastąpi przekierowanie...', - }, - enrollmentPage: { - title: 'Rejestracja', - controls: { - default: 'Domyślne', - save: 'Zapisz zmiany', - }, - messages: { - edit: { - error: 'Zapis nieudany', - success: 'Zapisano zmiany', - }, - }, - settings: { - welcomeMessage: { - title: 'Powitalna wiadomość', - messageBox: 'Ta informacja będzie wyświetlona w końcowym kroku rejestracj', - }, - welcomeEmail: { - subject: { - label: 'Temat wiadomości', - }, - title: 'Powitalny e-mail', - messageBox: 'Ta informacja będzie wysłana gdy użytkownik zakończy rejestrację.', - controls: { - duplicateWelcome: 'Identyczna jak wiadomość powitalna', - }, - }, - vpnOptionality: { - title: 'Opcjonalność kroku VPN', - select: { - options: { - optional: 'Opcjonalny', - mandatory: 'Obowiązkowy', - }, - }, - }, - }, - messageBox: - 'Proces rejestracji pozwala użytkownikowi na potwierdzenie swoich informacji, ustawienie hasła oraz skonfigurowanie VPN na swoim urządzeniu. Tutaj możesz skonfigurować ten proces.', - }, - supportPage: { - title: 'Wsparcie', - modals: { - confirmDataSend: { - title: 'Potwierdź przekazanie danych', - submit: 'Wyślij', - subTitle: - 'Potwierdź przesłanie danych diagnostycznych. Żadne poufne dane nie zostaną przesłane. (Klucze WireGuard, adresy e-mail, itp.)', - }, - }, - debugDataCard: { - title: 'Dane wsparcia technicznego', - body: ` -Jeśli potrzebujesz pomocy lub zostałeś poproszony przez nasz zespół o utworzenie danych wsparcia technicznego (np. na naszym kanale Matrix: **#defguard-support:teonite.com**), masz dwie opcje: -* Możesz skonfigurować ustawienia SMTP i kliknąć: "Wyślij dane wsparcia technicznego". -* Lub kliknąć "Pobierz dane wsparcia technicznego" i stworzyć zlecenie w naszym repozytorium GitHub załączając te pliki. -`, - downloadSupportData: 'Pobierz dane wsparcia technicznego', - downloadLogs: 'Pobierz dzienniki', - sendMail: 'Wyślij e-mail', - mailSent: 'E-mail wysłany', - mailError: 'Błąd wysyłania e-mail', - }, - - supportCard: { - title: 'Wsparcie', - body: ` -Przed zgłoszeniem problemów na GitHub należy zapoznać z dokumentacją dostępną na [docs.defguard.net](https://docs.defguard.net/) - -Aby zgłosić: -* Problem - przejdź do [GitHub](https://github.com/DefGuard/defguard/issues/new?assignees=&labels=bug&template=bug_report.md&title=) -* Prośbę o nową funkcjonalność - przejdź do [GitHub](https://github.com/DefGuard/defguard/issues/new?assignees=&labels=feature&template=feature_request.md&title=) - -W przypadku innych zgłoszeń skontaktuj się z nami: support@defguard.net -`, - }, - }, - devicesPage: { - title: 'Urządzenia sieciowe', - search: { - placeholder: 'Znajdź', - }, - bar: { - itemsCount: 'Wszystkie urządzenia', - filters: {}, - actions: { - addNewDevice: 'Dodaj nowe', - }, - }, - list: { - columns: { - labels: { - name: 'Nazwa', - location: 'Położenie', - description: 'Opis', - addedBy: 'Dodane przez', - addedAt: 'Data dodania', - edit: 'Zmień', - }, - }, - edit: { - actionLabels: { - config: 'Zobacz konfigurację', - generateToken: 'Utwórz kupon autoryzacyjny', - }, - }, - }, - }, - acl: { - messageBoxes: { - aclAliasKind: { - component: { - name: 'Komponent', - description: - 'w połączeniu z ręcznie skonfigurowanymi miejscami docelowymi w ACL', - }, - destination: { - name: 'Miejsce docelowe', - description: 'zostanie zamienione na osobny zestaw reguł firewalla', - }, - }, - networkSelectionIndicatorsHelper: { - //md - denied: ` - Dostęp do lokalizacji domyślnie jest **zabroniony** – ruch sieciowy nie określony przez reguły będzie blokowany. - `, - //md - allowed: ` - Dostęp do lokalizacji domyślnie jest **dozwolony** – ruch sieciowy nie określony przez reguły będzie przepuszczany. - `, - //md - unmanaged: ` - Dostęp do lokalizacji nie jest zarządzany (wyłączona kontrola ACL) - `, - }, - }, - sharedTitle: 'Lista kontroli dostępu', - fieldsSelectionLabels: { - ports: 'Wszystkie porty', - protocols: 'Wszystkie protokoły', - }, - ruleStatus: { - new: 'Nowa', - applied: 'Zastosowana', - modified: 'Czeka na zmianę', - deleted: 'Czeka na usunięcie', - enable: 'Włącz', - enabled: 'Włączona', - disable: 'Wyłącz', - disabled: 'Wyłączona', - expired: 'Przedawniona', - }, - listPage: { - message: { - changeDiscarded: 'Zmiana odrzucona', - changeAdded: 'Dodana zmiana oczekująca', - changeFail: 'Nie udało się wykonać zmiany', - applyChanges: 'Zmiana została zastosowana', - applyFail: 'Nie udało się zastosować zmiany', - }, - rules: { - modals: { - applyConfirm: { - title: 'Wdróż oczekujące zmiany', - subtitle: '{count} zmian zostanie zastosowanych', - submit: 'Wdróż zmiany', - }, - filterGroupsModal: { - groupHeaders: { - alias: 'Aliasy', - location: 'Lokalizacje', - groups: 'Grupy', - status: 'Status', - }, - submit: 'Zapisz filtr', - }, - }, - listControls: { - searchPlaceholder: 'Znajdź nazwę', - addNew: 'Dodaj nową', - filter: { - nothingApplied: 'Filtr', - applied: 'Filtry ({count})', - }, - apply: { - noChanges: 'Wdróż oczekujące zmiany', - all: 'Wdróż oczekujące zmiany ({count})', - selective: 'Wdróż zaznaczone zmiany ({count})', - }, - }, - list: { - pendingList: { - title: 'Oczekujące zmiany', - noData: 'Brak oczekujących zmian', - noDataSearch: 'Nie znaleziono oczekujących zmian', - }, - deployedList: { - title: 'Wdrożone reguły', - noData: 'Brak wdrożonych reguł', - noDataSearch: 'Nie znaleziono wdrożonych reguł', - }, - headers: { - name: 'Nazwa reguły', - id: 'ID', - destination: 'Miejsce docelowe', - allowed: 'Zazwolone', - denied: 'Zabronione', - locations: 'Lokalizacje', - status: 'Status', - edit: 'Zmień', - }, - tags: { - all: 'Wszystkie', - allDenied: 'Wszystkie zabronione', - allAllowed: 'Wszystkie zezwolne', - }, - editMenu: { - discard: 'Odrzuć zmiany', - delete: 'Zaznacz do usunięcia', - }, - }, - }, - aliases: { - message: { - rulesApply: 'Oczekujące zmiany zostały zastosowane', - rulesApplyFail: 'Nie udało się zastosować zmian', - aliasDeleted: 'Alias usunięty', - aliasDeleteFail: 'Nie udało się usunąć aliasu', - }, - modals: { - applyConfirm: { - title: 'Potwierdź wdrożenie aliasu', - message: `Uaktualnione aliasy zmienią następujące reguły obecnie wdrożone na Gatewayu.\nZanim przejdziesz dalej, upewnij się, że te zmiany są zamierzone.`, - listLabel: 'Dotyczy reguł', - submit: 'Wdóż zmiany', - }, - deleteBlock: { - title: 'Usuwanie zablokowane', - //md - content: ` -Ten alias jest obecnie używany przez nastąpujące reguły i nie może być usunięty. Aby go usunąć, należy najpierw wykasować go z tych reguł({rulesCount}): -`, - }, - filterGroupsModal: { - groupLabels: { - rules: 'Reguły', - status: 'Status', - }, - }, - create: { - labels: { - name: 'Nazwa aliasu', - kind: 'Rodzajj aliasu', - ip: 'Zakres adresów IPv4/6 CIDR', - ports: 'Porty lub zakres portów', - protocols: 'Protokoły', - }, - placeholders: { - protocols: 'Wszystkie protokoły', - ports: 'Wszystkie porty', - ip: 'Wszystkie adresy IP', - }, - kindOptions: { - destination: 'Miejsce docelowe', - component: 'Komponent', - }, - controls: { - cancel: 'Anuluj', - edit: 'Edytuj alias', - create: 'Utwórz alias', - }, - messages: { - modified: 'Alias zmienione', - created: 'Alias utworzony', - }, - }, - }, - listControls: { - searchPlaceholder: 'Znajdź nazwę', - addNew: 'Dodaj nową', - filter: { - nothingApplied: 'Filtr', - applied: 'Filtry ({count})', - }, - apply: { - noChanges: 'Wdróż oczkujące zmiany', - all: 'Wdróż oczkujące zmiany ({count})', - selective: 'Wdróż zaznaczone zmiany ({count})', - }, - }, - list: { - pendingList: { - title: 'Oczkujące zmiany', - noData: 'Brak oczkujących zmian', - noDataSearch: 'Nie znaleziono oczkujących zmian', - }, - deployedList: { - title: 'Wdrożone aliasy', - noData: 'Brak wdrożonych aliasów', - noDataSearch: 'Nie znaleziono wdrożonych aliasów', - }, - headers: { - id: 'ID', - name: 'Nazwa aliasu', - kind: 'Rodzaj aliasu', - ip: 'Zakres adresów IPv4/6 CIDR', - ports: 'Porty', - protocols: 'Protokoły', - status: 'Status', - edit: 'Zmień', - rules: 'Reguły', - }, - status: { - applied: 'Zastosowane', - changed: 'Zmieione', - }, - tags: { - allDenied: 'Wszystkie zabronione', - allAllowed: 'Wszystkie dozwolne', - }, - editMenu: { - discardChanges: 'Odrzuć zmiany', - delete: 'Usuń alias', - }, - }, - }, - }, - createPage: { - formError: { - allowDenyConflict: 'Konfliktujący członkowie', - allowNotConfigured: - 'Trzeba skonfigurowć dostęp dla użytkowników, grup lub urządzeń', - }, - infoBox: { - // md - allowInstructions: ` - Podaj jedno lub więcej pól (użytkownicy, grupy lub urządzenia) aby zdefinionwać tę regułę. Reguła uwzględni wszystkie podane wejścia dla pasujących warunków. Pozostaw puste pola, jeżeli nie są potrzebne.`, - // md - destinationInstructions: ` - Podaj jedno lub więcej pól (adresy IP lub porty) aby zdefinionwać tę regułę. Reguła uwzględni wszystkie podane wejścia dla pasujących warunków. Pozostaw puste pola, jeżeli nie są potrzebne.`, - }, - message: { - create: 'Reguła została utworzona i dodana do oczekujących zmian.', - createFail: 'Nie można było utworzyć reguły.', - }, - headers: { - rule: 'Reguła', - createRule: 'Utwórz regułę', - allowed: 'Zezwoleni użytkownicy/grupy/urządzenia', - denied: 'Zablokowani użytkownicy/grupy/urządzenia', - destination: 'Miejsce docelowe', - }, - labels: { - name: 'Nazwa reguły', - priority: 'Priorytet', - status: 'Status', - locations: 'Lokalizacje', - allowAllUsers: 'Zezwól wszystkim użytkownikom', - allowAllNetworks: 'Włącz wszystkie lokalizacje', - allowAllNetworkDevices: 'Zezwól wszystkim urządzeniom sieciowym', - denyAllUsers: 'Zablokuj wszystkich użytkowników', - denyAllNetworkDevices: 'Zablokuj wszystkie urządzenia sieciowe', - users: 'Użytkownicy', - groups: 'Grupy', - devices: 'Urządzenia sieciowe', - protocols: 'Protokoły', - manualIp: 'Zakres lub adres IPv4/6 CIDR', - ports: 'Porty', - aliases: 'Aliasy', - expires: 'Data wygaśnięcia', - manualInput: 'Ręczne wprowadzenie', - }, - placeholders: { - allProtocols: 'Wszystkie protokoły', - allIps: 'Wszystkie adresy IP', - }, - }, - }, -} as PartialDeep; - -const pl = deepmerge(en, translation) as Translation; - -export default pl; diff --git a/web/src/main.tsx b/web/src/main.tsx index ef6ffc73c..af59167f2 100644 --- a/web/src/main.tsx +++ b/web/src/main.tsx @@ -1,32 +1,12 @@ -import './shared/scss/styles.scss'; -import './shared/defguard-ui/scss/index.scss'; - -import { QueryClientProvider } from '@tanstack/react-query'; -// import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; -import dayjs from 'dayjs'; -import LocalizedFormat from 'dayjs/plugin/localizedFormat'; -import utc from 'dayjs/plugin/utc'; import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; +import './shared/defguard-ui/scss/index.scss'; +import 'react-loading-skeleton/dist/skeleton.css'; +import { App } from './app/App.tsx'; -import { AppLoader } from './components/AppLoader'; -import { I18nProvider } from './components/I18nProvider'; -import { ApiProvider } from './shared/hooks/api/provider'; -import queryClient from './shared/query-client'; - -dayjs.extend(utc); -dayjs.extend(LocalizedFormat); - -const root = createRoot(document.getElementById('root') as HTMLElement); -root.render( +// biome-ignore lint/style/noNonNullAssertion: always there +createRoot(document.getElementById('root')!).render( - - - - - {/* */} - - - + , ); diff --git a/web/src/markdown.d.ts b/web/src/markdown.d.ts deleted file mode 100644 index ddd15b04a..000000000 --- a/web/src/markdown.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -declare module '*.md' { - const val: string; - export default val; -} diff --git a/web/src/pages/ActivityLogPage/ActivityLogPage.tsx b/web/src/pages/ActivityLogPage/ActivityLogPage.tsx new file mode 100644 index 000000000..2ee22a070 --- /dev/null +++ b/web/src/pages/ActivityLogPage/ActivityLogPage.tsx @@ -0,0 +1,53 @@ +import { useInfiniteQuery } from '@tanstack/react-query'; +import { useMemo } from 'react'; +import api from '../../shared/api/api'; +import { Page } from '../../shared/components/Page/Page'; +import { SizedBox } from '../../shared/defguard-ui/components/SizedBox/SizedBox'; +import { ThemeSpacing } from '../../shared/defguard-ui/types'; +import { isPresent } from '../../shared/defguard-ui/utils/isPresent'; +import { ActivityLogTable } from './ActivityLogTable'; + +export const ActivityLogPage = () => { + const { data, fetchNextPage, isFetchingNextPage } = useInfiniteQuery({ + queryKey: ['activity-log'], + initialPageParam: 1, + queryFn: ({ pageParam }) => + api.getActivityLog({ + page: pageParam, + }), + getNextPageParam: (lastPage) => lastPage?.pagination.next_page, + getPreviousPageParam: (page) => { + if (page.pagination.current_page !== 1) { + return page.pagination.current_page - 1; + } + return null; + }, + }); + + const flatQueryData = useMemo(() => data?.pages.flat() ?? null, [data?.pages]); + const flatData = useMemo( + () => flatQueryData?.flatMap((page) => page.data) ?? null, + [flatQueryData], + ); + + const lastItem = flatQueryData ? flatQueryData[flatQueryData?.length - 1] : null; + const pagination = lastItem ? lastItem.pagination : null; + + return ( + + + {isPresent(flatData) && isPresent(pagination) && ( + { + fetchNextPage(); + }} + hasNextPage={pagination.next_page !== null} + /> + )} + + ); +}; diff --git a/web/src/pages/ActivityLogPage/ActivityLogTable.tsx b/web/src/pages/ActivityLogPage/ActivityLogTable.tsx new file mode 100644 index 000000000..cdcdb46c3 --- /dev/null +++ b/web/src/pages/ActivityLogPage/ActivityLogTable.tsx @@ -0,0 +1,167 @@ +import { + createColumnHelper, + getCoreRowModel, + useReactTable, +} from '@tanstack/react-table'; +import { useMemo } from 'react'; +import { activityLogEventDisplay } from '../../shared/api/activity-log-types'; +import type { + ActivityLogEvent, + ActivityLogFilters, + PaginationMeta, +} from '../../shared/api/types'; +import { EmptyStateFlexible } from '../../shared/defguard-ui/components/EmptyStateFlexible/EmptyStateFlexible'; +import { TableBody } from '../../shared/defguard-ui/components/table/TableBody/TableBody'; +import { TableCell } from '../../shared/defguard-ui/components/table/TableCell/TableCell'; +import { TableTop } from '../../shared/defguard-ui/components/table/TableTop/TableTop'; +import { useApiToTableState } from '../../shared/defguard-ui/hooks/useApiToTableState'; +import { isPresent } from '../../shared/defguard-ui/utils/isPresent'; +import { displayDate } from '../../shared/utils/displayDate'; + +type RowData = ActivityLogEvent; + +const columnHelper = createColumnHelper(); + +interface Props { + data: RowData[]; + filters: Partial; + pagination: PaginationMeta; + hasNextPage: boolean; + loadingNextPage: boolean; + onNextPage: () => void; +} + +export const ActivityLogTable = ({ + data, + filters, + pagination, + loadingNextPage, + hasNextPage, + onNextPage, +}: Props) => { + const { sortingState } = useApiToTableState({ + ...filters, + ...pagination, + defaultSortingKey: 'timestamp', + }); + + const columns = useMemo( + () => [ + columnHelper.accessor('timestamp', { + header: 'Date', + enableSorting: true, + minSize: 160, + cell: (info) => { + const data = info.getValue(); + const formatted = displayDate(data); + return ( + + {formatted} + + ); + }, + }), + columnHelper.accessor('username', { + header: 'User', + minSize: 150, + cell: (info) => ( + + {info.getValue()} + + ), + }), + columnHelper.accessor('ip', { + header: 'IP', + minSize: 150, + cell: (info) => ( + + {info.getValue()} + + ), + }), + columnHelper.accessor('location', { + header: 'Location', + minSize: 130, + cell: (info) => { + const value = info.getValue(); + return ( + + {isPresent(value) ? {value} : {`~`}} + + ); + }, + }), + columnHelper.accessor('event', { + header: 'Event', + minSize: 190, + cell: (info) => { + const event = info.getValue(); + return ( + + {activityLogEventDisplay[event]} + + ); + }, + }), + columnHelper.accessor('module', { + header: 'Module', + minSize: 140, + cell: (info) => { + const value = info.getValue(); + return ( + + {value} + + ); + }, + }), + columnHelper.accessor('description', { + header: 'Description', + minSize: 300, + cell: (info) => { + const value = info.getValue(); + return ( + + {value} + + ); + }, + }), + ], + [], + ); + + const table = useReactTable({ + state: { + sorting: sortingState, + }, + data, + columns, + columnResizeMode: 'onChange', + getCoreRowModel: getCoreRowModel(), + enableRowSelection: false, + enableExpanding: false, + enableSorting: true, + }); + + if (data.length === 0) + return ( + + ); + + return ( + <> + + + + ); +}; diff --git a/web/src/pages/AddExternalOpenIdWizardPage/AddExternalOpenIdWizardPage.tsx b/web/src/pages/AddExternalOpenIdWizardPage/AddExternalOpenIdWizardPage.tsx new file mode 100644 index 000000000..997785f3b --- /dev/null +++ b/web/src/pages/AddExternalOpenIdWizardPage/AddExternalOpenIdWizardPage.tsx @@ -0,0 +1,73 @@ +import { useNavigate } from '@tanstack/react-router'; +import { type ReactNode, useMemo } from 'react'; +import type { WizardPageStep } from '../../shared/components/wizard/types'; +import { WizardPage } from '../../shared/components/wizard/WizardPage/WizardPage'; +import { externalProviderName, SUPPORTED_SYNC_PROVIDERS } from '../../shared/constants'; +import { AddExternalOpenIdClientSettingsStep } from './steps/AddExternalOpenIdClientSettingsStep/AddExternalOpenIdClientSettingsStep'; +import { AddExternalOpenIdDirectoryStep } from './steps/AddExternalOpenIdDirectoryStep/AddExternalOpenIdDirectoryStep'; +import { AddExternalOpenIdValidationStep } from './steps/AddExternalOpenIdValidationStep/AddExternalOpenIdValidationStep'; +import { AddExternalProviderStep, type AddExternalProviderStepValue } from './types'; +import { useAddExternalOpenIdStore } from './useAddExternalOpenIdStore'; + +const steps: Record = { + 'client-settings': , + 'directory-sync': , + validation: , +}; + +export const AddExternalOpenIdWizardPage = () => { + const provider = useAddExternalOpenIdStore((s) => s.provider); + const activeStep = useAddExternalOpenIdStore((s) => s.activeStep); + const navigate = useNavigate(); + + const stepsConfig = useMemo(() => { + const res: Record = { + 'client-settings': { + id: AddExternalProviderStep.ClientSettings, + label: 'Client Settings', + order: 0, + description: + 'Manage core details and connection parameters for your VPN location.', + hidden: false, + }, + 'directory-sync': { + id: AddExternalProviderStep.DirectorySync, + label: 'Directory synchronization', + order: 1, + description: + 'Manage core details and connection parameters for your VPN location.', + hidden: !SUPPORTED_SYNC_PROVIDERS.has(provider), + }, + validation: { + id: AddExternalProviderStep.Validation, + label: 'Validation', + order: 2, + description: 'Checking that everything is configured as expected.', + hidden: false, + }, + }; + return res; + }, [provider]); + + return ( + { + navigate({ + to: '/settings', + search: { + tab: 'openid', + }, + replace: true, + }).then(() => { + useAddExternalOpenIdStore.getState().reset(); + }); + }} + > + {steps[activeStep]} + + ); +}; diff --git a/web/src/pages/AddExternalOpenIdWizardPage/consts.ts b/web/src/pages/AddExternalOpenIdWizardPage/consts.ts new file mode 100644 index 000000000..3a81386e0 --- /dev/null +++ b/web/src/pages/AddExternalOpenIdWizardPage/consts.ts @@ -0,0 +1,98 @@ +import { m } from '../../paraglide/messages'; +import api from '../../shared/api/api'; +import { + type AddOpenIdProvider, + DirectorySyncBehavior, + type DirectorySyncBehaviorValue, + DirectorySyncTarget, + type DirectorySyncTargetValue, + OpenIdProviderUsernameHandling, + type OpenIdProviderUsernameHandlingValue, + type TestDirectorySyncResponse, +} from '../../shared/api/types'; +import type { SelectOption } from '../../shared/defguard-ui/components/Select/types'; + +export const validateExternalProviderWizard = async ( + values: AddOpenIdProvider, +): Promise => { + try { + await api.openIdProvider.addOpenIdProvider(values); + if (values.directory_sync_enabled) { + const { data: result } = await api.openIdProvider.testDirectorySync(); + return result; + } + } catch (_) { + return false; + } + return true; +}; + +export const directorySyncBehaviorName: Record = { + delete: m.controls_delete(), + disable: m.controls_disable(), + keep: m.controls_keep(), +}; + +export const directorySyncTargetName: Record = { + all: m.cmp_sync_behavior_target_all(), + groups: m.cmp_sync_behavior_target_groups(), + users: m.cmp_sync_behavior_target_users(), +}; + +export const directorySyncBehaviorOptions: SelectOption[] = [ + { + key: DirectorySyncBehavior.Keep, + value: DirectorySyncBehavior.Keep, + label: directorySyncBehaviorName[DirectorySyncBehavior.Keep], + }, + { + label: directorySyncBehaviorName[DirectorySyncBehavior.Disable], + key: DirectorySyncBehavior.Disable, + value: DirectorySyncBehavior.Disable, + }, + { + key: DirectorySyncBehavior.Delete, + value: DirectorySyncBehavior.Delete, + label: directorySyncBehaviorName[DirectorySyncBehavior.Delete], + }, +]; + +export const directorySyncTargetOptions: SelectOption[] = [ + { + key: DirectorySyncTarget.All, + value: DirectorySyncTarget.All, + label: directorySyncTargetName[DirectorySyncTarget.All], + }, + { + key: DirectorySyncTarget.Users, + value: DirectorySyncTarget.Users, + label: directorySyncTargetName[DirectorySyncTarget.Users], + }, + { + key: DirectorySyncTarget.Groups, + value: DirectorySyncTarget.Groups, + label: directorySyncTargetName[DirectorySyncTarget.Groups], + }, +]; + +export const providerUsernameHandlingOptions: SelectOption[] = + [ + { + key: OpenIdProviderUsernameHandling.RemoveForbidden, + label: 'Remove forbidden characters', + value: OpenIdProviderUsernameHandling.RemoveForbidden, + }, + { + key: OpenIdProviderUsernameHandling.ReplaceForbidden, + label: 'Replace forbidden characters', + value: OpenIdProviderUsernameHandling.ReplaceForbidden, + }, + { + key: OpenIdProviderUsernameHandling.PruneEmailDomain, + label: 'Prune email domain', + value: OpenIdProviderUsernameHandling.PruneEmailDomain, + }, + ]; + +export const formatMicrosoftBaseUrl = (tenantId: string) => + `https://login.microsoftonline.com/${tenantId}/v2.0`; diff --git a/web/src/pages/AddExternalOpenIdWizardPage/steps/AddExternalOpenIdClientSettingsStep/AddExternalOpenIdClientSettingsStep.tsx b/web/src/pages/AddExternalOpenIdWizardPage/steps/AddExternalOpenIdClientSettingsStep/AddExternalOpenIdClientSettingsStep.tsx new file mode 100644 index 000000000..ccdd07a92 --- /dev/null +++ b/web/src/pages/AddExternalOpenIdWizardPage/steps/AddExternalOpenIdClientSettingsStep/AddExternalOpenIdClientSettingsStep.tsx @@ -0,0 +1,228 @@ +import { useMutation } from '@tanstack/react-query'; +import { useMemo } from 'react'; +import z from 'zod'; +import { m } from '../../../../paraglide/messages'; +import { OpenIdProviderUsernameHandling } from '../../../../shared/api/types'; +import { Controls } from '../../../../shared/components/Controls/Controls'; +import { WizardCard } from '../../../../shared/components/wizard/WizardCard/WizardCard'; +import { SUPPORTED_SYNC_PROVIDERS } from '../../../../shared/constants'; +import { Button } from '../../../../shared/defguard-ui/components/Button/Button'; +import { SizedBox } from '../../../../shared/defguard-ui/components/SizedBox/SizedBox'; +import { ThemeSpacing } from '../../../../shared/defguard-ui/types'; +import { useAppForm } from '../../../../shared/form'; +import { formChangeLogic } from '../../../../shared/formLogic'; +import { + ExternalProvider, + type ExternalProviderValue, +} from '../../../settings/shared/types'; +import { + formatMicrosoftBaseUrl, + providerUsernameHandlingOptions, + validateExternalProviderWizard, +} from '../../consts'; +import { useAddExternalOpenIdStore } from '../../useAddExternalOpenIdStore'; + +const baseUrlHidden: Set = new Set([ + ExternalProvider.JumpCloud, + ExternalProvider.Microsoft, + ExternalProvider.Google, +]); + +export const AddExternalOpenIdClientSettingsStep = () => { + const storeData = useAddExternalOpenIdStore((s) => s.providerState); + const provider = useAddExternalOpenIdStore((s) => s.provider); + const next = useAddExternalOpenIdStore((s) => s.next); + + const { mutateAsync } = useMutation({ + mutationFn: validateExternalProviderWizard, + onSuccess: (result) => { + if (typeof result === 'boolean') { + useAddExternalOpenIdStore.setState({ + testResult: result, + }); + next(); + } else { + useAddExternalOpenIdStore.setState({ + testResult: result.success, + testMessage: result.message, + }); + next(); + } + }, + meta: { + invalidate: [['settings'], ['info'], ['openid', 'provider']], + }, + }); + + const formSchema = useMemo( + () => + z + .object({ + base_url: z.url(m.form_error_invalid()).trim().min(1, m.form_error_required()), + client_id: z + .string(m.form_error_required()) + .trim() + .min(1, m.form_error_required()), + client_secret: z + .string(m.form_error_required()) + .trim() + .min(1, m.form_error_required()), + display_name: z + .string(m.form_error_required()) + .trim() + .min(1, m.form_error_required()), + create_account: z.boolean(m.form_error_invalid()), + username_handling: z.enum(OpenIdProviderUsernameHandling), + microsoftTenantId: z.string().trim().nullable(), + }) + .superRefine((values, ctx) => { + if (provider === ExternalProvider.Microsoft) { + const schema = z + .string(m.form_error_required()) + .trim() + .min(1, m.form_error_required()); + const result = schema.safeParse(values.microsoftTenantId); + if (!result.success) { + ctx.addIssue({ + code: 'custom', + continue: true, + message: result.error.message, + path: ['microsoftTenantId'], + }); + } + } + }), + [provider], + ); + + type FormFields = z.infer; + + const defaultValues = useMemo( + (): FormFields => ({ + base_url: storeData.base_url, + client_id: storeData.client_id, + client_secret: storeData.client_secret, + create_account: storeData.create_account, + display_name: storeData.display_name, + microsoftTenantId: storeData.microsoftTenantId ?? null, + username_handling: storeData.username_handling, + }), + [storeData], + ); + + const form = useAppForm({ + defaultValues, + validationLogic: formChangeLogic, + validators: { + onSubmit: formSchema, + onChange: formSchema, + }, + onSubmit: async ({ value }) => { + if (SUPPORTED_SYNC_PROVIDERS.has(provider)) { + next(value); + } else { + const storeState = useAddExternalOpenIdStore.getState().providerState; + await mutateAsync({ + ...storeState, + ...value, + }); + } + }, + }); + + return ( + +
{ + e.stopPropagation(); + e.preventDefault(); + form.handleSubmit(); + }} + > + + + {(field) => } + + {provider === ExternalProvider.Microsoft && ( + <> + + { + fieldApi.form.setFieldValue( + 'base_url', + formatMicrosoftBaseUrl(value ?? ''), + ); + }, + }} + > + {(field) => } + + + )} + {!baseUrlHidden.has(provider) && ( + <> + + + {(field) => } + + + )} + + + {(field) => } + + + + {(field) => ( + + )} + + + + {(field) => ( + + )} + + + + {(field) => ( + + )} + + + ({ + isSubmitting: s.isSubmitting, + })} + > + {({ isSubmitting }) => ( + +
+
+
+ )} +
+
+
+
+ ); +}; diff --git a/web/src/pages/AddExternalOpenIdWizardPage/steps/AddExternalOpenIdDirectoryStep/AddExternalOpenIdDirectoryStep.tsx b/web/src/pages/AddExternalOpenIdWizardPage/steps/AddExternalOpenIdDirectoryStep/AddExternalOpenIdDirectoryStep.tsx new file mode 100644 index 000000000..d3124116e --- /dev/null +++ b/web/src/pages/AddExternalOpenIdWizardPage/steps/AddExternalOpenIdDirectoryStep/AddExternalOpenIdDirectoryStep.tsx @@ -0,0 +1,79 @@ +import { useMutation } from '@tanstack/react-query'; +import { cloneDeep } from 'lodash-es'; +import { useCallback, useMemo } from 'react'; +import type { AddOpenIdProvider } from '../../../../shared/api/types'; +import { WizardCard } from '../../../../shared/components/wizard/WizardCard/WizardCard'; +import { AppText } from '../../../../shared/defguard-ui/components/AppText/AppText'; +import { SizedBox } from '../../../../shared/defguard-ui/components/SizedBox/SizedBox'; +import { + TextStyle, + ThemeSpacing, + ThemeVariable, +} from '../../../../shared/defguard-ui/types'; +import { validateExternalProviderWizard } from '../../consts'; +import { useAddExternalOpenIdStore } from '../../useAddExternalOpenIdStore'; +import { GoogleProviderForm } from './forms/GoogleProviderForm'; +import { JumpcloudProviderForm } from './forms/JumpcloudProviderForm'; +import { MicrosoftProviderForm } from './forms/MicrosoftProviderForm'; +import { OktaProviderForm } from './forms/OktaProviderForm'; + +export const AddExternalOpenIdDirectoryStep = () => { + const provider = useAddExternalOpenIdStore((s) => s.provider); + const next = useAddExternalOpenIdStore((s) => s.next); + + const { mutateAsync } = useMutation({ + mutationFn: validateExternalProviderWizard, + onSuccess: (result) => { + if (typeof result === 'boolean') { + useAddExternalOpenIdStore.setState({ + testResult: result, + }); + next(); + } else { + useAddExternalOpenIdStore.setState({ + testResult: result.success, + testMessage: result.message, + }); + next(); + } + }, + meta: { + invalidate: [['settings'], ['info'], ['openid', 'provider']], + }, + }); + + const handleValidSubmit = useCallback( + async (value: Partial) => { + const providerState = useAddExternalOpenIdStore.getState().providerState; + const submitValues = { ...cloneDeep(providerState), value }; + await mutateAsync(submitValues); + }, + [mutateAsync], + ); + + const formRender = useMemo(() => { + switch (provider) { + case 'google': + return ; + case 'microsoft': + return ; + case 'okta': + return ; + case 'jumpCloud': + return ; + } + return null; + }, [handleValidSubmit, provider]); + + return ( + + + { + "You can optionally enable directory synchronization to automatically sync user's status and groups from an external provider." + } + + + {formRender} + + ); +}; diff --git a/web/src/pages/AddExternalOpenIdWizardPage/steps/AddExternalOpenIdDirectoryStep/ProviderFormControls.tsx b/web/src/pages/AddExternalOpenIdWizardPage/steps/AddExternalOpenIdDirectoryStep/ProviderFormControls.tsx new file mode 100644 index 000000000..f76d3165a --- /dev/null +++ b/web/src/pages/AddExternalOpenIdWizardPage/steps/AddExternalOpenIdDirectoryStep/ProviderFormControls.tsx @@ -0,0 +1,44 @@ +import { m } from '../../../../paraglide/messages'; +import { Controls } from '../../../../shared/components/Controls/Controls'; +import { Button } from '../../../../shared/defguard-ui/components/Button/Button'; +import { useFormContext } from '../../../../shared/form'; +import { useAddExternalOpenIdStore } from '../../useAddExternalOpenIdStore'; + +export const ProviderFormControls = ({ + onBack, + onNext, + loading, +}: { + onBack: () => void; + onNext: () => void; + loading?: boolean; +}) => { + const form = useFormContext(); + const enabled = useAddExternalOpenIdStore( + (s) => s.providerState.directory_sync_enabled, + ); + + return ( + ({ isSubmitting: s.isSubmitting })}> + {({ isSubmitting }) => ( + +
+ + )} + + ); +}; diff --git a/web/src/pages/AddExternalOpenIdWizardPage/steps/AddExternalOpenIdDirectoryStep/ProviderSyncToggle.tsx b/web/src/pages/AddExternalOpenIdWizardPage/steps/AddExternalOpenIdDirectoryStep/ProviderSyncToggle.tsx new file mode 100644 index 000000000..3f7cbf3eb --- /dev/null +++ b/web/src/pages/AddExternalOpenIdWizardPage/steps/AddExternalOpenIdDirectoryStep/ProviderSyncToggle.tsx @@ -0,0 +1,33 @@ +import type { PropsWithChildren } from 'react'; +import { Divider } from '../../../../shared/defguard-ui/components/Divider/Divider'; +import { Fold } from '../../../../shared/defguard-ui/components/Fold/Fold'; +import { SizedBox } from '../../../../shared/defguard-ui/components/SizedBox/SizedBox'; +import { Toggle } from '../../../../shared/defguard-ui/components/Toggle/Toggle'; +import { ThemeSpacing } from '../../../../shared/defguard-ui/types'; +import { useAddExternalOpenIdStore } from '../../useAddExternalOpenIdStore'; + +export const ProviderSyncToggle = ({ children }: PropsWithChildren) => { + const enabled = useAddExternalOpenIdStore( + (s) => s.providerState.directory_sync_enabled, + ); + + return ( + <> + { + useAddExternalOpenIdStore.setState((s) => ({ + providerState: { ...s.providerState, directory_sync_enabled: !enabled }, + })); + }} + label="Directory synchronization" + /> + + + {children} + + + + + ); +}; diff --git a/web/src/pages/AddExternalOpenIdWizardPage/steps/AddExternalOpenIdDirectoryStep/forms/GoogleProviderForm.tsx b/web/src/pages/AddExternalOpenIdWizardPage/steps/AddExternalOpenIdDirectoryStep/forms/GoogleProviderForm.tsx new file mode 100644 index 000000000..ef1ed1b27 --- /dev/null +++ b/web/src/pages/AddExternalOpenIdWizardPage/steps/AddExternalOpenIdDirectoryStep/forms/GoogleProviderForm.tsx @@ -0,0 +1,173 @@ +import { useMutation } from '@tanstack/react-query'; +import { omit } from 'lodash-es'; +import { useCallback, useMemo } from 'react'; +import type z from 'zod'; +import { m } from '../../../../../paraglide/messages'; +import { DescriptionBlock } from '../../../../../shared/components/DescriptionBlock/DescriptionBlock'; +import { EvenSplit } from '../../../../../shared/defguard-ui/components/EvenSplit/EvenSplit'; +import { SizedBox } from '../../../../../shared/defguard-ui/components/SizedBox/SizedBox'; +import { ThemeSpacing } from '../../../../../shared/defguard-ui/types'; +import { isPresent } from '../../../../../shared/defguard-ui/utils/isPresent'; +import { useAppForm } from '../../../../../shared/form'; +import { formChangeLogic } from '../../../../../shared/formLogic'; +import { + directorySyncBehaviorOptions, + directorySyncTargetOptions, +} from '../../../consts'; +import { useAddExternalOpenIdStore } from '../../../useAddExternalOpenIdStore'; +import { ProviderFormControls } from '../ProviderFormControls'; +import { ProviderSyncToggle } from '../ProviderSyncToggle'; +import { + googleProviderSyncSchema, + parseGoogleKeyFile, + providerToGoogleKeyFile, +} from './schemas'; +import type { ProviderFormProps } from './types'; + +type FormFields = z.infer; + +export const GoogleProviderForm = ({ onSubmit }: ProviderFormProps) => { + const storeValues = useAddExternalOpenIdStore((s) => s.providerState); + const back = useAddExternalOpenIdStore((s) => s.back); + + const defaultValues = useMemo( + (): FormFields => ({ + admin_email: storeValues.admin_email ?? '', + directory_sync_admin_behavior: storeValues.directory_sync_admin_behavior, + directory_sync_interval: storeValues.directory_sync_interval, + directory_sync_target: storeValues.directory_sync_target, + directory_sync_user_behavior: storeValues.directory_sync_user_behavior, + google_service_account_file: providerToGoogleKeyFile( + storeValues.google_service_account_key, + storeValues.google_service_account_email, + ), + }), + [storeValues], + ); + + const form = useAppForm({ + defaultValues, + validationLogic: formChangeLogic, + validators: { + onSubmit: googleProviderSyncSchema, + onChange: googleProviderSyncSchema, + }, + onSubmit: async ({ value, formApi }) => { + const fileData = await parseGoogleKeyFile( + value.google_service_account_file as File, + ); + if (isPresent(fileData)) { + await onSubmit({ + ...value, + google_service_account_email: fileData?.client_email ?? '', + google_service_account_key: fileData?.private_key ?? '', + }); + } else { + formApi.setErrorMap({ + onSubmit: { + fields: { + google_service_account_file: + value.google_service_account_file === null + ? m.form_error_required() + : m.form_error_file_contents(), + }, + }, + }); + } + }, + }); + + const toStore = useCallback(async (state: FormFields) => { + const fileData = await parseGoogleKeyFile(state.google_service_account_file as File); + return { + ...omit(state, ['google_service_account_file']), + google_service_account_key: fileData?.private_key ?? null, + google_service_account_email: fileData?.client_email ?? null, + }; + }, []); + + const { mutate, isPending } = useMutation({ + mutationFn: async () => { + return onSubmit(await toStore(form.state.values)); + }, + }); + + const handleBack = useCallback(async () => { + back(await toStore(form.state.values)); + }, [form.state.values, back, toStore]); + + return ( +
{ + e.stopPropagation(); + e.preventDefault(); + form.handleSubmit(); + }} + > + + + + + {(field) => ( + + )} + + + {(field) => ( + + )} + + + + + + {(field) => ( + + )} + + + {(field) => ( + + )} + + + + + {(field) => } + + + +

{`Upload a new service account key file to set the service account used for synchronization. NOTE: The uploaded file won't be visible after saving the settings and reloading the page as it's contents are sensitive and are never sent back to the dashboard.`}

+
+ + + {(field) => } + +
+ { + mutate(); + }} + /> +
+
+ ); +}; diff --git a/web/src/pages/AddExternalOpenIdWizardPage/steps/AddExternalOpenIdDirectoryStep/forms/JumpcloudProviderForm.tsx b/web/src/pages/AddExternalOpenIdWizardPage/steps/AddExternalOpenIdDirectoryStep/forms/JumpcloudProviderForm.tsx new file mode 100644 index 000000000..ee51131d6 --- /dev/null +++ b/web/src/pages/AddExternalOpenIdWizardPage/steps/AddExternalOpenIdDirectoryStep/forms/JumpcloudProviderForm.tsx @@ -0,0 +1,123 @@ +import { useMutation } from '@tanstack/react-query'; +import { useMemo } from 'react'; +import type z from 'zod'; +import { useShallow } from 'zustand/react/shallow'; +import { EvenSplit } from '../../../../../shared/defguard-ui/components/EvenSplit/EvenSplit'; +import { SizedBox } from '../../../../../shared/defguard-ui/components/SizedBox/SizedBox'; +import { ThemeSpacing } from '../../../../../shared/defguard-ui/types'; +import { useAppForm } from '../../../../../shared/form'; +import { formChangeLogic } from '../../../../../shared/formLogic'; +import { + directorySyncBehaviorOptions, + directorySyncTargetOptions, +} from '../../../consts'; +import { useAddExternalOpenIdStore } from '../../../useAddExternalOpenIdStore'; +import { ProviderFormControls } from '../ProviderFormControls'; +import { ProviderSyncToggle } from '../ProviderSyncToggle'; +import { jumpcloudProviderSyncSchema } from './schemas'; +import type { ProviderFormProps } from './types'; + +type FormFields = z.infer; + +export const JumpcloudProviderForm = ({ onSubmit }: ProviderFormProps) => { + const providerState = useAddExternalOpenIdStore((s) => s.providerState); + const [back] = useAddExternalOpenIdStore(useShallow((s) => [s.back])); + + const { mutate, isPending } = useMutation({ + mutationFn: onSubmit, + }); + + const defaultValues = useMemo( + (): FormFields => ({ + directory_sync_admin_behavior: providerState.directory_sync_admin_behavior, + directory_sync_interval: providerState.directory_sync_interval, + directory_sync_target: providerState.directory_sync_target, + directory_sync_user_behavior: providerState.directory_sync_user_behavior, + jumpcloud_api_key: providerState.jumpcloud_api_key ?? '', + }), + [providerState], + ); + + const form = useAppForm({ + defaultValues, + validationLogic: formChangeLogic, + validators: { + onSubmit: jumpcloudProviderSyncSchema, + onChange: jumpcloudProviderSyncSchema, + }, + onSubmit: async ({ value }) => { + await onSubmit(value); + }, + }); + + return ( +
{ + e.stopPropagation(); + e.preventDefault(); + form.handleSubmit(); + }} + > + + + + + {(field) => ( + + )} + + + {(field) => ( + + )} + + + + + + {(field) => ( + + )} + + + {(field) => ( + + )} + + + + + {(field) => ( + + )} + + + { + back(form.state.values); + }} + onNext={() => { + mutate(form.state.values); + }} + /> + +
+ ); +}; diff --git a/web/src/pages/AddExternalOpenIdWizardPage/steps/AddExternalOpenIdDirectoryStep/forms/MicrosoftProviderForm.tsx b/web/src/pages/AddExternalOpenIdWizardPage/steps/AddExternalOpenIdDirectoryStep/forms/MicrosoftProviderForm.tsx new file mode 100644 index 000000000..2b3fb8c9b --- /dev/null +++ b/web/src/pages/AddExternalOpenIdWizardPage/steps/AddExternalOpenIdDirectoryStep/forms/MicrosoftProviderForm.tsx @@ -0,0 +1,125 @@ +import { useMutation } from '@tanstack/react-query'; +import { useMemo } from 'react'; +import type z from 'zod'; +import { EvenSplit } from '../../../../../shared/defguard-ui/components/EvenSplit/EvenSplit'; +import { SizedBox } from '../../../../../shared/defguard-ui/components/SizedBox/SizedBox'; +import { ThemeSpacing } from '../../../../../shared/defguard-ui/types'; +import { useAppForm } from '../../../../../shared/form'; +import { formChangeLogic } from '../../../../../shared/formLogic'; +import { + directorySyncBehaviorOptions, + directorySyncTargetOptions, +} from '../../../consts'; +import { useAddExternalOpenIdStore } from '../../../useAddExternalOpenIdStore'; +import { ProviderFormControls } from '../ProviderFormControls'; +import { ProviderSyncToggle } from '../ProviderSyncToggle'; +import { microsoftProviderSyncSchema } from './schemas'; +import type { ProviderFormProps } from './types'; + +type FormFields = z.infer; + +export const MicrosoftProviderForm = ({ onSubmit }: ProviderFormProps) => { + const providerState = useAddExternalOpenIdStore((s) => s.providerState); + const back = useAddExternalOpenIdStore((s) => s.back); + + const { mutate, isPending } = useMutation({ + mutationFn: onSubmit, + }); + + const defaultValues = useMemo( + (): FormFields => ({ + directory_sync_admin_behavior: providerState.directory_sync_admin_behavior, + directory_sync_group_match: providerState.directory_sync_group_match ?? null, + directory_sync_interval: providerState.directory_sync_interval, + directory_sync_target: providerState.directory_sync_target, + directory_sync_user_behavior: providerState.directory_sync_user_behavior, + prefetch_users: providerState.prefetch_users, + }), + [providerState], + ); + + const form = useAppForm({ + defaultValues, + validationLogic: formChangeLogic, + validators: { + onSubmit: microsoftProviderSyncSchema, + onChange: microsoftProviderSyncSchema, + }, + onSubmit: async ({ value }) => { + await onSubmit(value); + }, + }); + + return ( +
{ + e.stopPropagation(); + e.preventDefault(); + form.handleSubmit(); + }} + > + + + + + {(field) => ( + + )} + + + {(field) => ( + + )} + + + + + + {(field) => ( + + )} + + + {(field) => ( + + )} + + + + + {(field) => } + + + + {(field) => } + + + { + back(form.state.values); + }} + onNext={() => { + mutate(form.state.values); + }} + /> + +
+ ); +}; diff --git a/web/src/pages/AddExternalOpenIdWizardPage/steps/AddExternalOpenIdDirectoryStep/forms/OktaProviderForm.tsx b/web/src/pages/AddExternalOpenIdWizardPage/steps/AddExternalOpenIdDirectoryStep/forms/OktaProviderForm.tsx new file mode 100644 index 000000000..dce561626 --- /dev/null +++ b/web/src/pages/AddExternalOpenIdWizardPage/steps/AddExternalOpenIdDirectoryStep/forms/OktaProviderForm.tsx @@ -0,0 +1,132 @@ +import { useMutation } from '@tanstack/react-query'; +import { useMemo } from 'react'; +import type z from 'zod'; +import { EvenSplit } from '../../../../../shared/defguard-ui/components/EvenSplit/EvenSplit'; +import { SizedBox } from '../../../../../shared/defguard-ui/components/SizedBox/SizedBox'; +import { ThemeSpacing } from '../../../../../shared/defguard-ui/types'; +import { useAppForm } from '../../../../../shared/form'; +import { formChangeLogic } from '../../../../../shared/formLogic'; +import { + directorySyncBehaviorOptions, + directorySyncTargetOptions, +} from '../../../consts'; +import { useAddExternalOpenIdStore } from '../../../useAddExternalOpenIdStore'; +import { ProviderFormControls } from '../ProviderFormControls'; +import { ProviderSyncToggle } from '../ProviderSyncToggle'; +import { oktaProviderSyncSchema } from './schemas'; +import type { ProviderFormProps } from './types'; + +type FormFields = z.infer; + +export const OktaProviderForm = ({ onSubmit }: ProviderFormProps) => { + const providerState = useAddExternalOpenIdStore((s) => s.providerState); + const back = useAddExternalOpenIdStore((s) => s.back); + + const { mutate, isPending } = useMutation({ + mutationFn: onSubmit, + }); + + const defaultValues = useMemo( + (): FormFields => ({ + directory_sync_admin_behavior: providerState.directory_sync_admin_behavior, + directory_sync_interval: providerState.directory_sync_interval, + directory_sync_target: providerState.directory_sync_target, + directory_sync_user_behavior: providerState.directory_sync_user_behavior, + okta_dirsync_client_id: providerState.okta_dirsync_client_id ?? '', + okta_private_jwk: providerState.okta_private_jwk ?? '', + }), + [providerState], + ); + + const form = useAppForm({ + defaultValues, + validationLogic: formChangeLogic, + validators: { + onSubmit: oktaProviderSyncSchema, + onChange: oktaProviderSyncSchema, + }, + onSubmit: async ({ value }) => { + await onSubmit(value); + }, + }); + + return ( +
{ + e.stopPropagation(); + e.preventDefault(); + form.handleSubmit(); + }} + > + + + + + {(field) => ( + + )} + + + {(field) => ( + + )} + + + + + + {(field) => ( + + )} + + + {(field) => ( + + )} + + + + + + {(field) => } + + + {(field) => ( + + )} + + + + { + back(form.state.values); + }} + onNext={() => { + mutate(form.state.values); + }} + /> + +
+ ); +}; diff --git a/web/src/pages/AddExternalOpenIdWizardPage/steps/AddExternalOpenIdDirectoryStep/forms/schemas.ts b/web/src/pages/AddExternalOpenIdWizardPage/steps/AddExternalOpenIdDirectoryStep/forms/schemas.ts new file mode 100644 index 000000000..f33ee3ea6 --- /dev/null +++ b/web/src/pages/AddExternalOpenIdWizardPage/steps/AddExternalOpenIdDirectoryStep/forms/schemas.ts @@ -0,0 +1,90 @@ +import z from 'zod'; +import { m } from '../../../../../paraglide/messages'; +import { + DirectorySyncBehavior, + DirectorySyncTarget, + OpenIdProviderUsernameHandling, +} from '../../../../../shared/api/types'; +import { isPresent } from '../../../../../shared/defguard-ui/utils/isPresent'; + +export const baseExternalProviderConfigSchema = z.object({ + base_url: z.url(m.form_error_invalid()).trim().min(1, m.form_error_required()), + client_id: z.string(m.form_error_required()).trim().min(1, m.form_error_required()), + client_secret: z.string(m.form_error_required()).trim().min(1, m.form_error_required()), + display_name: z.string(m.form_error_required()).trim().min(1, m.form_error_required()), + create_account: z.boolean(m.form_error_invalid()), + username_handling: z.enum(OpenIdProviderUsernameHandling), +}); + +export const baseExternalProviderSyncSchema = z.object({ + directory_sync_interval: z.number().min(60, m.form_min_value({ value: 60 })), + directory_sync_user_behavior: z.enum(DirectorySyncBehavior), + directory_sync_admin_behavior: z.enum(DirectorySyncBehavior), + directory_sync_target: z.enum(DirectorySyncTarget), +}); + +export const googleProviderSyncSchema = baseExternalProviderSyncSchema.extend({ + admin_email: z.email(m.form_error_email()).trim().min(1, m.form_error_required()), + google_service_account_file: z + .file(m.form_error_required()) + .mime('application/json', m.form_error_file_format()) + .nullable(), +}); + +export const microsoftProviderSyncSchema = baseExternalProviderSyncSchema.extend({ + prefetch_users: z.boolean(), + directory_sync_group_match: z.string().trim().nullable(), +}); + +export const oktaProviderSyncSchema = baseExternalProviderSyncSchema.extend({ + okta_dirsync_client_id: z + .string(m.form_error_required()) + .trim() + .min(1, m.form_error_required(0)), + okta_private_jwk: z + .string(m.form_error_required()) + .trim() + .min(1, m.form_error_required()), +}); + +export const jumpcloudProviderSyncSchema = baseExternalProviderSyncSchema.extend({ + jumpcloud_api_key: z + .string(m.form_error_required()) + .trim() + .min(1, m.form_error_required()), +}); + +const fileSchema = z.object({ + private_key: z.string().trim().min(1), + client_email: z.string().trim().min(1), +}); + +type GoogleAccountFileObject = z.infer; + +export const parseGoogleKeyFile = async ( + value: File, +): Promise => { + try { + const obj = JSON.parse(await value.text()); + const result = fileSchema.safeParse(obj); + if (result.success) { + return result.data; + } + } catch (_) { + return null; + } + return null; +}; + +export const providerToGoogleKeyFile = ( + key?: string | null, + email?: string | null, +): File | null => { + if (!isPresent(key) || !isPresent(email)) return null; + + const obj: GoogleAccountFileObject = { + client_email: email, + private_key: key, + }; + return new File([JSON.stringify(obj)], 'Account key', { type: 'application/json' }); +}; diff --git a/web/src/pages/AddExternalOpenIdWizardPage/steps/AddExternalOpenIdDirectoryStep/forms/types.ts b/web/src/pages/AddExternalOpenIdWizardPage/steps/AddExternalOpenIdDirectoryStep/forms/types.ts new file mode 100644 index 000000000..d04644fa7 --- /dev/null +++ b/web/src/pages/AddExternalOpenIdWizardPage/steps/AddExternalOpenIdDirectoryStep/forms/types.ts @@ -0,0 +1,5 @@ +import type { AddOpenIdProvider } from '../../../../../shared/api/types'; + +export interface ProviderFormProps { + onSubmit: (values: Partial) => Promise; +} diff --git a/web/src/pages/AddExternalOpenIdWizardPage/steps/AddExternalOpenIdValidationStep/AddExternalOpenIdValidationStep.tsx b/web/src/pages/AddExternalOpenIdWizardPage/steps/AddExternalOpenIdValidationStep/AddExternalOpenIdValidationStep.tsx new file mode 100644 index 000000000..31721f898 --- /dev/null +++ b/web/src/pages/AddExternalOpenIdWizardPage/steps/AddExternalOpenIdValidationStep/AddExternalOpenIdValidationStep.tsx @@ -0,0 +1,111 @@ +import { useMutation } from '@tanstack/react-query'; +import { useNavigate } from '@tanstack/react-router'; +import { m } from '../../../../paraglide/messages'; +import api from '../../../../shared/api/api'; +import { Controls } from '../../../../shared/components/Controls/Controls'; +import { WizardCard } from '../../../../shared/components/wizard/WizardCard/WizardCard'; +import { AppText } from '../../../../shared/defguard-ui/components/AppText/AppText'; +import { Button } from '../../../../shared/defguard-ui/components/Button/Button'; +import { Divider } from '../../../../shared/defguard-ui/components/Divider/Divider'; +import { IconKind } from '../../../../shared/defguard-ui/components/Icon'; +import { InfoBanner } from '../../../../shared/defguard-ui/components/InfoBanner/InfoBanner'; +import { SizedBox } from '../../../../shared/defguard-ui/components/SizedBox/SizedBox'; +import { + TextStyle, + ThemeSpacing, + ThemeVariable, +} from '../../../../shared/defguard-ui/types'; +import { isPresent } from '../../../../shared/defguard-ui/utils/isPresent'; +import { useAddExternalOpenIdStore } from '../../useAddExternalOpenIdStore'; + +export const AddExternalOpenIdValidationStep = () => { + const navigate = useNavigate(); + const providerState = useAddExternalOpenIdStore((s) => s.providerState); + const message = useAddExternalOpenIdStore((s) => s.testMessage); + const result = useAddExternalOpenIdStore((s) => s.testResult); + const back = useAddExternalOpenIdStore((s) => s.back); + + const { mutate: deleteProvider, isPending } = useMutation({ + mutationFn: api.openIdProvider.deleteOpenIdProvider, + onSuccess: () => { + back(); + }, + }); + + return ( + + {result && ( + <> + + {'External ID provider successfully added.'} + + + + { + 'The connection to the external identity provider has been successfully verified.' + } + + + + {`The connection to your external identity provider is now verified. Your users can now log in using this provider for a faster and more convenient authentication experience.`} + + + )} + {!result && ( + <> + + {'External ID provider successfully added.'} + + + + { + 'The connection to the external identity provider has been successfully verified.' + } + + {isPresent(message) && ( + <> + + + + )} + + )} + + {!result && ( + + + ); + }} + + + +

{m.test_placeholder_long()}

+
+ +
+ {availableScopes.map((scope) => ( + { + toggleScope(scope); + }} + /> + ))} +
+ { + closeModal(modalNameValue); + }, + }} + submitProps={{ + text: isPresent(openIdClient) + ? m.controls_save_changes() + : m.controls_submit(), + testId: 'save-settings', + loading: isSubmitting, + onClick: () => { + form.handleSubmit(); + }, + }} + /> + + + ); +}; diff --git a/web/src/pages/OpenIdPage/modals/CEOpenIdClientModal/style.scss b/web/src/pages/OpenIdPage/modals/CEOpenIdClientModal/style.scss new file mode 100644 index 000000000..f2141130c --- /dev/null +++ b/web/src/pages/OpenIdPage/modals/CEOpenIdClientModal/style.scss @@ -0,0 +1,34 @@ +#ce-openid-client-modal form { + .fields { + display: flex; + flex-flow: column; + row-gap: var(--spacing-lg); + } + + .add-url { + background-color: transparent; + border: 0; + cursor: pointer; + display: flex; + flex-flow: row; + align-items: center; + justify-content: flex-start; + column-gap: var(--spacing-sm); + min-height: 28px; + + .icon path { + fill: var(--fg-action); + } + + span { + font: var(--t-button-label-primary); + color: var(--fg-action); + } + } + + .scopes { + display: flex; + flex-flow: column; + row-gap: var(--spacing-md); + } +} diff --git a/web/src/pages/RulesPage/RulesPage.tsx b/web/src/pages/RulesPage/RulesPage.tsx new file mode 100644 index 000000000..d20ec1e7d --- /dev/null +++ b/web/src/pages/RulesPage/RulesPage.tsx @@ -0,0 +1,64 @@ +import { useQuery } from '@tanstack/react-query'; +import { useMemo, useState } from 'react'; +import { AclStatus } from '../../shared/api/types'; +import { Page } from '../../shared/components/Page/Page'; +import { SizedBox } from '../../shared/defguard-ui/components/SizedBox/SizedBox'; +import { Tabs } from '../../shared/defguard-ui/components/Tabs/Tabs'; +import type { TabsItem } from '../../shared/defguard-ui/components/Tabs/types'; +import { ThemeSpacing } from '../../shared/defguard-ui/types'; +import { isPresent } from '../../shared/defguard-ui/utils/isPresent'; +import { getRulesQueryOptions } from '../../shared/query'; +import { RulesDeployedTab } from './tabs/RulesDeployedTab'; +import { RulesPendingTab } from './tabs/RulesPendingTab'; +import { RulesPageTab, type RulesPageTabValue } from './types'; + +export const RulesPage = () => { + const [activeTab, setActiveTab] = useState(RulesPageTab.Deployed); + + const tabs = useMemo( + (): TabsItem[] => [ + { + title: 'Deployed', + active: activeTab === RulesPageTab.Deployed, + onClick: () => { + setActiveTab(RulesPageTab.Deployed); + }, + }, + { + title: 'Pending', + active: activeTab === RulesPageTab.Pending, + onClick: () => { + setActiveTab(RulesPageTab.Pending); + }, + }, + ], + [activeTab], + ); + + const { data: rules } = useQuery(getRulesQueryOptions); + + const deployed = useMemo(() => { + if (isPresent(rules)) { + return rules.filter((rule) => rule.state === AclStatus.Applied); + } + }, [rules]); + + const pending = useMemo(() => { + if (isPresent(rules)) { + return rules.filter((rule) => rule.state !== AclStatus.Applied); + } + }, [rules]); + + return ( + + + + {activeTab === RulesPageTab.Deployed && isPresent(deployed) && ( + + )} + {activeTab === RulesPageTab.Pending && isPresent(pending) && ( + + )} + + ); +}; diff --git a/web/src/pages/RulesPage/tabs/RulesDeployedTab.tsx b/web/src/pages/RulesPage/tabs/RulesDeployedTab.tsx new file mode 100644 index 000000000..a8717fd34 --- /dev/null +++ b/web/src/pages/RulesPage/tabs/RulesDeployedTab.tsx @@ -0,0 +1,68 @@ +import { useNavigate } from '@tanstack/react-router'; +import { useMemo, useState } from 'react'; +import type { AclRule } from '../../../shared/api/types'; +import { Button } from '../../../shared/defguard-ui/components/Button/Button'; +import type { ButtonProps } from '../../../shared/defguard-ui/components/Button/types'; +import { EmptyStateFlexible } from '../../../shared/defguard-ui/components/EmptyStateFlexible/EmptyStateFlexible'; +import { Search } from '../../../shared/defguard-ui/components/Search/Search'; +import { TableTop } from '../../../shared/defguard-ui/components/table/TableTop/TableTop'; + +type Props = { + rules: AclRule[]; +}; + +export const RulesDeployedTab = ({ rules }: Props) => { + const isEmpty = rules.length === 0; + const navigate = useNavigate(); + + const [search, setSearch] = useState(''); + + const addRuleProps = useMemo( + (): ButtonProps => ({ + variant: 'primary', + text: 'Create new rule', + iconLeft: 'add-rule', + onClick: () => { + navigate({ to: '/acl/add-rule' }); + }, + }), + [navigate], + ); + + const visibleRules = useMemo(() => { + if (search?.length) { + return rules.filter((rule) => + rule.name.toLowerCase().includes(search.toLowerCase()), + ); + } + return rules; + }, [rules, search]); + + return ( + <> + {isEmpty && ( + + )} + {!isEmpty && ( + <> + + + - - - - - { - onChange?.(vals); - }} - /> - - ); -}; diff --git a/web/src/pages/acl/AclCreatePage/components/DialogSelect/DialogSelectButtonIcon.tsx b/web/src/pages/acl/AclCreatePage/components/DialogSelect/DialogSelectButtonIcon.tsx deleted file mode 100644 index a0e30aa4f..000000000 --- a/web/src/pages/acl/AclCreatePage/components/DialogSelect/DialogSelectButtonIcon.tsx +++ /dev/null @@ -1,13 +0,0 @@ -export const DialogSelectButtonIcon = () => { - return ( - - - - ); -}; diff --git a/web/src/pages/acl/AclCreatePage/components/DialogSelect/DialogSelectModal/DialogSelectModal.tsx b/web/src/pages/acl/AclCreatePage/components/DialogSelect/DialogSelectModal/DialogSelectModal.tsx deleted file mode 100644 index d970ddc8d..000000000 --- a/web/src/pages/acl/AclCreatePage/components/DialogSelect/DialogSelectModal/DialogSelectModal.tsx +++ /dev/null @@ -1,156 +0,0 @@ -import './style.scss'; - -import { type ReactNode, useCallback, useEffect, useMemo, useState } from 'react'; - -import { useI18nContext } from '../../../../../../i18n/i18n-react'; -import { Button } from '../../../../../../shared/defguard-ui/components/Layout/Button/Button'; -import { - ButtonSize, - ButtonStyleVariant, -} from '../../../../../../shared/defguard-ui/components/Layout/Button/types'; -import { CheckBox } from '../../../../../../shared/defguard-ui/components/Layout/Checkbox/CheckBox'; -import { Modal } from '../../../../../../shared/defguard-ui/components/Layout/modals/Modal/Modal'; -import { Search } from '../../../../../../shared/defguard-ui/components/Layout/Search/Search'; -import { isPresent } from '../../../../../../shared/defguard-ui/utils/isPresent'; -import { searchByKeys } from '../../../../../../shared/utils/searchByKeys'; -import type { DialogSelectProps } from '../types'; - -type Props = { - initiallySelected: I[]; - options: T[]; - getIdent: (value: T) => I; - getLabel: (value: T) => ReactNode; - open: boolean; - setOpen: (val: boolean) => void; - onChange: (selected: I[]) => void; - extrasTop?: ReactNode; -} & Pick, 'searchFn' | 'searchKeys'>; - -export const DialogSelectModal = ({ - getIdent, - initiallySelected, - getLabel, - open, - setOpen, - options, - onChange, - searchFn, - searchKeys, - extrasTop, -}: Props) => { - const { LL } = useI18nContext(); - const [searchValue, setSearch] = useState(''); - const [selected, setSelected] = useState(initiallySelected); - - const handleSelect = useCallback((id: I, selected: boolean) => { - if (selected) { - setSelected((s) => s.filter((i) => i !== id)); - } else { - setSelected((s) => [...s, id]); - } - }, []); - - const handleSelectAll = () => { - if (selected.length === options.length) { - setSelected([]); - } else { - setSelected(options.map((o) => getIdent(o))); - } - }; - - const searchEnabled = isPresent(searchFn) || isPresent(searchKeys); - - const filteredOptions = useMemo(() => { - if (!searchEnabled) return options; - if (searchFn) { - return options.filter((o) => searchFn(o, searchValue)); - } - if (searchKeys) { - return options.filter((o) => { - const res = searchByKeys(o, searchKeys, searchValue); - return res; - }); - } - return options; - }, [searchEnabled, searchFn, options, searchValue, searchKeys]); - - useEffect(() => { - setSelected(initiallySelected); - }, [initiallySelected]); - - return ( - { - setOpen(false); - }} - afterClose={() => { - setSearch(''); - }} - className="modal-dialog-select" - > - {extrasTop} - {searchEnabled && ( - { - setSearch(value); - }} - placeholder="Filter/Search" - /> - )} -
{ - handleSelectAll(); - }} - > - -

Select all

-
-
-
    - {filteredOptions.length === 0 && searchValue === '' && ( -

    No options

    - )} - {filteredOptions.length === 0 && searchValue !== '' && ( -

    Not found

    - )} - {filteredOptions.map((o) => { - const id = getIdent(o); - const isSelected = selected.includes(id); - return ( -
  • { - handleSelect(id, isSelected); - }} - > - - {getLabel(o)} -
  • - ); - })} -
-
-
-
-
- ); -}; diff --git a/web/src/pages/acl/AclCreatePage/components/DialogSelect/DialogSelectModal/style.scss b/web/src/pages/acl/AclCreatePage/components/DialogSelect/DialogSelectModal/style.scss deleted file mode 100644 index 8b1d29199..000000000 --- a/web/src/pages/acl/AclCreatePage/components/DialogSelect/DialogSelectModal/style.scss +++ /dev/null @@ -1,76 +0,0 @@ -.modal.modal-dialog-select { - min-height: 100dvh; - justify-items: center; - align-items: start; - padding: 10dvh 0 40px !important; - - .modal-content { - width: 100%; - max-width: 580px; - border-radius: 10px; - padding: 18px 10px; - box-sizing: border-box; - overflow: hidden; - - & > .spacer { - padding-bottom: var(--spacing-xs); - } - - hr { - border: 0.5px solid var(--border-primary); - margin: var(--spacing-xs) 0; - } - - & > .search { - margin-bottom: var(--spacing-xs); - } - - .no-data { - width: 100%; - text-align: center; - color: var(--text-body-tertiary); - - @include typography(app-body-2); - } - - .option { - min-height: 24px; - display: flex; - flex-flow: row; - align-items: center; - justify-content: flex-start; - column-gap: var(--spacing-xs); - cursor: pointer; - user-select: none; - - p, - span { - @include typography(app-modal-1); - } - - span { - color: var(--text-body-tertiary); - } - } - - .options { - list-style: none; - display: flex; - flex-flow: column; - row-gap: var(--spacing-xs); - min-height: calc(24px * 6 + 50px); - max-height: calc(240px + (9 * var(--spacing-xs))); - overflow-x: hidden; - overflow-y: auto; - } - - .controls { - padding-top: var(--spacing-xs); - display: flex; - flex-flow: row; - align-items: center; - justify-content: center; - column-gap: var(--spacing-l); - } - } -} diff --git a/web/src/pages/acl/AclCreatePage/components/DialogSelect/FormDialogSelect.tsx b/web/src/pages/acl/AclCreatePage/components/DialogSelect/FormDialogSelect.tsx deleted file mode 100644 index 661cf4785..000000000 --- a/web/src/pages/acl/AclCreatePage/components/DialogSelect/FormDialogSelect.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { useMemo } from 'react'; -import { - type FieldValues, - type UseControllerProps, - useController, -} from 'react-hook-form'; - -import { DialogSelect } from './DialogSelect'; -import type { DialogSelectProps } from './types'; - -type Props = { - controller: UseControllerProps; - forceShowErrorMessage?: boolean; - onChange?: () => void; -} & Omit, 'selected' | 'errorMessage'>; - -export const FormDialogSelect = < - T extends FieldValues, - B extends object, - I extends number | string, ->({ - controller, - onChange: onChangeExternal, - forceShowErrorMessage = false, - ...selectProps -}: Props) => { - const { - field: { value, onChange }, - fieldState: { error, isDirty, isTouched }, - formState: { isSubmitted }, - } = useController(controller); - - const errorMessage = useMemo(() => { - if ( - (error && (isDirty || isTouched)) || - (!error && isSubmitted) || - forceShowErrorMessage - ) { - return error?.message; - } - return undefined; - }, [error, forceShowErrorMessage, isDirty, isSubmitted, isTouched]); - - return ( - { - onChange(selected); - onChangeExternal?.(); - }} - selected={value} - errorMessage={errorMessage} - /> - ); -}; diff --git a/web/src/pages/acl/AclCreatePage/components/DialogSelect/style.scss b/web/src/pages/acl/AclCreatePage/components/DialogSelect/style.scss deleted file mode 100644 index f51846251..000000000 --- a/web/src/pages/acl/AclCreatePage/components/DialogSelect/style.scss +++ /dev/null @@ -1,180 +0,0 @@ -.dialog-select-field { - width: 100%; - display: inline-grid; - grid-template-rows: auto; - grid-template-columns: 1fr 40px; - column-gap: var(--spacing-xs); - align-items: center; - - .open-button { - grid-row: 1; - grid-column: 2 / 3; - background-color: var(--surface-icon-on-dark); - border: none; - border-radius: 10px; - width: 40px; - height: 40px; - display: flex; - flex-flow: row; - align-items: center; - justify-content: center; - cursor: pointer; - opacity: 1; - - @include animate-field; - transition-property: background-color, opacity; - - svg { - path { - @include animate-field; - transition-property: fill; - fill: var(--surface-icon-primary); - } - } - - &:disabled { - opacity: var(--disabled-opacity); - } - - &:not(:disabled) { - &:hover { - background-color: var(--surface-main-primary); - - svg { - path { - fill: var(--surface-icon-secondary); - } - } - } - } - } - - & > .track { - grid-row: 1; - grid-column: 1 / 2; - width: 100%; - box-sizing: border-box; - padding: 8px 10px 8px 18px; - border-radius: 10px; - border: 1px solid var(--border-primary); - min-height: 50px; - background-color: var(--surface-frame-bg); - position: relative; - overflow: hidden; - opacity: 1; - - @include animate-field; - transition-property: border-color, opacity; - - &.overflows { - &::after { - position: absolute; - top: 0; - right: 0; - width: 65px; - height: 100%; - content: ' '; - background: linear-gradient( - 90deg, - rgba(0, 0, 0, 0) 0%, - var(--surface-default-modal) 100% - ); - } - } - - .options { - display: flex; - flex-flow: row nowrap; - align-items: center; - justify-content: flex-start; - column-gap: 8px; - z-index: 1; - - .dialog-select-tag { - display: inline-flex; - flex-flow: row; - align-items: center; - justify-content: flex-start; - box-sizing: border-box; - padding: 7px 10px; - border: 1px solid var(--border-primary); - column-gap: 10px; - border-radius: 10px; - background-color: var(--surface-default-modal); - - span, - p { - @include typography(app-input); - user-select: none; - text-wrap: nowrap; - } - } - } - - & > svg { - border-radius: 10px; - user-select: none; - z-index: 2; - position: absolute; - right: -1px; - top: 0px; - } - } - - &.disabled { - & > .track { - cursor: not-allowed; - } - - & > .open-button { - cursor: not-allowed; - } - } - - &.invalid { - & > .track { - border-color: var(--border-alert); - } - } - - &.disabled { - & > .open-button { - opacity: var(--disabled-opacity); - } - - & > .track { - opacity: var(--disabled-opacity); - } - } -} - -.dialog-select { - & > .inner { - position: relative; - - & > label { - padding-bottom: var(--spacing-xs); - } - } -} - -.dialog-select-track-floating-menu { - min-width: 200px; - max-width: 100dvh; - - ul { - list-style: none; - display: flex; - flex-flow: column; - row-gap: var(--spacing-xs); - - li { - @include typography(app-modal-2); - display: flex; - flex-flow: row wrap; - gap: var(--spacing-xs); - align-items: center; - justify-content: flex-start; - } - } -} diff --git a/web/src/pages/acl/AclCreatePage/components/DialogSelect/types.ts b/web/src/pages/acl/AclCreatePage/components/DialogSelect/types.ts deleted file mode 100644 index e276c3090..000000000 --- a/web/src/pages/acl/AclCreatePage/components/DialogSelect/types.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { ReactNode } from 'react'; - -export type DialogSelectProps = { - options: T[]; - identKey: keyof T; - selected: I[]; - renderTagContent: (value: T) => ReactNode; - // if not provided will use renderTagContent instead - renderDialogListItem?: (value: T) => ReactNode; - errorMessage?: string; - label?: string; - // Can replace searchFn, when given only keys it will use util searchByKeys, searchFn prop takes priority if both given. - searchKeys?: Array; - disabled?: boolean; - searchFn?: DialogSelectSearch; - onChange?: (values: I[]) => void; - modalExtrasTop?: ReactNode; -}; - -export type DialogSelectSearch = (obj: T, searchedValue: string) => boolean; diff --git a/web/src/pages/acl/AclCreatePage/style.scss b/web/src/pages/acl/AclCreatePage/style.scss deleted file mode 100644 index a620f85d0..000000000 --- a/web/src/pages/acl/AclCreatePage/style.scss +++ /dev/null @@ -1,115 +0,0 @@ -#acl-create-page { - .labeled-checkbox, - .form-checkbox { - label { - @include typography(app-modal-1); - color: var(--text-body-primary); - } - } - - & > .page-content { - box-sizing: border-box; - padding: var(--spacing-s); - - @include media-breakpoint-up(lg) { - padding: var(--spacing-xl); - } - - & > .header { - display: flex; - flex-flow: column; - row-gap: var(--spacing-s); - padding-bottom: var(--spacing-m); - - @include media-breakpoint-up(lg) { - flex-flow: row; - align-items: center; - justify-content: flex-start; - gap: var(--spacing-s); - } - - h1 { - @include typography(app-title); - } - - .controls { - margin-left: auto; - display: flex; - flex-flow: row; - align-items: center; - justify-content: flex-end; - column-gap: var(--spacing-s); - } - } - } -} - -#acl-sections { - width: 100%; - display: flex; - flex-flow: column; - row-gap: var(--spacing-s); - - @include media-breakpoint-up(xl) { - flex-flow: row; - column-gap: var(--spacing-m); - } - - #rule-card { - grid-area: rule; - } - - #allowed-card { - grid-area: allowed; - } - - #destination-card { - grid-area: destination; - } - - #denied-card { - grid-area: denied; - } - - & > .column { - display: flex; - flex-flow: column; - row-gap: var(--spacing-s); - } - - h3 { - text-wrap: nowrap; - @include typography(app-side-bar); - } - - .card { - .input, - .dialog-select-spacer, - .labeled-checkbox, - .form-checkbox, - .spacer, - & > .header { - &:not(:last-child) { - padding-bottom: var(--spacing-s); - } - } - - .header { - display: flex; - flex-flow: row nowrap; - align-items: center; - justify-content: flex-start; - column-gap: 8px; - width: 100%; - min-height: 23px; - - hr { - width: 100%; - align-self: flex-end; - border: 0.5px solid var(--border-primary); - margin: 0; - padding: 0; - } - } - } -} diff --git a/web/src/pages/acl/AclIndexPage/AclIndexPage.tsx b/web/src/pages/acl/AclIndexPage/AclIndexPage.tsx deleted file mode 100644 index 9f3a9a57b..000000000 --- a/web/src/pages/acl/AclIndexPage/AclIndexPage.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import './style.scss'; - -import { useMemo, useState } from 'react'; - -import { useI18nContext } from '../../../i18n/i18n-react'; -import { PageLayout } from '../../../shared/components/Layout/PageLayout/PageLayout'; -import { CardTabs } from '../../../shared/defguard-ui/components/Layout/CardTabs/CardTabs'; -import type { CardTabsData } from '../../../shared/defguard-ui/components/Layout/CardTabs/types'; -import { AclIndexAliases } from './components/AclIndexAliases/AclIndexAliases'; -import { AclIndexRules } from './components/AclIndexRules/AclIndexRules'; - -enum AclTab { - ALIASES = 'aliases', - RULES = 'rules', -} - -export const AclIndexPage = () => { - const [activeTab, setActiveTab] = useState(AclTab.RULES); - const { LL } = useI18nContext(); - - const availableTabs: CardTabsData[] = [ - { - key: AclTab.RULES, - active: activeTab === AclTab.RULES, - content:

{LL.acl.listPage.tabs.rules()}

, - onClick: () => setActiveTab(AclTab.RULES), - }, - { - key: AclTab.ALIASES, - active: activeTab === AclTab.ALIASES, - content:

{LL.acl.listPage.tabs.aliases()}

, - onClick: () => setActiveTab(AclTab.ALIASES), - }, - ]; - - const tabRender = useMemo(() => { - switch (activeTab) { - case AclTab.RULES: - return ; - case AclTab.ALIASES: - return ; - } - }, [activeTab]); - - return ( - -
-

{LL.acl.sharedTitle()}

-
- -
{tabRender}
-
- ); -}; diff --git a/web/src/pages/acl/AclIndexPage/components/AclIndexAliases/AclIndexAliases.tsx b/web/src/pages/acl/AclIndexPage/components/AclIndexAliases/AclIndexAliases.tsx deleted file mode 100644 index 56ad450cf..000000000 --- a/web/src/pages/acl/AclIndexPage/components/AclIndexAliases/AclIndexAliases.tsx +++ /dev/null @@ -1,470 +0,0 @@ -import './style.scss'; - -import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; -import { intersection } from 'lodash-es'; -import { useCallback, useEffect, useMemo, useState } from 'react'; -import { shallow } from 'zustand/shallow'; - -import { useI18nContext } from '../../../../../i18n/i18n-react'; -import { FilterGroupsModal } from '../../../../../shared/components/modals/FilterGroupsModal/FilterGroupsModal'; -import type { FilterGroupsModalFilter } from '../../../../../shared/components/modals/FilterGroupsModal/types'; -import { Button } from '../../../../../shared/defguard-ui/components/Layout/Button/Button'; -import { - ButtonSize, - ButtonStyleVariant, -} from '../../../../../shared/defguard-ui/components/Layout/Button/types'; -import { ListItemCount } from '../../../../../shared/defguard-ui/components/Layout/ListItemCount/ListItemCount'; -import { Search } from '../../../../../shared/defguard-ui/components/Layout/Search/Search'; -import useApi from '../../../../../shared/hooks/useApi'; -import { useToaster } from '../../../../../shared/hooks/useToaster'; -import { QueryKeys } from '../../../../../shared/queries'; -import { useAclLoadedContext } from '../../../acl-context'; -import { type AclAlias, AclAliasStatus } from '../../../types'; -import { - aclAliasStatusToInt, - aclDestinationToListTagDisplay, - aclPortsToListTagDisplay, - aclProtocolsToListTagDisplay, - aclRuleToListTagDisplay, -} from '../../../utils'; -import { AclListSkeleton } from '../AclListSkeleton/AclListSkeleton'; -import { DeployChangesIcon } from '../DeployChangesIcon'; -import { AliasesList } from './components/AliasesList'; -import { AclAliasApplyConfirmModal } from './modals/AclAliasApplyConfirmModal/AclAliasApplyConfirmModal'; -import { AclAliasDeleteBlockModal } from './modals/AclAliasDeleteBlockModal/AclAliasDeleteBlockModal'; -import { AlcAliasCEModal } from './modals/AlcAliasCEModal/AlcAliasCEModal'; -import { useAclAliasCEModal } from './modals/AlcAliasCEModal/store'; -import type { AclAliasListData } from './types'; - -type ListTagDisplay = { - key: string | number; - label: string; - displayAsTag?: boolean; -}; - -type AliasesFilters = { - rules: number[]; - status: number[]; -}; - -type ApplyConfirmState = { - rules: string[]; - open: boolean; - aliasesToApply: number[]; -}; - -const defaultApplyConfirmState: ApplyConfirmState = { - rules: [], - aliasesToApply: [], - open: false, -}; - -export type ListData = { - context: { - usedBy: ListTagDisplay[]; - }; -} & AclAlias; - -const defaultFilters: AliasesFilters = { - rules: [], - status: [], -}; - -const intersects = (...args: Array): boolean => intersection(args).length > 0; - -export const AclIndexAliases = () => { - const toaster = useToaster(); - const { - acl: { - rules: { getRules }, - aliases: { applyAliases }, - }, - } = useApi(); - - const { data: aclRules, isLoading: aliasesLoading } = useQuery({ - queryFn: getRules, - queryKey: [QueryKeys.FETCH_ACL_RULES], - refetchOnMount: true, - }); - - const queryClient = useQueryClient(); - const openCEModal = useAclAliasCEModal((s) => s.open, shallow); - const [applyConfirmModalState, setApplyConfirmModalState] = useState( - defaultApplyConfirmState, - ); - const aclContext = useAclLoadedContext(); - const { aliases } = aclContext; - const [appliedFilters, setAppliedFilters] = useState(defaultFilters); - const filtersPresent = useMemo( - () => Object.values(appliedFilters).flat(1).length > 0, - [appliedFilters], - ); - const [filtersModalOpen, setFiltersModalOpen] = useState(false); - const [searchValue, setSearchValue] = useState(''); - const { LL } = useI18nContext(); - const localLL = LL.acl.listPage.aliases; - - const { mutate: applyMutation, isPending: applyPending } = useMutation({ - mutationFn: applyAliases, - onSuccess: () => { - toaster.success(LL.acl.listPage.message.applyChanges()); - void queryClient.invalidateQueries({ - predicate: (query) => query.queryKey.includes(QueryKeys.FETCH_ACL_ALIASES), - }); - }, - onError: (error) => { - toaster.error(LL.acl.listPage.message.applyFail()); - console.error(error); - }, - }); - - const pendingAliasesCount = useMemo(() => { - if (aliases) { - return aliases.filter((alias) => alias.state !== AclAliasStatus.APPLIED).length; - } - return 0; - }, [aliases]); - - const applySearch = useCallback( - (data: AclAlias[]) => { - if (searchValue) { - return data.filter((alias) => - alias.name.trim().toLowerCase().includes(searchValue.toLowerCase().trim()), - ); - } - return data; - }, - [searchValue], - ); - - const [selectedPending, setSelectedPending] = useState< - Record - >({}); - - const handlePendingSelect = useCallback((key: number, value: boolean) => { - setSelectedPending((s) => ({ ...s, [key]: value })); - }, []); - - const handlePendingSelectAll = useCallback( - (value: boolean, state: Record) => { - const newState = { ...state }; - for (const key in newState) { - newState[key] = value; - } - setSelectedPending(newState); - }, - [], - ); - - const pendingSelectionCount = useMemo(() => { - let count = 0; - for (const key in selectedPending) { - if (selectedPending[key]) count++; - } - return count; - }, [selectedPending]); - - const prepareDisplay = useCallback( - (data: AclAlias[], filters: AliasesFilters) => { - if (!aclRules) return []; - if (filters.rules.length) { - data = data.filter((alias) => intersects(alias.rules, filters.rules)); - } - const res: AclAliasListData[] = []; - for (const alias of data) { - const rules = aclRules.filter((rule) => alias.rules.includes(rule.id)); - res.push({ - ...alias, - display: { - destination: aclDestinationToListTagDisplay(alias.destination), - ports: aclPortsToListTagDisplay(alias.ports), - protocols: aclProtocolsToListTagDisplay(alias.protocols), - rules: aclRuleToListTagDisplay(rules), - }, - }); - } - return res; - }, - [aclRules], - ); - - const deployed = useMemo(() => { - if (aliases) { - return aliases.filter((alias) => alias.state === AclAliasStatus.APPLIED); - } - return []; - }, [aliases]); - - const deployedDisplay = useMemo(() => { - if (aliases) { - return prepareDisplay(applySearch(deployed), appliedFilters); - } - return []; - }, [aliases, prepareDisplay, applySearch, deployed, appliedFilters]); - - const pending = useMemo(() => { - if (aliases) { - return aliases.filter((alias) => alias.state === AclAliasStatus.MODIFIED); - } - return []; - }, [aliases]); - - const pendingDisplay = useMemo(() => { - if (aliases) { - return prepareDisplay(applySearch(pending), appliedFilters); - } - return []; - }, [aliases, appliedFilters, applySearch, pending, prepareDisplay]); - - const displayItemsCount = useMemo( - () => deployedDisplay.length + pendingDisplay.length, - [deployedDisplay.length, pendingDisplay.length], - ); - - const applyText = useMemo(() => { - if (!pending.length) return localLL.listControls.apply.noChanges(); - if (pendingSelectionCount) { - return localLL.listControls.apply.selective({ - count: pendingSelectionCount, - }); - } - return localLL.listControls.apply.all({ - count: pending.length, - }); - }, [localLL.listControls.apply, pending.length, pendingSelectionCount]); - - const filters = useMemo(() => { - const res: Record = { - rules: { - identifier: 'rules', - label: localLL.modals.filterGroupsModal.groupLabels.rules(), - items: - aclRules?.map((rule) => ({ - label: rule.name, - searchValues: [rule.name], - value: rule.id, - })) ?? [], - order: 2, - }, - status: { - identifier: 'status', - label: localLL.modals.filterGroupsModal.groupLabels.status(), - items: [ - { - label: localLL.list.status.changed(), - searchValues: [LL.acl.ruleStatus.modified()], - value: aclAliasStatusToInt(AclAliasStatus.MODIFIED), - }, - { - label: localLL.list.status.applied(), - searchValues: [localLL.list.status.applied()], - value: aclAliasStatusToInt(AclAliasStatus.APPLIED), - }, - ], - order: 1, - }, - }; - return res; - }, [ - LL.acl.ruleStatus, - aclRules, - localLL.list.status, - localLL.modals.filterGroupsModal.groupLabels, - ]); - - const handleApply = () => { - if (aliases && aclRules) { - let toApply: AclAlias[]; - if (!pendingSelectionCount) { - toApply = pending; - } else { - const ids: number[] = Object.keys(selectedPending) - .filter((key: string) => selectedPending[Number(key)]) - .map((key) => Number(key)); - toApply = pending.filter((alias) => ids.includes(alias.id)); - } - const aliasesIds = toApply.map((alias) => alias.id); - const rulesWithin = new Set(); - toApply.forEach((alias) => { - alias.rules.forEach((rule) => { - rulesWithin.add(rule); - }); - }); - // check if need to confirm - if (rulesWithin.size) { - //prepare and open modal - const ruleNames: string[] = aclRules - .filter((rule) => rulesWithin.has(rule.id)) - .map((rule) => rule.name) - .sort(); - setApplyConfirmModalState({ - aliasesToApply: aliasesIds, - open: true, - rules: ruleNames, - }); - } else { - void applyMutation(aliasesIds); - } - } - }; - - // update or build selection state for list when rules are done loading - useEffect(() => { - if (aliases) { - const pending = aliases.filter((rule) => rule.state !== AclAliasStatus.APPLIED); - const selectionEntries = Object.keys(selectedPending).length; - if (selectionEntries !== pending.length) { - const newSelectionState: Record = {}; - for (const rule of pending) { - newSelectionState[rule.id] = newSelectionState[rule.id] ?? false; - } - setSelectedPending(newSelectionState); - } - } - }, [aliases, selectedPending]); - - return ( - <> - - { - setApplyConfirmModalState((s) => ({ ...s, open: val })); - }} - onSubmit={() => { - void applyMutation(applyConfirmModalState.aliasesToApply); - setApplyConfirmModalState(defaultApplyConfirmState); - }} - /> -
-
-

Aliases

- - { - setSearchValue(searchChange); - }} - /> -
-
-
- {aliasesLoading && } - {!aliasesLoading && ( - <> - - - - )} - - { - setAppliedFilters(newFilters as AliasesFilters); - }} - onCancel={() => { - setFiltersModalOpen(false); - }} - /> -
- - ); -}; diff --git a/web/src/pages/acl/AclIndexPage/components/AclIndexAliases/components/AclAliasStatus/AclAliasStatus.tsx b/web/src/pages/acl/AclIndexPage/components/AclIndexAliases/components/AclAliasStatus/AclAliasStatus.tsx deleted file mode 100644 index b53fe3931..000000000 --- a/web/src/pages/acl/AclIndexPage/components/AclIndexAliases/components/AclAliasStatus/AclAliasStatus.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import './style.scss'; - -import clsx from 'clsx'; -import { useMemo } from 'react'; - -import { useI18nContext } from '../../../../../../../i18n/i18n-react'; -import { ActivityIcon } from '../../../../../../../shared/defguard-ui/components/icons/ActivityIcon/ActivityIcon'; -import { ActivityIconVariant } from '../../../../../../../shared/defguard-ui/components/icons/ActivityIcon/types'; -import { AclAliasStatus } from '../../../../../types'; - -type Props = { - status: AclAliasStatus; -}; - -export const AclAliasStatusDisplay = ({ status }: Props) => { - const { LL } = useI18nContext(); - const statusLL = LL.acl.listPage.aliases.list.status; - - const [label, iconStatus] = useMemo(() => { - switch (status) { - case AclAliasStatus.APPLIED: - return [statusLL.applied(), ActivityIconVariant.CONNECTED]; - case AclAliasStatus.MODIFIED: - return [statusLL.changed(), ActivityIconVariant.DISCONNECTED]; - } - }, [status, statusLL]); - - return ( -
-

{label}

- -
- ); -}; diff --git a/web/src/pages/acl/AclIndexPage/components/AclIndexAliases/components/AclAliasStatus/style.scss b/web/src/pages/acl/AclIndexPage/components/AclIndexAliases/components/AclAliasStatus/style.scss deleted file mode 100644 index c422f4b39..000000000 --- a/web/src/pages/acl/AclIndexPage/components/AclIndexAliases/components/AclAliasStatus/style.scss +++ /dev/null @@ -1,28 +0,0 @@ -.acl-alias-status { - width: 100%; - max-width: 100%; - display: inline-flex; - flex-flow: row nowrap; - column-gap: 5px; - user-select: none; - overflow: hidden; - align-items: center; - justify-content: flex-start; - - & > p { - @include typography(app-modal-3); - color: inherit; - max-width: calc(100% - 13px); - overflow: hidden; - - @include text-overflow-dots; - } - - &.status-modified { - color: var(--text-important); - } - - &.status-applied { - color: var(--text-positive); - } -} diff --git a/web/src/pages/acl/AclIndexPage/components/AclIndexAliases/components/AliasEditButton.tsx b/web/src/pages/acl/AclIndexPage/components/AclIndexAliases/components/AliasEditButton.tsx deleted file mode 100644 index b13f1802f..000000000 --- a/web/src/pages/acl/AclIndexPage/components/AclIndexAliases/components/AliasEditButton.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import type { AxiosError } from 'axios'; -import { useCallback } from 'react'; -import { shallow } from 'zustand/shallow'; - -import { useI18nContext } from '../../../../../../i18n/i18n-react'; -import { EditButton } from '../../../../../../shared/defguard-ui/components/Layout/EditButton/EditButton'; -import { EditButtonOption } from '../../../../../../shared/defguard-ui/components/Layout/EditButton/EditButtonOption'; -import { EditButtonOptionStyleVariant } from '../../../../../../shared/defguard-ui/components/Layout/EditButton/types'; -import useApi from '../../../../../../shared/hooks/useApi'; -import { useToaster } from '../../../../../../shared/hooks/useToaster'; -import { QueryKeys } from '../../../../../../shared/queries'; -import { AclAliasStatus } from '../../../../types'; -import { useAclAliasDeleteBlockModal } from '../modals/AclAliasDeleteBlockModal/store'; -import { useAclAliasCEModal } from '../modals/AlcAliasCEModal/store'; -import type { AclAliasListData } from '../types'; - -type EditProps = { - alias: AclAliasListData; -}; - -export const AliasEditButton = ({ alias }: EditProps) => { - const queryClient = useQueryClient(); - const isApplied = alias.state === AclAliasStatus.APPLIED; - const { LL } = useI18nContext(); - const localLL = LL.acl.listPage.aliases.list.editMenu; - const toaster = useToaster(); - const openDeleteBlockModal = useAclAliasDeleteBlockModal((s) => s.open, shallow); - - const { - acl: { - aliases: { deleteAlias }, - }, - } = useApi(); - - const invalidateQueries = useCallback(() => { - void queryClient.invalidateQueries({ - predicate: (query) => query.queryKey.includes(QueryKeys.FETCH_ACL_ALIASES), - }); - }, [queryClient]); - - const handleError = useCallback( - (err: AxiosError) => { - toaster.error(LL.acl.listPage.message.changeFail()); - console.error(err.message ?? err); - }, - [LL.acl.listPage.message, toaster], - ); - - const { mutate: deleteAliasMutation, isPending: deletionPending } = useMutation({ - mutationFn: deleteAlias, - onSuccess: () => { - invalidateQueries(); - if (isApplied) { - toaster.success(LL.acl.listPage.aliases.message.aliasDeleted()); - } else { - toaster.success(LL.acl.listPage.message.changeDiscarded()); - } - }, - onError: handleError, - }); - - const openEditModal = useAclAliasCEModal((s) => s.open, shallow); - - return ( - - { - openEditModal({ alias }); - }} - disabled={deletionPending} - /> - { - if (alias.rules.length === 0) { - deleteAliasMutation(alias.id); - } else { - openDeleteBlockModal( - alias, - alias.display.rules.map((tag) => tag.label).sort(), - ); - } - }} - /> - - ); -}; diff --git a/web/src/pages/acl/AclIndexPage/components/AclIndexAliases/components/AliasesList.tsx b/web/src/pages/acl/AclIndexPage/components/AclIndexAliases/components/AliasesList.tsx deleted file mode 100644 index cdcc24bf9..000000000 --- a/web/src/pages/acl/AclIndexPage/components/AclIndexAliases/components/AliasesList.tsx +++ /dev/null @@ -1,198 +0,0 @@ -import clsx from 'clsx'; -import { orderBy } from 'lodash-es'; -import { type ReactNode, useMemo, useState } from 'react'; -import { upperCaseFirst } from 'text-case'; - -import { useI18nContext } from '../../../../../../i18n/i18n-react'; -import { ListCellTags } from '../../../../../../shared/components/Layout/ListCellTags/ListCellTags'; -import { ListHeader } from '../../../../../../shared/components/Layout/ListHeader/ListHeader'; -import type { ListHeaderColumnConfig } from '../../../../../../shared/components/Layout/ListHeader/types'; -import { CheckBox } from '../../../../../../shared/defguard-ui/components/Layout/Checkbox/CheckBox'; -import { InteractionBox } from '../../../../../../shared/defguard-ui/components/Layout/InteractionBox/InteractionBox'; -import { ListCellText } from '../../../../../../shared/defguard-ui/components/Layout/ListCellText/ListCellText'; -import { NoData } from '../../../../../../shared/defguard-ui/components/Layout/NoData/NoData'; -import { ListSortDirection } from '../../../../../../shared/defguard-ui/components/Layout/VirtualizedList/types'; -import { isPresent } from '../../../../../../shared/defguard-ui/utils/isPresent'; -import type { AclAlias } from '../../../../types'; -import { DividerHeader } from '../../shared/DividerHeader'; -import type { AclAliasListData } from '../types'; -import { AclAliasStatusDisplay } from './AclAliasStatus/AclAliasStatus'; -import { AliasEditButton } from './AliasEditButton'; - -type AliasesListProps = { - data: AclAliasListData[]; - header: { - text: string; - extras?: ReactNode; - }; - noDataMessage: string; - isAppliedList?: boolean; - selected?: Record; - allSelected?: boolean; - onSelect?: (key: number, value: boolean) => void; - onSelectAll?: (value: boolean, state: Record) => void; -}; - -export const AliasesList = ({ - data, - header, - noDataMessage, - selected, - allSelected, - onSelect, - onSelectAll, -}: AliasesListProps) => { - const { LL } = useI18nContext(); - const headersLL = LL.acl.listPage.aliases.list.headers; - const [sortKey, setSortKey] = useState('name'); - const [sortDir, setSortDir] = useState(ListSortDirection.ASC); - - const selectionEnabled = useMemo( - () => - isPresent(onSelect) && - isPresent(onSelectAll) && - isPresent(selected) && - isPresent(allSelected), - [onSelect, onSelectAll, selected, allSelected], - ); - - const sortedAliases = useMemo( - () => orderBy(data, [sortKey], [sortDir.valueOf().toLowerCase() as 'asc' | 'desc']), - [data, sortDir, sortKey], - ); - - const listHeaders = useMemo( - (): ListHeaderColumnConfig[] => [ - { - label: headersLL.name(), - sortKey: 'name', - enabled: true, - }, - { - label: headersLL.ip(), - key: 'ip', - enabled: false, - }, - { - label: headersLL.ports(), - key: 'ports', - enabled: false, - }, - { - label: headersLL.protocols(), - key: 'protocols', - enabled: false, - }, - { - label: headersLL.rules(), - key: 'rules', - enabled: false, - }, - { - label: headersLL.status(), - key: 'status', - enabled: false, - }, - { - label: headersLL.kind(), - sortKey: 'kind', - enabled: true, - }, - { - label: headersLL.edit(), - key: 'edit', - enabled: false, - }, - ], - [headersLL], - ); - - return ( -
- {header.extras} - {sortedAliases.length === 0 && ( - - )} - {sortedAliases.length > 0 && ( -
-
- - headers={listHeaders} - sortDirection={sortDir} - activeKey={sortKey} - selectAll={allSelected} - onSelectAll={(val) => { - if (selectionEnabled) { - onSelectAll?.(val, selected ?? {}); - } - }} - onChange={(key, dir) => { - setSortKey(key); - setSortDir(dir); - }} - /> -
-
    - {sortedAliases.map((alias) => { - let aliasSelected = false; - if (selected) { - aliasSelected = selected[alias.id] ?? false; - } - return ( -
  • - {!selectionEnabled &&
    } - {selectionEnabled && ( -
    - { - onSelect?.(alias.id, !aliasSelected); - }} - > - - -
    - )} -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
  • - ); - })} -
-
- )} -
- ); -}; diff --git a/web/src/pages/acl/AclIndexPage/components/AclIndexAliases/modals/AclAliasApplyConfirmModal/AclAliasApplyConfirmModal.tsx b/web/src/pages/acl/AclIndexPage/components/AclIndexAliases/modals/AclAliasApplyConfirmModal/AclAliasApplyConfirmModal.tsx deleted file mode 100644 index eae2e06aa..000000000 --- a/web/src/pages/acl/AclIndexPage/components/AclIndexAliases/modals/AclAliasApplyConfirmModal/AclAliasApplyConfirmModal.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import './style.scss'; - -import { useI18nContext } from '../../../../../../../i18n/i18n-react'; -import { ConfirmModal } from '../../../../../../../shared/defguard-ui/components/Layout/modals/ConfirmModal/ConfirmModal'; - -type Props = { - isOpen: boolean; - setOpen: (val: boolean) => void; - rules: string[]; - onSubmit: () => void; -}; - -export const AclAliasApplyConfirmModal = ({ - isOpen, - onSubmit, - rules, - setOpen, -}: Props) => { - const { LL } = useI18nContext(); - const localLL = LL.acl.listPage.aliases.modals.applyConfirm; - - return ( - { - setOpen(false); - }} - onSubmit={() => { - onSubmit(); - }} - submitText={localLL.submit()} - title={localLL.title()} - > -
-

{localLL.message()}

-

{`${localLL.listLabel()}(${rules.length})`}:

-

{rules.join(', ')}

-
-
- ); -}; diff --git a/web/src/pages/acl/AclIndexPage/components/AclIndexAliases/modals/AclAliasApplyConfirmModal/style.scss b/web/src/pages/acl/AclIndexPage/components/AclIndexAliases/modals/AclAliasApplyConfirmModal/style.scss deleted file mode 100644 index 284adbede..000000000 --- a/web/src/pages/acl/AclIndexPage/components/AclIndexAliases/modals/AclAliasApplyConfirmModal/style.scss +++ /dev/null @@ -1,26 +0,0 @@ -#acl-aliases-apply-confirm-modal { - .title { - color: var(--text-button-primary); - text-align: center; - } - - .content { - display: flex; - flex-flow: column; - row-gap: var(--spacing-xs); - max-width: 100%; - overflow: hidden; - - p { - @include typography(app-body-2); - color: var(--text-button-primary); - text-align: left; - overflow-wrap: break-word; - width: 100%; - - &.rules { - font-weight: 700; - } - } - } -} diff --git a/web/src/pages/acl/AclIndexPage/components/AclIndexAliases/modals/AclAliasDeleteBlockModal/AclAliasDeleteBlockModal.tsx b/web/src/pages/acl/AclIndexPage/components/AclIndexAliases/modals/AclAliasDeleteBlockModal/AclAliasDeleteBlockModal.tsx deleted file mode 100644 index 0cd72d66b..000000000 --- a/web/src/pages/acl/AclIndexPage/components/AclIndexAliases/modals/AclAliasDeleteBlockModal/AclAliasDeleteBlockModal.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import './style.scss'; - -import { useEffect } from 'react'; -import { shallow } from 'zustand/shallow'; - -import { useI18nContext } from '../../../../../../../i18n/i18n-react'; -import { RenderMarkdown } from '../../../../../../../shared/components/Layout/RenderMarkdown/RenderMarkdown'; -import { ConfirmModal } from '../../../../../../../shared/defguard-ui/components/Layout/modals/ConfirmModal/ConfirmModal'; -import { ConfirmModalType } from '../../../../../../../shared/defguard-ui/components/Layout/modals/ConfirmModal/types'; -import { isPresent } from '../../../../../../../shared/defguard-ui/utils/isPresent'; -import { useAclAliasDeleteBlockModal } from './store'; - -export const AclAliasDeleteBlockModal = () => { - const { LL } = useI18nContext(); - const localLL = LL.acl.listPage.aliases.modals.deleteBlock; - const [close, reset] = useAclAliasDeleteBlockModal((s) => [s.close, s.reset], shallow); - const alias = useAclAliasDeleteBlockModal((s) => s.alias); - const rules = useAclAliasDeleteBlockModal((s) => s.rulesNames); - const isOpen = useAclAliasDeleteBlockModal((s) => s.visible); - - // biome-ignore lint/correctness/useExhaustiveDependencies: migration, checkMeLater - useEffect(() => { - return () => { - reset?.(); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - return ( - { - close(); - }} - afterClose={() => { - reset(); - }} - > -
- {isPresent(alias) && ( - - )} - {rules.length > 0 &&

{rules.join(', ')}

} -
-
- ); -}; diff --git a/web/src/pages/acl/AclIndexPage/components/AclIndexAliases/modals/AclAliasDeleteBlockModal/store.tsx b/web/src/pages/acl/AclIndexPage/components/AclIndexAliases/modals/AclAliasDeleteBlockModal/store.tsx deleted file mode 100644 index a6685026a..000000000 --- a/web/src/pages/acl/AclIndexPage/components/AclIndexAliases/modals/AclAliasDeleteBlockModal/store.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { createWithEqualityFn } from 'zustand/traditional'; - -import type { AclAlias } from '../../../../../types'; - -const defaults: StoreValues = { - visible: false, - alias: undefined, - rulesNames: [], -}; - -export const useAclAliasDeleteBlockModal = createWithEqualityFn( - (set) => ({ - ...defaults, - open: (alias, rules) => set({ alias: alias, rulesNames: rules, visible: true }), - close: () => set({ visible: false }), - reset: () => set(defaults), - }), - Object.is, -); - -type Store = StoreValues & StoreMethods; - -type StoreValues = { - visible: boolean; - rulesNames: string[]; - alias?: AclAlias; -}; - -type StoreMethods = { - open: (alias: AclAlias, rules: string[]) => void; - close: () => void; - reset: () => void; -}; diff --git a/web/src/pages/acl/AclIndexPage/components/AclIndexAliases/modals/AclAliasDeleteBlockModal/style.scss b/web/src/pages/acl/AclIndexPage/components/AclIndexAliases/modals/AclAliasDeleteBlockModal/style.scss deleted file mode 100644 index 71223bade..000000000 --- a/web/src/pages/acl/AclIndexPage/components/AclIndexAliases/modals/AclAliasDeleteBlockModal/style.scss +++ /dev/null @@ -1,26 +0,0 @@ -#acl-aliases-delete-alias-block-modal { - .title { - text-align: center; - color: var(--text-button-primary); - } - - .content { - display: flex; - flex-flow: column; - row-gap: var(--spacing-xs); - text-align: left; - - p { - @include typography(app-body-2); - color: var(--text-button-primary); - text-align: left; - - &.rules { - text-wrap: wrap; - max-width: 100%; - overflow-wrap: break-word; - font-weight: 700; - } - } - } -} diff --git a/web/src/pages/acl/AclIndexPage/components/AclIndexAliases/modals/AlcAliasCEModal/AlcAliasCEModal.tsx b/web/src/pages/acl/AclIndexPage/components/AclIndexAliases/modals/AlcAliasCEModal/AlcAliasCEModal.tsx deleted file mode 100644 index 6a68bf790..000000000 --- a/web/src/pages/acl/AclIndexPage/components/AclIndexAliases/modals/AlcAliasCEModal/AlcAliasCEModal.tsx +++ /dev/null @@ -1,216 +0,0 @@ -import './style.scss'; - -import { zodResolver } from '@hookform/resolvers/zod'; -import { useQueryClient } from '@tanstack/react-query'; -import { omit } from 'lodash-es'; -import { useEffect, useMemo } from 'react'; -import { type SubmitHandler, useForm } from 'react-hook-form'; -import { z } from 'zod'; -import { shallow } from 'zustand/shallow'; - -import { useI18nContext } from '../../../../../../../i18n/i18n-react'; -import { FormInput } from '../../../../../../../shared/defguard-ui/components/Form/FormInput/FormInput'; -import { FormSelect } from '../../../../../../../shared/defguard-ui/components/Form/FormSelect/FormSelect'; -import { Button } from '../../../../../../../shared/defguard-ui/components/Layout/Button/Button'; -import { - ButtonSize, - ButtonStyleVariant, -} from '../../../../../../../shared/defguard-ui/components/Layout/Button/types'; -import { ModalWithTitle } from '../../../../../../../shared/defguard-ui/components/Layout/modals/ModalWithTitle/ModalWithTitle'; -import type { SelectOption } from '../../../../../../../shared/defguard-ui/components/Layout/Select/types'; -import { isPresent } from '../../../../../../../shared/defguard-ui/utils/isPresent'; -import useApi from '../../../../../../../shared/hooks/useApi'; -import { useToaster } from '../../../../../../../shared/hooks/useToaster'; -import { QueryKeys } from '../../../../../../../shared/queries'; -import { AclAliasKind } from '../../../../../types'; -import { protocolOptions, protocolToString } from '../../../../../utils'; -import { aclDestinationValidator, aclPortsValidator } from '../../../../../validators'; -import { AclMessageBoxes } from '../../../shared/AclMessageBoxes/AclMessageBoxes'; -import { useAclAliasCEModal } from './store'; - -export const AlcAliasCEModal = () => { - const isOpen = useAclAliasCEModal((s) => s.visible); - const alias = useAclAliasCEModal((s) => s.alias); - const isEdit = isPresent(alias); - - const [close, reset] = useAclAliasCEModal((s) => [s.close, s.reset], shallow); - - // biome-ignore lint/correctness/useExhaustiveDependencies: migration, checkMeLater - useEffect(() => { - return () => { - reset(); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - return ( - { - close(); - }} - afterClose={() => { - reset(); - }} - > - - - ); -}; - -const ModalContent = () => { - const queryClient = useQueryClient(); - const closeModal = useAclAliasCEModal((s) => s.close, shallow); - const initialAlias = useAclAliasCEModal((s) => s.alias); - const isEditMode = isPresent(initialAlias); - const toaster = useToaster(); - - const { LL } = useI18nContext(); - const localLL = LL.acl.listPage.aliases.modals.create; - const formErrors = LL.form.error; - const { - acl: { - aliases: { createAlias, editAlias }, - }, - } = useApi(); - - const schema = useMemo( - () => - z.object({ - name: z - .string({ - required_error: formErrors.required(), - }) - .trim() - .min(1, formErrors.required()), - kind: z.nativeEnum(AclAliasKind), - ports: aclPortsValidator(LL), - destination: aclDestinationValidator(LL), - protocols: z.number().array(), - }), - [LL, formErrors], - ); - - type FormFields = z.infer; - - const defaultValues = useMemo((): FormFields => { - let defaultValues: FormFields; - if (isPresent(initialAlias)) { - defaultValues = omit(initialAlias, ['id']); - } else { - defaultValues = { - destination: '', - name: '', - kind: AclAliasKind.DESTINATION, - ports: '', - protocols: [], - }; - } - return defaultValues; - }, [initialAlias]); - - const { - handleSubmit, - control, - formState: { isSubmitting }, - } = useForm({ - mode: 'all', - resolver: zodResolver(schema), - defaultValues, - }); - - const handleValidSubmit: SubmitHandler = async (values) => { - try { - if (isEditMode) { - await editAlias({ - ...values, - id: initialAlias.id, - }); - toaster.success(localLL.messages.modified()); - } else { - await createAlias(values); - toaster.success(localLL.messages.created()); - } - await queryClient.invalidateQueries({ - predicate: (query) => query.queryKey.includes(QueryKeys.FETCH_ACL_ALIASES), - }); - closeModal(); - } catch (e) { - toaster.error(LL.messages.error()); - console.error(e); - } - }; - - const aliasKindOptions = useMemo( - (): SelectOption[] => [ - { - key: AclAliasKind.DESTINATION, - value: AclAliasKind.DESTINATION, - label: localLL.kindOptions.destination(), - }, - { - key: AclAliasKind.COMPONENT, - value: AclAliasKind.COMPONENT, - label: localLL.kindOptions.component(), - }, - ], - [localLL.kindOptions], - ); - - return ( -
- - - -
-

Destination

-
- - - ({ displayValue: protocolToString(val), key: val })} - disposable - /> -
-
- - ); -}; diff --git a/web/src/pages/acl/AclIndexPage/components/AclIndexAliases/modals/AlcAliasCEModal/store.tsx b/web/src/pages/acl/AclIndexPage/components/AclIndexAliases/modals/AlcAliasCEModal/store.tsx deleted file mode 100644 index 72ba8e2b6..000000000 --- a/web/src/pages/acl/AclIndexPage/components/AclIndexAliases/modals/AlcAliasCEModal/store.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import { createWithEqualityFn } from 'zustand/traditional'; - -import type { AclAlias } from '../../../../../types'; - -const defaults: StoreValues = { - visible: false, - alias: undefined, -}; - -export const useAclAliasCEModal = createWithEqualityFn( - (set) => ({ - ...defaults, - open: (vals) => { - if (vals) { - set({ ...defaults, ...vals, visible: true }); - } else { - set({ ...defaults, visible: true }); - } - }, - close: () => { - set({ visible: false }); - }, - reset: () => { - set(defaults); - }, - }), - Object.is, -); - -type Store = StoreValues & StoreMethods; - -type StoreValues = { - visible: boolean; - alias?: AclAlias; -}; - -type StoreMethods = { - open: (vals?: Partial) => void; - close: () => void; - reset: () => void; -}; diff --git a/web/src/pages/acl/AclIndexPage/components/AclIndexAliases/modals/AlcAliasCEModal/style.scss b/web/src/pages/acl/AclIndexPage/components/AclIndexAliases/modals/AlcAliasCEModal/style.scss deleted file mode 100644 index bc944d874..000000000 --- a/web/src/pages/acl/AclIndexPage/components/AclIndexAliases/modals/AlcAliasCEModal/style.scss +++ /dev/null @@ -1,33 +0,0 @@ -#acl-alias-ce-modal { - form { - .input { - padding-bottom: var(--spacing-m); - } - - .select-container { - margin-bottom: var(--spacing-m); - } - - .header { - display: flex; - flex-flow: row; - align-items: center; - justify-content: flex-start; - padding-bottom: var(--spacing-m); - - h2 { - @include typography(app-body-1); - } - } - } - - .spacer { - padding-bottom: var(--spacing-m); - } -} - -.acl-alias-ce-modal-spacing { - @include media-breakpoint-up(lg) { - padding-top: var(--spacing-m) !important; - } -} diff --git a/web/src/pages/acl/AclIndexPage/components/AclIndexAliases/style.scss b/web/src/pages/acl/AclIndexPage/components/AclIndexAliases/style.scss deleted file mode 100644 index a4631311f..000000000 --- a/web/src/pages/acl/AclIndexPage/components/AclIndexAliases/style.scss +++ /dev/null @@ -1,65 +0,0 @@ -@mixin list-row { - display: inline-grid; - grid-template-rows: 1fr; - column-gap: var(--spacing-s); - justify-content: space-between; - align-items: center; - box-sizing: border-box; - - grid-template-columns: 40px 200px repeat(2, 250px) 160px 250px 100px 100px 60px; - - @include media-breakpoint-up(lg) { - grid-template-columns: 40px 0.5fr repeat(2, 0.75fr) 160px 0.75fr 100px 100px 60px; - } - - .cell { - display: inline-flex; - justify-content: flex-start; - align-items: center; - width: 100%; - overflow: hidden; - - &:last-child { - justify-content: center; - } - } - - .select-cell { - align-items: center; - justify-content: center; - width: 100%; - - & > .interaction-box { - width: 18px; - height: 18px; - } - } -} - -.aliases-list { - .list-headers { - @include list-row; - padding-bottom: var(--spacing-xs); - } - - ul { - list-style: none; - display: flex; - flex-flow: column; - row-gap: var(--spacing-s); - - & > li { - @include list-row; - box-sizing: border-box; - padding: 9px 0px; - - @include typography(app-modal-2); - - .cell { - &.name { - @include typography(app-modal-1); - } - } - } - } -} diff --git a/web/src/pages/acl/AclIndexPage/components/AclIndexAliases/types.ts b/web/src/pages/acl/AclIndexPage/components/AclIndexAliases/types.ts deleted file mode 100644 index 3c13d9ea7..000000000 --- a/web/src/pages/acl/AclIndexPage/components/AclIndexAliases/types.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { AclAlias } from '../../../types'; -import type { ListCellTag } from '../shared/types'; - -export type AclAliasListData = { - display: { - ports: ListCellTag[]; - destination: ListCellTag[]; - protocols: ListCellTag[]; - rules: ListCellTag[]; - }; -} & AclAlias; - -export type AclAliasListSelection = Record; diff --git a/web/src/pages/acl/AclIndexPage/components/AclIndexRules/AclIndexRules.tsx b/web/src/pages/acl/AclIndexPage/components/AclIndexRules/AclIndexRules.tsx deleted file mode 100644 index d277cf280..000000000 --- a/web/src/pages/acl/AclIndexPage/components/AclIndexRules/AclIndexRules.tsx +++ /dev/null @@ -1,891 +0,0 @@ -import './style.scss'; - -import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; -import type { AxiosError } from 'axios'; -import clsx from 'clsx'; -import { concat, intersection, orderBy } from 'lodash-es'; -import { type ReactNode, useCallback, useEffect, useMemo, useState } from 'react'; -import { useNavigate } from 'react-router'; -import { upperCaseFirst } from 'text-case'; - -import { useI18nContext } from '../../../../../i18n/i18n-react'; -import { ListCellTags } from '../../../../../shared/components/Layout/ListCellTags/ListCellTags'; -import { ListHeader } from '../../../../../shared/components/Layout/ListHeader/ListHeader'; -import type { ListHeaderColumnConfig } from '../../../../../shared/components/Layout/ListHeader/types'; -import { FilterGroupsModal } from '../../../../../shared/components/modals/FilterGroupsModal/FilterGroupsModal'; -import type { FilterGroupsModalFilter } from '../../../../../shared/components/modals/FilterGroupsModal/types'; -import { Button } from '../../../../../shared/defguard-ui/components/Layout/Button/Button'; -import { - ButtonSize, - ButtonStyleVariant, -} from '../../../../../shared/defguard-ui/components/Layout/Button/types'; -import { CheckBox } from '../../../../../shared/defguard-ui/components/Layout/Checkbox/CheckBox'; -import { EditButton } from '../../../../../shared/defguard-ui/components/Layout/EditButton/EditButton'; -import { EditButtonOption } from '../../../../../shared/defguard-ui/components/Layout/EditButton/EditButtonOption'; -import { EditButtonOptionStyleVariant } from '../../../../../shared/defguard-ui/components/Layout/EditButton/types'; -import { InteractionBox } from '../../../../../shared/defguard-ui/components/Layout/InteractionBox/InteractionBox'; -import { ListCellText } from '../../../../../shared/defguard-ui/components/Layout/ListCellText/ListCellText'; -import { ListItemCount } from '../../../../../shared/defguard-ui/components/Layout/ListItemCount/ListItemCount'; -import { NoData } from '../../../../../shared/defguard-ui/components/Layout/NoData/NoData'; -import { Search } from '../../../../../shared/defguard-ui/components/Layout/Search/Search'; -import { ListSortDirection } from '../../../../../shared/defguard-ui/components/Layout/VirtualizedList/types'; -import { isPresent } from '../../../../../shared/defguard-ui/utils/isPresent'; -import useApi from '../../../../../shared/hooks/useApi'; -import { useToaster } from '../../../../../shared/hooks/useToaster'; -import { QueryKeys } from '../../../../../shared/queries'; -import type { AclRuleInfo } from '../../../../../shared/types'; -import { useAclLoadedContext } from '../../../acl-context'; -import { - type AclAlias, - AclAliasStatus, - type AclCreateContextLoaded, - AclStatus, -} from '../../../types'; -import { aclRuleToStatusInt, aclStatusToInt } from '../../../utils'; -import { AclListSkeleton } from '../AclListSkeleton/AclListSkeleton'; -import { DeployChangesIcon } from '../DeployChangesIcon'; -import { DividerHeader } from '../shared/DividerHeader'; -import type { ListCellTag } from '../shared/types'; -import { AclRuleStatus } from './components/AclRuleStatus/AclRuleStatus'; -import { AclRulesApplyConfirmModal } from './components/AclRulesApplyConfirmModal/AclRulesApplyConfirmModal'; - -type RulesFilters = { - networks: number[]; - aliases: number[]; - status: number[]; - groups: number[]; -}; - -type ListData = { - context: { - denied: ListCellTag[]; - allowed: ListCellTag[]; - networks: ListCellTag[]; - destination: ListCellTag[]; - }; -} & AclRuleInfo; - -const defaultFilters: RulesFilters = { - aliases: [], - networks: [], - status: [], - groups: [], -}; - -export const AclIndexRules = () => { - const navigate = useNavigate(); - const { - acl: { - rules: { applyRules }, - }, - } = useApi(); - const { LL } = useI18nContext(); - const localLL = LL.acl.listPage.rules; - const messagesLL = LL.acl.listPage.message; - const ruleStatusLL = LL.acl.ruleStatus; - const aclContext = useAclLoadedContext(); - const [filtersOpen, setFiltersOpen] = useState(false); - const [appliedFilters, setAppliedFilters] = useState(defaultFilters); - const appliedFiltersCount = useMemo( - () => Object.values(appliedFilters).reduce((acc, filters) => acc + filters.length, 0), - [appliedFilters], - ); - const [searchValue, setSearchValue] = useState(''); - const [applyConfirmOpen, setApplyConfirmOpen] = useState(false); - const toaster = useToaster(); - const queryClient = useQueryClient(); - - const { mutate: applyPendingChangesMutation, isPending: applyPending } = useMutation({ - mutationFn: applyRules, - onSuccess: () => { - toaster.success(messagesLL.applyChanges()); - void queryClient.invalidateQueries({ - predicate: (query) => query.queryKey.includes(QueryKeys.FETCH_ACL_RULES), - }); - }, - onError: (e) => { - toaster.error(messagesLL.applyFail()); - console.error(e); - }, - }); - - const { - acl: { - rules: { getRules }, - }, - } = useApi(); - - const { data: aclRules, isLoading: rulesLoading } = useQuery({ - queryFn: getRules, - queryKey: [QueryKeys.FETCH_ACL_RULES], - refetchOnMount: true, - }); - - const pendingRulesCount = useMemo(() => { - if (aclRules) { - return aclRules.filter( - (rule) => rule.state !== AclStatus.APPLIED && rule.state !== AclStatus.EXPIRED, - ).length; - } - return 0; - }, [aclRules]); - - const rulesAfterSearch = useMemo(() => { - if (aclRules && searchValue) { - return aclRules.filter((rule) => - rule.name.trim().toLowerCase().includes(searchValue.toLowerCase().trim()), - ); - } - return aclRules ?? []; - }, [aclRules, searchValue]); - - const pendingRules = useMemo( - () => - isPresent(aclRules) - ? prepareDisplay( - rulesAfterSearch, - appliedFilters, - aclContext.aliases, - localLL.list.tags.all(), - localLL.list.tags.all(), - true, - aclContext, - ) - : [], - [aclContext, aclRules, appliedFilters, localLL.list.tags, rulesAfterSearch], - ); - - const [selectedPending, setSelectedPending] = useState>({}); - const handlePendingSelect = useCallback((key: number, value: boolean) => { - setSelectedPending((s) => ({ ...s, [key]: value })); - }, []); - const handlePendingSelectAll = useCallback( - (value: boolean, state: Record) => { - const newState = { ...state }; - for (const key in newState) { - newState[key] = value; - } - setSelectedPending(newState); - }, - [], - ); - const pendingSelectionCount = useMemo(() => { - let count = 0; - for (const key in selectedPending) { - if (selectedPending[key]) count++; - } - return count; - }, [selectedPending]); - - const deployedRules = useMemo(() => { - if (aclRules) { - return prepareDisplay( - rulesAfterSearch, - appliedFilters, - aclContext.aliases, - localLL.list.tags.allAllowed(), - localLL.list.tags.allDenied(), - false, - aclContext, - ); - } - return []; - }, [aclContext, aclRules, appliedFilters, localLL.list.tags, rulesAfterSearch]); - - const displayItemsCount = useMemo( - () => deployedRules.length + pendingRules.length, - [deployedRules.length, pendingRules.length], - ); - - const filters = useMemo(() => { - const res: Record = {}; - const filterLL = localLL.modals.filterGroupsModal.groupHeaders; - res.groups = { - identifier: 'id', - label: filterLL.groups(), - order: 3, - items: aclContext.groups.map((group) => ({ - label: group.name, - searchValues: [group.name], - value: group.id, - })), - }; - res.networks = { - label: filterLL.location(), - order: 1, - identifier: 'id', - items: aclContext.networks.map((network) => ({ - label: network.name, - searchValues: [network.name], - value: network.id, - })), - }; - res.aliases = { - identifier: 'id', - label: filterLL.alias(), - order: 2, - items: aclContext.aliases - .filter((alias) => alias.state === AclAliasStatus.APPLIED) - .map((alias) => ({ - label: alias.name, - searchValues: [alias.name], - value: alias.id, - })), - }; - - res.status = { - identifier: 'value', - label: filterLL.status(), - order: 4, - items: [ - { - label: ruleStatusLL.enabled(), - value: 1000, - searchValues: [ruleStatusLL.enabled()], - }, - { - label: ruleStatusLL.disabled(), - value: 999, - searchValues: [ruleStatusLL.disabled()], - }, - { - label: ruleStatusLL.new(), - value: aclStatusToInt(AclStatus.NEW), - searchValues: [ruleStatusLL.new()], - }, - { - label: ruleStatusLL.modified(), - value: aclStatusToInt(AclStatus.MODIFIED), - searchValues: [ruleStatusLL.modified()], - }, - { - label: ruleStatusLL.deleted(), - value: aclStatusToInt(AclStatus.DELETED), - searchValues: [ruleStatusLL.deleted()], - }, - { - label: ruleStatusLL.expired(), - value: aclStatusToInt(AclStatus.EXPIRED), - searchValues: [ruleStatusLL.expired()], - }, - ], - }; - return res; - }, [ - aclContext.aliases, - aclContext.groups, - aclContext.networks, - localLL.modals.filterGroupsModal.groupHeaders, - ruleStatusLL, - ]); - - const controlFilterDisplay = useMemo(() => { - return appliedFiltersCount - ? localLL.listControls.filter.applied({ count: appliedFiltersCount }) - : localLL.listControls.filter.nothingApplied(); - }, [appliedFiltersCount, localLL.listControls.filter]); - - const filtersPresent = appliedFiltersCount > 0; - - const applyText = useMemo(() => { - if (pendingSelectionCount) { - return localLL.listControls.apply.selective({ count: pendingSelectionCount }); - } - if (pendingRulesCount) { - return localLL.listControls.apply.all({ count: pendingRulesCount }); - } - return localLL.listControls.apply.noChanges(); - }, [localLL.listControls.apply, pendingRulesCount, pendingSelectionCount]); - - const handleApply = useCallback(() => { - if (aclRules) { - if (pendingSelectionCount === 0) { - const rulesToApply = aclRules - .filter( - (rule) => - rule.state !== AclStatus.APPLIED && rule.state !== AclStatus.EXPIRED, - ) - .map((rule) => rule.id); - applyPendingChangesMutation(rulesToApply); - } else { - const rulesToApply: number[] = []; - for (const key in selectedPending) { - if (selectedPending[key]) { - rulesToApply.push(Number(key)); - } - } - applyPendingChangesMutation(rulesToApply); - } - } - }, [aclRules, applyPendingChangesMutation, pendingSelectionCount, selectedPending]); - - // update or build selection state for list when rules are done loading - useEffect(() => { - if (aclRules) { - const pending = aclRules.filter((rule) => rule.state !== AclStatus.APPLIED); - const selectionEntries = Object.keys(selectedPending).length; - if (selectionEntries !== pending.length) { - const newSelectionState: Record = {}; - for (const rule of pending) { - newSelectionState[rule.id] = newSelectionState[rule.id] ?? false; - } - setSelectedPending(newSelectionState); - } - } - }, [aclRules, selectedPending]); - - return ( -
-
-

Rules

- - { - setSearchValue(searchChange); - }} - /> -
-
-
- {rulesLoading && } - {!rulesLoading && ( - <> - - - - )} - { - setFiltersOpen(false); - }} - onSubmit={(vals) => { - setAppliedFilters(vals as RulesFilters); - setFiltersOpen(false); - }} - /> - -
- ); -}; - -type RulesListProps = { - data: ListData[]; - header: { - text: string; - extras?: ReactNode; - }; - noDataMessage: string; - isAppliedList?: boolean; - selected?: Record; - allSelected?: boolean; - onSelect?: (key: number, value: boolean) => void; - onSelectAll?: (value: boolean, state: Record) => void; -}; - -const RulesList = ({ - data, - header, - noDataMessage, - selected, - allSelected, - onSelect, - onSelectAll, -}: RulesListProps) => { - const { LL } = useI18nContext(); - const headersLL = LL.acl.listPage.rules.list.headers; - const [sortKey, setSortKey] = useState('name'); - const [sortDir, setSortDir] = useState(ListSortDirection.ASC); - - const selectionEnabled = useMemo( - () => - isPresent(onSelect) && - isPresent(onSelectAll) && - isPresent(selected) && - isPresent(allSelected), - [onSelect, onSelectAll, selected, allSelected], - ); - - const sortedRules = useMemo( - () => orderBy(data, [sortKey], [sortDir.valueOf().toLowerCase() as 'asc' | 'desc']), - [data, sortDir, sortKey], - ); - - const listHeaders = useMemo( - (): ListHeaderColumnConfig[] => [ - { - label: headersLL.name(), - sortKey: 'name', - enabled: true, - }, - { - label: headersLL.destination(), - sortKey: 'destination', - enabled: false, - }, - { - label: headersLL.allowed(), - key: 'allowed', - enabled: false, - }, - { - label: headersLL.denied(), - key: 'denied', - enabled: false, - }, - { - label: headersLL.locations(), - key: 'networks', - enabled: false, - }, - { - label: headersLL.status(), - key: 'status', - enabled: false, - }, - { - label: headersLL.edit(), - key: 'edit', - enabled: false, - }, - ], - [headersLL], - ); - - return ( -
- {header.extras} - {sortedRules.length === 0 && ( - - )} - {sortedRules.length > 0 && ( -
-
- - headers={listHeaders} - sortDirection={sortDir} - activeKey={sortKey} - selectAll={allSelected} - onSelectAll={(val) => { - if (selectionEnabled) { - onSelectAll?.(val, selected ?? {}); - } - }} - onChange={(key, dir) => { - setSortKey(key); - setSortDir(dir); - }} - /> -
-
    - {sortedRules.map((rule) => { - let ruleSelected = false; - if (selected) { - ruleSelected = selected[rule.id] ?? false; - } - return ( -
  • - {!selectionEnabled &&
    } - {selectionEnabled && ( -
    - { - onSelect?.(rule.id, !ruleSelected); - }} - > - - -
    - )} -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
    - -
    -
  • - ); - })} -
-
- )} -
- ); -}; - -type EditProps = { - rule: ListData; -}; - -const RuleEditButton = ({ rule }: EditProps) => { - const queryClient = useQueryClient(); - const isApplied = rule.state === AclStatus.APPLIED; - const isDeleted = rule.state === AclStatus.DELETED; - const { LL } = useI18nContext(); - const localLL = LL.acl.listPage.rules.list.editMenu; - const statusLL = LL.acl.ruleStatus; - const toaster = useToaster(); - - const { - acl: { - rules: { deleteRule, editRule }, - }, - } = useApi(); - - const invalidateQueries = useCallback(() => { - void queryClient.invalidateQueries({ - predicate: (query) => query.queryKey.includes(QueryKeys.FETCH_ACL_RULES), - }); - }, [queryClient]); - - const handleError = useCallback( - (err: AxiosError) => { - toaster.error(LL.acl.listPage.message.changeFail()); - console.error(err.message ?? err); - }, - [LL.acl.listPage.message, toaster], - ); - - const { mutate: editRuleMutation, isPending: editPending } = useMutation({ - mutationFn: editRule, - mutationKey: ['rule', 'delete', rule.id], - onSuccess: () => { - invalidateQueries(); - toaster.success(LL.acl.listPage.message.changeAdded()); - }, - onError: handleError, - }); - - const { mutate: deleteRuleMutation, isPending: deletionPending } = useMutation({ - mutationFn: deleteRule, - onSuccess: () => { - invalidateQueries(); - if (isApplied) { - toaster.success(LL.acl.listPage.message.changeAdded()); - } else { - toaster.success(LL.acl.listPage.message.changeDiscarded()); - } - }, - onError: handleError, - }); - - const handleEnableChange = useCallback( - (newState: boolean, rule: AclRuleInfo | ListData) => { - editRuleMutation({ ...rule, enabled: newState, expires: rule.expires ?? null }); - }, - [editRuleMutation], - ); - - const navigate = useNavigate(); - - return ( - - {!isDeleted && ( - { - navigate(`/admin/acl/form?edit=1&rule=${rule.id}`); - }} - /> - )} - {isApplied && ( - <> - {!rule.enabled && ( - { - handleEnableChange(true, rule); - }} - /> - )} - {rule.enabled && ( - { - handleEnableChange(false, rule); - }} - /> - )} - - )} - { - deleteRuleMutation(rule.id); - }} - /> - - ); -}; - -const prepareDisplay = ( - aclRules: AclRuleInfo[], - appliedFilters: RulesFilters, - aliases: AclAlias[], - allAllowedLabel: string, - allDeniedLabel: string, - pending: boolean, - aclContext: Omit, -): ListData[] => { - let rules: AclRuleInfo[]; - - if (pending) { - rules = aclRules.filter( - (rule) => rule.state !== AclStatus.APPLIED && rule.state !== AclStatus.EXPIRED, - ); - } else { - rules = aclRules.filter( - (rule) => rule.state === AclStatus.APPLIED || rule.state === AclStatus.EXPIRED, - ); - } - - rules = rules.filter((rule) => { - const filterChecks: boolean[] = []; - if (appliedFilters.status.length) { - filterChecks.push(appliedFilters.status.includes(aclRuleToStatusInt(rule))); - } - if (appliedFilters.networks.length && !rule.all_networks) { - filterChecks.push(intersection(rule.networks, appliedFilters.networks).length > 0); - } - if (appliedFilters.aliases.length) { - filterChecks.push(intersection(rule.aliases, appliedFilters.aliases).length > 0); - } - if (appliedFilters.groups.length) { - const groups = concat(rule.denied_groups, rule.allowed_groups); - filterChecks.push(intersection(groups, appliedFilters.groups).length > 0); - } - return !filterChecks.includes(false); - }); - - const listData: ListData[] = rules.map((rule) => { - let allowed: ListCellTag[]; - let denied: ListCellTag[]; - let networks: ListCellTag[]; - - if (rule.allow_all_users) { - allowed = [{ key: 'all', label: allAllowedLabel, displayAsTag: false }]; - } else { - allowed = concat( - aclContext.users - .filter((u) => rule.allowed_users.includes(u.id)) - .map((u) => ({ - key: `user-${u.id}`, - label: u.username, - displayAsTag: true, - })), - aclContext.groups - .filter((g) => rule.allowed_groups.includes(g.id)) - .map((group) => ({ - key: `group-${group.id}`, - label: group.name, - displayAsTag: true, - })), - aclContext.devices - .filter((device) => rule.allowed_devices.includes(device.id)) - .map((device) => ({ - key: `device-${device.id}`, - label: device.name, - displayAsTag: true, - })), - ); - } - - if (rule.deny_all_users) { - denied = [{ key: 'all', label: allDeniedLabel, displayAsTag: false }]; - } else { - denied = concat( - aclContext.users - .filter((u) => rule.denied_users.includes(u.id)) - .map((user) => ({ - key: `user-${user.id}`, - label: user.username, - displayAsTag: true, - })), - aclContext.groups - .filter((g) => rule.denied_groups.includes(g.id)) - .map((group) => ({ - key: `group-${group.id}`, - label: group.name, - displayAsTag: true, - })), - aclContext.devices - .filter((device) => rule.denied_devices.includes(device.id)) - .map((device) => ({ - key: `device-${device.id}`, - label: device.name, - displayAsTag: true, - })), - ); - } - - if (rule.all_networks) { - networks = [ - { - key: 'all', - label: allAllowedLabel, - }, - ]; - } else { - networks = aclContext.networks - .filter((network) => rule.networks.includes(network.id)) - .map((network) => ({ - key: network.id, - label: network.name, - displayAsTag: true, - })); - } - - const destination: ListCellTag[] = concat( - aliases - .filter((alias) => rule.aliases.includes(alias.id)) - .map((alias) => ({ - key: `alias-${alias.id}`, - label: alias.name, - displayAsTag: true, - })), - rule.destination - .split(',') - .filter((s) => s !== '') - .map((dest, index) => ({ - key: `rule-destination-${index}`, - label: dest, - displayAsTag: false, - })), - ); - - return { - ...rule, - context: { - allowed, - denied, - destination, - networks, - }, - }; - }); - - return listData; -}; diff --git a/web/src/pages/acl/AclIndexPage/components/AclIndexRules/components/AclRuleStatus/AclRuleStatus.tsx b/web/src/pages/acl/AclIndexPage/components/AclIndexRules/components/AclRuleStatus/AclRuleStatus.tsx deleted file mode 100644 index 30b4d8ace..000000000 --- a/web/src/pages/acl/AclIndexPage/components/AclIndexRules/components/AclRuleStatus/AclRuleStatus.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import './style.scss'; - -import clsx from 'clsx'; -import { useMemo } from 'react'; - -import { useI18nContext } from '../../../../../../../i18n/i18n-react'; -import { ActivityIcon } from '../../../../../../../shared/defguard-ui/components/icons/ActivityIcon/ActivityIcon'; -import { ActivityIconVariant } from '../../../../../../../shared/defguard-ui/components/icons/ActivityIcon/types'; -import { AclStatus } from '../../../../../types'; - -type Props = { - status: AclStatus; - enabled: boolean; -}; - -export const AclRuleStatus = ({ enabled, status }: Props) => { - const { LL } = useI18nContext(); - const statusLL = LL.acl.ruleStatus; - - const [label, iconStatus] = useMemo(() => { - if (status === AclStatus.APPLIED) { - switch (enabled) { - case true: - return [statusLL.enabled(), ActivityIconVariant.CONNECTED]; - case false: - return [statusLL.disabled(), ActivityIconVariant.DISCONNECTED]; - } - } - switch (status) { - case AclStatus.DELETED: - return [statusLL.deleted(), ActivityIconVariant.ERROR]; - case AclStatus.NEW: - return [statusLL.new(), ActivityIconVariant.CONNECTED]; - case AclStatus.MODIFIED: - return [statusLL.modified(), ActivityIconVariant.DISCONNECTED]; - case AclStatus.EXPIRED: - return [statusLL.expired(), ActivityIconVariant.DISABLED]; - default: - return [statusLL.new(), ActivityIconVariant.CONNECTED]; - } - }, [enabled, status, statusLL]); - - return ( -
-

{label}

- -
- ); -}; diff --git a/web/src/pages/acl/AclIndexPage/components/AclIndexRules/components/AclRuleStatus/style.scss b/web/src/pages/acl/AclIndexPage/components/AclIndexRules/components/AclRuleStatus/style.scss deleted file mode 100644 index 64a3f3242..000000000 --- a/web/src/pages/acl/AclIndexPage/components/AclIndexRules/components/AclRuleStatus/style.scss +++ /dev/null @@ -1,46 +0,0 @@ -.acl-rule-status { - width: 100%; - max-width: 100%; - display: inline-flex; - flex-flow: row nowrap; - column-gap: 5px; - user-select: none; - overflow: hidden; - align-items: center; - justify-content: flex-start; - - & > p { - @include typography(app-modal-3); - color: inherit; - max-width: calc(100% - 13px); - overflow: hidden; - - @include text-overflow-dots; - } - - &.status-new { - color: var(--text-positive); - } - - &.status-modified { - color: var(--text-important); - } - - &.status-deleted { - color: var(--text-alert); - } - - &.status-applied { - &.disabled { - color: var(--text-important); - } - - &.enabled { - color: var(--text-positive); - } - } - - &.status-expired { - color: var(--text-body-tertiary); - } -} diff --git a/web/src/pages/acl/AclIndexPage/components/AclIndexRules/components/AclRulesApplyConfirmModal/AclRulesApplyConfirmModal.tsx b/web/src/pages/acl/AclIndexPage/components/AclIndexRules/components/AclRulesApplyConfirmModal/AclRulesApplyConfirmModal.tsx deleted file mode 100644 index d7bd89e3a..000000000 --- a/web/src/pages/acl/AclIndexPage/components/AclIndexRules/components/AclRulesApplyConfirmModal/AclRulesApplyConfirmModal.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { useI18nContext } from '../../../../../../../i18n/i18n-react'; -import { ConfirmModal } from '../../../../../../../shared/defguard-ui/components/Layout/modals/ConfirmModal/ConfirmModal'; - -type Props = { - isOpen: boolean; - setOpen: (val: boolean) => void; - onSubmit: () => void; - changesCount: number; -}; - -export const AclRulesApplyConfirmModal = ({ - isOpen, - setOpen, - onSubmit, - changesCount, -}: Props) => { - const { LL } = useI18nContext(); - const localLL = LL.acl.listPage.rules.modals.applyConfirm; - const close = () => setOpen(false); - - return ( - { - close(); - }} - onSubmit={() => { - onSubmit(); - close(); - }} - /> - ); -}; diff --git a/web/src/pages/acl/AclIndexPage/components/AclIndexRules/style.scss b/web/src/pages/acl/AclIndexPage/components/AclIndexRules/style.scss deleted file mode 100644 index 5b5638f82..000000000 --- a/web/src/pages/acl/AclIndexPage/components/AclIndexRules/style.scss +++ /dev/null @@ -1,111 +0,0 @@ -@mixin grid-config { - box-sizing: border-box; - display: inline-grid; - grid-template-rows: 1fr; - justify-content: space-between; - padding: 0 var(--spacing-s) 0 0; - column-gap: var(--spacing-xs); - - grid-template-columns: 40px 250px 350px repeat(3, 250px) 100px 40px; - - @include media-breakpoint-up(lg) { - grid-template-columns: 40px 1fr 1.25fr repeat(3, 1fr) 100px 45px; - } - - & > .cell { - display: inline-flex; - justify-content: flex-start; - align-items: center; - width: 100%; - overflow: hidden; - - &:last-child { - justify-content: center; - } - } - - .select-cell { - align-items: center; - justify-content: center; - width: 100%; - - & > .interaction-box { - width: 18px; - height: 18px; - } - } -} - -#acl-rules { - width: 100%; - - .rules-list { - width: 100%; - - &:not(:last-child) { - padding-bottom: var(--spacing-s); - } - - .list-headers { - @include grid-config; - } - - .header-track { - display: flex; - flex-flow: row; - align-items: center; - justify-content: flex-start; - column-gap: var(--spacing-xs); - padding-bottom: var(--spacing-xs); - - .select-cell { - width: 40px; - height: 22px; - display: inline-flex; - flex-flow: row; - align-items: center; - justify-content: center; - - .interaction-box { - width: 18px; - height: 18px; - } - } - } - - .list-container { - overflow: auto; - position: relative; - max-height: 600px; - scrollbar-gutter: stable; - - & > .header-track { - position: sticky; - top: 0; - background-color: var(--surface-default-modal); - z-index: 2; - } - } - - ul { - list-style: none; - display: flex; - flex-flow: column; - row-gap: var(--spacing-m); - - li { - @include grid-config(); - - .btn { - width: 100%; - } - - .cell { - &.name { - @include typography(app-modal-1); - } - } - } - } - } -} diff --git a/web/src/pages/acl/AclIndexPage/components/AclListSkeleton/AclListSkeleton.tsx b/web/src/pages/acl/AclIndexPage/components/AclListSkeleton/AclListSkeleton.tsx deleted file mode 100644 index 474c94a22..000000000 --- a/web/src/pages/acl/AclIndexPage/components/AclListSkeleton/AclListSkeleton.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import './style.scss'; - -import { range } from 'lodash-es'; -import Skeleton from 'react-loading-skeleton'; - -export const AclListSkeleton = () => { - return ( -
- {range(10).map((val) => ( - - ))} -
- ); -}; diff --git a/web/src/pages/acl/AclIndexPage/components/AclListSkeleton/style.scss b/web/src/pages/acl/AclIndexPage/components/AclListSkeleton/style.scss deleted file mode 100644 index b23c71c50..000000000 --- a/web/src/pages/acl/AclIndexPage/components/AclListSkeleton/style.scss +++ /dev/null @@ -1,11 +0,0 @@ -.acl-list-skeleton { - display: flex; - flex-flow: column; - row-gap: var(--spacing-s); - width: 100%; - - .react-loading-skeleton { - border-radius: 10px; - height: 60px; - } -} diff --git a/web/src/pages/acl/AclIndexPage/components/DeployChangesIcon.tsx b/web/src/pages/acl/AclIndexPage/components/DeployChangesIcon.tsx deleted file mode 100644 index 36a09072d..000000000 --- a/web/src/pages/acl/AclIndexPage/components/DeployChangesIcon.tsx +++ /dev/null @@ -1,20 +0,0 @@ -export const DeployChangesIcon = () => { - return ( - - - - - ); -}; diff --git a/web/src/pages/acl/AclIndexPage/components/shared/AclAliasKindIcon.tsx b/web/src/pages/acl/AclIndexPage/components/shared/AclAliasKindIcon.tsx deleted file mode 100644 index 9a5c3cba4..000000000 --- a/web/src/pages/acl/AclIndexPage/components/shared/AclAliasKindIcon.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import { AclAliasKind } from '../../../types'; - -type Props = { - kind: AclAliasKind; -}; -export const AclAliasKindIcon = ({ kind }: Props) => { - switch (kind) { - case AclAliasKind.COMPONENT: - return ( - - - - - - - ); - case AclAliasKind.DESTINATION: - return ( - - - - - - - - ); - } -}; diff --git a/web/src/pages/acl/AclIndexPage/components/shared/AclMessageBoxes/AclMessageBoxes.tsx b/web/src/pages/acl/AclIndexPage/components/shared/AclMessageBoxes/AclMessageBoxes.tsx deleted file mode 100644 index fa424e96e..000000000 --- a/web/src/pages/acl/AclIndexPage/components/shared/AclMessageBoxes/AclMessageBoxes.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import './style.scss'; - -import { useI18nContext } from '../../../../../../i18n/i18n-react'; -import { RenderMarkdown } from '../../../../../../shared/components/Layout/RenderMarkdown/RenderMarkdown'; -import { MessageBox } from '../../../../../../shared/defguard-ui/components/Layout/MessageBox/MessageBox'; -import { MessageBoxType } from '../../../../../../shared/defguard-ui/components/Layout/MessageBox/types'; -import { AclAliasKind, NetworkAccessType } from '../../../../types'; -import { AclAliasKindIcon } from '../AclAliasKindIcon'; -import { NetworkAccessTypeIcon } from '../NetworkAccessTypeIcon'; - -type Props = { - message: 'acl-alias-kind' | 'acl-network-access'; - dismissable?: boolean; -}; - -export const AclMessageBoxes = ({ message, dismissable = true }: Props) => { - const { LL } = useI18nContext(); - const aliasKindLL = LL.acl.messageBoxes.aclAliasKind; - const networkAccessLL = LL.acl.messageBoxes.networkSelectionIndicatorsHelper; - - switch (message) { - case 'acl-alias-kind': - return ( - -
    -
  • - -

    {`${aliasKindLL.destination.name()} — ${aliasKindLL.destination.description()}`}

    -
  • -
  • - -

    {`${aliasKindLL.component.name()} — ${aliasKindLL.component.description()}`}

    -
  • -
-
- ); - case 'acl-network-access': - return ( - -
    -
  • - -

    - -
  • -
  • - -

    - -
  • -
  • - -

    - -
  • -
-
- ); - } -}; diff --git a/web/src/pages/acl/AclIndexPage/components/shared/AclMessageBoxes/style.scss b/web/src/pages/acl/AclIndexPage/components/shared/AclMessageBoxes/style.scss deleted file mode 100644 index 7ef4f2cdb..000000000 --- a/web/src/pages/acl/AclIndexPage/components/shared/AclMessageBoxes/style.scss +++ /dev/null @@ -1,18 +0,0 @@ -.acl-explain-message-box { - ul { - width: 100%; - display: flex; - flex-flow: column; - row-gap: var(--spacing-xs); - - li { - display: inline-flex; - flex-flow: row nowrap; - align-items: center; - - svg { - display: inline-block; - } - } - } -} diff --git a/web/src/pages/acl/AclIndexPage/components/shared/DividerHeader.tsx b/web/src/pages/acl/AclIndexPage/components/shared/DividerHeader.tsx deleted file mode 100644 index 86243ea08..000000000 --- a/web/src/pages/acl/AclIndexPage/components/shared/DividerHeader.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import type { PropsWithChildren } from 'react'; - -type DividerHeaderProps = { - text: string; -} & PropsWithChildren; - -export const DividerHeader = ({ text, children }: DividerHeaderProps) => { - return ( -
-
-

{text}

- {children} -
-
- ); -}; diff --git a/web/src/pages/acl/AclIndexPage/components/shared/NetworkAccessTypeIcon.tsx b/web/src/pages/acl/AclIndexPage/components/shared/NetworkAccessTypeIcon.tsx deleted file mode 100644 index f0e545e19..000000000 --- a/web/src/pages/acl/AclIndexPage/components/shared/NetworkAccessTypeIcon.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import { NetworkAccessType } from '../../../types'; - -type Props = { - type: NetworkAccessType; -}; - -export const NetworkAccessTypeIcon = ({ type }: Props) => { - switch (type) { - case NetworkAccessType.ALLOWED: - return ( - - - - ); - case NetworkAccessType.DENIED: - return ( - - - - - ); - case NetworkAccessType.UNMANAGED: - return ( - - - - ); - } -}; diff --git a/web/src/pages/acl/AclIndexPage/components/shared/types.ts b/web/src/pages/acl/AclIndexPage/components/shared/types.ts deleted file mode 100644 index b30a13c1a..000000000 --- a/web/src/pages/acl/AclIndexPage/components/shared/types.ts +++ /dev/null @@ -1,5 +0,0 @@ -export type ListCellTag = { - key: string | number; - label: string; - displayAsTag?: boolean; -}; diff --git a/web/src/pages/acl/AclIndexPage/style.scss b/web/src/pages/acl/AclIndexPage/style.scss deleted file mode 100644 index 3a70698f4..000000000 --- a/web/src/pages/acl/AclIndexPage/style.scss +++ /dev/null @@ -1,105 +0,0 @@ -#acl-index-page { - #content-card { - background-color: var(--surface-default-modal); - border-radius: 15px; - box-sizing: border-box; - padding: var(--spacing-s); - border-top-left-radius: 0; - - & > div { - & > header { - width: 100%; - display: flex; - flex-flow: row; - align-items: center; - justify-content: flex-start; - column-gap: var(--spacing-xs); - padding-bottom: var(--spacing-m); - - & > .controls { - margin-left: auto; - display: flex; - flex-flow: row; - align-items: center; - justify-content: flex-end; - column-gap: var(--spacing-s); - - .btn.filter { - svg path { - @include animate-standard; - transition-property: fill; - fill: var(--text-button-primary); - } - - &:hover { - svg path { - fill: var(--surface-main-primary); - } - } - } - } - } - } - - .divider-header { - padding-bottom: var(--spacing-s); - - .inner { - display: flex; - flex-flow: row; - align-items: center; - justify-content: flex-start; - border-bottom: 1px solid var(--border-primary); - } - - .header { - @include typography(app-side-bar); - } - } - } - - .page-content { - & > header { - padding-bottom: var(--spacing-l); - } - } - - .no-data { - @include typography(app-side-bar); - color: var(--text-body-tertiary); - margin: var(--spacing-s) 0; - } -} - -#acl-index-page { - .acl-list { - width: 100%; - - &:not(:last-child) { - padding-bottom: var(--spacing-s); - } - - .header-track { - display: flex; - flex-flow: row; - align-items: center; - justify-content: flex-start; - column-gap: var(--spacing-xs); - padding-bottom: var(--spacing-xs); - - .select-cell { - width: 40px; - height: 22px; - display: inline-flex; - flex-flow: row; - align-items: center; - justify-content: center; - - .interaction-box { - width: 18px; - height: 18px; - } - } - } - } -} diff --git a/web/src/pages/acl/AclRoutes.tsx b/web/src/pages/acl/AclRoutes.tsx deleted file mode 100644 index 9c83f8089..000000000 --- a/web/src/pages/acl/AclRoutes.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import type { PropsWithChildren } from 'react'; -import { Route, Routes } from 'react-router'; -import { AclCreateDataProvider } from './AclCreateDataProvider'; -import { AlcCreatePage } from './AclCreatePage/AclCreatePage'; -import { AclIndexPage } from './AclIndexPage/AclIndexPage'; -import { AclCreateTrackedProvider } from './acl-context'; - -const AclProvide = ({ children }: PropsWithChildren) => { - return ( - - {children} - - ); -}; - -export const AclRoutes = () => { - return ( - - - - - } - /> - - - - } - /> - - ); -}; diff --git a/web/src/pages/acl/acl-context.tsx b/web/src/pages/acl/acl-context.tsx deleted file mode 100644 index 6713bb076..000000000 --- a/web/src/pages/acl/acl-context.tsx +++ /dev/null @@ -1,64 +0,0 @@ -/* eslint-disable react-refresh/only-export-components */ -import { useCallback, useState } from 'react'; -import { createContainer } from 'react-tracked'; - -import type { AclCreateContext, AclCreateContextLoaded } from './types'; - -const init: AclCreateContext = { - devices: undefined, - groups: undefined, - networks: undefined, - users: undefined, - editRule: undefined, -}; - -const useValue = () => useState(init); - -const { - useUpdate, - Provider: AclCreateTrackedProvider, - useTrackedState, - useSelector: useAclCreateSelector, -} = createContainer(useValue); - -const useUpdateAclCreateContext = () => { - const updateInner = useUpdate(); - - const update = useCallback( - (values: Partial) => { - updateInner((s) => ({ ...s, ...values })); - }, - [updateInner], - ); - - return update; -}; - -const useAclLoadedContext = () => { - const { devices, groups, networks, users, editRule, aliases } = useTrackedState(); - - if ( - devices === undefined || - groups === undefined || - networks === undefined || - users === undefined || - aliases === undefined - ) { - throw Error('Use of ACL data before it was loaded'); - } - return { - devices, - groups, - networks, - users, - aliases, - ruleToEdit: editRule, - } as AclCreateContextLoaded; -}; - -export { - AclCreateTrackedProvider, - useAclCreateSelector, - useAclLoadedContext, - useUpdateAclCreateContext, -}; diff --git a/web/src/pages/acl/types.ts b/web/src/pages/acl/types.ts deleted file mode 100644 index aeb6b6a96..000000000 --- a/web/src/pages/acl/types.ts +++ /dev/null @@ -1,73 +0,0 @@ -import type { - AclRuleInfo, - GroupInfo, - Network, - StandaloneDevice, - User, -} from '../../shared/types'; - -export type AclCreateContext = { - groups?: GroupInfo[]; - users?: User[]; - devices?: StandaloneDevice[]; - networks?: Network[]; - editRule?: AclRuleInfo; - aliases?: AclAlias[]; -}; - -export type AclCreateContextLoaded = { - groups: GroupInfo[]; - users: User[]; - devices: StandaloneDevice[]; - networks: Network[]; - aliases: AclAlias[]; - ruleToEdit?: AclRuleInfo; -}; - -export type AclAlias = { - id: number; - name: string; - kind: AclAliasKind; - state: AclAliasStatus; - destination: string; - ports: string; - protocols: AclProtocol[]; - rules: number[]; -}; - -export type AclAliasPost = Omit; - -export enum AclProtocol { - TCP = 6, - UDP = 17, - ICMP = 1, -} - -export enum AclStatus { - NEW = 'New', - APPLIED = 'Applied', - MODIFIED = 'Modified', - DELETED = 'Deleted', - EXPIRED = 'Expired', -} - -export enum AclAliasStatus { - APPLIED = AclStatus.APPLIED, - MODIFIED = AclStatus.MODIFIED, -} - -export enum AclKind { - DESTINATION = 'Destination', - COMPONENT = 'Component', -} - -export enum AclAliasKind { - DESTINATION = AclKind.DESTINATION, - COMPONENT = AclKind.COMPONENT, -} - -export enum NetworkAccessType { - ALLOWED, - DENIED, - UNMANAGED, -} diff --git a/web/src/pages/acl/utils.ts b/web/src/pages/acl/utils.ts deleted file mode 100644 index abbca3b6f..000000000 --- a/web/src/pages/acl/utils.ts +++ /dev/null @@ -1,141 +0,0 @@ -import type { SelectOption } from '../../shared/defguard-ui/components/Layout/Select/types'; -import type { AclRuleInfo, Network } from '../../shared/types'; -import type { ListCellTag } from './AclIndexPage/components/shared/types'; -import { AclAliasStatus, AclProtocol, AclStatus, NetworkAccessType } from './types'; - -// used by acl rules index page, bcs we don't show Applied in UI but instead enabled / disabled when state is "applied" -export const aclRuleToStatusInt = (rule: AclRuleInfo): number => { - const status = rule.state; - if (status === AclStatus.APPLIED) { - if (rule.enabled) { - return 1000; - } else { - return 999; - } - } - return aclStatusToInt(rule.state); -}; - -export const aclStatusToInt = (status: AclStatus): number => { - switch (status) { - case AclStatus.NEW: - return 0; - case AclStatus.MODIFIED: - return 1; - case AclStatus.APPLIED: - return 2; - case AclStatus.DELETED: - return 3; - case AclStatus.EXPIRED: - return 4; - } -}; - -export const aclAliasStatusToInt = (status: AclAliasStatus): number => { - switch (status) { - case AclAliasStatus.APPLIED: - return 2; - case AclAliasStatus.MODIFIED: - return 1; - } -}; - -export const aclStatusFromInt = (statusInt: number): AclStatus => { - switch (statusInt) { - case 0: - return AclStatus.NEW; - case 1: - return AclStatus.MODIFIED; - case 2: - return AclStatus.APPLIED; - case 3: - return AclStatus.DELETED; - default: - throw Error(`Mapping ACL Rule from int failed ! Unrecognized int of ${statusInt}`); - } -}; - -export const aclAliasStatusFromInt = (statusInt: number): AclAliasStatus => { - switch (statusInt) { - case 1: - return AclAliasStatus.APPLIED; - case 2: - return AclAliasStatus.MODIFIED; - default: - throw Error(`Unexpected alias status code of ${statusInt}`); - } -}; - -export const protocolToString = (value: AclProtocol): string => { - switch (value) { - case AclProtocol.TCP: - return 'TCP'; - case AclProtocol.UDP: - return 'UDP'; - case AclProtocol.ICMP: - return 'ICMP'; - } -}; - -export const protocolOptions: SelectOption[] = [ - { - key: AclProtocol.TCP, - label: 'TCP', - value: AclProtocol.TCP, - }, - { - key: AclProtocol.UDP, - label: 'UDP', - value: AclProtocol.UDP, - }, - { - key: AclProtocol.ICMP, - label: 'ICMP', - value: AclProtocol.ICMP, - }, -]; - -export const aclDestinationToListTagDisplay = (destination: string): ListCellTag[] => - destination - .split(',') - .filter((s) => s !== '') - .map((dest, index) => ({ - key: `destination-${index}`, - label: dest, - displayAsTag: false, - })); - -export const aclPortsToListTagDisplay = (ports: string): ListCellTag[] => - ports - .split(',') - .filter((s) => s !== '') - .map((port, index) => ({ - key: `port-${index}`, - label: port, - displayAsTag: false, - })); - -export const aclProtocolsToListTagDisplay = (protocols: AclProtocol[]): ListCellTag[] => - protocols.map((protocol) => ({ - key: protocol.toString(), - label: protocolToString(protocol), - displayAsTag: false, - })); - -export const aclRuleToListTagDisplay = (rules: AclRuleInfo[]): ListCellTag[] => - rules.map((rule) => ({ - key: rule.id, - label: rule.name, - displayAsTag: true, - })); - -export const networkToNetworkAccessType = (network: Network): NetworkAccessType => { - if (!network.acl_enabled) { - return NetworkAccessType.UNMANAGED; - } - if (network.acl_default_allow) { - return NetworkAccessType.ALLOWED; - } else { - return NetworkAccessType.DENIED; - } -}; diff --git a/web/src/pages/acl/validators.ts b/web/src/pages/acl/validators.ts deleted file mode 100644 index 83151eb68..000000000 --- a/web/src/pages/acl/validators.ts +++ /dev/null @@ -1,150 +0,0 @@ -import * as ipaddr from 'ipaddr.js'; -import { z } from 'zod'; - -import type { TranslationFunctions } from '../../i18n/i18n-types'; -import { patternStrictIpV4 } from '../../shared/patterns'; - -export const aclPortsValidator = (LL: TranslationFunctions) => - z - .string() - .refine((value: string) => { - if (value === '') return true; - const regexp = new RegExp(/^(?:\d+(?:-\d+)*)(?:(?:\s*,\s*|\s+)\d+(?:-\d+)*)*$/); - return regexp.test(value); - }, LL.form.error.invalid()) - .refine((value: string) => { - if (value === '') return true; - // check if there is no duplicates in given port field - const trimmed = value - .replaceAll(' ', '') - .replaceAll('-', ' ') - .replaceAll(',', ' ') - .split(' ') - .filter((v) => v !== ''); - const found: number[] = []; - for (const entry of trimmed) { - const num = parseInt(entry, 10); - if (Number.isNaN(num)) { - return false; - } - if (found.includes(num)) { - return false; - } - found.push(num); - } - return true; - }, LL.form.error.invalid()) - .refine((value: string) => { - if (value === '') return true; - // check if ranges in input are valid means follow pattern - - const matches = value.match(/\b\d+-\d+\b/g); - if (Array.isArray(matches)) { - for (const match of matches) { - const split = match.split('-'); - if (split.length !== 2) { - return false; - } - const start = split[0]; - const end = split[1]; - if (start >= end) { - return false; - } - } - } - return true; - }, LL.form.error.invalid()); - -function dottedMaskToPrefix(mask: string): number | null { - if (!mask.includes('.')) return Number(mask); - const maskTest = - /^(?:255\.255\.255\.(?:0|128|192|224|240|248|252|254|255)|255\.255\.(?:0|128|192|224|240|248|252|254|255)\.0|255\.(?:0|128|192|224|240|248|252|254|255)\.0\.0|(?:0|128|192|224|240|248|252|254|255)\.0\.0\.0)$/; - if (!maskTest.test(mask)) return null; - if (mask.split('.').length !== 4) return null; - const parts = mask.split('.').map(Number); - if (parts.length !== 4 || parts.some((part) => part < 0 || part > 255)) return null; - - const binary = parts.map((part) => part.toString(2).padStart(8, '0')).join(''); - if (!/^1*0*$/.test(binary)) return null; - - return binary.indexOf('0') === -1 ? 32 : binary.indexOf('0'); -} - -const validateIpPart = (input: string): ipaddr.IPv4 | ipaddr.IPv6 | null => { - if (!ipaddr.isValid(input)) return null; - const ip = ipaddr.parse(input); - if (ip.kind() === 'ipv6') { - return ip; - } - if (!patternStrictIpV4.test(input)) return null; - return ip; -}; - -function parseSubnet(input: string): [ipaddr.IPv4 | ipaddr.IPv6, number] | null { - const [ipPart, maskPart] = input.split('/'); - if (!ipaddr.isValid(ipPart) || !maskPart) return null; - const ip = ipaddr.parse(ipPart); - const kind = ip.kind(); - - if (kind === 'ipv6') { - const prefix = parseInt(maskPart, 10); - if (typeof prefix !== 'number' || Number.isNaN(prefix)) { - return null; - } - return [ip, prefix]; - } - if (!patternStrictIpV4.test(ipPart)) return null; - - const prefix = dottedMaskToPrefix(maskPart); - if (prefix === null) return null; - - return [ip, prefix]; -} - -function isValidIpOrCidr(input: string): boolean { - try { - if (input.includes('/')) { - const parsed = parseSubnet(input); - if (!parsed) return false; - const [ip, mask] = parsed; - const cidr = ipaddr.parseCIDR(`${ip.toString()}/${mask}`); - return cidr[0] !== undefined && typeof cidr[1] === 'number'; - } else { - return validateIpPart(input) !== null; - } - } catch { - return false; - } -} - -export const aclDestinationValidator = (LL: TranslationFunctions) => - z.string().refine((value: string) => { - if (value === '') return true; - - const entries = value.split(',').map((s) => s.trim()); - - for (const entry of entries) { - if (entry.includes('-')) { - const [start, end] = entry.split('-').map((s) => s.trim()); - - // reject CIDR notation used in ranges - if (start.includes('/') || end.includes('/')) return false; - - if (!ipaddr.isValid(start) || !ipaddr.isValid(end)) return false; - - const startAddr = ipaddr.parse(start); - const endAddr = ipaddr.parse(end); - - // reject different ip versions in ranges - if (startAddr.kind() !== endAddr.kind()) return false; - - // reject invalid order in ranges - if (startAddr.toByteArray().join('.') > endAddr.toByteArray().join('.')) { - return false; - } - } else { - if (!isValidIpOrCidr(entry)) return false; - } - } - - return true; - }, LL.form.error.invalid()); diff --git a/web/src/pages/activity-log/ActivityLogPage.tsx b/web/src/pages/activity-log/ActivityLogPage.tsx deleted file mode 100644 index 7f1b42b45..000000000 --- a/web/src/pages/activity-log/ActivityLogPage.tsx +++ /dev/null @@ -1,319 +0,0 @@ -import './style.scss'; - -import { type QueryKey, useInfiniteQuery, useQuery } from '@tanstack/react-query'; -import dayjs from 'dayjs'; -import { range } from 'lodash-es'; -import { useMemo, useState } from 'react'; -import Skeleton from 'react-loading-skeleton'; - -import { useI18nContext } from '../../i18n/i18n-react'; -import { FilterButton } from '../../shared/components/Layout/buttons/FilterButton/FilterButton'; -import { PageContainer } from '../../shared/components/Layout/PageContainer/PageContainer'; -import { FilterGroupsModal } from '../../shared/components/modals/FilterGroupsModal/FilterGroupsModal'; -import type { FilterGroupsModalFilter } from '../../shared/components/modals/FilterGroupsModal/types'; -import { Button } from '../../shared/defguard-ui/components/Layout/Button/Button'; -import { ButtonSize } from '../../shared/defguard-ui/components/Layout/Button/types'; -import { Card } from '../../shared/defguard-ui/components/Layout/Card/Card'; -import { ListItemCount } from '../../shared/defguard-ui/components/Layout/ListItemCount/ListItemCount'; -import { NoData } from '../../shared/defguard-ui/components/Layout/NoData/NoData'; -import { Search } from '../../shared/defguard-ui/components/Layout/Search/Search'; -import { ListSortDirection } from '../../shared/defguard-ui/components/Layout/VirtualizedList/types'; -import { isPresent } from '../../shared/defguard-ui/utils/isPresent'; -import { useAuthStore } from '../../shared/hooks/store/useAuthStore'; -import useApi from '../../shared/hooks/useApi'; -import type { ActivityLogSortKey } from '../../shared/types'; -import { ActivityList } from './components/ActivityList'; -import { ActivityTimeRangeModal } from './components/ActivityTimeRangeModal'; -import { - type ActivityLogEventType, - type ActivityLogModule, - activityLogEventTypeValues, - activityLogModuleValues, -} from './types'; - -export const ActivityLogPage = () => { - return ( - - - - ); -}; - -const applyFilterArray = (val: Array | undefined | T): undefined | Array => { - if (val && Array.isArray(val) && val.length > 0) { - return val; - } -}; - -const applyFilter = (val: T | undefined | null): T | undefined => { - if (isPresent(val)) { - return val; - } -}; - -const applySearch = (val: string): string | undefined => { - if (val.length > 0) return val; - return undefined; -}; - -type Filters = 'event' | 'username' | 'module' | 'location'; - -const PageContent = () => { - const [activeFilters, setActiveFilters] = useState< - Record> - >({ - event: [], - module: [], - username: [], - location: [], - }); - const [searchValue, setSearchValue] = useState(''); - const [filtersModalOpen, setFiltersModalOpen] = useState(false); - const [from, setForm] = useState(dayjs.utc().startOf('M').toISOString()); - const [until, setUntil] = useState(null); - const [timeSelectionModalOpen, setTimeSelectionModal] = useState(false); - const [sortKey, setSortKey] = useState('timestamp'); - const [sortDirection, setSortDirection] = useState( - ListSortDirection.DESC, - ); - const isAdmin = useAuthStore((s) => s.user?.is_admin ?? false); - - const activeFiltersCount = useMemo( - () => Object.values(activeFilters).flat().length, - [activeFilters], - ); - - const { LL } = useI18nContext(); - const localLL = LL.activity; - - const { - activityLog: { getActivityLog }, - user: { getUsers }, - network: { getNetworks }, - } = useApi(); - - const { data: users } = useQuery({ - queryFn: getUsers, - queryKey: ['user'], - enabled: isAdmin, - }); - - const { data: locations } = useQuery({ - queryFn: getNetworks, - queryKey: ['location'], - }); - - const queryKey = useMemo( - (): QueryKey => [ - 'activity_log', - { - sortDirection, - sortKey, - from, - until, - searchValue, - filters: activeFilters, - }, - ], - [activeFilters, from, searchValue, sortDirection, sortKey, until], - ); - - const { - data, - hasNextPage, - isFetchingNextPage, - fetchNextPage, - isLoading, - // hasPreviousPage, - // fetchPreviousPage, - } = useInfiniteQuery({ - queryKey, - initialPageParam: 1, - queryFn: ({ pageParam }) => - getActivityLog({ - page: pageParam, - event: applyFilterArray(activeFilters.event as ActivityLogEventType[]), - module: applyFilterArray(activeFilters.module as ActivityLogModule[]), - username: applyFilterArray(activeFilters.username as string[]), - location: applyFilterArray(activeFilters.location as string[]), - sort_order: sortDirection, - sort_by: sortKey, - search: applySearch(searchValue), - from: applyFilter(from), - until: applyFilter(until), - }), - getNextPageParam: (lastPage) => lastPage?.pagination?.next_page, - getPreviousPageParam: (page) => { - if (page.pagination.current_page !== 1) { - return page.pagination.current_page - 1; - } - return undefined; - }, - }); - - const filterOptions = useMemo(() => { - const res: Record = {}; - if (users) { - res.users = { - label: 'Users', - identifier: 'username', - order: 3, - items: users.map((user) => ({ - label: `${user.first_name} ${user.last_name} (${user.username})`, - searchValues: [user.first_name, user.username, user.last_name, user.email], - value: user.username, - })), - }; - } - if (locations) { - res.locations = { - label: 'Locations', - identifier: 'location', - order: 4, - items: locations.map((location) => ({ - label: location.name, - searchValues: [location.name], - value: location.name, - })), - }; - } - res.module = { - identifier: 'module', - label: 'Module', - order: 2, - items: activityLogModuleValues.map((activityLogModule) => { - const translation = LL.enums.activityLogModule[activityLogModule](); - return { - label: translation, - searchValues: [translation], - value: activityLogModule, - }; - }), - }; - res.event = { - identifier: 'event', - label: 'Event', - order: 1, - items: activityLogEventTypeValues.map((eventType) => { - const translation = LL.enums.activityLogEventType[eventType](); - return { - label: translation, - searchValues: [translation], - value: eventType, - }; - }), - }; - return res; - }, [LL.enums, users, locations]); - - const activityData = useMemo(() => { - if (data) { - return data.pages.flatMap((page) => page.data); - } - return undefined; - }, [data]); - - return ( - <> -
-

{localLL.title()}

-
-
-
-

{localLL.list.allLabel()}

- -
- { - setSearchValue(search); - }} - /> - { - setFiltersModalOpen(true); - }} - /> -
-
- - {isPresent(activityData) && activityData.length > 0 && ( - { - setSortDirection(sortDirection); - setSortKey(sortKey as ActivityLogSortKey); - }} - data={activityData} - hasNextPage={hasNextPage} - isFetchingNextPage={isFetchingNextPage} - onNextPage={() => { - void fetchNextPage(); - }} - /> - )} - {!isPresent(activityData) && isLoading && ( -
- {range(10).map((index) => ( - - ))} -
- )} - {(activeFiltersCount > 0 || searchValue.length > 0) && - isPresent(activityData) && - activityData.length === 0 && } -
-
- { - setFiltersModalOpen(false); - }} - onSubmit={(state) => { - setActiveFilters(state as Record); - setFiltersModalOpen(false); - }} - /> - { - setForm(from); - setUntil(until); - }} - /> - - ); -}; diff --git a/web/src/pages/activity-log/components/ActivityList.tsx b/web/src/pages/activity-log/components/ActivityList.tsx deleted file mode 100644 index 552a1a491..000000000 --- a/web/src/pages/activity-log/components/ActivityList.tsx +++ /dev/null @@ -1,173 +0,0 @@ -import { useVirtualizer } from '@tanstack/react-virtual'; -import dayjs from 'dayjs'; -import { useMemo, useRef } from 'react'; -import { useInView } from 'react-intersection-observer'; - -import { useI18nContext } from '../../../i18n/i18n-react'; -import { ListHeader } from '../../../shared/components/Layout/ListHeader/ListHeader'; -import type { ListHeaderColumnConfig } from '../../../shared/components/Layout/ListHeader/types'; -import { ListCellText } from '../../../shared/defguard-ui/components/Layout/ListCellText/ListCellText'; -import { LoaderSpinner } from '../../../shared/defguard-ui/components/Layout/LoaderSpinner/LoaderSpinner'; -import type { ListSortDirection } from '../../../shared/defguard-ui/components/Layout/VirtualizedList/types'; -import type { ActivityLogEvent, ActivityLogSortKey } from '../../../shared/types'; - -type Props = { - data: ActivityLogEvent[]; - hasNextPage: boolean; - isFetchingNextPage: boolean; - sortKey: ActivityLogSortKey; - sortDirection: ListSortDirection; - onNextPage: () => void; - onSortChange: ( - sortKey: keyof ActivityLogEvent, - sortDirection: ListSortDirection, - ) => void; -}; - -export const ActivityList = ({ - data, - isFetchingNextPage, - hasNextPage, - sortDirection, - sortKey, - onSortChange, - onNextPage, -}: Props) => { - const { LL } = useI18nContext(); - const localLL = LL.activity.list; - const headersLL = localLL.headers; - const { ref: infiniteLoadMoreElement } = useInView({ - threshold: 0, - trackVisibility: false, - onChange: (inView) => { - if (inView) { - onNextPage(); - } - }, - }); - const parentRef = useRef(null); - const count = data.length; - const virtualizer = useVirtualizer({ - count, - estimateSize: () => 40, - getScrollElement: () => parentRef.current, - enabled: true, - paddingStart: 45, - paddingEnd: 10, - }); - const items = virtualizer.getVirtualItems(); - const listHeaders = useMemo( - (): ListHeaderColumnConfig[] => [ - { - label: headersLL.date(), - enabled: true, - key: 'date', - sortKey: 'timestamp', - }, - { - label: headersLL.user(), - key: 'user', - }, - { - label: headersLL.ip(), - key: 'ip', - }, - { - label: headersLL.location(), - key: 'location', - }, - { - label: headersLL.event(), - key: 'event', - }, - { - label: headersLL.module(), - key: 'module', - }, - { - label: headersLL.device(), - key: 'device', - }, - { - label: headersLL.description(), - key: 'description', - }, - ], - [headersLL], - ); - return ( -
-
- -
- {items.map((virtualRow) => { - const activity = data[virtualRow.index]; - return ( -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- ); - })} - {hasNextPage && ( -
- {isFetchingNextPage && } -
- )} -
-
-
- ); -}; diff --git a/web/src/pages/activity-log/components/ActivityTimeRangeModal.tsx b/web/src/pages/activity-log/components/ActivityTimeRangeModal.tsx deleted file mode 100644 index 34c9f8323..000000000 --- a/web/src/pages/activity-log/components/ActivityTimeRangeModal.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import { zodResolver } from '@hookform/resolvers/zod'; -import { useMemo } from 'react'; -import { type SubmitHandler, useForm } from 'react-hook-form'; -import { z } from 'zod'; - -import { useI18nContext } from '../../../i18n/i18n-react'; -import { FormDateInput } from '../../../shared/components/Layout/DateInput/FormDateInput'; -import { Button } from '../../../shared/defguard-ui/components/Layout/Button/Button'; -import { - ButtonSize, - ButtonStyleVariant, -} from '../../../shared/defguard-ui/components/Layout/Button/types'; -import { ModalWithTitle } from '../../../shared/defguard-ui/components/Layout/modals/ModalWithTitle/ModalWithTitle'; - -type Props = { - isOpen: boolean; - onOpenChange: (val: boolean) => void; - // Time in UTC ISO without timezone string - activityFrom: string | null; - activityUntil: string | null; - onChange: (from: string | null, until: string | null) => void; -}; - -export const ActivityTimeRangeModal = (props: Props) => { - const { LL } = useI18nContext(); - return ( - { - props.onOpenChange(false); - }} - > - - - ); -}; - -const ModalContent = ({ onOpenChange, activityFrom, activityUntil, onChange }: Props) => { - const { LL } = useI18nContext(); - const schema = useMemo( - () => - z.object({ - from: z.string().nullable(), - until: z.string().nullable(), - }), - [], - ); - - type FormFields = z.infer; - - const defaultValues = useMemo( - (): FormFields => ({ - from: activityFrom, - until: activityUntil, - }), - [activityFrom, activityUntil], - ); - - const { control, handleSubmit } = useForm({ - resolver: zodResolver(schema), - mode: 'all', - defaultValues, - }); - - const handleValidSubmit: SubmitHandler = (values) => { - onChange(values.from, values.until); - onOpenChange(false); - }; - - return ( -
- - -
-
- - ); -}; diff --git a/web/src/pages/activity-log/style.scss b/web/src/pages/activity-log/style.scss deleted file mode 100644 index aecfdc547..000000000 --- a/web/src/pages/activity-log/style.scss +++ /dev/null @@ -1,168 +0,0 @@ -#activity-log-page { - h1 { - @include typography(app-title); - } - - h2 { - @include typography(app-body-1); - } - - .page-header { - display: flex; - flex-flow: row; - gap: var(--spacing-m); - align-items: center; - justify-content: flex-start; - padding-bottom: var(--spacing-l); - - .search { - height: 40px; - width: 100%; - max-width: 350px; - } - } - - .activity-list-skeleton { - width: 100%; - display: flex; - flex-flow: column; - row-gap: var(--spacing-xs); - max-height: 100%; - overflow: hidden; - - .react-loading-skeleton { - height: 40px; - } - } -} - -@mixin list-sizing() { - grid-template-columns: 150px 120px 150px 150px 300px 100px 200px minmax(300px, 1fr); - justify-content: space-between; - column-gap: var(--spacing-xs); - - .cell { - display: flex; - flex-flow: row; - align-items: center; - justify-content: flex-start; - - &.select-cell { - .interaction-box { - width: 18px; - height: 18px; - } - } - } -} - -#activity-list { - & > .top { - display: flex; - flex-flow: row wrap; - align-items: center; - justify-content: flex-start; - row-gap: var(--spacing-xs); - padding-bottom: var(--spacing-m); - - h2 { - padding-right: 10px; - } - - .controls { - display: flex; - flex-flow: row; - column-gap: var(--spacing-xs); - margin-left: auto; - } - } -} - -#activity-list-card { - padding: var(--spacing-s) 15px; - width: 100%; - max-width: 100%; - - @include media-breakpoint-up(lg) { - min-height: min(600px, 75dvh); - } - - .list-headers { - position: sticky; - top: 0; - z-index: 1; - box-sizing: border-box; - background-color: var(--surface-default-modal); - height: 40px; - width: unset; - min-width: 100%; - padding-left: var(--spacing-xs); - - .cell.empty { - display: none; - } - - @include list-sizing(); - } - - .virtual-list { - overflow: auto; - contain: strict; - width: 100%; - max-width: 100%; - max-height: 610px; - height: 600px; - padding-right: 15px; - box-sizing: border-box; - scrollbar-gutter: stable; - - .end-row { - display: flex; - flex-flow: row; - align-items: center; - justify-content: center; - height: 40px; - width: 100%; - } - - .list-row { - display: grid; - max-width: 100%; - @include list-sizing(); - height: 40px; - background-color: var(--surface-default-modal); - padding-left: var(--spacing-xs); - - &:hover { - background-color: var(--surface-button); - } - - .cell { - max-width: 100%; - overflow: hidden; - - p { - color: var(--text-button-primary); - @include typography(app-code); - } - } - } - } -} - -#activity-time-selection-modal-form { - display: flex; - flex-flow: column; - row-gap: var(--spacing-s); - - & > * { - width: 100%; - } - - .controls { - display: grid; - grid-template-columns: 1fr 1fr; - width: 100%; - column-gap: var(--spacing-xs); - } -} diff --git a/web/src/pages/activity-log/types.ts b/web/src/pages/activity-log/types.ts deleted file mode 100644 index 3485b9243..000000000 --- a/web/src/pages/activity-log/types.ts +++ /dev/null @@ -1,163 +0,0 @@ -export type ActivityLogModule = 'defguard' | 'client' | 'vpn' | 'enrollment'; - -export const activityLogModuleValues: ActivityLogModule[] = [ - 'defguard', - 'client', - 'enrollment', - 'vpn', -]; - -export type ActivityLogEventType = - | 'user_login' - | 'user_login_failed' - | 'user_mfa_login' - | 'user_mfa_login_failed' - | 'recovery_code_used' - | 'user_logout' - | 'user_added' - | 'user_modified' - | 'user_removed' - | 'user_groups_modified' - | 'mfa_disabled' - | 'user_mfa_disabled' - | 'mfa_totp_enabled' - | 'mfa_totp_disabled' - | 'mfa_email_enabled' - | 'mfa_email_disabled' - | 'mfa_security_key_added' - | 'mfa_security_key_removed' - | 'device_added' - | 'device_modified' - | 'device_removed' - | 'network_device_added' - | 'network_device_modified' - | 'network_device_removed' - | 'activity_log_stream_created' - | 'activity_log_stream_modified' - | 'activity_log_stream_removed' - | 'vpn_client_connected' - | 'vpn_client_disconnected' - | 'vpn_client_connected_mfa' - | 'vpn_client_disconnected_mfa' - | 'vpn_client_mfa_failed' - | 'enrollment_token_added' - | 'enrollment_started' - | 'enrollment_device_added' - | 'enrollment_completed' - | 'password_reset_requested' - | 'password_reset_started' - | 'password_reset_completed' - | 'vpn_location_added' - | 'vpn_location_removed' - | 'vpn_location_modified' - | 'api_token_added' - | 'api_token_removed' - | 'api_token_renamed' - | 'open_id_app_added' - | 'open_id_app_removed' - | 'open_id_app_modified' - | 'open_id_app_state_changed' - | 'open_id_provider_removed' - | 'open_id_provider_modified' - | 'settings_updated' - | 'settings_updated_partial' - | 'settings_default_branding_restored' - | 'groups_bulk_assigned' - | 'group_added' - | 'group_modified' - | 'group_removed' - | 'group_member_added' - | 'group_member_removed' - | 'group_members_modified' - | 'web_hook_added' - | 'web_hook_modified' - | 'web_hook_removed' - | 'web_hook_state_changed' - | 'authentication_key_added' - | 'authentication_key_removed' - | 'authentication_key_renamed' - | 'password_changed' - | 'password_changed_by_admin' - | 'password_reset' - | 'client_configuration_token_added' - | 'user_snat_binding_added' - | 'user_snat_binding_modified' - | 'user_snat_binding_removed'; - -export const activityLogEventTypeValues: ActivityLogEventType[] = [ - 'user_login', - 'user_login_failed', - 'user_mfa_login', - 'user_mfa_login_failed', - 'user_groups_modified', - 'recovery_code_used', - 'user_logout', - 'user_added', - 'user_modified', - 'user_removed', - 'mfa_disabled', - 'user_mfa_disabled', - 'mfa_totp_enabled', - 'mfa_totp_disabled', - 'mfa_email_enabled', - 'mfa_email_disabled', - 'mfa_security_key_added', - 'mfa_security_key_removed', - 'device_added', - 'device_modified', - 'device_removed', - 'network_device_added', - 'network_device_modified', - 'network_device_removed', - 'activity_log_stream_created', - 'activity_log_stream_modified', - 'activity_log_stream_removed', - 'vpn_client_connected', - 'vpn_client_disconnected', - 'vpn_client_connected_mfa', - 'vpn_client_disconnected_mfa', - 'vpn_client_mfa_failed', - 'enrollment_token_added', - 'enrollment_started', - 'enrollment_device_added', - 'enrollment_completed', - 'password_reset_requested', - 'password_reset_started', - 'password_reset_completed', - 'vpn_location_added', - 'vpn_location_removed', - 'vpn_location_modified', - 'api_token_added', - 'api_token_removed', - 'api_token_renamed', - 'open_id_app_added', - 'open_id_app_removed', - 'open_id_app_modified', - 'open_id_app_state_changed', - 'open_id_provider_removed', - 'open_id_provider_modified', - 'settings_updated', - 'settings_updated_partial', - 'settings_default_branding_restored', - 'groups_bulk_assigned', - 'group_added', - 'group_modified', - 'group_removed', - 'group_member_added', - 'group_member_removed', - 'group_members_modified', - 'web_hook_added', - 'web_hook_modified', - 'web_hook_removed', - 'web_hook_state_changed', - 'authentication_key_added', - 'authentication_key_removed', - 'authentication_key_renamed', - 'password_changed', - 'password_changed_by_admin', - 'password_reset', - 'client_configuration_token_added', - 'user_snat_binding_added', - 'user_snat_binding_modified', - 'user_snat_binding_removed', -]; diff --git a/web/src/pages/addDevice/AddDevicePage.tsx b/web/src/pages/addDevice/AddDevicePage.tsx deleted file mode 100644 index 8366ab960..000000000 --- a/web/src/pages/addDevice/AddDevicePage.tsx +++ /dev/null @@ -1,169 +0,0 @@ -import './style.scss'; - -import { useEffect } from 'react'; -import { useNavigate } from 'react-router'; -import { shallow } from 'zustand/shallow'; - -import { useI18nContext } from '../../i18n/i18n-react'; -import { PageContainer } from '../../shared/components/Layout/PageContainer/PageContainer'; -import { ArrowSingle } from '../../shared/defguard-ui/components/icons/ArrowSingle/ArrowSingle'; -import { - ArrowSingleDirection, - ArrowSingleSize, -} from '../../shared/defguard-ui/components/icons/ArrowSingle/types'; -import { Button } from '../../shared/defguard-ui/components/Layout/Button/Button'; -import { - ButtonSize, - ButtonStyleVariant, -} from '../../shared/defguard-ui/components/Layout/Button/types'; -import { useAppStore } from '../../shared/hooks/store/useAppStore'; -import { useAuthStore } from '../../shared/hooks/store/useAuthStore'; -import { useEnterpriseUpgradeStore } from '../../shared/hooks/store/useEnterpriseUpgradeStore'; -import useApi from '../../shared/hooks/useApi'; -import { useAddDevicePageStore } from './hooks/useAddDevicePageStore'; -import { AddDeviceClientConfigurationStep } from './steps/AddDeviceClientConfigurationStep/AddDeviceClientConfigurationStep'; -import { AddDeviceConfigStep } from './steps/AddDeviceConfigStep/AddDeviceConfigStep'; -import { AddDeviceSetupMethodStep } from './steps/AddDeviceSetupMethodStep/AddDeviceSetupMethodStep'; -import { AddDeviceSetupStep } from './steps/AddDeviceSetupStep/AddDeviceSetupStep'; -import { AddDeviceNavigationEvent, AddDeviceStep } from './types'; - -const finalSteps: AddDeviceStep[] = [ - AddDeviceStep.NATIVE_CONFIGURATION, - AddDeviceStep.CLIENT_CONFIGURATION, -]; - -export const AddDevicePage = () => { - const { LL } = useI18nContext(); - const pageLL = LL.addDevicePage; - const navigate = useNavigate(); - const { getAppInfo } = useApi(); - - const userData = useAddDevicePageStore((state) => state.userData); - const isAdmin = useAuthStore((s) => s.user?.is_admin ?? false); - const setAppStore = useAppStore((s) => s.setState); - const showUpgradeToast = useEnterpriseUpgradeStore((s) => s.show); - const currentStep = useAddDevicePageStore((state) => state.currentStep); - const [navSubject, resetStore, setStep] = useAddDevicePageStore( - (s) => [s.navigationSubject, s.reset, s.setStep], - shallow, - ); - - const isFinalStep = finalSteps.includes(currentStep); - - useEffect(() => { - if (!userData) { - navigate('/', { replace: true }); - } - }, [navigate, userData]); - - useEffect(() => { - const sub = navSubject.subscribe((event) => { - if ( - event === AddDeviceNavigationEvent.NEXT && - [AddDeviceStep.CLIENT_CONFIGURATION, AddDeviceStep.NATIVE_CONFIGURATION].includes( - currentStep, - ) && - userData - ) { - if (isAdmin) { - void getAppInfo().then((resp) => { - setAppStore({ appInfo: resp }); - if (resp.license_info.any_limit_exceeded) { - showUpgradeToast(); - } - }); - } - navigate(userData.originRoutePath, { replace: true }); - setTimeout(() => { - resetStore(); - }, 250); - } - if (event === AddDeviceNavigationEvent.BACK) { - if (currentStep === AddDeviceStep.NATIVE_CHOOSE_METHOD) { - setStep(AddDeviceStep.CHOOSE_METHOD); - } - } - }); - return () => { - sub.unsubscribe(); - }; - }, [ - currentStep, - getAppInfo, - isAdmin, - navSubject, - navigate, - resetStore, - setAppStore, - setStep, - showUpgradeToast, - userData, - ]); - - return ( - -
-
-

{pageLL.title()}

-
-
-
- {steps[currentStep]} -
-
- ); -}; - -const steps = { - [AddDeviceStep.CHOOSE_METHOD]: , - [AddDeviceStep.NATIVE_CHOOSE_METHOD]: , - [AddDeviceStep.NATIVE_CONFIGURATION]: , - [AddDeviceStep.CLIENT_CONFIGURATION]: , -}; diff --git a/web/src/pages/addDevice/hooks/useAddDevicePageStore.tsx b/web/src/pages/addDevice/hooks/useAddDevicePageStore.tsx deleted file mode 100644 index 583a7f3e3..000000000 --- a/web/src/pages/addDevice/hooks/useAddDevicePageStore.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { omit } from 'lodash-es'; -import { Subject } from 'rxjs'; -import { createJSONStorage, persist } from 'zustand/middleware'; -import { createWithEqualityFn } from 'zustand/traditional'; - -import type { DeviceConfigsCardNetworkInfo } from '../../../shared/components/network/DeviceConfigsCard/types'; -import type { AddDeviceResponseDevice } from '../../../shared/types'; -import { type AddDeviceNavigationEvent, AddDeviceStep } from '../types'; - -const defaultValues: StoreValues = { - navigationSubject: new Subject(), - currentStep: AddDeviceStep.CHOOSE_METHOD, - userData: undefined, - loading: false, - publicKey: undefined, - privateKey: undefined, - device: undefined, - networks: undefined, - clientSetup: undefined, -}; - -export const useAddDevicePageStore = createWithEqualityFn()( - persist( - (set) => ({ - ...defaultValues, - reset: () => set(defaultValues), - init: (userData) => { - set({ ...defaultValues, userData }); - }, - setState: (values) => set({ ...values }), - setStep: (step, values) => { - set({ ...values, currentStep: step }); - }, - }), - { - name: 'add-device-store', - partialize: (store) => omit(store, ['navigationSubject', 'loading']), - storage: createJSONStorage(() => sessionStorage), - }, - ), - Object.is, -); - -type Store = StoreValues & StoreMethods; - -type StoreValues = { - navigationSubject: Subject; - currentStep: AddDeviceStep; - loading: boolean; - privateKey?: string; - publicKey?: string; - device?: AddDeviceResponseDevice; - networks?: DeviceConfigsCardNetworkInfo[]; - userData?: { - id: number; - username: string; - reservedDevices: string[]; - email: string; - // this should be current path that user entered add-device page from, due to brave blocking history relative back doesn't work correctly. - originRoutePath: string; - }; - clientSetup?: { - token: string; - url: string; - }; -}; - -type StoreMethods = { - init: (userData: StoreValues['userData']) => void; - reset: () => void; - setState: (values: Partial) => void; - setStep: (step: AddDeviceStep, values?: Partial) => void; -}; diff --git a/web/src/pages/addDevice/steps/AddDeviceClientConfigurationStep/AddDeviceClientConfigurationStep.tsx b/web/src/pages/addDevice/steps/AddDeviceClientConfigurationStep/AddDeviceClientConfigurationStep.tsx deleted file mode 100644 index eaf9bf391..000000000 --- a/web/src/pages/addDevice/steps/AddDeviceClientConfigurationStep/AddDeviceClientConfigurationStep.tsx +++ /dev/null @@ -1,306 +0,0 @@ -import './style.scss'; - -import { useEffect } from 'react'; -import QRCode from 'react-qr-code'; -import { shallow } from 'zustand/shallow'; - -import { useI18nContext } from '../../../../i18n/i18n-react'; -import { OpenDesktopClientButton } from '../../../../shared/components/Layout/buttons/OpenDesktopClientButton/OpenDesktopClientButton'; -import { RenderMarkdown } from '../../../../shared/components/Layout/RenderMarkdown/RenderMarkdown'; -import { Button } from '../../../../shared/defguard-ui/components/Layout/Button/Button'; -import { - ButtonSize, - ButtonStyleVariant, -} from '../../../../shared/defguard-ui/components/Layout/Button/types'; -import { Card } from '../../../../shared/defguard-ui/components/Layout/Card/Card'; -import { CopyField } from '../../../../shared/defguard-ui/components/Layout/CopyField/CopyField'; -import { MessageBox } from '../../../../shared/defguard-ui/components/Layout/MessageBox/MessageBox'; -import { isPresent } from '../../../../shared/defguard-ui/utils/isPresent'; -import { useClipboard } from '../../../../shared/hooks/useClipboard'; -import { externalLink } from '../../../../shared/links'; -import { useAddDevicePageStore } from '../../hooks/useAddDevicePageStore'; -import { AddDeviceStep } from '../../types'; -import { enrollmentToImportToken } from '../../utils/enrollmentToToken'; - -export const AddDeviceClientConfigurationStep = () => { - const { LL } = useI18nContext(); - const localLL = LL.addDevicePage.steps.client; - const clientData = useAddDevicePageStore((s) => s.clientSetup); - const clientSetup = useAddDevicePageStore((s) => s.clientSetup); - const tokenValue = useAddDevicePageStore((s) => - s.clientSetup - ? enrollmentToImportToken(s.clientSetup.url, s.clientSetup.token) - : null, - ); - const setStep = useAddDevicePageStore((s) => s.setStep, shallow); - const { writeToClipboard } = useClipboard(); - - useEffect(() => { - if (!isPresent(tokenValue)) { - setStep(AddDeviceStep.CHOOSE_METHOD); - } - }, [setStep, tokenValue]); - - if (!isPresent(tokenValue) || !isPresent(clientData)) return null; - - return ( - -

{localLL.title()}

- {isPresent(clientSetup) && ( - <> - -
- -
- - )} - - - - {/* { - void writeToClipboard(value, localLL.tokenCopy()); - }} - /> */} - { - void writeToClipboard(value, localLL.tokenCopy()); - }} - /> - { - void writeToClipboard(value, localLL.tokenCopy()); - }} - /> - -
- -
-
-

{localLL.qrDescription()}

-
- -
- ); -}; diff --git a/web/src/pages/addDevice/steps/AddDeviceClientConfigurationStep/style.scss b/web/src/pages/addDevice/steps/AddDeviceClientConfigurationStep/style.scss deleted file mode 100644 index df62be874..000000000 --- a/web/src/pages/addDevice/steps/AddDeviceClientConfigurationStep/style.scss +++ /dev/null @@ -1,50 +0,0 @@ -#add-device-client-configuration { - .message-box.spacer { - padding-bottom: var(--spacing-s); - } - - .qr-description { - color: var(--text-body-secondary); - text-align: center; - max-width: 480px; - user-select: none; - @include typography(app-input); - } - - .copy-field.spacer { - &:not(:last-of-type) { - padding-bottom: var(--spacing-s); - } - } - - .row { - display: flex; - flex-flow: row; - align-items: center; - justify-content: center; - column-gap: var(--spacing-xs); - - &:not(:last-child) { - padding-bottom: var(--spacing-s); - } - - a { - display: flex; - cursor: pointer; - user-select: none; - } - } - - .links:first-of-type { - padding-bottom: var(--spacing-s); - } - - .qr { - display: flex; - flex-flow: row; - align-items: center; - justify-content: center; - padding: var(--spacing-m) var(--spacing-s) var(--spacing-l); - box-sizing: border-box; - } -} diff --git a/web/src/pages/addDevice/steps/AddDeviceConfigStep/AddDeviceConfigStep.tsx b/web/src/pages/addDevice/steps/AddDeviceConfigStep/AddDeviceConfigStep.tsx deleted file mode 100644 index ca397de7e..000000000 --- a/web/src/pages/addDevice/steps/AddDeviceConfigStep/AddDeviceConfigStep.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import './style.scss'; - -import parse from 'html-react-parser'; -import { isUndefined } from 'lodash-es'; -import { useEffect, useMemo } from 'react'; -import { shallow } from 'zustand/shallow'; - -import { useI18nContext } from '../../../../i18n/i18n-react'; -import { DeviceConfigsCard } from '../../../../shared/components/network/DeviceConfigsCard/DeviceConfigsCard'; -import { Card } from '../../../../shared/defguard-ui/components/Layout/Card/Card'; -import { Input } from '../../../../shared/defguard-ui/components/Layout/Input/Input'; -import { MessageBox } from '../../../../shared/defguard-ui/components/Layout/MessageBox/MessageBox'; -import { MessageBoxType } from '../../../../shared/defguard-ui/components/Layout/MessageBox/types'; -import { useAddDevicePageStore } from '../../hooks/useAddDevicePageStore'; -import { AddDeviceStep } from '../../types'; - -enum SetupMode { - AUTO, - MANUAL, -} - -export const AddDeviceConfigStep = () => { - const { LL } = useI18nContext(); - const localLL = LL.addDevicePage.steps.configDevice; - - const [userData, device, publicKey, privateKey, networks] = useAddDevicePageStore( - (state) => [ - state.userData, - state.device, - state.publicKey, - state.privateKey, - state.networks, - ], - shallow, - ); - - const setStep = useAddDevicePageStore((state) => state.setStep, shallow); - - const setupMode = isUndefined(privateKey) ? SetupMode.MANUAL : SetupMode.AUTO; - - const getWarningMessageContent = useMemo(() => { - if (setupMode === SetupMode.AUTO) { - return parse(localLL.helpers.warningAutoMode()); - } - return parse(localLL.helpers.warningManualMode()); - }, [localLL.helpers, setupMode]); - - useEffect(() => { - if (!device || !userData || !publicKey || !networks) { - setStep(AddDeviceStep.NATIVE_CHOOSE_METHOD); - } - }, [device, networks, publicKey, setStep, userData]); - - if (!device || !userData || !publicKey || !networks) return null; - - return ( - -

{localLL.title()}

- {getWarningMessageContent} - { - return; - }} - disabled={true} - /> -
-

{localLL.qrInfo()}

-
- {networks.length > 0 && ( - - )} - {networks.length === 0 && ( - - {localLL.helpers.warningNoNetworks()} - - )} -
- ); -}; diff --git a/web/src/pages/addDevice/steps/AddDeviceConfigStep/style.scss b/web/src/pages/addDevice/steps/AddDeviceConfigStep/style.scss deleted file mode 100644 index fd2175705..000000000 --- a/web/src/pages/addDevice/steps/AddDeviceConfigStep/style.scss +++ /dev/null @@ -1,21 +0,0 @@ -@use '@scssutils' as *; - -#add-device-page { - #add-device-config-step { - & > .info { - width: 100%; - display: flex; - flex-flow: row nowrap; - align-items: center; - justify-content: center; - padding-bottom: 30px; - - & > p { - @include typography(app-input); - color: var(--text-body-secondary); - text-align: center; - max-width: 410px; - } - } - } -} diff --git a/web/src/pages/addDevice/steps/AddDeviceSetupMethodStep/AddDeviceSetupMethodStep.tsx b/web/src/pages/addDevice/steps/AddDeviceSetupMethodStep/AddDeviceSetupMethodStep.tsx deleted file mode 100644 index 26aa2eb03..000000000 --- a/web/src/pages/addDevice/steps/AddDeviceSetupMethodStep/AddDeviceSetupMethodStep.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import './style.scss'; - -import { useMutation } from '@tanstack/react-query'; -import { useCallback, useEffect, useState } from 'react'; -import { shallow } from 'zustand/shallow'; - -import { useI18nContext } from '../../../../i18n/i18n-react'; -import { Card } from '../../../../shared/defguard-ui/components/Layout/Card/Card'; -import { LoaderSpinner } from '../../../../shared/defguard-ui/components/Layout/LoaderSpinner/LoaderSpinner'; -import { MessageBox } from '../../../../shared/defguard-ui/components/Layout/MessageBox/MessageBox'; -import { useAppStore } from '../../../../shared/hooks/store/useAppStore'; -import useApi from '../../../../shared/hooks/useApi'; -import { useAddDevicePageStore } from '../../hooks/useAddDevicePageStore'; -import { AddDeviceNavigationEvent, AddDeviceStep } from '../../types'; -import { DeviceSetupMethodCard } from './components/DeviceSetupMethodCard/DeviceSetupMethodCard'; -import { DeviceSetupMethod } from './types'; - -export const AddDeviceSetupMethodStep = () => { - const { - user: { startDesktopActivation }, - } = useApi(); - const { LL } = useI18nContext(); - const localLL = LL.addDevicePage.steps.setupMethod; - - const [setupMethod, setSetupMethod] = useState(AddDeviceStep.CLIENT_CONFIGURATION); - const userData = useAddDevicePageStore((state) => state.userData); - - const enterpriseSettings = useAppStore((state) => state.enterprise_settings); - const [navSubject, setPageState, setStep] = useAddDevicePageStore( - (s) => [s.navigationSubject, s.setState, s.setStep], - shallow, - ); - - const { mutate, isPending } = useMutation({ - mutationFn: startDesktopActivation, - onSuccess: (resp) => { - setStep(setupMethod, { - clientSetup: { - url: resp.enrollment_url, - token: resp.enrollment_token, - }, - }); - }, - }); - - const startActivation = useCallback(() => { - mutate({ - username: userData?.username as string, - send_enrollment_notification: true, - email: userData?.email as string, - }); - }, [mutate, userData?.email, userData?.username]); - - useEffect(() => { - const sub = navSubject.subscribe((event) => { - if (event === AddDeviceNavigationEvent.NEXT) { - switch (setupMethod) { - case AddDeviceStep.NATIVE_CHOOSE_METHOD: - setPageState({ currentStep: AddDeviceStep.NATIVE_CHOOSE_METHOD }); - break; - case AddDeviceStep.CLIENT_CONFIGURATION: - startActivation(); - break; - } - } - }); - return () => { - sub.unsubscribe(); - }; - }, [navSubject, setPageState, setupMethod, startActivation]); - - useEffect(() => { - if ( - enterpriseSettings?.only_client_activation && - setupMethod === AddDeviceStep.NATIVE_CHOOSE_METHOD - ) { - setSetupMethod(AddDeviceStep.CLIENT_CONFIGURATION); - } - }, [enterpriseSettings?.only_client_activation, setupMethod]); - - return ( - <> - {!isPending ? ( - -

{localLL.title()}

- -
- { - setSetupMethod(AddDeviceStep.CLIENT_CONFIGURATION); - }} - /> - { - setSetupMethod(AddDeviceStep.NATIVE_CHOOSE_METHOD); - }} - /> -
-
- ) : ( -
- -
- )} - - ); -}; diff --git a/web/src/pages/addDevice/steps/AddDeviceSetupMethodStep/components/DeviceSetupMethodCard/DeviceSetupMethodCard.tsx b/web/src/pages/addDevice/steps/AddDeviceSetupMethodStep/components/DeviceSetupMethodCard/DeviceSetupMethodCard.tsx deleted file mode 100644 index cb1dbecc5..000000000 --- a/web/src/pages/addDevice/steps/AddDeviceSetupMethodStep/components/DeviceSetupMethodCard/DeviceSetupMethodCard.tsx +++ /dev/null @@ -1,365 +0,0 @@ -import './style.scss'; - -import clsx from 'clsx'; -import { type PropsWithChildren, type ReactNode, useId, useMemo } from 'react'; - -import { useI18nContext } from '../../../../../../i18n/i18n-react'; -import { isPresent } from '../../../../../../shared/defguard-ui/utils/isPresent'; -import { DeviceSetupMethod } from '../../types'; - -type StandaloneConfig = { - icon: ReactNode; - title: string; - description: string; - testId: string; - extras?: ReactNode; -}; - -type Props = { - active: boolean; - onClick: () => void; - methodType?: DeviceSetupMethod; - custom?: StandaloneConfig; - disabled?: boolean; -}; - -type ContentConfiguration = { - title: string; - description: string; - testId: string; -} & Pick & - PropsWithChildren; - -export const DeviceSetupMethodCard = ({ - methodType, - active, - onClick, - custom, - disabled = false, -}: Props) => { - const { LL } = useI18nContext(); - const localLL = LL.addDevicePage.steps.setupMethod.methods; - - const [title, description, testId] = useMemo(() => { - if (!isPresent(custom) && methodType) { - const testId = `add-device-method-${methodType.valueOf()}`; - switch (methodType) { - case DeviceSetupMethod.CLIENT: - return [localLL.client.title(), localLL.client.description(), testId]; - case DeviceSetupMethod.NATIVE_WG: - return [localLL.wg.title(), localLL.wg.description(), testId]; - default: - throw Error('Unimplemented setup method supplied to method card.'); - } - } - if (isPresent(custom)) { - return [custom.title, custom.description, custom.testId]; - } - throw Error('Bad props for DeviceSetupMethodCard'); - }, [custom, localLL.client, localLL.wg, methodType]); - - return ( - - {methodType === DeviceSetupMethod.CLIENT && ( - <> -
- - -
-
- -
- - )} - {methodType === DeviceSetupMethod.NATIVE_WG && ( -
- -
- )} - {isPresent(custom) && ( - <> -
{custom.icon}
- {custom.extras} - - )} -
- ); -}; - -const Content = ({ - active, - description, - onClick, - testId, - title, - children, - disabled = false, -}: ContentConfiguration) => { - return ( -
{ - if (!disabled) { - onClick?.(); - } - }} - > -

{title}

-

{description}

- {children} -
- ); -}; - -const PhoneSvg = () => { - return ( - - - - - ); -}; - -const DesktopSvg = () => { - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - ); -}; - -const DefguardLogo = () => { - const id = useId(); - return ( - - - - - - - - - - - ); -}; - -const WireguardLogo = () => { - return ( - - - - - - - - - - - ); -}; diff --git a/web/src/pages/addDevice/steps/AddDeviceSetupMethodStep/components/DeviceSetupMethodCard/style.scss b/web/src/pages/addDevice/steps/AddDeviceSetupMethodStep/components/DeviceSetupMethodCard/style.scss deleted file mode 100644 index b48c11c67..000000000 --- a/web/src/pages/addDevice/steps/AddDeviceSetupMethodStep/components/DeviceSetupMethodCard/style.scss +++ /dev/null @@ -1,67 +0,0 @@ -.device-method-card { - width: 100%; - display: flex; - flex-flow: column; - box-sizing: border-box; - align-items: center; - justify-content: flex-start; - background-color: var(--surface-frame-bg); - border-radius: 15px; - padding: var(--spacing-l) var(--spacing-s); - box-shadow: var(--box-shadow); - opacity: 1; - cursor: pointer; - outline: 0px solid transparent; - transition-property: outline, opacity; - - @include animate-standard; - - &:not(.active):not(.disabled):hover { - outline: 1px solid var(--surface-main-primary); - } - - &.active { - outline: 3px solid var(--surface-main-primary); - } - - &.disabled { - cursor: not-allowed; - opacity: 0.5; - } - - .title { - text-align: center; - padding-bottom: var(--spacing-s); - @include typography(app-body-1); - } - - .description { - text-align: center; - max-width: 280px; - color: var(--text-body-tertiary); - padding-bottom: var(--spacing-m); - - @include typography(welcome-h2); - } - - & > .icon { - display: flex; - flex-flow: row; - align-items: center; - justify-content: center; - column-gap: var(--spacing-s); - width: 100%; - - &:first-of-type { - padding-bottom: var(--spacing-l); - } - } - - .wg-icon { - display: flex; - flex-flow: column; - align-items: center; - justify-content: center; - min-height: 210px; - } -} diff --git a/web/src/pages/addDevice/steps/AddDeviceSetupMethodStep/style.scss b/web/src/pages/addDevice/steps/AddDeviceSetupMethodStep/style.scss deleted file mode 100644 index 10ed6ff23..000000000 --- a/web/src/pages/addDevice/steps/AddDeviceSetupMethodStep/style.scss +++ /dev/null @@ -1,45 +0,0 @@ -@use '@scssutils' as *; - -#add-device-page { - #setup-method-step { - display: flex; - flex-flow: column; - row-gap: var(--spacing-s); - - .message-box-spacer { - padding-bottom: 0px !important; - } - - & > .title { - @include typography(app-body-1); - user-select: none; - } - - .primary-methods { - display: grid; - grid-template-columns: 1fr 1fr; - grid-template-rows: 1fr; - column-gap: var(--spacing-m); - } - - .native-method { - display: flex; - flex-flow: row; - align-items: center; - justify-content: center; - - label { - @include typography(app-body-2); - color: var(--text-body-primary); - } - } - } - - #spinner-box { - display: flex; - flex-flow: column; - align-items: center; - justify-content: center; - min-height: 500px; - } -} diff --git a/web/src/pages/addDevice/steps/AddDeviceSetupMethodStep/types.ts b/web/src/pages/addDevice/steps/AddDeviceSetupMethodStep/types.ts deleted file mode 100644 index ce07d7633..000000000 --- a/web/src/pages/addDevice/steps/AddDeviceSetupMethodStep/types.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum DeviceSetupMethod { - CLIENT = 'client', - NATIVE_WG = 'native-wg', -} diff --git a/web/src/pages/addDevice/steps/AddDeviceSetupStep/AddDeviceSetupStep.tsx b/web/src/pages/addDevice/steps/AddDeviceSetupStep/AddDeviceSetupStep.tsx deleted file mode 100644 index f71c05c16..000000000 --- a/web/src/pages/addDevice/steps/AddDeviceSetupStep/AddDeviceSetupStep.tsx +++ /dev/null @@ -1,215 +0,0 @@ -import './style.scss'; - -import { zodResolver } from '@hookform/resolvers/zod'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import parser from 'html-react-parser'; -import { useEffect, useMemo, useRef } from 'react'; -import { type SubmitHandler, useController, useForm } from 'react-hook-form'; -import { z } from 'zod'; -import { shallow } from 'zustand/shallow'; - -import { useI18nContext } from '../../../../i18n/i18n-react'; -import { FormInput } from '../../../../shared/defguard-ui/components/Form/FormInput/FormInput'; -import { FormToggle } from '../../../../shared/defguard-ui/components/Form/FormToggle/FormToggle'; -import { Card } from '../../../../shared/defguard-ui/components/Layout/Card/Card'; -import { MessageBox } from '../../../../shared/defguard-ui/components/Layout/MessageBox/MessageBox'; -import { MessageBoxType } from '../../../../shared/defguard-ui/components/Layout/MessageBox/types'; -import type { ToggleOption } from '../../../../shared/defguard-ui/components/Layout/Toggle/types'; -import useApi from '../../../../shared/hooks/useApi'; -import { useToaster } from '../../../../shared/hooks/useToaster'; -import { externalLink } from '../../../../shared/links'; -import { MutationKeys } from '../../../../shared/mutations'; -import { patternValidWireguardKey } from '../../../../shared/patterns'; -import { QueryKeys } from '../../../../shared/queries'; -import { generateWGKeys } from '../../../../shared/utils/generateWGKeys'; -import { trimObjectStrings } from '../../../../shared/utils/trimObjectStrings'; -import { useAddDevicePageStore } from '../../hooks/useAddDevicePageStore'; -import { - AddDeviceNavigationEvent, - AddDeviceStep, - AddNativeWgDeviceMode, -} from '../../types'; - -export const AddDeviceSetupStep = () => { - const { LL } = useI18nContext(); - const localLL = LL.addDevicePage.steps.setupDevice; - const toaster = useToaster(); - const { - device: { addDevice }, - } = useApi(); - const submitRef = useRef(null); - const userData = useAddDevicePageStore((state) => state.userData); - const [setStep, navSubject] = useAddDevicePageStore( - (state) => [state.setStep, state.navigationSubject], - shallow, - ); - - const toggleOptions = useMemo(() => { - const res: ToggleOption[] = [ - { - text: localLL.options.auto(), - value: AddNativeWgDeviceMode.AUTO, - }, - { - text: localLL.options.manual(), - value: AddNativeWgDeviceMode.MANUAL, - }, - ]; - return res; - }, [localLL.options]); - - const zodSchema = useMemo( - () => - z - .object({ - choice: z.nativeEnum(AddNativeWgDeviceMode), - name: z - .string() - .trim() - .min(4, LL.form.error.minimumLength()) - .refine((val) => !userData?.reservedDevices?.includes(val), { - message: localLL.form.errors.name.duplicatedName(), - }), - publicKey: z.string().trim(), - }) - .superRefine((val, ctx) => { - const { publicKey, choice } = val; - if (choice === AddNativeWgDeviceMode.MANUAL) { - const pubKeyRes = z - .string() - .min(44, LL.form.error.minimumLength()) - .max(44, LL.form.error.maximumLength()) - .regex(patternValidWireguardKey, LL.form.error.invalid()) - .safeParse(publicKey); - if (!pubKeyRes.success) { - ctx.addIssue({ - code: 'custom', - message: pubKeyRes.error.message, - path: ['publicKey'], - }); - } - } else { - const pubKeyRes = z.string().safeParse(publicKey); - if (!pubKeyRes.success) { - ctx.addIssue({ - code: 'custom', - path: ['publicKey'], - }); - } - } - }), - [LL.form.error, localLL.form.errors.name, userData?.reservedDevices], - ); - - type FormFields = z.infer; - - const { handleSubmit, control } = useForm({ - defaultValues: { - name: '', - choice: AddNativeWgDeviceMode.AUTO, - publicKey: '', - }, - resolver: zodResolver(zodSchema), - mode: 'all', - }); - - const queryClient = useQueryClient(); - - const { mutateAsync: addDeviceMutation } = useMutation({ - mutationFn: addDevice, - mutationKey: [MutationKeys.ADD_DEVICE], - onSuccess: () => { - void queryClient.invalidateQueries({ - queryKey: [QueryKeys.FETCH_USER_PROFILE], - }); - void queryClient.invalidateQueries({ - queryKey: ['user'], - }); - toaster.success(LL.addDevicePage.messages.deviceAdded()); - }, - onError: (err) => { - toaster.error(LL.messages.error()); - console.error(err); - }, - }); - - const validSubmitHandler: SubmitHandler = (values) => { - if (!userData) return; - values = trimObjectStrings(values); - if (values.choice === AddNativeWgDeviceMode.AUTO) { - const keys = generateWGKeys(); - void addDeviceMutation({ - name: values.name, - wireguard_pubkey: keys.publicKey, - username: userData.username, - }).then((response) => { - setStep(AddDeviceStep.NATIVE_CONFIGURATION, { - device: response.device, - publicKey: keys.publicKey, - privateKey: keys.privateKey, - networks: response.configs.map((c) => ({ - networkName: c.network_name, - networkId: c.network_id, - })), - }); - }); - } else { - void addDeviceMutation({ - name: values.name, - wireguard_pubkey: values.publicKey, - username: userData.username, - }).then((response) => { - setStep(AddDeviceStep.NATIVE_CONFIGURATION, { - device: response.device, - publicKey: values.publicKey, - privateKey: undefined, - networks: response.configs.map((c) => ({ - networkName: c.network_name, - networkId: c.network_id, - })), - }); - }); - } - }; - - const { - field: { value: choiceValue }, - } = useController({ control, name: 'choice' }); - - useEffect(() => { - const sub = navSubject.subscribe((event) => { - if (event === AddDeviceNavigationEvent.NEXT) { - submitRef.current?.click(); - } - }); - return () => { - sub.unsubscribe(); - }; - }, [navSubject]); - - return ( - -

{localLL.title()}

- - {parser( - localLL.infoMessage({ - addDevicesDocs: externalLink.gitbook.wireguard.addDevices, - }), - )} - -
- - - - - -
- ); -}; diff --git a/web/src/pages/addDevice/steps/AddDeviceSetupStep/style.scss b/web/src/pages/addDevice/steps/AddDeviceSetupStep/style.scss deleted file mode 100644 index 41c9e4e89..000000000 --- a/web/src/pages/addDevice/steps/AddDeviceSetupStep/style.scss +++ /dev/null @@ -1,9 +0,0 @@ -@use '@scssutils' as *; - -#add-device-page { - #add-device-setup-step { - h2 { - margin-bottom: 25px; - } - } -} diff --git a/web/src/pages/addDevice/style.scss b/web/src/pages/addDevice/style.scss deleted file mode 100644 index a66e5b930..000000000 --- a/web/src/pages/addDevice/style.scss +++ /dev/null @@ -1,111 +0,0 @@ -@use '@scssutils' as *; - -#add-device-page { - & > .page-content { - box-sizing: border-box; - padding: 100px 40px; - display: flex; - flex-flow: column; - align-items: center; - justify-content: flex-start; - overflow: auto; - - h1 { - color: var(--text-body-primary); - user-select: none; - - @include typography(app-title); - } - - h2 { - color: var(--text-body-primary); - padding-bottom: var(--spacing-s); - user-select: none; - - @include typography(app-body-1); - } - - & > .content-wrapper { - width: 100%; - max-width: 920px; - display: flex; - flex-flow: column; - align-items: center; - justify-content: flex-start; - width: 100%; - row-gap: 40px; - - & > * { - width: 100%; - } - - & > .card { - box-sizing: border-box; - padding: var(--spacing-l); - - .message-box-spacer { - padding-bottom: 20px; - } - - form { - & > * { - width: 100%; - } - } - } - - & > header { - display: flex; - flex-flow: row wrap; - align-items: center; - justify-content: flex-start; - user-select: none; - - & > .controls { - margin-left: auto; - display: flex; - flex-flow: row nowrap; - align-items: center; - justify-content: flex-start; - column-gap: 32px; - - & > .btn { - min-width: 180px; - - &.nav-back { - .arrow-single { - svg { - g { - fill: var(--surface-icon-primary); - } - } - } - } - - .arrow-single { - svg { - g { - fill: var(--surface-icon-secondary); - } - } - } - } - } - } - } - - form { - width: 100%; - display: flex; - flex-flow: column; - align-items: center; - justify-content: flex-start; - - & > * { - &:not(.input, .select) { - padding-bottom: 20px; - } - } - } - } -} diff --git a/web/src/pages/addDevice/types.ts b/web/src/pages/addDevice/types.ts deleted file mode 100644 index f0771818b..000000000 --- a/web/src/pages/addDevice/types.ts +++ /dev/null @@ -1,16 +0,0 @@ -export enum AddDeviceStep { - CHOOSE_METHOD, - NATIVE_CHOOSE_METHOD, - NATIVE_CONFIGURATION, - CLIENT_CONFIGURATION, -} - -export enum AddNativeWgDeviceMode { - AUTO, - MANUAL, -} - -export enum AddDeviceNavigationEvent { - NEXT, - BACK, -} diff --git a/web/src/pages/addDevice/utils/enrollmentToToken.ts b/web/src/pages/addDevice/utils/enrollmentToToken.ts deleted file mode 100644 index 5d3cdf52d..000000000 --- a/web/src/pages/addDevice/utils/enrollmentToToken.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { fromUint8Array } from 'js-base64'; - -export type EnrollmentData = { - url: string; - token: string; -}; - -export const enrollmentToImportToken = (url: string, token: string): string => { - const data: EnrollmentData = { - token, - url, - }; - const jsonString = JSON.stringify(data); - const textEncoder = new TextEncoder(); - const encoded = textEncoder.encode(jsonString); - return fromUint8Array(encoded); -}; diff --git a/web/src/pages/allow/OpenidAllowPage.tsx b/web/src/pages/allow/OpenidAllowPage.tsx deleted file mode 100644 index b600084d4..000000000 --- a/web/src/pages/allow/OpenidAllowPage.tsx +++ /dev/null @@ -1,156 +0,0 @@ -import './style.scss'; - -import type { AxiosError } from 'axios'; -import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { useNavigate, useSearchParams } from 'react-router-dom'; - -import { useI18nContext } from '../../i18n/i18n-react'; -import SvgDefguardLogoLogin from '../../shared/components/svg/DefguardLogoLogin'; -import SvgIconCheckmarkWhite from '../../shared/components/svg/IconCheckmarkWhite'; -import SvgIconDelete from '../../shared/components/svg/IconDelete'; -import { Button } from '../../shared/defguard-ui/components/Layout/Button/Button'; -import { - ButtonSize, - ButtonStyleVariant, -} from '../../shared/defguard-ui/components/Layout/Button/types'; -import { useAuthStore } from '../../shared/hooks/store/useAuthStore'; -import useApi from '../../shared/hooks/useApi'; -import { useToaster } from '../../shared/hooks/useToaster'; -import { LoaderPage } from '../loader/LoaderPage'; - -export const OpenidAllowPage = () => { - const navigate = useNavigate(); - - const [allowLoading, setAllowLoading] = useState(false); - const [cancelLoading, setCancelLoading] = useState(false); - const [params] = useSearchParams(); - const [scope, setScope] = useState(''); - const [responseType, setResponseType] = useState(''); - const [clientId, setClientId] = useState(''); - const [redirectUri, setRedirectUri] = useState(''); - const [state, setState] = useState(''); - const [name, setName] = useState(''); - const inputRef = useRef(null); - const { - openid: { getOpenidClient }, - } = useApi(); - const setAuthStore = useAuthStore((state) => state.setState); - const [loadingInfo, setLoadingInfo] = useState(true); - const toaster = useToaster(); - - const { LL } = useI18nContext(); - - const paramsValid = useMemo(() => { - // nonce is optional in the auth code flow, just pass it as is further if it's in the params - const check = [scope, responseType, clientId, redirectUri, state]; - for (const item of check) { - if (typeof item === 'undefined' || item === null) { - toaster.error('OpenID Params invalid.'); - return false; - } - } - return true; - }, [clientId, redirectUri, responseType, scope, state, toaster]); - - const handleSubmit = useCallback( - (allow: boolean) => { - params.append('allow', String(allow)); - const formAction = `/api/v1/oauth/authorize?${params.toString()}`; - if (inputRef.current) { - inputRef.current.formAction = formAction; - inputRef.current.click(); - } - }, - [params], - ); - - useEffect(() => { - setScope(params.get('scope')); - setResponseType(params.get('response_type')); - setClientId(params.get('client_id')); - setState(params.get('state')); - setRedirectUri(params.get('redirect_uri')); - }, [params]); - - // biome-ignore lint/correctness/useExhaustiveDependencies: migration, checkMeLater - useEffect(() => { - if (paramsValid && clientId) { - getOpenidClient(clientId) - .then((res) => { - setName(res.name); - setLoadingInfo(false); - }) - .catch((error: AxiosError) => { - if (error.response?.status === 401) { - setAuthStore({ openIdParams: params }); - setLoadingInfo(false); - navigate('/auth', { replace: true }); - } else { - navigate('/', { replace: true }); - } - }); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [paramsValid, clientId]); - - const scopes: Record = { - openid: LL.openidAllow.scopes.openid(), - profile: LL.openidAllow.scopes.profile(), - email: LL.openidAllow.scopes.email(), - phone: LL.openidAllow.scopes.phone(), - groups: LL.openidAllow.scopes.groups(), - }; - - if (loadingInfo) return ; - - return ( - - ); -}; diff --git a/web/src/pages/allow/style.scss b/web/src/pages/allow/style.scss deleted file mode 100644 index fe252e197..000000000 --- a/web/src/pages/allow/style.scss +++ /dev/null @@ -1,68 +0,0 @@ -#openid-consent { - background-color: var(--bg-light); - height: 100%; - width: 100%; - display: flex; - flex-direction: row; - align-content: center; - justify-content: center; - align-items: center; - justify-items: center; - min-height: 100dvh; - - & > * { - @include media-breakpoint-up(lg) { - min-height: 100dvh; - } - } - - .logo-container { - display: none; - flex-direction: column; - align-content: center; - align-items: center; - justify-content: center; - background-color: var(--primary); - flex-grow: 1; - flex-shrink: 0; - width: 50%; - height: 100%; - - @include media-breakpoint-up(lg) { - display: flex; - } - } - - & > .consent { - width: 100%; - display: flex; - justify-content: center; - align-items: center; - flex-direction: column; - - ul { - margin: 0; - box-sizing: border-box; - padding: var(--spacing-l) 0; - width: 330px; - } - - .controls { - flex-direction: column; - gap: 1rem; - - button { - width: 330px; - margin-bottom: var(--spacing-s); - - &:last-of-type { - margin: 0; - } - } - } - } - - & > form { - display: none; - } -} diff --git a/web/src/pages/auth/AuthPage.tsx b/web/src/pages/auth/AuthPage.tsx deleted file mode 100644 index 5a81371ae..000000000 --- a/web/src/pages/auth/AuthPage.tsx +++ /dev/null @@ -1,178 +0,0 @@ -import './style.scss'; - -import { useEffect, useMemo, useState } from 'react'; -import { Navigate, Route, Routes, useNavigate, useSearchParams } from 'react-router-dom'; -import { shallow } from 'zustand/shallow'; - -import { useI18nContext } from '../../i18n/i18n-react'; -import SvgDefguardLogoLogin from '../../shared/components/svg/DefguardLogoLogin'; -import { useAppStore } from '../../shared/hooks/store/useAppStore'; -import { useAuthStore } from '../../shared/hooks/store/useAuthStore'; -import useApi from '../../shared/hooks/useApi'; -import { useToaster } from '../../shared/hooks/useToaster'; -import { UserMFAMethod } from '../../shared/types'; -import { RedirectPage } from '../redirect/RedirectPage'; -import { OpenIDCallback } from './Callback/Callback'; -import { Login } from './Login/Login'; -import { MFARoute } from './MFARoute/MFARoute'; -import { useMFAStore } from './shared/hooks/useMFAStore'; - -const VALID_URL_PATTERN = - /^(https?:\/\/[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9](?:\.[a-zA-Z]{2,})+(?::[0-9]{1,5})?(?:\/[a-zA-Z0-9\-._~%!$&'()*+,;=:@/]*)?(?:\?[a-zA-Z0-9\-._~%!$&'()*+,;=:@/?]*)?|\/[a-zA-Z0-9\-._~%!$&'()*+,;=:@/]*(?:\?[a-zA-Z0-9\-._~%!$&'()*+,;=:@/?]*)?)$/gi; - -// Return redirect URL only if it matches a safe pattern: -// - starts with http/https -// - contains only safe characters (no <, >) -// - can include query params -// -// Once a URL matches this pattern we also explicitly check for unsafe elements in case they are a part of redirect URL query params -const sanitizeRedirectUrl = (url: string | null) => { - if (url?.match(VALID_URL_PATTERN) && !/javascript:|data:|\\/.test(url)) return url; - - return null; -}; - -export const AuthPage = () => { - const { - getAppInfo, - settings: { getSettings }, - } = useApi(); - const { LL } = useI18nContext(); - const navigate = useNavigate(); - const [showRedirect, setShowRedirect] = useState(false); - - const loginSubject = useAuthStore((state) => state.loginSubject); - - const setAuthStore = useAuthStore((state) => state.setState); - - const [openIdParams, user] = useAuthStore( - (state) => [state.openIdParams, state.user], - shallow, - ); - - const mfaMethod = useMFAStore((state) => state.mfa_method); - - const [setMFAStore, resetMFAStore] = useMFAStore( - (state) => [state.setState, state.resetState], - shallow, - ); - - const settings = useAppStore((state) => state.settings); - - const toaster = useToaster(); - - const setAppStore = useAppStore((state) => state.setState); - - const [params] = useSearchParams(); - const redirectUrl = useMemo(() => sanitizeRedirectUrl(params.get('r')), [params]); - - useEffect(() => { - if (user && (!mfaMethod || mfaMethod === UserMFAMethod.NONE) && !openIdParams) { - navigate('/', { replace: true }); - } - }, [mfaMethod, navigate, openIdParams, user]); - - // biome-ignore lint/correctness/useExhaustiveDependencies: migration, checkMeLater - useEffect(() => { - // eslint-disable-next-line @typescript-eslint/no-misused-promises - const sub = loginSubject.subscribe(async ({ user, url, mfa }): Promise => { - // handle forward auth redirect - if (redirectUrl && user) { - setShowRedirect(true); - resetMFAStore(); - window.location.replace(redirectUrl); - return; - } - - // handle openid scenarios - - // user authenticated but app needs consent - if (openIdParams && user && !mfa) { - navigate(`/consent?${openIdParams.toString()}`, { replace: true }); - return; - } - - // application already had consent from user - if (url?.length && user) { - setShowRedirect(true); - resetMFAStore(); - window.location.replace(url); - return; - } - - if (mfa) { - setMFAStore(mfa); - let mfaUrl = ''; - switch (mfa.mfa_method) { - case UserMFAMethod.WEB_AUTH_N: - mfaUrl = '/auth/mfa/webauthn'; - break; - case UserMFAMethod.ONE_TIME_PASSWORD: - mfaUrl = '/auth/mfa/totp'; - break; - case UserMFAMethod.EMAIL: - mfaUrl = '/auth/mfa/email'; - break; - default: - toaster.error(LL.messages.error()); - console.error('API did not return any MFA method in MFA flow.'); - return; - } - navigate(mfaUrl, { replace: true }); - return; - } - - // authorization finished - if (user) { - let navigateURL = '/me'; - if (user.is_admin) { - // check where to navigate administrator - const appInfo = await getAppInfo(); - const settings = await getSettings(); - setAppStore({ - appInfo, - settings, - }); - if (settings.wireguard_enabled) { - if (!appInfo?.network_present) { - navigateURL = '/admin/wizard'; - } else { - navigateURL = '/admin/overview'; - } - } else { - navigateURL = '/admin/users'; - } - } - setAuthStore({ user }); - resetMFAStore(); - navigate(navigateURL, { replace: true }); - } - }); - return () => sub?.unsubscribe(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [loginSubject, openIdParams, redirectUrl]); - - if (showRedirect) return ; - - return ( -
- - - } /> - } /> - } /> - } /> - } /> - } /> - -
- ); -}; diff --git a/web/src/pages/auth/Callback/Callback.tsx b/web/src/pages/auth/Callback/Callback.tsx deleted file mode 100644 index 70c620233..000000000 --- a/web/src/pages/auth/Callback/Callback.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import './style.scss'; - -import { useMutation } from '@tanstack/react-query'; -import type { AxiosError } from 'axios'; -import { useEffect, useState } from 'react'; -import { useNavigate } from 'react-router'; - -import { useI18nContext } from '../../../i18n/i18n-react'; -import { Button } from '../../../shared/defguard-ui/components/Layout/Button/Button'; -import { LoaderSpinner } from '../../../shared/defguard-ui/components/Layout/LoaderSpinner/LoaderSpinner'; -import { useAuthStore } from '../../../shared/hooks/store/useAuthStore'; -import useApi from '../../../shared/hooks/useApi'; -import { useToaster } from '../../../shared/hooks/useToaster'; -import { MutationKeys } from '../../../shared/mutations'; -import type { CallbackData } from '../../../shared/types'; - -type ErrorResponse = { - msg: string; -}; - -export const OpenIDCallback = () => { - const { - auth: { - openid: { callback }, - }, - } = useApi(); - const loginSubject = useAuthStore((state) => state.loginSubject); - const toaster = useToaster(); - const { LL } = useI18nContext(); - const [error, setError] = useState(null); - const navigate = useNavigate(); - - const callbackMutation = useMutation({ - mutationFn: callback, - mutationKey: [MutationKeys.OPENID_CALLBACK], - onSuccess: (data) => loginSubject.next(data), - onError: (error: AxiosError) => { - toaster.error(LL.messages.error()); - console.error(error); - const errorResponse = error.response?.data as ErrorResponse; - if (errorResponse.msg) { - setError(errorResponse.msg); - } else { - setError(JSON.stringify(error)); - } - }, - retry: false, - }); - - // biome-ignore lint/correctness/useExhaustiveDependencies: migration, checkMeLater - useEffect(() => { - if (window.location.search && window.location.search.length > 0) { - // const hashFragment = window.location.search.substring(1); - const params = new URLSearchParams(window.location.search); - - // check if error occurred - const error = params.get('error'); - - if (error) { - setError(error); - toaster.error(LL.messages.error()); - return; - } - - const code = params.get('code'); - const state = params.get('state'); - - if (code && state) { - const data: CallbackData = { - code, - state, - }; - callbackMutation.mutate(data); - } else { - setError('Expected data not returned by the OpenID provider'); - toaster.error(LL.messages.error()); - return; - } - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - // FIXME: make it a bit more user friendly - return error ? ( -
-

{LL.loginPage.callback.error()}:

-

{error}

-
- ) : ( - - ); -}; diff --git a/web/src/pages/auth/Callback/style.scss b/web/src/pages/auth/Callback/style.scss deleted file mode 100644 index 2a75550de..000000000 --- a/web/src/pages/auth/Callback/style.scss +++ /dev/null @@ -1,12 +0,0 @@ -.error-info { - display: flex; - flex-direction: column; - gap: 10px; - text-align: center; - align-items: center; - justify-content: center; -} - -#back-to-login { - width: fit-content; -} diff --git a/web/src/pages/auth/Login/Login.tsx b/web/src/pages/auth/Login/Login.tsx deleted file mode 100644 index 6e55a34df..000000000 --- a/web/src/pages/auth/Login/Login.tsx +++ /dev/null @@ -1,163 +0,0 @@ -import './style.scss'; - -import { zodResolver } from '@hookform/resolvers/zod'; -import { useMutation, useQuery } from '@tanstack/react-query'; -import type { AxiosError } from 'axios'; -import { useMemo } from 'react'; -import { type SubmitHandler, useForm } from 'react-hook-form'; -import { z } from 'zod'; -import { useI18nContext } from '../../../i18n/i18n-react'; -import { FormInput } from '../../../shared/defguard-ui/components/Form/FormInput/FormInput'; -import { Button } from '../../../shared/defguard-ui/components/Layout/Button/Button'; -import { - ButtonSize, - ButtonStyleVariant, -} from '../../../shared/defguard-ui/components/Layout/Button/types'; -import { LoaderSpinner } from '../../../shared/defguard-ui/components/Layout/LoaderSpinner/LoaderSpinner'; -import { isPresent } from '../../../shared/defguard-ui/utils/isPresent'; -import { useAppStore } from '../../../shared/hooks/store/useAppStore'; -import { useAuthStore } from '../../../shared/hooks/store/useAuthStore'; -import useApi from '../../../shared/hooks/useApi'; -import { useToaster } from '../../../shared/hooks/useToaster'; -import { MutationKeys } from '../../../shared/mutations'; -import { patternLoginCharacters } from '../../../shared/patterns'; -import { QueryKeys } from '../../../shared/queries'; -import { trimObjectStrings } from '../../../shared/utils/trimObjectStrings'; -import { OpenIdLoginButton } from './components/OidcButtons'; - -type Inputs = { - username: string; - password: string; -}; - -export const Login = () => { - const { LL } = useI18nContext(); - const { - auth: { - login, - openid: { getOpenIdInfo: getOpenidInfo }, - }, - } = useApi(); - const toaster = useToaster(); - - const enterpriseEnabled = useAppStore((s) => s.appInfo?.license_info.enterprise); - - const { data: openIdInfo, isLoading: openIdLoading } = useQuery({ - enabled: enterpriseEnabled, - queryKey: [QueryKeys.FETCH_OPENID_INFO], - queryFn: getOpenidInfo, - refetchOnMount: true, - refetchOnWindowFocus: false, - retry: false, - }); - - const zodSchema = useMemo( - () => - z.object({ - username: z - .string() - .trim() - .min(1, LL.form.error.minimumLength()) - .max(64) - .regex(patternLoginCharacters, LL.form.error.forbiddenCharacter()), - password: z - .string() - .trim() - .min(1, LL.form.error.required()) - .max(128, LL.form.error.maximumLength()), - }), - [LL.form.error], - ); - - const { handleSubmit, control, setError } = useForm({ - resolver: zodResolver(zodSchema), - mode: 'all', - defaultValues: { - password: '', - username: '', - }, - }); - - const loginSubject = useAuthStore((state) => state.loginSubject); - - const loginMutation = useMutation({ - mutationFn: login, - mutationKey: [MutationKeys.LOG_IN], - onSuccess: (data) => loginSubject.next(data), - onError: (error: AxiosError) => { - const status = error.response?.status; - if (isPresent(status)) { - switch (status) { - case 401: { - setError( - 'password', - { - message: 'username or password is incorrect', - }, - { shouldFocus: true }, - ); - break; - } - case 429: { - toaster.error(LL.form.error.tooManyBadLoginAttempts()); - break; - } - default: { - toaster.error(LL.messages.error()); - } - } - } else { - toaster.error(LL.messages.error()); - } - }, - }); - - const onSubmit: SubmitHandler = (data) => { - if (!loginMutation.isPending) { - loginMutation.mutate(trimObjectStrings(data)); - } - }; - - return ( -
- {!enterpriseEnabled || !openIdLoading ? ( - <> -

{LL.loginPage.pageTitle()}

-
- - -
- ); -}; diff --git a/web/src/pages/auth/Login/components/OidcButtons.tsx b/web/src/pages/auth/Login/components/OidcButtons.tsx deleted file mode 100644 index c3f0c27c0..000000000 --- a/web/src/pages/auth/Login/components/OidcButtons.tsx +++ /dev/null @@ -1,193 +0,0 @@ -import './style.scss'; - -import { useI18nContext } from '../../../../i18n/i18n-react'; -import { Button } from '../../../../shared/defguard-ui/components/Layout/Button/Button'; -import { - ButtonSize, - ButtonStyleVariant, -} from '../../../../shared/defguard-ui/components/Layout/Button/types'; - -export const OpenIdLoginButton = ({ - url, - display_name, -}: { - url: string; - display_name?: string; -}) => { - const { hostname } = new URL(url); - - if (hostname === 'accounts.google.com') { - return ; - } else if (hostname === 'login.microsoftonline.com') { - return ; - } else { - return ; - } -}; - -const GoogleButton = ({ url }: { url: string }) => { - return ( - - ); -}; - -const CustomButton = ({ url, display_name }: { url: string; display_name?: string }) => { - const { LL } = useI18nContext(); - return ( - - ); -}; diff --git a/web/src/pages/auth/Login/components/style.scss b/web/src/pages/auth/Login/components/style.scss deleted file mode 100644 index 422c0fcec..000000000 --- a/web/src/pages/auth/Login/components/style.scss +++ /dev/null @@ -1,120 +0,0 @@ -.gsi-material-button { - -moz-user-select: none; - -webkit-user-select: none; - -ms-user-select: none; - -webkit-appearance: none; - background-color: WHITE; - background-image: none; - border: 1px solid #747775; - -webkit-border-radius: 4px; - border-radius: 4px; - -webkit-box-sizing: border-box; - box-sizing: border-box; - color: #1f1f1f; - cursor: pointer; - font-family: 'Roboto', arial, sans-serif; - font-size: 14px; - height: 40px; - letter-spacing: 0.25px; - outline: none; - overflow: hidden; - padding: 0 12px; - position: relative; - text-align: center; - -webkit-transition: - background-color 0.218s, - border-color 0.218s, - box-shadow 0.218s; - transition: - background-color 0.218s, - border-color 0.218s, - box-shadow 0.218s; - vertical-align: middle; - white-space: nowrap; - width: auto; - max-width: 400px; - min-width: min-content; -} - -.gsi-material-button .gsi-material-button-icon { - height: 20px; - margin-right: 12px; - min-width: 20px; - width: 20px; -} - -.gsi-material-button .gsi-material-button-content-wrapper { - -webkit-align-items: center; - align-items: center; - display: flex; - -webkit-flex-direction: row; - flex-direction: row; - -webkit-flex-wrap: nowrap; - flex-wrap: nowrap; - height: 100%; - justify-content: space-between; - position: relative; - width: 100%; -} - -.gsi-material-button .gsi-material-button-contents { - -webkit-flex-grow: 1; - flex-grow: 1; - font-family: 'Roboto', arial, sans-serif; - font-weight: 500; - overflow: hidden; - text-overflow: ellipsis; - vertical-align: top; -} - -.gsi-material-button .gsi-material-button-state { - -webkit-transition: opacity 0.218s; - transition: opacity 0.218s; - bottom: 0; - left: 0; - opacity: 0; - position: absolute; - right: 0; - top: 0; -} - -.gsi-material-button:disabled { - cursor: default; - background-color: #ffffff61; - border-color: #1f1f1f1f; -} - -.gsi-material-button:disabled .gsi-material-button-contents { - opacity: 38%; -} - -.gsi-material-button:disabled .gsi-material-button-icon { - opacity: 38%; -} - -.gsi-material-button:not(:disabled):active .gsi-material-button-state, -.gsi-material-button:not(:disabled):focus .gsi-material-button-state { - background-color: #303030; - opacity: 12%; -} - -.gsi-material-button:not(:disabled):hover { - -webkit-box-shadow: - 0 1px 2px 0 rgba(60, 64, 67, 0.3), - 0 1px 3px 1px rgba(60, 64, 67, 0.15); - box-shadow: - 0 1px 2px 0 rgba(60, 64, 67, 0.3), - 0 1px 3px 1px rgba(60, 64, 67, 0.15); -} - -.gsi-material-button:not(:disabled):hover .gsi-material-button-state { - background-color: #303030; - opacity: 8%; -} - -.ms-button { - background-color: transparent; - border: none; - cursor: pointer; - padding: 0; -} diff --git a/web/src/pages/auth/Login/style.scss b/web/src/pages/auth/Login/style.scss deleted file mode 100644 index a35ae9200..000000000 --- a/web/src/pages/auth/Login/style.scss +++ /dev/null @@ -1,47 +0,0 @@ -@use '@scssutils' as *; - -#login-container { - min-height: 100vh; - width: 100%; - display: flex; - flex-flow: column; - align-items: center; - justify-content: center; - box-sizing: border-box; - padding: 20px 0; - - @include media-breakpoint-up(lg) { - padding: 40px 0; - } - - h1 { - @include typography(app-welcome-1); - - color: var(--text-body-primary); - text-align: center; - width: 100%; - margin-bottom: 24px; - } - - form { - width: 100%; - max-width: 300px; - box-sizing: border-box; - - @include media-breakpoint-down(lg) { - padding: 10px; - } - - & > .input { - margin-bottom: 5px; - } - - & > .btn { - margin-bottom: 10px; - } - - & > * { - width: 100%; - } - } -} diff --git a/web/src/pages/auth/LoginEmail/LoginEmail.tsx b/web/src/pages/auth/LoginEmail/LoginEmail.tsx new file mode 100644 index 000000000..ecf5cca79 --- /dev/null +++ b/web/src/pages/auth/LoginEmail/LoginEmail.tsx @@ -0,0 +1,75 @@ +import { useMutation, useQuery } from '@tanstack/react-query'; +import type z from 'zod'; +import { m } from '../../../paraglide/messages'; +import api from '../../../shared/api/api'; +import { LoginPage } from '../../../shared/components/LoginPage/LoginPage'; +import { SizedBox } from '../../../shared/defguard-ui/components/SizedBox/SizedBox'; +import { ThemeSpacing } from '../../../shared/defguard-ui/types'; +import { useAppForm } from '../../../shared/form'; +import { formChangeLogic } from '../../../shared/formLogic'; +import { useAuth } from '../../../shared/hooks/useAuth'; +import { totpCodeFormSchema } from '../../../shared/schema/totpCode'; +import { MfaLinks } from '../shared/MfaLinks/MfaLinks'; + +const formSchema = totpCodeFormSchema; + +type FormFields = z.infer; + +const defaultValues: FormFields = { + code: '', +}; + +export const LoginEmail = () => { + useQuery({ + queryFn: api.auth.mfa.email.resend, + queryKey: ['auth', 'email'], + refetchOnWindowFocus: false, + refetchOnReconnect: true, + refetchOnMount: true, + }); + + const { mutateAsync } = useMutation({ + mutationFn: api.auth.mfa.email.verify, + meta: { + invalidate: ['me'], + }, + onSuccess: (response) => { + useAuth.getState().authSubject.next(response.data); + }, + }); + + const form = useAppForm({ + defaultValues, + validationLogic: formChangeLogic, + validators: { + onSubmit: formSchema, + onChange: formSchema, + }, + onSubmit: async ({ value }) => { + await mutateAsync(value.code); + }, + }); + + return ( + +

{m.login_mfa_title()}

+

{m.login_mfa_email_subtitle()}

+ +
{ + e.stopPropagation(); + e.preventDefault(); + form.handleSubmit(); + }} + > + + + {(field) => } + + + +
+ +
+ ); +}; diff --git a/web/src/pages/auth/LoginLoading/LoginLoadingPage.tsx b/web/src/pages/auth/LoginLoading/LoginLoadingPage.tsx new file mode 100644 index 000000000..0e1cf989b --- /dev/null +++ b/web/src/pages/auth/LoginLoading/LoginLoadingPage.tsx @@ -0,0 +1,13 @@ +import './style.scss'; +import { LoginPage } from '../../../shared/components/LoginPage/LoginPage'; +import { LoaderSpinner } from '../../../shared/defguard-ui/components/LoaderSpinner/LoaderSpinner'; + +export const LoginLoadingPage = () => { + return ( + +
+ +
+
+ ); +}; diff --git a/web/src/pages/auth/LoginLoading/style.scss b/web/src/pages/auth/LoginLoading/style.scss new file mode 100644 index 000000000..8a9824ff8 --- /dev/null +++ b/web/src/pages/auth/LoginLoading/style.scss @@ -0,0 +1,10 @@ +#login-loading-page { + .loader-track { + display: flex; + flex-flow: column; + width: 100%; + height: max(50%, var(--spacing-9xl)); + align-items: center; + justify-content: center; + } +} diff --git a/web/src/pages/auth/LoginMain/LoginMainPage.tsx b/web/src/pages/auth/LoginMain/LoginMainPage.tsx new file mode 100644 index 000000000..69d97f250 --- /dev/null +++ b/web/src/pages/auth/LoginMain/LoginMainPage.tsx @@ -0,0 +1,164 @@ +import z from 'zod'; +import { m } from '../../../paraglide/messages'; +import { LoginPage } from '../../../shared/components/LoginPage/LoginPage'; +import { Button } from '../../../shared/defguard-ui/components/Button/Button'; +import { SizedBox } from '../../../shared/defguard-ui/components/SizedBox/SizedBox'; +import { ThemeSize, ThemeSpacing } from '../../../shared/defguard-ui/types'; +import { useAppForm } from '../../../shared/form'; +import './style.scss'; +import { revalidateLogic } from '@tanstack/react-form'; +import { useMutation, useQuery } from '@tanstack/react-query'; +import type { AxiosError } from 'axios'; +import { useEffect, useMemo, useRef, useState } from 'react'; +import api from '../../../shared/api/api'; +import type { OpenIdAuthInfo } from '../../../shared/api/types'; +import { Divider } from '../../../shared/defguard-ui/components/Divider/Divider'; +import { InfoBanner } from '../../../shared/defguard-ui/components/InfoBanner/InfoBanner'; +import { OIDCButton } from '../../../shared/defguard-ui/components/SSOButton/OIDCButton'; +import { isPresent } from '../../../shared/defguard-ui/utils/isPresent'; +import { createZodIssue } from '../../../shared/defguard-ui/utils/zod'; +import { useAuth } from '../../../shared/hooks/useAuth'; + +const formSchema = z.object({ + username: z.string(m.form_error_required()).trim().min(1, m.form_error_required()), + password: z.string(m.form_error_required()).trim().min(1, m.form_error_required()), +}); + +type FormFields = z.infer; + +const defaults: FormFields = { + username: '', + password: '', +}; + +export const LoginMainPage = () => { + const [tooManyAttempts, setTooManyAttempts] = useState(false); + const attemptsTimeoutRef = useRef(null); + + const { data: openIdAuthInfo } = useQuery({ + queryFn: api.openid.authInfo, + queryKey: ['openid', 'auth_info'], + select: (resp) => resp.data, + retry: false, + }); + + const form = useAppForm({ + defaultValues: defaults, + validationLogic: revalidateLogic({ + mode: 'change', + modeAfterSubmission: 'change', + }), + validators: { + onChange: formSchema, + onSubmit: formSchema, + }, + onSubmit: async ({ value }) => { + if (tooManyAttempts) return; + try { + const { data } = await mutateAsync(value); + useAuth.getState().authSubject.next(data); + } catch (_) {} + }, + }); + + const { mutateAsync } = useMutation({ + mutationFn: api.auth.login, + onError: (error: AxiosError) => { + const status = error.response?.status; + if (isPresent(status)) { + if (status === 401) { + form.setErrorMap({ + onSubmit: { + fields: { + password: createZodIssue(m.login_error_invalid(), ['password']), + }, + }, + }); + } + if (status === 429) { + setTooManyAttempts(true); + const timeoutId = setTimeout(() => { + setTooManyAttempts(false); + }, 300_000); + attemptsTimeoutRef.current = timeoutId; + } + } + }, + }); + + useEffect(() => { + return () => { + if (attemptsTimeoutRef.current !== null) { + clearTimeout(attemptsTimeoutRef.current); + } + }; + }, []); + + return ( + +

{m.login_main_title()}

+

{m.login_main_subtitle()}

+ + {tooManyAttempts && ( + <> + + + + )} + {isPresent(openIdAuthInfo) && } + +
{ + e.preventDefault(); + e.stopPropagation(); + form.handleSubmit(); + }} + > + + {(field) => } + + + {(field) => ( + + )} + +
- } - /> - -
- -
-
- -
-
- -
-
- {formatDate} -
-
- -
- - ); -}; - -const DeviceRowEditButton = (props: { data: StandaloneDevice }) => { - const { LL } = useI18nContext(); - const { - standaloneDevice: { getDeviceConfig, generateAuthToken }, - } = useApi(); - const toaster = useToaster(); - const { mutateAsync, isPending: deviceConfigLoading } = useMutation({ - mutationFn: getDeviceConfig, - onError: (e) => { - toaster.error(LL.modals.standaloneDeviceConfigModal.toasters.getConfig.error()); - console.error(e); - }, - }); - - const { mutateAsync: mutateTokenGen, isPending: deviceTokenLoading } = useMutation({ - mutationFn: generateAuthToken, - onError: (e) => { - toaster.error(LL.modals.standaloneDeviceEnrollmentModal.toasters.error()); - console.error(e); - }, - }); - - const openDelete = useDeleteStandaloneDeviceModal((s) => s.open, shallow); - const openEdit = useEditStandaloneDeviceModal((s) => s.open, shallow); - const openConfig = useStandaloneDeviceConfigModal((s) => s.open); - const openEnrollment = useStandaloneDeviceEnrollmentModal((s) => s.open); - - const handleOpenConfig = useCallback(() => { - void mutateAsync(props.data.id).then((config) => { - openConfig({ - device: props.data, - config, - }); - }); - }, [mutateAsync, openConfig, props.data]); - - const handleTokenGen = useCallback(() => { - void mutateTokenGen(props.data.id).then((res) => { - openEnrollment({ - device: props.data, - enrollment: res, - }); - }); - }, [mutateTokenGen, openEnrollment, props.data]); - - return ( - - openEdit(props.data)} - /> - handleOpenConfig()} - disabled={!props.data.configured || deviceConfigLoading} - /> - handleTokenGen()} - disabled={deviceTokenLoading} - /> - openDelete(props.data)} - /> - - ); -}; diff --git a/web/src/pages/devices/components/DevicesList/modals/ConfirmDeviceDeleteModal.tsx b/web/src/pages/devices/components/DevicesList/modals/ConfirmDeviceDeleteModal.tsx deleted file mode 100644 index 12e4cc195..000000000 --- a/web/src/pages/devices/components/DevicesList/modals/ConfirmDeviceDeleteModal.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { shallow } from 'zustand/shallow'; - -import { useI18nContext } from '../../../../../i18n/i18n-react'; -import { ConfirmModal } from '../../../../../shared/defguard-ui/components/Layout/modals/ConfirmModal/ConfirmModal'; -import useApi from '../../../../../shared/hooks/useApi'; -import { useToaster } from '../../../../../shared/hooks/useToaster'; -import { QueryKeys } from '../../../../../shared/queries'; -import { useDeleteStandaloneDeviceModal } from '../../../hooks/useDeleteStandaloneDeviceModal'; - -export const ConfirmDeviceDeleteModal = () => { - const { LL } = useI18nContext(); - const localLL = LL.modals.deleteStandaloneDevice; - const [visible, device] = useDeleteStandaloneDeviceModal( - (s) => [s.visible, s.device], - shallow, - ); - const queryClient = useQueryClient(); - const [close, reset] = useDeleteStandaloneDeviceModal( - (s) => [s.close, s.reset], - shallow, - ); - - const { - standaloneDevice: { deleteDevice }, - } = useApi(); - - const toaster = useToaster(); - - const { mutate, isPending: isLoading } = useMutation({ - mutationFn: deleteDevice, - onSuccess: () => { - void queryClient.invalidateQueries({ - queryKey: [QueryKeys.FETCH_STANDALONE_DEVICE_LIST], - }); - close(); - toaster.success(localLL.messages.success()); - }, - onError: (e) => { - toaster.error(localLL.messages.error()); - close(); - console.error(e); - }, - }); - - const isOpen = visible && device !== undefined; - - return ( - { - if (device) { - mutate(device.id); - } - }} - onClose={close} - afterClose={reset} - loading={isLoading} - /> - ); -}; diff --git a/web/src/pages/devices/components/DevicesList/style.scss b/web/src/pages/devices/components/DevicesList/style.scss deleted file mode 100644 index a8c0a2131..000000000 --- a/web/src/pages/devices/components/DevicesList/style.scss +++ /dev/null @@ -1,118 +0,0 @@ -@mixin spacing { - display: inline-grid; - grid-template-rows: 1fr; - grid-template-columns: 250px 150px 350px 1fr 180px 200px 50px; - column-gap: 15px; -} - -#devices-page-devices-list { - overflow-x: auto; - min-width: 1540px; - - .scroll-container { - max-height: 650px; - } - - .headers { - @include spacing; - - & > :nth-child(1) { - padding-left: 50px; - } - - & > :nth-child(7) { - align-items: center; - justify-content: center; - - span { - text-align: center; - } - } - } - - .device-row { - width: 100%; - height: 60px; - box-sizing: border-box; - padding: 0 15px; - border: 1px solid transparent; - background-color: var(--surface-default-modal); - border-radius: 15px; - transition-property: border; - transition-duration: 100ms; - transition-timing-function: ease-in-out; - align-items: center; - - &:hover, - &.active { - border-color: var(--border-primary); - } - - @include spacing; - } -} - -.device-item-floating { - font-family: 'Roboto'; - font-size: 15px; - font-weight: 500; -} - -#devices-page-devices-list .device-row { - .avatar-icon { - min-width: 40px; - max-width: 40px; - } - - p, - span { - font-family: 'Roboto'; - font-size: 15px; - font-weight: 500; - } - - [class^='cell-'] { - height: 100%; - width: 100%; - max-width: 100%; - overflow: hidden; - display: flex; - flex-flow: row; - align-items: center; - justify-content: flex-start; - } - - .text-limited { - height: 100%; - } - - .cell-7 { - justify-content: center; - } - - .cell-1 { - display: flex; - flex-flow: row; - align-items: center; - justify-content: flex-start; - column-gap: 10px; - - .avatar-icon { - width: 40px; - height: 40px; - - svg { - width: 30px; - height: 30px; - } - } - } -} - -.copy { - background-color: transparent; - border: 0 solid transparent; - cursor: pointer; - display: flex; - align-items: center; -} diff --git a/web/src/pages/devices/hooks/useDeleteStandaloneDeviceModal.tsx b/web/src/pages/devices/hooks/useDeleteStandaloneDeviceModal.tsx deleted file mode 100644 index ba415d5bc..000000000 --- a/web/src/pages/devices/hooks/useDeleteStandaloneDeviceModal.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { createWithEqualityFn } from 'zustand/traditional'; - -import type { StandaloneDevice } from '../../../shared/types'; - -const defaultValues: StoreValues = { - visible: false, - device: undefined, -}; - -export const useDeleteStandaloneDeviceModal = createWithEqualityFn( - (set) => ({ - ...defaultValues, - open: (device) => set({ visible: true, device }), - close: () => set({ visible: false }), - reset: () => set(defaultValues), - }), - Object.is, -); - -type Store = StoreValues & StoreMethods; - -type StoreValues = { - visible: boolean; - device?: StandaloneDevice; -}; -type StoreMethods = { - open: (device: StandaloneDevice) => void; - close: () => void; - reset: () => void; -}; diff --git a/web/src/pages/devices/hooks/useDevicesPage.tsx b/web/src/pages/devices/hooks/useDevicesPage.tsx deleted file mode 100644 index e1fcc98a0..000000000 --- a/web/src/pages/devices/hooks/useDevicesPage.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { useState } from 'react'; -import { createContainer } from 'react-tracked'; - -import type { StandaloneDevice } from '../../../shared/types'; - -export type DevicesPageContext = { - devices: StandaloneDevice[]; - reservedDeviceNames: string[]; - search: string; -}; - -const initialState: DevicesPageContext = { - devices: [], - reservedDeviceNames: [], - search: '', -}; - -const useValue = () => useState(initialState); - -export const { Provider: DevicesPageProvider, useTracked: useDevicesPage } = - createContainer(useValue); diff --git a/web/src/pages/devices/hooks/useEditStandaloneDeviceModal.tsx b/web/src/pages/devices/hooks/useEditStandaloneDeviceModal.tsx deleted file mode 100644 index 5a17156f9..000000000 --- a/web/src/pages/devices/hooks/useEditStandaloneDeviceModal.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { createWithEqualityFn } from 'zustand/traditional'; - -import type { StandaloneDevice } from '../../../shared/types'; - -const defaults: StoreValues = { - visible: false, - device: undefined, -}; - -export const useEditStandaloneDeviceModal = createWithEqualityFn( - (set) => ({ - ...defaults, - open: (device) => set({ device: device, visible: true }), - close: () => set({ visible: false }), - reset: () => set(defaults), - }), - Object.is, -); - -type Store = StoreValues & StoreMethods; -type StoreValues = { - visible: boolean; - device?: StandaloneDevice; -}; -type StoreMethods = { - open: (device: StandaloneDevice) => void; - close: () => void; - reset: () => void; -}; diff --git a/web/src/pages/devices/modals/AddStandaloneDeviceModal/AddStandaloneDeviceModal.tsx b/web/src/pages/devices/modals/AddStandaloneDeviceModal/AddStandaloneDeviceModal.tsx deleted file mode 100644 index 414d3d545..000000000 --- a/web/src/pages/devices/modals/AddStandaloneDeviceModal/AddStandaloneDeviceModal.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import './style.scss'; - -import { useEffect, useMemo } from 'react'; -import { shallow } from 'zustand/shallow'; - -import { useI18nContext } from '../../../../i18n/i18n-react'; -import { ModalWithTitle } from '../../../../shared/defguard-ui/components/Layout/modals/ModalWithTitle/ModalWithTitle'; -import { FinishCliStep } from './steps/FinishCliStep/FinishCliStep'; -import { FinishManualStep } from './steps/FinishManualStep/FinishManualStep'; -import { MethodStep } from './steps/MethodStep/MethodStep'; -import { SetupCliStep } from './steps/SetupCliStep/SetupCliStep'; -import { SetupManualStep } from './steps/SetupManualStep/SetupManualStep'; -import { useAddStandaloneDeviceModal } from './store'; - -const steps = [ - , - , - , - , - , -]; - -export const AddStandaloneDeviceModal = () => { - const { LL } = useI18nContext(); - const localLL = LL.modals.addStandaloneDevice; - const [currentStep, visible] = useAddStandaloneDeviceModal( - (s) => [s.currentStep, s.visible], - shallow, - ); - const [close, reset] = useAddStandaloneDeviceModal((s) => [s.close, s.reset], shallow); - - const getTitle = useMemo(() => { - switch (currentStep.valueOf()) { - case 0: - return localLL.steps.method.title(); - case 1: - return localLL.steps.cli.title(); - case 2: - return localLL.steps.cli.title(); - case 3: - return localLL.steps.manual.title(); - case 4: - return localLL.steps.manual.title(); - } - }, [currentStep, localLL.steps.cli, localLL.steps.manual, localLL.steps.method]); - - // biome-ignore lint/correctness/useExhaustiveDependencies: migration, checkMeLater - useEffect(() => { - return () => { - reset(); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - return ( - close()} - afterClose={() => reset()} - /> - ); -}; diff --git a/web/src/pages/devices/modals/AddStandaloneDeviceModal/steps/FinishCliStep/FinishCliStep.tsx b/web/src/pages/devices/modals/AddStandaloneDeviceModal/steps/FinishCliStep/FinishCliStep.tsx deleted file mode 100644 index b42923e4e..000000000 --- a/web/src/pages/devices/modals/AddStandaloneDeviceModal/steps/FinishCliStep/FinishCliStep.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import './style.scss'; - -import { shallow } from 'zustand/shallow'; - -import { useI18nContext } from '../../../../../../i18n/i18n-react'; -import { Button } from '../../../../../../shared/defguard-ui/components/Layout/Button/Button'; -import { - ButtonSize, - ButtonStyleVariant, -} from '../../../../../../shared/defguard-ui/components/Layout/Button/types'; -import { StandaloneDeviceModalEnrollmentContent } from '../../../components/StandaloneDeviceModalEnrollmentContent/StandaloneDeviceModalEnrollmentContent'; -import { useAddStandaloneDeviceModal } from '../../store'; - -export const FinishCliStep = () => { - const { LL } = useI18nContext(); - const [closeModal] = useAddStandaloneDeviceModal((s) => [s.close], shallow); - const enroll = useAddStandaloneDeviceModal((s) => s.enrollResponse); - - if (!enroll) return null; - return ( -
- -
-
-
- ); -}; diff --git a/web/src/pages/devices/modals/AddStandaloneDeviceModal/steps/FinishCliStep/style.scss b/web/src/pages/devices/modals/AddStandaloneDeviceModal/steps/FinishCliStep/style.scss deleted file mode 100644 index 98058a417..000000000 --- a/web/src/pages/devices/modals/AddStandaloneDeviceModal/steps/FinishCliStep/style.scss +++ /dev/null @@ -1,27 +0,0 @@ -#add-standalone-device-modal { - .finish-cli-step { - .download { - width: 100%; - display: flex; - flex-flow: row; - align-items: center; - justify-content: center; - padding-bottom: 30px; - - a { - text-decoration: none; - cursor: pointer; - } - - .btn { - height: 47px; - } - } - - .expanded-content { - p { - @include typography(app-code); - } - } - } -} diff --git a/web/src/pages/devices/modals/AddStandaloneDeviceModal/steps/FinishManualStep/FinishManualStep.tsx b/web/src/pages/devices/modals/AddStandaloneDeviceModal/steps/FinishManualStep/FinishManualStep.tsx deleted file mode 100644 index 24895d80f..000000000 --- a/web/src/pages/devices/modals/AddStandaloneDeviceModal/steps/FinishManualStep/FinishManualStep.tsx +++ /dev/null @@ -1,164 +0,0 @@ -import './style.scss'; - -import { type ReactNode, useCallback, useMemo, useState } from 'react'; -import QRCode from 'react-qr-code'; -import { shallow } from 'zustand/shallow'; - -import { useI18nContext } from '../../../../../../i18n/i18n-react'; -import { RenderMarkdown } from '../../../../../../shared/components/Layout/RenderMarkdown/RenderMarkdown'; -import { ActionButton } from '../../../../../../shared/defguard-ui/components/Layout/ActionButton/ActionButton'; -import { ActionButtonVariant } from '../../../../../../shared/defguard-ui/components/Layout/ActionButton/types'; -import { Button } from '../../../../../../shared/defguard-ui/components/Layout/Button/Button'; -import { - ButtonSize, - ButtonStyleVariant, -} from '../../../../../../shared/defguard-ui/components/Layout/Button/types'; -import { ExpandableCard } from '../../../../../../shared/defguard-ui/components/Layout/ExpandableCard/ExpandableCard'; -import { MessageBox } from '../../../../../../shared/defguard-ui/components/Layout/MessageBox/MessageBox'; -import { MessageBoxType } from '../../../../../../shared/defguard-ui/components/Layout/MessageBox/types'; -import { useClipboard } from '../../../../../../shared/hooks/useClipboard'; -import { downloadWGConfig } from '../../../../../../shared/utils/downloadWGConfig'; -import { useAddStandaloneDeviceModal } from '../../store'; - -export const FinishManualStep = () => { - const { LL } = useI18nContext(); - const localLL = LL.modals.addStandaloneDevice.steps.manual.finish; - const [closeModal] = useAddStandaloneDeviceModal((s) => [s.close], shallow); - const manual = useAddStandaloneDeviceModal((s) => s.manualResponse); - const generatedKeys = useAddStandaloneDeviceModal((s) => s.genKeys); - - return ( -
- -
-

{LL.modals.addStandaloneDevice.form.labels.deviceName()}:

-

{manual?.device.name}

-
-
-

{localLL.ctaInstruction()}

-
- - - - {manual && ( - - )} -
-
-
- ); -}; - -type ConfigCardProps = { - config: string; - privateKey?: string; - publicKey: string; - deviceName: string; -}; - -enum ConfigCardView { - FILE, - QR, -} - -const DeviceConfigCard = ({ - config, - privateKey, - publicKey, - deviceName, -}: ConfigCardProps) => { - const { LL } = useI18nContext(); - const { writeToClipboard } = useClipboard(); - const localLL = LL.modals.addStandaloneDevice.steps.manual.finish; - const [view, setView] = useState(ConfigCardView.FILE); - - const configForExport = useMemo(() => { - if (privateKey) { - return config.replace('YOUR_PRIVATE_KEY', privateKey); - } - return config; - }, [config, privateKey]); - - const getQRConfig = useMemo((): string => { - if (privateKey) { - return config.replace('YOUR_PRIVATE_KEY', privateKey); - } - return config.replace('YOUR_PRIVATE_KEY', publicKey); - }, [config, privateKey, publicKey]); - - const renderTextConfig = () => { - const content = configForExport.split('\n'); - return ( -

- {content.map((text, index) => ( - <> - {text} - {index !== content.length - 1 &&
} - - ))} -

- ); - }; - - const handleConfigCopy = useCallback(() => { - void writeToClipboard( - configForExport, - LL.components.deviceConfigsCard.messages.copyConfig(), - ); - }, [LL.components.deviceConfigsCard.messages, configForExport, writeToClipboard]); - - const handleConfigDownload = useCallback(() => { - downloadWGConfig(configForExport, deviceName.toLowerCase().replace(' ', '-')); - }, [configForExport, deviceName]); - - const actions = useMemo( - (): ReactNode[] => [ - setView(ConfigCardView.FILE)} - />, - setView(ConfigCardView.QR)} - />, - , - , - ], - [handleConfigCopy, handleConfigDownload, view], - ); - return ( - - {view === ConfigCardView.FILE && renderTextConfig()} - {view === ConfigCardView.QR && } - - ); -}; diff --git a/web/src/pages/devices/modals/AddStandaloneDeviceModal/steps/FinishManualStep/style.scss b/web/src/pages/devices/modals/AddStandaloneDeviceModal/steps/FinishManualStep/style.scss deleted file mode 100644 index 8b4d33261..000000000 --- a/web/src/pages/devices/modals/AddStandaloneDeviceModal/steps/FinishManualStep/style.scss +++ /dev/null @@ -1,37 +0,0 @@ -#add-standalone-device-modal { - .finish-manual-step { - & > .device-name { - padding-bottom: 20px; - - .label { - color: var(--text-body-tertiary); - @include typography(app-wizard-1); - user-select: none; - padding-bottom: 10px; - } - - .name { - color: var(--text-body-primary); - @include typography(app-input); - } - } - - & > .cta { - width: 100%; - display: flex; - flex-flow: row; - align-items: center; - justify-content: center; - padding-bottom: 20px; - - p { - color: var(--text-body-secondary); - text-align: center; - width: 100%; - max-width: 410px; - text-wrap: balance; - @include typography(app-input); - } - } - } -} diff --git a/web/src/pages/devices/modals/AddStandaloneDeviceModal/steps/MethodStep/MethodStep.tsx b/web/src/pages/devices/modals/AddStandaloneDeviceModal/steps/MethodStep/MethodStep.tsx deleted file mode 100644 index a054a9150..000000000 --- a/web/src/pages/devices/modals/AddStandaloneDeviceModal/steps/MethodStep/MethodStep.tsx +++ /dev/null @@ -1,238 +0,0 @@ -import './style.scss'; - -import { useQuery } from '@tanstack/react-query'; -import { useCallback, useEffect, useId } from 'react'; -import { shallow } from 'zustand/shallow'; - -import { useI18nContext } from '../../../../../../i18n/i18n-react'; -import SvgWireguardLogo from '../../../../../../shared/components/svg/WireguardLogo'; -import { Button } from '../../../../../../shared/defguard-ui/components/Layout/Button/Button'; -import { - ButtonSize, - ButtonStyleVariant, -} from '../../../../../../shared/defguard-ui/components/Layout/Button/types'; -import type { SelectOption } from '../../../../../../shared/defguard-ui/components/Layout/Select/types'; -import SvgIconOutsideLink from '../../../../../../shared/defguard-ui/components/svg/IconOutsideLink'; -import useApi from '../../../../../../shared/hooks/useApi'; -import { externalLink } from '../../../../../../shared/links'; -import { QueryKeys } from '../../../../../../shared/queries'; -import type { Network } from '../../../../../../shared/types'; -import { DeviceSetupMethodCard } from '../../../../../addDevice/steps/AddDeviceSetupMethodStep/components/DeviceSetupMethodCard/DeviceSetupMethodCard'; -import { useAddStandaloneDeviceModal } from '../../store'; -import { - AddStandaloneDeviceModalChoice, - AddStandaloneDeviceModalStep, -} from '../../types'; - -export const MethodStep = () => { - // this is needs bcs opening modal again and again would prevent availableIp to refetch - const modalSessionID = useId(); - const { LL } = useI18nContext(); - const localLL = LL.modals.addStandaloneDevice.steps.method; - const choice = useAddStandaloneDeviceModal((s) => s.choice); - const { - network: { getNetworks }, - standaloneDevice: { getAvailableIp }, - } = useApi(); - - const { data: networks } = useQuery({ - queryKey: [QueryKeys.FETCH_NETWORKS], - queryFn: getNetworks, - refetchOnWindowFocus: false, - refetchOnMount: true, - }); - - const { - data: availableIpResponse, - refetch: refetchAvailableIp, - isLoading: availableIpLoading, - } = useQuery({ - queryKey: [ - 'ADD_STANDALONE_DEVICE_MODAL_FETCH_INITIAL_AVAILABLE_IP', - networks, - modalSessionID, - ], - queryFn: () => - getAvailableIp({ - locationId: (networks as Network[])[0].id, - }), - enabled: networks !== undefined && Array.isArray(networks) && networks.length > 0, - refetchOnMount: true, - refetchOnReconnect: true, - refetchOnWindowFocus: false, - }); - - const [setState, close, next] = useAddStandaloneDeviceModal( - (s) => [s.setStore, s.close, s.changeStep], - shallow, - ); - - const handleChange = useCallback( - (choice: AddStandaloneDeviceModalChoice) => { - setState({ choice }); - }, - [setState], - ); - - const handleNext = () => { - switch (choice) { - case AddStandaloneDeviceModalChoice.CLI: - next(AddStandaloneDeviceModalStep.SETUP_CLI); - break; - case AddStandaloneDeviceModalChoice.MANUAL: - next(AddStandaloneDeviceModalStep.SETUP_MANUAL); - break; - } - }; - - // biome-ignore lint/correctness/useExhaustiveDependencies: migration, checkMeLater - useEffect(() => { - if (networks) { - const options: SelectOption[] = networks.map((n) => ({ - key: n.id, - value: n.id, - label: n.name, - })); - setState({ - networks, - networkOptions: options, - }); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [networks]); - - // biome-ignore lint/correctness/useExhaustiveDependencies: migration, checkMeLater - useEffect(() => { - if (availableIpResponse) { - setState({ initLocationIpResponse: availableIpResponse }); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [availableIpResponse]); - - return ( -
-
- , - testId: 'standalone-device-choice-card-cli', - extras: ( - - ), - }} - active={choice === AddStandaloneDeviceModalChoice.CLI} - onClick={() => handleChange(AddStandaloneDeviceModalChoice.CLI)} - /> - , - testId: 'standalone-device-choice-card-manual', - }} - active={choice === AddStandaloneDeviceModalChoice.MANUAL} - onClick={() => handleChange(AddStandaloneDeviceModalChoice.MANUAL)} - /> -
-
-
-
- ); -}; - -const DefguardIcon = () => { - return ( - - - - - - - - - - - - - - - - - - - ); -}; diff --git a/web/src/pages/devices/modals/AddStandaloneDeviceModal/steps/MethodStep/style.scss b/web/src/pages/devices/modals/AddStandaloneDeviceModal/steps/MethodStep/style.scss deleted file mode 100644 index 43f3d1125..000000000 --- a/web/src/pages/devices/modals/AddStandaloneDeviceModal/steps/MethodStep/style.scss +++ /dev/null @@ -1,43 +0,0 @@ -#add-standalone-device-modal .method-step { - box-sizing: border-box; - padding: 0 55px; - - .choices { - display: grid; - grid-template-columns: 1fr 1fr; - padding-bottom: 40px; - column-gap: 50px; - - .device-method-card { - .link-container { - width: 100%; - display: flex; - flex-flow: row; - align-items: center; - justify-content: center; - - .link { - width: 100%; - text-decoration: none; - text-align: center; - @include typography(app-body-2); - - &:hover { - text-decoration: underline; - } - - .spacer { - display: inline-block; - width: var(--spacing-xs); - height: 1px; - } - - svg { - width: 13; - height: 13; - } - } - } - } - } -} diff --git a/web/src/pages/devices/modals/AddStandaloneDeviceModal/steps/SetupCliStep/SetupCliStep.tsx b/web/src/pages/devices/modals/AddStandaloneDeviceModal/steps/SetupCliStep/SetupCliStep.tsx deleted file mode 100644 index 0490ec38c..000000000 --- a/web/src/pages/devices/modals/AddStandaloneDeviceModal/steps/SetupCliStep/SetupCliStep.tsx +++ /dev/null @@ -1,149 +0,0 @@ -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { useCallback, useMemo, useState } from 'react'; -import { shallow } from 'zustand/shallow'; - -import { useI18nContext } from '../../../../../../i18n/i18n-react'; -import { Button } from '../../../../../../shared/defguard-ui/components/Layout/Button/Button'; -import { - ButtonSize, - ButtonStyleVariant, -} from '../../../../../../shared/defguard-ui/components/Layout/Button/types'; -import { MessageBox } from '../../../../../../shared/defguard-ui/components/Layout/MessageBox/MessageBox'; -import { MessageBoxType } from '../../../../../../shared/defguard-ui/components/Layout/MessageBox/types'; -import { useAppStore } from '../../../../../../shared/hooks/store/useAppStore'; -import { useAuthStore } from '../../../../../../shared/hooks/store/useAuthStore'; -import { useEnterpriseUpgradeStore } from '../../../../../../shared/hooks/store/useEnterpriseUpgradeStore'; -import useApi from '../../../../../../shared/hooks/useApi'; -import { useToaster } from '../../../../../../shared/hooks/useToaster'; -import { QueryKeys } from '../../../../../../shared/queries'; -import { invalidateMultipleQueries } from '../../../../../../shared/utils/invalidateMultipleQueries'; -import { useDevicesPage } from '../../../../hooks/useDevicesPage'; -import { StandaloneDeviceModalForm } from '../../../components/StandaloneDeviceModalForm/StandaloneDeviceModalForm'; -import { StandaloneDeviceModalFormMode } from '../../../components/types'; -import { useAddStandaloneDeviceModal } from '../../store'; -import { - type AddStandaloneDeviceFormFields, - AddStandaloneDeviceModalStep, - WGConfigGenChoice, -} from '../../types'; - -export const SetupCliStep = () => { - const [{ reservedDeviceNames }] = useDevicesPage(); - const { LL } = useI18nContext(); - const localLL = LL.modals.addStandaloneDevice.steps.cli.setup; - const [formLoading, setFormLoading] = useState(false); - const queryClient = useQueryClient(); - const [setState, close, next, submitSubject] = useAddStandaloneDeviceModal( - (s) => [s.setStore, s.close, s.changeStep, s.submitSubject], - shallow, - ); - const currentUserId = useAuthStore((s) => s.user?.id); - - const toast = useToaster(); - - const { - standaloneDevice: { createCliDevice }, - } = useApi(); - - const showUpgradeToast = useEnterpriseUpgradeStore((s) => s.show); - const { getAppInfo } = useApi(); - const setAppStore = useAppStore((s) => s.setState, shallow); - - const { mutateAsync } = useMutation({ - mutationFn: createCliDevice, - onSuccess: () => { - toast.success(LL.modals.addStandaloneDevice.toasts.deviceCreated()); - invalidateMultipleQueries(queryClient, [ - [QueryKeys.FETCH_USER_PROFILE, currentUserId], - [QueryKeys.FETCH_STANDALONE_DEVICE_LIST], - ]); - void getAppInfo().then((response) => { - setAppStore({ - appInfo: response, - }); - if (response.license_info.any_limit_exceeded) { - showUpgradeToast(); - } - }); - }, - onError: (e) => { - toast.error(LL.modals.addStandaloneDevice.toasts.creationFailed()); - console.error(e); - }, - }); - - const [initIpResponse, locationOptions] = useAddStandaloneDeviceModal( - (s) => [s.initLocationIpResponse, s.networkOptions], - shallow, - ); - - const defaultValues = useMemo(() => { - if (initIpResponse && locationOptions) { - const res: AddStandaloneDeviceFormFields = { - modifiableIpParts: initIpResponse.map((ip) => ip.modifiable_part), - generationChoice: WGConfigGenChoice.AUTO, - location_id: locationOptions[0].value, - name: '', - wireguard_pubkey: '', - description: '', - }; - return res; - } - return undefined; - }, [initIpResponse, locationOptions]); - - const handleSubmit = useCallback( - async (values: AddStandaloneDeviceFormFields) => { - const response = await mutateAsync({ - assigned_ips: values.modifiableIpParts, - location_id: values.location_id, - name: values.name, - description: values.description, - }); - setState({ enrollResponse: response }); - next(AddStandaloneDeviceModalStep.FINISH_CLI); - }, - [mutateAsync, next, setState], - ); - - if (initIpResponse === undefined || defaultValues === undefined) return null; - - return ( -
- - -
-
-
- ); -}; diff --git a/web/src/pages/devices/modals/AddStandaloneDeviceModal/steps/SetupManualStep/SetupManualStep.tsx b/web/src/pages/devices/modals/AddStandaloneDeviceModal/steps/SetupManualStep/SetupManualStep.tsx deleted file mode 100644 index a96d93905..000000000 --- a/web/src/pages/devices/modals/AddStandaloneDeviceModal/steps/SetupManualStep/SetupManualStep.tsx +++ /dev/null @@ -1,150 +0,0 @@ -import './style.scss'; - -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { useCallback, useMemo, useState } from 'react'; -import { shallow } from 'zustand/shallow'; - -import { useI18nContext } from '../../../../../../i18n/i18n-react'; -import { Button } from '../../../../../../shared/defguard-ui/components/Layout/Button/Button'; -import { - ButtonSize, - ButtonStyleVariant, -} from '../../../../../../shared/defguard-ui/components/Layout/Button/types'; -import { useAppStore } from '../../../../../../shared/hooks/store/useAppStore'; -import { useAuthStore } from '../../../../../../shared/hooks/store/useAuthStore'; -import { useEnterpriseUpgradeStore } from '../../../../../../shared/hooks/store/useEnterpriseUpgradeStore'; -import useApi from '../../../../../../shared/hooks/useApi'; -import { QueryKeys } from '../../../../../../shared/queries'; -import { generateWGKeys } from '../../../../../../shared/utils/generateWGKeys'; -import { invalidateMultipleQueries } from '../../../../../../shared/utils/invalidateMultipleQueries'; -import { useDevicesPage } from '../../../../hooks/useDevicesPage'; -import { StandaloneDeviceModalForm } from '../../../components/StandaloneDeviceModalForm/StandaloneDeviceModalForm'; -import { StandaloneDeviceModalFormMode } from '../../../components/types'; -import { useAddStandaloneDeviceModal } from '../../store'; -import { - type AddStandaloneDeviceFormFields, - AddStandaloneDeviceModalStep, - WGConfigGenChoice, -} from '../../types'; - -export const SetupManualStep = () => { - const { LL } = useI18nContext(); - const [formLoading, setFormLoading] = useState(false); - const [setState, next, submitSubject, close] = useAddStandaloneDeviceModal( - (s) => [s.setStore, s.changeStep, s.submitSubject, s.close], - shallow, - ); - const [initialIpResponse, locationOptions] = useAddStandaloneDeviceModal( - (s) => [s.initLocationIpResponse, s.networkOptions], - shallow, - ); - - const queryClient = useQueryClient(); - - const currentUserId = useAuthStore((s) => s.user?.id); - - const [{ reservedDeviceNames }] = useDevicesPage(); - - const { - standaloneDevice: { createManualDevice: createDevice }, - } = useApi(); - - const showUpgradeToast = useEnterpriseUpgradeStore((s) => s.show); - const { getAppInfo } = useApi(); - const setAppStore = useAppStore((s) => s.setState, shallow); - - const { mutateAsync } = useMutation({ - mutationFn: createDevice, - onSuccess: () => { - invalidateMultipleQueries(queryClient, [ - [QueryKeys.FETCH_USER_PROFILE, currentUserId], - [QueryKeys.FETCH_STANDALONE_DEVICE_LIST], - ]); - void getAppInfo().then((response) => { - setAppStore({ - appInfo: response, - }); - if (response.license_info.any_limit_exceeded) { - showUpgradeToast(); - } - }); - }, - }); - - const handleSubmit = useCallback( - async (values: AddStandaloneDeviceFormFields) => { - let pub = values.wireguard_pubkey; - if (values.generationChoice === WGConfigGenChoice.AUTO) { - const keys = generateWGKeys(); - pub = keys.publicKey; - setState({ - genKeys: keys, - }); - } - const response = await mutateAsync({ - assigned_ips: values.modifiableIpParts, - location_id: values.location_id, - name: values.name, - description: values.description, - wireguard_pubkey: pub, - }); - setState({ - genChoice: values.generationChoice, - manualResponse: response, - }); - next(AddStandaloneDeviceModalStep.FINISH_MANUAL); - }, - [mutateAsync, next, setState], - ); - - const defaultFormValues = useMemo(() => { - if (locationOptions && initialIpResponse) { - const res: AddStandaloneDeviceFormFields = { - modifiableIpParts: initialIpResponse.map((ip) => ip.modifiable_part), - generationChoice: WGConfigGenChoice.AUTO, - location_id: locationOptions[0].value, - name: '', - wireguard_pubkey: '', - description: '', - }; - return res; - } - return undefined; - }, [initialIpResponse, locationOptions]); - - if (initialIpResponse === undefined || defaultFormValues === undefined) return null; - - return ( -
- -
-
-
- ); -}; diff --git a/web/src/pages/devices/modals/AddStandaloneDeviceModal/steps/SetupManualStep/style.scss b/web/src/pages/devices/modals/AddStandaloneDeviceModal/steps/SetupManualStep/style.scss deleted file mode 100644 index d6d0f5e91..000000000 --- a/web/src/pages/devices/modals/AddStandaloneDeviceModal/steps/SetupManualStep/style.scss +++ /dev/null @@ -1,7 +0,0 @@ -#add-standalone-device-modal { - .setup-manual { - .toggle { - margin-bottom: 20px; - } - } -} diff --git a/web/src/pages/devices/modals/AddStandaloneDeviceModal/store.tsx b/web/src/pages/devices/modals/AddStandaloneDeviceModal/store.tsx deleted file mode 100644 index e724ba7cd..000000000 --- a/web/src/pages/devices/modals/AddStandaloneDeviceModal/store.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import { isObject } from 'lodash-es'; -import { Subject } from 'rxjs'; -import { createWithEqualityFn } from 'zustand/traditional'; - -import type { SelectOption } from '../../../../shared/defguard-ui/components/Layout/Select/types'; -import type { - CreateStandaloneDeviceResponse, - GetAvailableLocationIpResponse, - Network, - StartEnrollmentResponse, -} from '../../../../shared/types'; -import { - AddStandaloneDeviceModalChoice, - AddStandaloneDeviceModalStep, - WGConfigGenChoice, -} from './types'; - -const defaultValues: StoreValues = { - visible: false, - currentStep: AddStandaloneDeviceModalStep.METHOD_CHOICE, - choice: AddStandaloneDeviceModalChoice.CLI, - networks: undefined, - networkOptions: [], - genChoice: WGConfigGenChoice.AUTO, - submitSubject: new Subject(), - initLocationIpResponse: undefined, - genKeys: undefined, - manualResponse: undefined, - enrollResponse: undefined, -}; - -export const useAddStandaloneDeviceModal = createWithEqualityFn( - (set) => ({ - ...defaultValues, - setStore: (v) => set(v), - reset: () => set(defaultValues), - close: () => set({ visible: false }), - open: () => set({ ...defaultValues, visible: true }), - changeStep: (step) => set({ currentStep: step }), - }), - isObject, -); - -type Store = StoreValues & StoreMethods; - -type StoreValues = { - visible: boolean; - currentStep: AddStandaloneDeviceModalStep; - choice: AddStandaloneDeviceModalChoice; - networkOptions: SelectOption[]; - genChoice: WGConfigGenChoice; - submitSubject: Subject; - initLocationIpResponse?: GetAvailableLocationIpResponse; - networks?: Network[]; - genKeys?: { - publicKey: string; - privateKey: string; - }; - manualResponse?: CreateStandaloneDeviceResponse; - enrollResponse?: StartEnrollmentResponse; -}; - -type StoreMethods = { - setStore: (values: Partial) => void; - reset: () => void; - close: () => void; - open: () => void; - changeStep: (step: AddStandaloneDeviceModalStep) => void; -}; diff --git a/web/src/pages/devices/modals/AddStandaloneDeviceModal/style.scss b/web/src/pages/devices/modals/AddStandaloneDeviceModal/style.scss deleted file mode 100644 index 9e8b7949e..000000000 --- a/web/src/pages/devices/modals/AddStandaloneDeviceModal/style.scss +++ /dev/null @@ -1,59 +0,0 @@ -#add-standalone-device-modal { - width: 100%; - max-width: 920px; - - & > .header { - padding-left: 55px; - } - - .controls { - display: flex; - flex-flow: row; - align-items: center; - justify-content: flex-start; - width: 100%; - column-gap: 20px; - padding-top: 40px; - - .btn { - width: 100%; - height: 47px; - } - - &.solo { - width: 100%; - align-items: center; - justify-content: center; - - .btn { - width: 50%; - } - } - } - - .step-content > div { - box-sizing: border-box; - padding: 0 55px; - - .message-box-spacer { - padding-bottom: 20px; - } - } - - .step-content { - form { - .row { - width: 100%; - display: flex; - flex-flow: column; - row-gap: 0; - - @include media-breakpoint-up(lg) { - display: grid; - grid-template-columns: 1fr 1fr; - column-gap: 20px; - } - } - } - } -} diff --git a/web/src/pages/devices/modals/AddStandaloneDeviceModal/types.ts b/web/src/pages/devices/modals/AddStandaloneDeviceModal/types.ts deleted file mode 100644 index 0c43cda03..000000000 --- a/web/src/pages/devices/modals/AddStandaloneDeviceModal/types.ts +++ /dev/null @@ -1,31 +0,0 @@ -export enum AddStandaloneDeviceModalStep { - METHOD_CHOICE, - SETUP_CLI, - FINISH_CLI, - SETUP_MANUAL, - FINISH_MANUAL, -} - -export enum AddStandaloneDeviceModalChoice { - CLI, - MANUAL, -} - -export enum WGConfigGenChoice { - MANUAL, - AUTO, -} - -export type AddStandaloneDeviceFormFields = { - name: string; - location_id: number; - modifiableIpParts: string[]; - wireguard_pubkey?: string; - generationChoice: WGConfigGenChoice; - description?: string; -}; - -export type AddStandaloneDeviceCLIFormFields = Omit< - AddStandaloneDeviceFormFields, - 'generationChoice' | 'wireguard_pubkey' ->; diff --git a/web/src/pages/devices/modals/EditStandaloneDeviceModal/EditStandaloneModal.tsx b/web/src/pages/devices/modals/EditStandaloneDeviceModal/EditStandaloneModal.tsx deleted file mode 100644 index 1b140e833..000000000 --- a/web/src/pages/devices/modals/EditStandaloneDeviceModal/EditStandaloneModal.tsx +++ /dev/null @@ -1,184 +0,0 @@ -import './style.scss'; - -import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; -import { useCallback, useEffect, useMemo, useState } from 'react'; -import { Subject } from 'rxjs'; -import { shallow } from 'zustand/shallow'; - -import { useI18nContext } from '../../../../i18n/i18n-react'; -import { Button } from '../../../../shared/defguard-ui/components/Layout/Button/Button'; -import { - ButtonSize, - ButtonStyleVariant, -} from '../../../../shared/defguard-ui/components/Layout/Button/types'; -import { LoaderSpinner } from '../../../../shared/defguard-ui/components/Layout/LoaderSpinner/LoaderSpinner'; -import { ModalWithTitle } from '../../../../shared/defguard-ui/components/Layout/modals/ModalWithTitle/ModalWithTitle'; -import { useAuthStore } from '../../../../shared/hooks/store/useAuthStore'; -import useApi from '../../../../shared/hooks/useApi'; -import { useToaster } from '../../../../shared/hooks/useToaster'; -import { QueryKeys } from '../../../../shared/queries'; -import { selectifyNetworks } from '../../../../shared/utils/form/selectifyNetwork'; -import { invalidateMultipleQueries } from '../../../../shared/utils/invalidateMultipleQueries'; -import { useDevicesPage } from '../../hooks/useDevicesPage'; -import { useEditStandaloneDeviceModal } from '../../hooks/useEditStandaloneDeviceModal'; -import { - type AddStandaloneDeviceFormFields, - WGConfigGenChoice, -} from '../AddStandaloneDeviceModal/types'; -import { StandaloneDeviceModalForm } from '../components/StandaloneDeviceModalForm/StandaloneDeviceModalForm'; -import { StandaloneDeviceModalFormMode } from '../components/types'; - -export const EditStandaloneModal = () => { - const { LL } = useI18nContext(); - const localLL = LL.modals.editStandaloneModal; - const [close, reset] = useEditStandaloneDeviceModal((s) => [s.close, s.reset], shallow); - const isOpen = useEditStandaloneDeviceModal((s) => s.visible); - - // biome-ignore lint/correctness/useExhaustiveDependencies: migration, checkMeLater - useEffect(() => { - return () => { - reset(); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - return ( - - - - ); -}; - -const ModalContent = () => { - const { LL } = useI18nContext(); - const localLL = LL.modals.editStandaloneModal; - const device = useEditStandaloneDeviceModal((s) => s.device); - const [submitSubject] = useState(new Subject()); - const [formLoading, setFormLoading] = useState(false); - const [closeModal] = useEditStandaloneDeviceModal((s) => [s.close], shallow); - const toaster = useToaster(); - const queryClient = useQueryClient(); - const currentUserId = useAuthStore((s) => s.user?.id); - const [{ reservedDeviceNames }] = useDevicesPage(); - - const { - network: { getNetworks }, - standaloneDevice: { editDevice }, - } = useApi(); - - const { mutateAsync } = useMutation({ - mutationFn: editDevice, - onSuccess: () => { - toaster.success(localLL.toasts.success()); - invalidateMultipleQueries(queryClient, [ - [QueryKeys.FETCH_USER_PROFILE, currentUserId], - [QueryKeys.FETCH_STANDALONE_DEVICE_LIST], - ]); - closeModal(); - }, - onError: (e) => { - toaster.error(localLL.toasts.failure()); - console.error(e); - }, - }); - - const { data: networks } = useQuery({ - queryKey: [QueryKeys.FETCH_NETWORKS], - queryFn: getNetworks, - refetchOnWindowFocus: false, - refetchOnMount: true, - }); - - const locationOptions = useMemo(() => { - if (networks) { - return selectifyNetworks(networks); - } - return []; - }, [networks]); - - const defaultValues = useMemo(() => { - if (locationOptions && device) { - const modifiableParts = device.assigned_ips.map( - (ip, i) => - ip.split(device.split_ips[i].network_part)[1] || - device.split_ips[i].modifiable_part, - ); - - const res: AddStandaloneDeviceFormFields = { - name: device?.name, - modifiableIpParts: modifiableParts, - location_id: device.location.id, - description: device.description, - generationChoice: WGConfigGenChoice.AUTO, - wireguard_pubkey: '', - }; - return res; - } - return undefined; - }, [device, locationOptions]); - - const handleSubmit = useCallback( - async (values: AddStandaloneDeviceFormFields) => { - if (device) { - await mutateAsync({ - assigned_ips: values.modifiableIpParts, - id: device.id, - name: values.name, - description: values.description, - }); - } - }, - [device, mutateAsync], - ); - - if (!device) { - return null; - } - - return ( - <> - {defaultValues && ( - ({ - ip: device.assigned_ips[i], - ...device.split_ips[i], - }))} - /> - )} - {!defaultValues && ( -
- -
- )} -
-
- - ); -}; diff --git a/web/src/pages/devices/modals/EditStandaloneDeviceModal/style.scss b/web/src/pages/devices/modals/EditStandaloneDeviceModal/style.scss deleted file mode 100644 index fd45ad7f6..000000000 --- a/web/src/pages/devices/modals/EditStandaloneDeviceModal/style.scss +++ /dev/null @@ -1,47 +0,0 @@ -#edit-standalone-device-modal { - width: 100%; - max-width: 920px; - - .loader { - width: 100%; - display: flex; - flex-flow: row; - align-items: center; - justify-content: center; - min-height: 300px; - } - - .controls { - display: flex; - width: 100%; - gap: 20px; - padding-top: 40px; - box-sizing: border-box; - padding-left: 30px; - padding-right: 30px; - flex-flow: column; - align-items: center; - justify-content: flex-start; - - @include media-breakpoint-up(lg) { - flex-flow: row; - align-items: center; - justify-content: flex-start; - } - - .btn { - width: 100%; - height: 47px; - } - - &.solo { - width: 100%; - align-items: center; - justify-content: center; - - .btn { - width: 50%; - } - } - } -} diff --git a/web/src/pages/devices/modals/StandaloneDeviceConfigModal/StandaloneDeviceConfigModal.tsx b/web/src/pages/devices/modals/StandaloneDeviceConfigModal/StandaloneDeviceConfigModal.tsx deleted file mode 100644 index 0626cc64e..000000000 --- a/web/src/pages/devices/modals/StandaloneDeviceConfigModal/StandaloneDeviceConfigModal.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { useEffect } from 'react'; -import { shallow } from 'zustand/shallow'; - -import { useI18nContext } from '../../../../i18n/i18n-react'; -import { WireguardConfigExpandable } from '../../../../shared/components/Layout/WireguardConfigExpandable/WireguardConfigExpandable'; -import { Button } from '../../../../shared/defguard-ui/components/Layout/Button/Button'; -import { ModalWithTitle } from '../../../../shared/defguard-ui/components/Layout/modals/ModalWithTitle/ModalWithTitle'; -import { useStandaloneDeviceConfigModal } from './store'; - -export const StandaloneDeviceConfigModal = () => { - const { LL } = useI18nContext(); - const isOpen = useStandaloneDeviceConfigModal((s) => s.visible); - const [close, reset] = useStandaloneDeviceConfigModal( - (s) => [s.close, s.reset], - shallow, - ); - - // biome-ignore lint/correctness/useExhaustiveDependencies: migration, checkMeLater - useEffect(() => { - return () => { - reset(); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - return ( - - - - ); -}; - -const ModalContent = () => { - const { LL } = useI18nContext(); - const data = useStandaloneDeviceConfigModal((s) => s.data); - const close = useStandaloneDeviceConfigModal((s) => s.close, shallow); - - if (!data) return null; - return ( - <> - -
-
- - ); -}; diff --git a/web/src/pages/devices/modals/StandaloneDeviceConfigModal/store.tsx b/web/src/pages/devices/modals/StandaloneDeviceConfigModal/store.tsx deleted file mode 100644 index e02d0481f..000000000 --- a/web/src/pages/devices/modals/StandaloneDeviceConfigModal/store.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { createWithEqualityFn } from 'zustand/traditional'; - -import type { StandaloneDevice } from '../../../../shared/types'; - -const defaults: StoreValues = { - visible: false, -}; - -export const useStandaloneDeviceConfigModal = createWithEqualityFn( - (set) => ({ - ...defaults, - open: (data) => set({ data, visible: true }), - close: () => set({ visible: false }), - reset: () => set(defaults), - }), - Object.is, -); - -type Store = StoreValues & StoreMethods; -type StoreValues = { - visible: boolean; - data?: { - device: StandaloneDevice; - config: string; - }; -}; - -type StoreMethods = { - open: (values: StoreValues['data']) => void; - close: () => void; - reset: () => void; -}; diff --git a/web/src/pages/devices/modals/StandaloneDeviceEnrollmentModal/StandaloneDeviceEnrollmentModal.tsx b/web/src/pages/devices/modals/StandaloneDeviceEnrollmentModal/StandaloneDeviceEnrollmentModal.tsx deleted file mode 100644 index 967dfd872..000000000 --- a/web/src/pages/devices/modals/StandaloneDeviceEnrollmentModal/StandaloneDeviceEnrollmentModal.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { shallow } from 'zustand/shallow'; - -import { useI18nContext } from '../../../../i18n/i18n-react'; -import { Button } from '../../../../shared/defguard-ui/components/Layout/Button/Button'; -import { ModalWithTitle } from '../../../../shared/defguard-ui/components/Layout/modals/ModalWithTitle/ModalWithTitle'; -import { StandaloneDeviceModalEnrollmentContent } from '../components/StandaloneDeviceModalEnrollmentContent/StandaloneDeviceModalEnrollmentContent'; -import { useStandaloneDeviceEnrollmentModal } from './store'; - -export const StandaloneDeviceEnrollmentModal = () => { - const { LL } = useI18nContext(); - const localLL = LL.modals.standaloneDeviceEnrollmentModal; - const [close, reset] = useStandaloneDeviceEnrollmentModal( - (s) => [s.close, s.reset], - shallow, - ); - const isOpen = useStandaloneDeviceEnrollmentModal((s) => s.visible); - return ( - - - - ); -}; - -const ModalContent = () => { - const { LL } = useI18nContext(); - const modalData = useStandaloneDeviceEnrollmentModal((s) => s.data); - const closeModal = useStandaloneDeviceEnrollmentModal((s) => s.close, shallow); - if (!modalData) return null; - return ( - <> - -
-
- - ); -}; diff --git a/web/src/pages/devices/modals/StandaloneDeviceEnrollmentModal/store.tsx b/web/src/pages/devices/modals/StandaloneDeviceEnrollmentModal/store.tsx deleted file mode 100644 index ee55db40e..000000000 --- a/web/src/pages/devices/modals/StandaloneDeviceEnrollmentModal/store.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { createWithEqualityFn } from 'zustand/traditional'; - -import type { StandaloneDevice, StartEnrollmentResponse } from '../../../../shared/types'; - -const defaults: StoreValues = { - visible: false, - data: undefined, -}; - -export const useStandaloneDeviceEnrollmentModal = createWithEqualityFn( - (set) => ({ - ...defaults, - open: (data) => set({ data: data, visible: true }), - close: () => set({ visible: false }), - reset: () => set(defaults), - }), - Object.is, -); - -type Store = StoreValues & StoreMethods; -type StoreValues = { - visible: boolean; - data?: { - device: StandaloneDevice; - enrollment: StartEnrollmentResponse; - }; -}; -type StoreMethods = { - open: (data: StoreValues['data']) => void; - close: () => void; - reset: () => void; -}; diff --git a/web/src/pages/devices/modals/components/StandaloneDeviceModalEnrollmentContent/StandaloneDeviceModalEnrollmentContent.tsx b/web/src/pages/devices/modals/components/StandaloneDeviceModalEnrollmentContent/StandaloneDeviceModalEnrollmentContent.tsx deleted file mode 100644 index 33348212d..000000000 --- a/web/src/pages/devices/modals/components/StandaloneDeviceModalEnrollmentContent/StandaloneDeviceModalEnrollmentContent.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import './style.scss'; - -import { useMemo } from 'react'; - -import { useI18nContext } from '../../../../../i18n/i18n-react'; -import { ActionButton } from '../../../../../shared/defguard-ui/components/Layout/ActionButton/ActionButton'; -import { ActionButtonVariant } from '../../../../../shared/defguard-ui/components/Layout/ActionButton/types'; -import { Button } from '../../../../../shared/defguard-ui/components/Layout/Button/Button'; -import { - ButtonSize, - ButtonStyleVariant, -} from '../../../../../shared/defguard-ui/components/Layout/Button/types'; -import { ExpandableCard } from '../../../../../shared/defguard-ui/components/Layout/ExpandableCard/ExpandableCard'; -import { MessageBox } from '../../../../../shared/defguard-ui/components/Layout/MessageBox/MessageBox'; -import { MessageBoxType } from '../../../../../shared/defguard-ui/components/Layout/MessageBox/types'; -import { useClipboard } from '../../../../../shared/hooks/useClipboard'; -import { externalLink } from '../../../../../shared/links'; -import type { StartEnrollmentResponse } from '../../../../../shared/types'; - -type Props = { - enrollmentData: StartEnrollmentResponse; -}; -export const StandaloneDeviceModalEnrollmentContent = ({ - enrollmentData: { enrollment_token, enrollment_url }, -}: Props) => { - const { LL } = useI18nContext(); - const localLL = LL.components.standaloneDeviceTokenModalContent; - const { writeToClipboard } = useClipboard(); - const commandToCopy = useMemo(() => { - return `dg enroll -u ${enrollment_url} -t ${enrollment_token}`; - }, [enrollment_token, enrollment_url]); - - return ( -
- - - { - void writeToClipboard(commandToCopy); - }} - key={0} - />, - ]} - expanded={true} - disableExpand={true} - > -

{commandToCopy}

-
-
- ); -}; diff --git a/web/src/pages/devices/modals/components/StandaloneDeviceModalEnrollmentContent/style.scss b/web/src/pages/devices/modals/components/StandaloneDeviceModalEnrollmentContent/style.scss deleted file mode 100644 index 35cbfc50b..000000000 --- a/web/src/pages/devices/modals/components/StandaloneDeviceModalEnrollmentContent/style.scss +++ /dev/null @@ -1,19 +0,0 @@ -.standalone-device-enrollment-content { - .download { - width: 100%; - display: flex; - flex-flow: row; - align-items: center; - justify-content: center; - padding-bottom: 30px; - - a { - text-decoration: none; - cursor: pointer; - } - - .btn { - height: 47px; - } - } -} diff --git a/web/src/pages/devices/modals/components/StandaloneDeviceModalForm/StandaloneDeviceModalForm.tsx b/web/src/pages/devices/modals/components/StandaloneDeviceModalForm/StandaloneDeviceModalForm.tsx deleted file mode 100644 index acc2c31a7..000000000 --- a/web/src/pages/devices/modals/components/StandaloneDeviceModalForm/StandaloneDeviceModalForm.tsx +++ /dev/null @@ -1,267 +0,0 @@ -import './style.scss'; - -import { zodResolver } from '@hookform/resolvers/zod'; -import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { type SubmitHandler, useForm } from 'react-hook-form'; -import type { Subject } from 'rxjs'; - -import { useI18nContext } from '../../../../../i18n/i18n-react'; -import { FormInput } from '../../../../../shared/defguard-ui/components/Form/FormInput/FormInput'; -import { FormLocationIp } from '../../../../../shared/defguard-ui/components/Form/FormLocationIp/FormLocationIp'; -import { FormSelect } from '../../../../../shared/defguard-ui/components/Form/FormSelect/FormSelect'; -import { FormToggle } from '../../../../../shared/defguard-ui/components/Form/FormToggle/FormToggle'; -import type { - SelectOption, - SelectSelectedValue, -} from '../../../../../shared/defguard-ui/components/Layout/Select/types'; -import type { ToggleOption } from '../../../../../shared/defguard-ui/components/Layout/Toggle/types'; -import useApi from '../../../../../shared/hooks/useApi'; -import { useToaster } from '../../../../../shared/hooks/useToaster'; -import type { GetAvailableLocationIpResponse } from '../../../../../shared/types'; -import { - type AddStandaloneDeviceFormFields, - WGConfigGenChoice, -} from '../../AddStandaloneDeviceModal/types'; -import { StandaloneDeviceModalFormMode } from '../types'; -import { - type StandaloneDeviceFormFields, - standaloneDeviceFormSchema, -} from './formSchema'; - -type Props = { - onSubmit: (formValues: AddStandaloneDeviceFormFields) => Promise; - mode: StandaloneDeviceModalFormMode; - onLoadingChange: (value: boolean) => void; - locationOptions: SelectOption[]; - submitSubject: Subject; - defaults: StandaloneDeviceFormFields; - reservedNames: string[]; - initialIpRecommendation: GetAvailableLocationIpResponse; -}; - -export const StandaloneDeviceModalForm = ({ - onSubmit, - mode, - onLoadingChange, - locationOptions, - submitSubject, - defaults, - reservedNames, - initialIpRecommendation, -}: Props) => { - const [internalRecommendedIps, setInternalRecommendedIps] = useState< - GetAvailableLocationIpResponse | undefined - >(); - const { LL } = useI18nContext(); - const { - standaloneDevice: { validateLocationIp, getAvailableIp }, - } = useApi(); - // auto assign upon location change is happening - const [ipIsLoading, setIpIsLoading] = useState(false); - const localLL = LL.modals.addStandaloneDevice.form; - const labels = localLL.labels; - const submitRef = useRef(null); - const toaster = useToaster(); - const renderSelectedOption = useCallback( - (val?: number): SelectSelectedValue => { - const empty: SelectSelectedValue = { - displayValue: '', - key: 'empty', - }; - if (val !== undefined) { - const option = locationOptions.find((n) => n.value === val); - if (option) { - return { - displayValue: option.label, - key: option.key, - }; - } - } - return empty; - }, - [locationOptions], - ); - - const toggleOptions = useMemo( - (): ToggleOption[] => [ - { - text: labels.generation.auto(), - value: WGConfigGenChoice.AUTO, - disabled: false, - }, - { - text: labels.generation.manual(), - value: WGConfigGenChoice.MANUAL, - disabled: false, - }, - ], - [labels.generation], - ); - - const schema = useMemo( - () => - standaloneDeviceFormSchema(LL, { - mode, - reservedNames, - originalName: defaults.name, - }), - [mode, reservedNames, defaults.name, LL], - ); - - const { - handleSubmit, - control, - watch, - formState: { isSubmitting }, - setError, - resetField, - } = useForm({ - defaultValues: defaults, - resolver: zodResolver(schema), - mode: 'all', - }); - - const generationChoiceValue = watch('generationChoice'); - - const submitHandler: SubmitHandler = async (formValues) => { - const values = formValues; - const recommendationResponse = internalRecommendedIps ?? initialIpRecommendation; - let validationList = recommendationResponse.map((recommendation, index) => ({ - ip: recommendation.network_part + formValues.modifiableIpParts[index], - index, - })); - values.modifiableIpParts = validationList.map((item) => item.ip); - // try to validate explicitly chosen IPs before submission - let validationErrors = false; - - // if edit exclude initial ip's from validation as they are reserved already by edited device - if (mode === StandaloneDeviceModalFormMode.EDIT) { - const reservedByDevice = initialIpRecommendation.map( - (item) => item.network_part + item.modifiable_part, - ); - validationList = validationList.filter( - (item) => !reservedByDevice.includes(item.ip), - ); - } - - if (validationList.length) { - try { - const response = await validateLocationIp({ - ips: validationList.map((item) => item.ip), - location: values.location_id, - }); - - response.forEach(({ available, valid }, index) => { - const fieldIndex = validationList[index].index; - if (!available) { - validationErrors = true; - setError(`modifiableIpParts.${fieldIndex}`, { - message: LL.form.error.reservedIp(), - }); - } - if (!valid) { - validationErrors = true; - setError(`modifiableIpParts.${fieldIndex}`, { - message: LL.form.error.invalidIp(), - }); - } - }); - } catch (_) { - validationErrors = true; - toaster.error(LL.messages.error()); - } - } - - // submit form if no validation errors occurred - if (!validationErrors) { - try { - await onSubmit(values); - } catch (_) { - toaster.error(LL.messages.error()); - } - } - }; - - const autoAssignRecommendedIp = useCallback( - (locationId: number | undefined) => { - if (locationId !== undefined && mode !== StandaloneDeviceModalFormMode.EDIT) { - setIpIsLoading(true); - void getAvailableIp({ - locationId, - }) - .then((resp) => { - setInternalRecommendedIps(resp); - resetField('modifiableIpParts', { - defaultValue: resp.map((r) => r.modifiable_part), - }); - }) - .finally(() => { - setIpIsLoading(false); - }); - } - }, - [getAvailableIp, resetField, mode], - ); - - // inform parent that form is processing stuff - useEffect(() => { - const res = isSubmitting; - onLoadingChange(res); - }, [isSubmitting, onLoadingChange]); - - // handle form sub from outside - useEffect(() => { - const sub = submitSubject.subscribe(() => { - if (submitRef.current) { - submitRef.current.click(); - } - }); - return () => sub.unsubscribe(); - }, [submitSubject]); - - const recommendedIps = internalRecommendedIps || initialIpRecommendation; - return ( -
- - - - {recommendedIps.map((ip, i) => ( - - ))} - {mode === StandaloneDeviceModalFormMode.CREATE_MANUAL && ( - <> - - - - )} - - - ); -}; diff --git a/web/src/pages/devices/modals/components/StandaloneDeviceModalForm/formSchema.ts b/web/src/pages/devices/modals/components/StandaloneDeviceModalForm/formSchema.ts deleted file mode 100644 index e43a1aaaf..000000000 --- a/web/src/pages/devices/modals/components/StandaloneDeviceModalForm/formSchema.ts +++ /dev/null @@ -1,64 +0,0 @@ -import z from 'zod'; -import type { TranslationFunctions } from '../../../../../i18n/i18n-types'; -import { isPresent } from '../../../../../shared/defguard-ui/utils/isPresent'; -import { validateWireguardPublicKey } from '../../../../../shared/validators'; -import { WGConfigGenChoice } from '../../AddStandaloneDeviceModal/types'; -import { StandaloneDeviceModalFormMode } from '../types'; - -type SchemaProps = { - mode: StandaloneDeviceModalFormMode; - reservedNames: string[]; - originalName?: string; -}; - -export const standaloneDeviceFormSchema = ( - LL: TranslationFunctions, - { mode, reservedNames, originalName }: SchemaProps, -) => { - const errors = LL.form.error; - - return z - .object({ - name: z - .string() - .trim() - .min(1, LL.form.error.required()) - .refine((value) => { - if (mode === StandaloneDeviceModalFormMode.EDIT && isPresent(originalName)) { - const filtered = reservedNames.filter((n) => n !== originalName.trim()); - return !filtered.includes(value.trim()); - } - return !reservedNames.includes(value.trim()); - }, LL.form.error.reservedName()), - location_id: z.number(), - description: z.string().trim().optional(), - modifiableIpParts: z.array(z.string().trim().min(1, LL.form.error.required())), - generationChoice: z.nativeEnum(WGConfigGenChoice), - wireguard_pubkey: z.string().trim().optional(), - }) - .superRefine((vals, ctx) => { - if (mode === StandaloneDeviceModalFormMode.CREATE_MANUAL) { - if (vals.generationChoice === WGConfigGenChoice.MANUAL) { - const result = validateWireguardPublicKey({ - requiredError: errors.required(), - maxError: errors.maximumLengthOf({ length: 44 }), - minError: errors.minimumLengthOf({ length: 44 }), - validKeyError: errors.invalid(), - }).safeParse(vals.wireguard_pubkey); - if (!result.success) { - result.error.errors.forEach((e) => { - ctx.addIssue({ - path: ['wireguard_pubkey'], - message: e.message, - code: 'custom', - }); - }); - } - } - } - }); -}; - -export type StandaloneDeviceFormFields = z.infer< - ReturnType ->; diff --git a/web/src/pages/devices/modals/components/StandaloneDeviceModalForm/style.scss b/web/src/pages/devices/modals/components/StandaloneDeviceModalForm/style.scss deleted file mode 100644 index ef5f509ce..000000000 --- a/web/src/pages/devices/modals/components/StandaloneDeviceModalForm/style.scss +++ /dev/null @@ -1,14 +0,0 @@ -.standalone-device-modal-form { - .row { - width: 100%; - display: flex; - flex-flow: column; - row-gap: 0; - - @include media-breakpoint-up(lg) { - display: grid; - grid-template-columns: 1fr 1fr; - column-gap: 20px; - } - } -} diff --git a/web/src/pages/devices/modals/components/types.ts b/web/src/pages/devices/modals/components/types.ts deleted file mode 100644 index 408ff8b14..000000000 --- a/web/src/pages/devices/modals/components/types.ts +++ /dev/null @@ -1,5 +0,0 @@ -export enum StandaloneDeviceModalFormMode { - CREATE_CLI, - CREATE_MANUAL, - EDIT, -} diff --git a/web/src/pages/devices/style.scss b/web/src/pages/devices/style.scss deleted file mode 100644 index eb7878e3b..000000000 --- a/web/src/pages/devices/style.scss +++ /dev/null @@ -1,7 +0,0 @@ -#standalone-devices-page { - .list-container { - position: relative; - max-width: 100%; - overflow: auto; - } -} diff --git a/web/src/pages/enrollment/EnrollmentPage.tsx b/web/src/pages/enrollment/EnrollmentPage.tsx deleted file mode 100644 index 120d198a0..000000000 --- a/web/src/pages/enrollment/EnrollmentPage.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import './style.scss'; - -import { useQuery } from '@tanstack/react-query'; -import { useEffect } from 'react'; - -import { useI18nContext } from '../../i18n/i18n-react'; -import { PageContainer } from '../../shared/components/Layout/PageContainer/PageContainer'; -import { MessageBox } from '../../shared/defguard-ui/components/Layout/MessageBox/MessageBox'; -import { MessageBoxType } from '../../shared/defguard-ui/components/Layout/MessageBox/types'; -import useApi from '../../shared/hooks/useApi'; -import { QueryKeys } from '../../shared/queries'; -import { EnrollmentEmail } from './components/EnrollmentEmail/EnrollmentEmail'; -import { EnrollmentVPN } from './components/EnrollmentVPN/EnrollmentVPN'; -import { EnrollmentWelcomeMessage } from './components/EnrollmentWelcomeMessage/EnrollmentWelcomeMessage'; -import { useEnrollmentStore } from './hooks/useEnrollmentStore'; - -export const EnrollmentPage = () => { - const { - settings: { getSettings }, - } = useApi(); - - const { LL } = useI18nContext(); - - const pageLL = LL.enrollmentPage; - - const setEnrollment = useEnrollmentStore((state) => state.setState); - - const { data: settingsData, isLoading } = useQuery({ - queryFn: getSettings, - queryKey: [QueryKeys.FETCH_SETTINGS], - refetchOnMount: true, - refetchOnWindowFocus: false, - }); - - // biome-ignore lint/correctness/useExhaustiveDependencies: migration, checkMeLater - useEffect(() => { - if (settingsData) { - setEnrollment({ settings: settingsData }); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [settingsData]); - - return ( - -

{pageLL.title()}

- - {!isLoading && ( -
-
- - -
-
- -
-
- )} -
- ); -}; diff --git a/web/src/pages/enrollment/components/EnrollmentEmail/EnrollmentEmail.tsx b/web/src/pages/enrollment/components/EnrollmentEmail/EnrollmentEmail.tsx deleted file mode 100644 index d581f9383..000000000 --- a/web/src/pages/enrollment/components/EnrollmentEmail/EnrollmentEmail.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import './style.scss'; - -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { isUndefined } from 'lodash-es'; -import { type ChangeEvent, useEffect, useState } from 'react'; - -import { useI18nContext } from '../../../../i18n/i18n-react'; -import SvgIconCheckmark from '../../../../shared/components/svg/IconCheckmark'; -import { Button } from '../../../../shared/defguard-ui/components/Layout/Button/Button'; -import { - ButtonSize, - ButtonStyleVariant, -} from '../../../../shared/defguard-ui/components/Layout/Button/types'; -import { Card } from '../../../../shared/defguard-ui/components/Layout/Card/Card'; -import { CheckBox } from '../../../../shared/defguard-ui/components/Layout/Checkbox/CheckBox'; -import { Input } from '../../../../shared/defguard-ui/components/Layout/Input/Input'; -import { MessageBox } from '../../../../shared/defguard-ui/components/Layout/MessageBox/MessageBox'; -import { MessageBoxType } from '../../../../shared/defguard-ui/components/Layout/MessageBox/types'; -import { TextareaAutoResizable } from '../../../../shared/defguard-ui/components/Layout/TextareaAutoResizable/TextareaAutoResizable'; -import useApi from '../../../../shared/hooks/useApi'; -import { useToaster } from '../../../../shared/hooks/useToaster'; -import { QueryKeys } from '../../../../shared/queries'; -import { useEnrollmentStore } from '../../hooks/useEnrollmentStore'; - -export const EnrollmentEmail = () => { - const { - settings: { editSettings }, - } = useApi(); - const queryClient = useQueryClient(); - const { LL } = useI18nContext(); - const [duplicateMessage, setDuplicateMessage] = useState(false); - const settings = useEnrollmentStore((state) => state.settings); - const [email, setEmail] = useState(settings?.enrollment_welcome_email ?? ''); - const [subject, setSubject] = useState( - settings?.enrollment_welcome_email_subject ?? '', - ); - const componentLL = LL.enrollmentPage.settings.welcomeEmail; - const toaster = useToaster(); - - const { isPending: isLoading, mutate } = useMutation({ - mutationFn: editSettings, - onSuccess: () => { - void queryClient.invalidateQueries({ - queryKey: [QueryKeys.FETCH_SETTINGS], - }); - toaster.success(LL.enrollmentPage.messages.edit.success()); - }, - onError: (e) => { - toaster.error(LL.enrollmentPage.messages.edit.error()); - console.error(e); - }, - }); - - const handleSave = () => { - if (!isLoading && settings) { - mutate({ - ...settings, - enrollment_use_welcome_message_as_email: duplicateMessage, - enrollment_welcome_email: email, - enrollment_welcome_email_subject: subject, - }); - } - }; - - useEffect(() => { - if (settings) { - setDuplicateMessage(settings.enrollment_use_welcome_message_as_email); - setEmail(settings.enrollment_welcome_email); - setSubject(settings.enrollment_welcome_email_subject); - } - }, [settings]); - - return ( -
-
-

{componentLL.title()}

-
- - -
-
- setDuplicateMessage((state) => !state)} - disabled={isLoading} - /> - setDuplicateMessage((state) => !state)}> - {componentLL.controls.duplicateWelcome()} - -
-
- setSubject(e.target.value)} - disabled={isLoading || isUndefined(settings)} - /> -
- ) => setEmail(ev.target.value)} - disabled={isLoading || isUndefined(settings)} - /> -
-
-
- ); -}; diff --git a/web/src/pages/enrollment/components/EnrollmentEmail/style.scss b/web/src/pages/enrollment/components/EnrollmentEmail/style.scss deleted file mode 100644 index d5fa90074..000000000 --- a/web/src/pages/enrollment/components/EnrollmentEmail/style.scss +++ /dev/null @@ -1,82 +0,0 @@ -@use '@scssutils' as *; - -#enrollment-email { - & > .message-box-spacer { - padding-bottom: 26px; - - .message-box { - background-color: var(--surface-tag-modal); - } - } - - & > .card { - @include media-breakpoint-up(lg) { - padding: 17px 15px; - } - - & > .controls { - width: 100%; - display: flex; - flex-flow: row wrap; - column-gap: 10px; - margin-bottom: 16px; - - & > .checkbox-wrap { - display: flex; - flex-flow: row nowrap; - column-gap: 5px; - align-items: center; - justify-content: flex-start; - - & > span { - @include typography(app-modal-1); - - color: var(--text-body-secondary); - user-select: none; - cursor: pointer; - } - } - - & > .btn { - min-width: 140px; - - &:nth-child(2) { - margin-left: auto; - } - } - } - - & > .input { - box-sizing: border-box; - margin: 20px 0; - width: 100%; - padding: 0; - } - - & > .text-wrapper { - border-radius: 15px; - box-sizing: border-box; - padding: 20px; - border: 1px solid var(--border-primary); - width: 100%; - min-height: 700px; - overflow: hidden; - - & > textarea { - min-height: 660px; - width: 100%; - height: inherit; - border: none; - padding: 0; - margin: 0; - resize: none; - overflow: none; - background-color: transparent; - - @include typography(app-welcome-2); - - color: var(--text-body-primary); - } - } - } -} diff --git a/web/src/pages/enrollment/components/EnrollmentVPN/EnrollmentVPN.tsx b/web/src/pages/enrollment/components/EnrollmentVPN/EnrollmentVPN.tsx deleted file mode 100644 index 21023642a..000000000 --- a/web/src/pages/enrollment/components/EnrollmentVPN/EnrollmentVPN.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import './style.scss'; - -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { isUndefined } from 'lodash-es'; -import { useCallback, useMemo, useState } from 'react'; - -import { useI18nContext } from '../../../../i18n/i18n-react'; -import { Select } from '../../../../shared/defguard-ui/components/Layout/Select/Select'; -import { - type SelectSelectedValue, - SelectSizeVariant, -} from '../../../../shared/defguard-ui/components/Layout/Select/types'; -import useApi from '../../../../shared/hooks/useApi'; -import { useToaster } from '../../../../shared/hooks/useToaster'; -import { QueryKeys } from '../../../../shared/queries'; -import { useEnrollmentStore } from '../../hooks/useEnrollmentStore'; - -export const EnrollmentVPN = () => { - const [isLoading, setLoading] = useState(false); - - const { LL } = useI18nContext(); - - const componentLL = LL.enrollmentPage.settings.vpnOptionality; - - const { - settings: { editSettings }, - } = useApi(); - - const queryClient = useQueryClient(); - - const toaster = useToaster(); - - const settings = useEnrollmentStore((state) => state.settings); - - const { mutate } = useMutation({ - mutationFn: editSettings, - onSuccess: () => { - void queryClient.invalidateQueries({ - queryKey: [QueryKeys.FETCH_SETTINGS], - }); - toaster.success(LL.enrollmentPage.messages.edit.success()); - setLoading(false); - }, - onError: () => { - toaster.error(LL.enrollmentPage.messages.edit.error()); - setLoading(false); - }, - }); - - const vpnOptionalityOptions = useMemo( - () => [ - { - key: 1, - value: true, - label: componentLL.select.options.optional(), - }, - { - key: 2, - value: false, - label: componentLL.select.options.mandatory(), - }, - ], - [componentLL.select.options], - ); - - const renderSelectedVpn = useCallback( - (selected: boolean): SelectSelectedValue => { - const option = vpnOptionalityOptions.find((o) => o.value === selected); - - if (!option) throw Error("Selected value doesn't exist"); - - return { - key: option.key, - displayValue: option.label, - }; - }, - [vpnOptionalityOptions], - ); - - const handleChange = useCallback( - (val: boolean) => { - if (!isLoading && settings) { - setLoading(true); - try { - mutate({ ...settings, enrollment_vpn_step_optional: val }); - } catch (e) { - setLoading(false); - toaster.error(LL.enrollmentPage.messages.edit.error()); - console.error(e); - } - } - }, - [LL.enrollmentPage.messages.edit, isLoading, mutate, settings, toaster], - ); - - return ( -
-
-

{componentLL.title()}

-
- setNetworkState({ selectedNetworkId: res })} - onCreate={() => { - resetWizardState(); - navigate('/admin/wizard', { replace: true }); - }} - /> -
- )} - - - - ); -}; diff --git a/web/src/pages/network/NetworkEditForm/components/DividerHeader.tsx b/web/src/pages/network/NetworkEditForm/components/DividerHeader.tsx deleted file mode 100644 index 86243ea08..000000000 --- a/web/src/pages/network/NetworkEditForm/components/DividerHeader.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import type { PropsWithChildren } from 'react'; - -type DividerHeaderProps = { - text: string; -} & PropsWithChildren; - -export const DividerHeader = ({ text, children }: DividerHeaderProps) => { - return ( -
-
-

{text}

- {children} -
-
- ); -}; diff --git a/web/src/pages/network/NetworkEditForm/style.scss b/web/src/pages/network/NetworkEditForm/style.scss deleted file mode 100644 index c8840e9d8..000000000 --- a/web/src/pages/network/NetworkEditForm/style.scss +++ /dev/null @@ -1,61 +0,0 @@ -#network-page, -.network-setup { - .network-config { - & > .card { - padding: 15px 30px 40px; - - & > form { - justify-content: flex-start; - align-content: flex-start; - align-items: flex-start; - - & > .hidden { - @include visually-hidden; - } - - & > label { - margin-bottom: 12px; - } - - & > .input-container { - width: 100%; - } - - & > .message-box-spacer { - &:not(:last-child) { - padding-bottom: 25px; - } - } - } - } - } - - #location-mfa-mode-explain-message-box { - ul { - list-style-position: inside; - margin-top: 8px; - - li { - p { - display: inline; - } - } - } - } - - .divider-header { - padding-bottom: var(--spacing-s); - - .inner { - display: flex; - flex-flow: row; - align-items: center; - justify-content: flex-start; - border-bottom: 1px solid var(--border-primary); - } - - .header { - @include typography(app-side-bar); - } - } -} diff --git a/web/src/pages/network/NetworkGateway/NetworkGateway.tsx b/web/src/pages/network/NetworkGateway/NetworkGateway.tsx deleted file mode 100644 index 319c86355..000000000 --- a/web/src/pages/network/NetworkGateway/NetworkGateway.tsx +++ /dev/null @@ -1,154 +0,0 @@ -import './style.scss'; - -import { useQuery } from '@tanstack/react-query'; -import { useCallback, useMemo } from 'react'; -import ReactMarkdown from 'react-markdown'; - -import { useI18nContext } from '../../../i18n/i18n-react'; -import { NetworkGatewaysStatus } from '../../../shared/components/network/GatewaysStatus/NetworkGatewaysStatus/NetworkGatewaysStatus'; -import { ActionButton } from '../../../shared/defguard-ui/components/Layout/ActionButton/ActionButton'; -import { ActionButtonVariant } from '../../../shared/defguard-ui/components/Layout/ActionButton/types'; -import { Button } from '../../../shared/defguard-ui/components/Layout/Button/Button'; -import { - ButtonSize, - ButtonStyleVariant, -} from '../../../shared/defguard-ui/components/Layout/Button/types'; -import { ExpandableCard } from '../../../shared/defguard-ui/components/Layout/ExpandableCard/ExpandableCard'; -import { MessageBox } from '../../../shared/defguard-ui/components/Layout/MessageBox/MessageBox'; -import useApi from '../../../shared/hooks/useApi'; -import { useClipboard } from '../../../shared/hooks/useClipboard'; -import { externalLink } from '../../../shared/links'; -import { QueryKeys } from '../../../shared/queries'; -import { useNetworkPageStore } from '../hooks/useNetworkPageStore'; - -export const NetworkGatewaySetup = () => { - const { writeToClipboard } = useClipboard(); - const selectedNetworkId = useNetworkPageStore((state) => state.selectedNetworkId); - const { LL } = useI18nContext(); - const { - network: { getNetworkToken }, - } = useApi(); - - const { data: networkToken } = useQuery({ - queryKey: [QueryKeys.FETCH_NETWORK_TOKEN, selectedNetworkId], - queryFn: () => getNetworkToken(selectedNetworkId as number), - refetchOnMount: true, - refetchOnWindowFocus: false, - enabled: Boolean(selectedNetworkId), - }); - - const command = useCallback(() => { - return `docker run -e DEFGUARD_TOKEN=${networkToken?.token} -e DEFGUARD_GRPC_URL=${networkToken?.grpc_url} --restart unless-stopped --network host --cap-add NET_ADMIN ghcr.io/defguard/gateway:latest`; - }, [networkToken]); - - const returnNetworkToken = useCallback(() => { - return `${networkToken?.token}`; - }, [networkToken]); - - const getActions = useMemo( - () => [ - { - void writeToClipboard(command()); - }} - />, - ], - [command, writeToClipboard], - ); - - const getNetworkTokenActions = useMemo( - () => [ - { - void writeToClipboard(returnNetworkToken()); - }} - />, - ], - [returnNetworkToken, writeToClipboard], - ); - - // TODO: consider a better way to redirect to the gateway releases page - const handleSubmit = () => { - window.location.href = 'https://github.com/DefGuard/gateway/releases'; - }; - - return ( -
-
-

{LL.gatewaySetup.header.main()}

- {/* {parse( - LL.gatewaySetup.messages.runCommand({ - setupGatewayDocs: externalLink.gitbook.setup.gateway, - }), - )} */} - - {LL.gatewaySetup.messages.runCommand({ - setupGatewayDocs: externalLink.gitbook.setup.gateway, - })} - -
- - - {networkToken - ? LL.gatewaySetup.messages.authToken({ - setupGatewayDocs: externalLink.gitbook.setup.gateway, - }) - : LL.gatewaySetup.messages.createNetwork()} - - - {networkToken && ( - -

{returnNetworkToken()}

-
- )} -

{LL.gatewaySetup.header.dockerBasedGatewaySetup()}

- - - {networkToken - ? LL.gatewaySetup.messages.dockerBasedGatewaySetup({ - setupGatewayDocs: externalLink.gitbook.setup.gateway, - }) - : LL.gatewaySetup.messages.createNetwork()} - - - {networkToken && ( - -

{command()}

-
- )} -

{LL.gatewaySetup.header.fromPackage()}

- - - {LL.gatewaySetup.messages.fromPackage({ - setupGatewayDocs: externalLink.gitbook.setup.gateway, - })} - - -
- ); -}; diff --git a/web/src/pages/network/NetworkGateway/style.scss b/web/src/pages/network/NetworkGateway/style.scss deleted file mode 100644 index 9bd3147d0..000000000 --- a/web/src/pages/network/NetworkGateway/style.scss +++ /dev/null @@ -1,74 +0,0 @@ -#network-page { - .gateway { - display: flex; - flex-direction: column; - row-gap: 20px; - - & > section.header-section { - h2, - p { - margin-bottom: 35px; - } - } - - & > .message-box-spacer { - padding-bottom: 20px; - - @include media-breakpoint-up(lg) { - padding-bottom: 40px; - } - } - - & > .expandable-card { - & > .expanded-content { - overflow: hidden; - box-sizing: border-box; - - & > p { - width: 100%; - max-width: 100%; - text-overflow: ellipsis; - text-align: left; - word-break: break-all; - white-space: normal; - @include small-text; - - line-height: 22px; - color: var(--text-body-secondary); - padding: 20px 0px; - } - } - - margin-bottom: 30px; - } - - h2 { - @include typography(app-body-1); - color: var(--text-body-primary); - text-align: center; - } - - h3 { - @include typography(modal-title); - - color: var(--text-body-primary); - } - - p { - & > a { - color: var(--text-body-secondary); - } - - @include typography(app-modal-2); - color: var(--text-body-secondary); - } - - & > p { - text-align: center; - } - - & > .btn { - width: 100%; - } - } -} diff --git a/web/src/pages/network/NetworkPage.tsx b/web/src/pages/network/NetworkPage.tsx deleted file mode 100644 index b6eb19d25..000000000 --- a/web/src/pages/network/NetworkPage.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import './style.scss'; - -import { useQuery } from '@tanstack/react-query'; -import { useEffect } from 'react'; -import { useBreakpoint } from 'use-breakpoint'; - -import { useI18nContext } from '../../i18n/i18n-react'; -import { PageContainer } from '../../shared/components/Layout/PageContainer/PageContainer'; -import { deviceBreakpoints } from '../../shared/constants'; -import { Card } from '../../shared/defguard-ui/components/Layout/Card/Card'; -import useApi from '../../shared/hooks/useApi'; -import { QueryKeys } from '../../shared/queries'; -import { useNetworkPageStore } from './hooks/useNetworkPageStore'; -import { NetworkControls } from './NetworkControls/NetworkControls'; -import { NetworkEditForm } from './NetworkEditForm/NetworkEditForm'; -import { NetworkGatewaySetup } from './NetworkGateway/NetworkGateway'; -import { NetworkTabs } from './NetworkTabs/NetworkTabs'; - -export const NetworkPage = () => { - const { - network: { getNetworks }, - } = useApi(); - const { LL } = useI18nContext(); - const setNetworks = useNetworkPageStore((state) => state.setNetworks); - const { breakpoint } = useBreakpoint(deviceBreakpoints); - - const { data: networksData } = useQuery({ - queryKey: [QueryKeys.FETCH_NETWORKS], - queryFn: getNetworks, - refetchOnWindowFocus: false, - }); - - useEffect(() => { - if (networksData) { - setNetworks(networksData); - } - }, [networksData, setNetworks]); - - return ( - -
-

{LL.networkPage.pageTitle()}

-
- {breakpoint === 'desktop' && } - - - - - -
- ); -}; diff --git a/web/src/pages/network/NetworkTabs/NetworkTabs.tsx b/web/src/pages/network/NetworkTabs/NetworkTabs.tsx deleted file mode 100644 index fce9d2516..000000000 --- a/web/src/pages/network/NetworkTabs/NetworkTabs.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { useMemo } from 'react'; -import { useNavigate } from 'react-router'; - -import { useI18nContext } from '../../../i18n/i18n-react'; -import { CardTabs } from '../../../shared/defguard-ui/components/Layout/CardTabs/CardTabs'; -import { useWizardStore } from '../../wizard/hooks/useWizardStore'; -import { useNetworkPageStore } from '../hooks/useNetworkPageStore'; - -export const NetworkTabs = () => { - const navigate = useNavigate(); - const { LL } = useI18nContext(); - const networks = useNetworkPageStore((state) => state.networks); - const selectedNetworkId = useNetworkPageStore((state) => state.selectedNetworkId); - const setPageState = useNetworkPageStore((state) => state.setState); - const resetWizardState = useWizardStore((state) => state.resetState); - const tabs = useMemo( - () => - networks.map((n) => ({ - key: n.id, - onClick: () => { - if (n.id !== selectedNetworkId) { - setPageState({ selectedNetworkId: n.id }); - } - }, - content: n.name, - active: n.id === selectedNetworkId, - })), - [networks, selectedNetworkId, setPageState], - ); - - return ( - { - resetWizardState(); - navigate('/admin/wizard', { replace: true }); - }} - loading={!networks || networks.length === 0} - /> - ); -}; diff --git a/web/src/pages/network/hooks/useNetworkPageStore.ts b/web/src/pages/network/hooks/useNetworkPageStore.ts deleted file mode 100644 index edac1fce3..000000000 --- a/web/src/pages/network/hooks/useNetworkPageStore.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Subject } from 'rxjs'; -import { createWithEqualityFn } from 'zustand/traditional'; - -import type { Network } from '../../../shared/types'; - -type NetworkPageStore = { - saveSubject: Subject; - loading: boolean; - networks: Network[]; - selectedNetworkId?: number; - setState: (data: Partial) => void; - setNetworks: (data: Network[]) => void; -}; - -export const useNetworkPageStore = createWithEqualityFn()( - (set, get) => ({ - saveSubject: new Subject(), - loading: false, - networks: [], - selectedNetworkId: undefined, - setState: (newState) => set(() => newState), - setNetworks: (networks) => { - const sortedNetworks = networks.sort((a, b) => a.name.localeCompare(b.name)); - if (get().selectedNetworkId === undefined) { - set({ selectedNetworkId: sortedNetworks[0]?.id }); - } - set({ networks: sortedNetworks }); - }, - }), - Object.is, -); diff --git a/web/src/pages/network/style.scss b/web/src/pages/network/style.scss deleted file mode 100644 index 78f356703..000000000 --- a/web/src/pages/network/style.scss +++ /dev/null @@ -1,128 +0,0 @@ -#network-page { - & > .page-content { - box-sizing: border-box; - overflow-y: auto; - overflow-x: hidden; - display: block; - padding: 0; - background-color: var(--white); - - @include media-breakpoint-up(lg) { - background-color: var(--bg-light); - padding: 64px 70px 45px; - } - - & > header { - display: none; - height: auto; - margin-bottom: 5px; - width: 100%; - - & > h1 { - user-select: none; - - @include page-header; - } - - @include media-breakpoint-up(lg) { - display: block; - - & > h1 { - line-height: 62px; - } - } - } - - & > .network-card { - border-radius: 0 15px 15px 15px; - width: 100%; - max-width: 100%; - padding: 30px 20px 20px; - box-sizing: border-box; - display: grid; - grid-template-rows: repeat(3, auto); - grid-template-columns: 1fr; - row-gap: 15px; - grid-template-areas: - 'controls' - 'config' - 'gateway'; - - @include media-breakpoint-up(lg) { - padding: 20px; - row-gap: 30px; - column-gap: 30px; - grid-template-rows: 40px repeat(2, auto); - grid-template-columns: 1fr; - grid-template-areas: - 'controls' - 'config' - 'gateway'; - } - - @include media-breakpoint-up(xl) { - padding: 20px; - row-gap: 48px; - column-gap: 57px; - grid-template-rows: 40px auto; - grid-template-columns: 1fr 1fr; - grid-template-areas: - 'controls controls' - 'config gateway'; - } - - & > .network-controls { - grid-area: controls; - } - - & > .network-config { - grid-area: config; - } - - & > .gateway { - grid-area: gateway; - } - - & > * { - width: 100%; - max-width: 100%; - } - - header { - display: flex; - flex-flow: row nowrap; - align-items: center; - justify-content: flex-start; - column-gap: 7px; - user-select: none; - margin-bottom: 20px; - - h2 { - @include typography-legacy(20px, 30px, semiBold, var(--text-main), 'Poppins'); - } - } - - .message-box-spacer { - &:not(:last-child) { - padding-bottom: 20px; - } - } - - form { - & > * { - width: 100%; - } - - & > .message-box-spacer { - &:not(:last-child) { - padding-bottom: 32px; - } - } - - & > .form-checkbox { - margin-bottom: 25px; - } - } - } - } -} diff --git a/web/src/pages/openid/OpenidClientsListPage/OpenidClientsListPage.tsx b/web/src/pages/openid/OpenidClientsListPage/OpenidClientsListPage.tsx deleted file mode 100644 index 91ddb2d7e..000000000 --- a/web/src/pages/openid/OpenidClientsListPage/OpenidClientsListPage.tsx +++ /dev/null @@ -1,372 +0,0 @@ -import './style.scss'; - -import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; -import { isUndefined, orderBy } from 'lodash-es'; -import { useCallback, useEffect, useMemo, useState } from 'react'; -import { useBreakpoint } from 'use-breakpoint'; - -import { useI18nContext } from '../../../i18n/i18n-react'; -import { PageContainer } from '../../../shared/components/Layout/PageContainer/PageContainer'; -import IconCheckmarkGreen from '../../../shared/components/svg/IconCheckmarkGreen'; -import IconDeactivated from '../../../shared/components/svg/IconDeactivated'; -import SvgIconPlusWhite from '../../../shared/components/svg/IconPlusWhite'; -import { deviceBreakpoints } from '../../../shared/constants'; -import { Button } from '../../../shared/defguard-ui/components/Layout/Button/Button'; -import { - ButtonSize, - ButtonStyleVariant, -} from '../../../shared/defguard-ui/components/Layout/Button/types'; -import { EditButton } from '../../../shared/defguard-ui/components/Layout/EditButton/EditButton'; -import { EditButtonOption } from '../../../shared/defguard-ui/components/Layout/EditButton/EditButtonOption'; -import { EditButtonOptionStyleVariant } from '../../../shared/defguard-ui/components/Layout/EditButton/types'; -import { LoaderSpinner } from '../../../shared/defguard-ui/components/Layout/LoaderSpinner/LoaderSpinner'; -import { ConfirmModal } from '../../../shared/defguard-ui/components/Layout/modals/ConfirmModal/ConfirmModal'; -import { ConfirmModalType } from '../../../shared/defguard-ui/components/Layout/modals/ConfirmModal/types'; -import { NoData } from '../../../shared/defguard-ui/components/Layout/NoData/NoData'; -import { Search } from '../../../shared/defguard-ui/components/Layout/Search/Search'; -import { Select } from '../../../shared/defguard-ui/components/Layout/Select/Select'; -import type { - SelectOption, - SelectSelectedValue, -} from '../../../shared/defguard-ui/components/Layout/Select/types'; -import { - type ListHeader, - type ListRowCell, - ListSortDirection, -} from '../../../shared/defguard-ui/components/Layout/VirtualizedList/types'; -import { VirtualizedList } from '../../../shared/defguard-ui/components/Layout/VirtualizedList/VirtualizedList'; -import { useModalStore } from '../../../shared/hooks/store/useModalStore'; -import useApi from '../../../shared/hooks/useApi'; -import { useClipboard } from '../../../shared/hooks/useClipboard'; -import { useToaster } from '../../../shared/hooks/useToaster'; -import { MutationKeys } from '../../../shared/mutations'; -import { QueryKeys } from '../../../shared/queries'; -import type { OpenidClient } from '../../../shared/types'; -import { OpenIdClientModal } from '../modals/OpenIdClientModal/OpenIdClientModal'; - -export const OpenidClientsListPage = () => { - const { writeToClipboard } = useClipboard(); - const { LL } = useI18nContext(); - const toaster = useToaster(); - const queryClient = useQueryClient(); - const { breakpoint } = useBreakpoint(deviceBreakpoints); - const [deleteClientModalOpen, setDeleteClientModalOpen] = useState(false); - const [deleteClient, setDeleteClient] = useState(undefined); - const [searchValue, setSearchValue] = useState(''); - const { - openid: { getOpenidClients, changeOpenidClientState, deleteOpenidClient }, - } = useApi(); - const setOpenIdClientModalState = useModalStore((state) => state.setOpenIdClientModal); - - const selectOptions = useMemo( - (): SelectOption[] => [ - { - key: 1, - label: LL.openidOverview.filterLabels.all(), - value: FilterOption.ALL, - }, - { - key: 3, - label: LL.openidOverview.filterLabels.enabled(), - value: FilterOption.ENABLED, - }, - { - key: 2, - label: LL.openidOverview.filterLabels.disabled(), - value: FilterOption.DISABLED, - }, - ], - [LL], - ); - - const [selectedFilter, setSelectedFilter] = useState(FilterOption.ALL); - - const { mutate: deleteClientMutation, isPending: deleteClientLoading } = useMutation({ - mutationKey: [MutationKeys.DELETE_OPENID_CLIENT], - mutationFn: deleteOpenidClient, - onSuccess: () => { - toaster.success(LL.openidOverview.deleteApp.messages.success()); - void queryClient.invalidateQueries({ - queryKey: [QueryKeys.FETCH_CLIENTS], - }); - setDeleteClientModalOpen(false); - }, - onError: (err) => { - toaster.error(LL.messages.error()); - setDeleteClientModalOpen(false); - console.error(err); - }, - }); - - const { mutate: editClientStatusMutation } = useMutation({ - mutationFn: (client: OpenidClient) => - changeOpenidClientState({ - clientId: client.client_id, - enabled: !client.enabled, - }), - onSuccess: (_, client) => { - if (client.enabled) { - toaster.success(LL.openidOverview.disableApp.messages.success()); - } else { - toaster.success(LL.openidOverview.enableApp.messages.success()); - } - void queryClient.invalidateQueries({ - queryKey: [QueryKeys.FETCH_CLIENTS], - }); - }, - onError: (err) => { - toaster.error(LL.messages.error()); - console.error(err); - }, - }); - - const { data: clients, isLoading } = useQuery({ - queryKey: [QueryKeys.FETCH_CLIENTS], - queryFn: getOpenidClients, - refetchOnWindowFocus: false, - refetchInterval: 15000, - }); - - const filteredClients = useMemo(() => { - if (!clients || (clients && clients.length === 0)) return []; - let res = orderBy(clients, ['name'], ['asc']); - res = res.filter((c) => c.name.toLowerCase().includes(searchValue.toLowerCase())); - switch (selectedFilter) { - case FilterOption.ALL: - break; - case FilterOption.ENABLED: - res = res.filter((c) => c.enabled); - break; - case FilterOption.DISABLED: - res = res.filter((c) => !c.enabled); - break; - } - return res; - }, [clients, searchValue, selectedFilter]); - - const listHeaders = useMemo(() => { - const res: ListHeader[] = [ - { - key: 'name', - text: LL.openidOverview.list.headers.name(), - active: true, - sortDirection: ListSortDirection.ASC, - }, - { - key: 'status', - text: LL.openidOverview.list.headers.status(), - }, - { - key: 'actions', - text: LL.openidOverview.list.headers.actions(), - sortable: false, - }, - ]; - return res; - }, [LL.openidOverview.list.headers]); - - const listCells = useMemo(() => { - const res: ListRowCell[] = [ - { - key: 'name', - render: (client) => {client.name}, - }, - { - key: 'status', - render: (client) => - client.enabled ? ( - <> - {' '} - {LL.openidOverview.list.status.enabled()} - - ) : ( - <> - {LL.openidOverview.list.status.disabled()} - - ), - }, - { - key: 'actions', - render: (client) => ( - - - setOpenIdClientModalState({ - visible: true, - viewMode: false, - client, - }) - } - /> - editClientStatusMutation(client)} - /> - { - void writeToClipboard( - client.client_id, - LL.openidOverview.messages.copySuccess(), - ); - }} - /> - { - setDeleteClient(client); - setDeleteClientModalOpen(true); - }} - /> - - ), - }, - ]; - return res; - }, [ - writeToClipboard, - LL.openidOverview.list.editButton, - LL.openidOverview.list.status, - LL.openidOverview.messages, - editClientStatusMutation, - setOpenIdClientModalState, - ]); - - const getListPadding = useMemo(() => { - if (breakpoint === 'desktop') { - return { - left: 60, - right: 60, - }; - } - return { - left: 20, - right: 20, - }; - }, [breakpoint]); - - const renderSelectedFilter = useCallback( - (selected: FilterOption): SelectSelectedValue => { - const option = selectOptions.find((o) => o.value === selected); - if (!option) throw Error("Selected value doesn't exists"); - return { - key: selected, - displayValue: option.label, - }; - }, - [selectOptions], - ); - - useEffect(() => { - if (breakpoint !== 'desktop' && selectedFilter !== FilterOption.ALL) { - setSelectedFilter(FilterOption.ALL); - } - }, [breakpoint, selectedFilter]); - - return ( - -
-

{LL.openidOverview.pageTitle()}

- setSearchValue(value)} - /> -
-
-
- {LL.openidOverview.clientCount()} -
- {clients && clients.length > 0 ? clients.length : 0} -
-
-
- {breakpoint === 'desktop' && ( - { - if (networkId !== null) { - navigate(`/admin/overview/${networkId}${location.search}`); - } else { - navigate(`/admin/overview${location.search}`); - } - }} - /> - ); -}; diff --git a/web/src/pages/overview-index/components/OverviewTimeSelection/OverviewTimeSelection.tsx b/web/src/pages/overview-index/components/OverviewTimeSelection/OverviewTimeSelection.tsx deleted file mode 100644 index 44c4539fa..000000000 --- a/web/src/pages/overview-index/components/OverviewTimeSelection/OverviewTimeSelection.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { useMemo } from 'react'; - -import { useI18nContext } from '../../../../i18n/i18n-react'; -import { Select } from '../../../../shared/defguard-ui/components/Layout/Select/Select'; -import { - type SelectOption, - SelectSizeVariant, -} from '../../../../shared/defguard-ui/components/Layout/Select/types'; -import { useOverviewTimeSelection } from '../hooks/useOverviewTimeSelection'; - -const availableFilters: number[] = [1, 2, 4, 6, 8, 12, 16, 24]; - -export const OverviewTimeSelection = () => { - const { from, setTimeSelection } = useOverviewTimeSelection(); - const { LL } = useI18nContext(); - const options = useMemo((): SelectOption[] => { - return availableFilters.map((filter) => ({ - key: filter, - label: LL.networkOverview.timeRangeSelectionLabel({ - value: filter, - }), - value: filter, - })); - }, [LL.networkOverview]); - - return ( - { - setOverviewStore({ selectedNetworkId: res }); - }} - /> - ); -}; diff --git a/web/src/pages/overview/OverviewPage.tsx b/web/src/pages/overview/OverviewPage.tsx deleted file mode 100644 index 9def60e10..000000000 --- a/web/src/pages/overview/OverviewPage.tsx +++ /dev/null @@ -1,176 +0,0 @@ -import './style.scss'; - -import { useQuery } from '@tanstack/react-query'; -import { isUndefined, orderBy } from 'lodash-es'; -import { useEffect, useMemo } from 'react'; -import { useLocation, useNavigate, useParams } from 'react-router'; -import { useBreakpoint } from 'use-breakpoint'; - -import { useI18nContext } from '../../i18n/i18n-react'; -import { PageContainer } from '../../shared/components/Layout/PageContainer/PageContainer'; -import { NetworkGatewaysStatus } from '../../shared/components/network/GatewaysStatus/NetworkGatewaysStatus/NetworkGatewaysStatus'; -import { deviceBreakpoints } from '../../shared/constants'; -import { LoaderSpinner } from '../../shared/defguard-ui/components/Layout/LoaderSpinner/LoaderSpinner'; -import { NoData } from '../../shared/defguard-ui/components/Layout/NoData/NoData'; -import useApi from '../../shared/hooks/useApi'; -import { QueryKeys } from '../../shared/queries'; -import { OverviewLayoutType } from '../../shared/types'; -import { sortByDate } from '../../shared/utils/sortByDate'; -import { useOverviewTimeSelection } from '../overview-index/components/hooks/useOverviewTimeSelection'; -import { useWizardStore } from '../wizard/hooks/useWizardStore'; -import { useOverviewStore } from './hooks/store/useOverviewStore'; -import { OverviewConnectedUsers } from './OverviewConnectedUsers/OverviewConnectedUsers'; -import { StandaloneDeviceConnectionCard } from './OverviewConnectedUsers/UserConnectionCard/UserConnectionCard'; -import { OverviewExpandable } from './OverviewExpandable/OverviewExpandable'; -import { OverviewHeader } from './OverviewHeader/OverviewHeader'; -import { OverviewStats } from './OverviewStats/OverviewStats'; - -const STATUS_REFETCH_TIMEOUT = 30 * 1000; - -export const OverviewPage = () => { - const navigate = useNavigate(); - const { breakpoint } = useBreakpoint(deviceBreakpoints); - const setOverViewStore = useOverviewStore((state) => state.setState); - const resetWizard = useWizardStore((state) => state.resetState); - const viewMode = useOverviewStore((state) => state.viewMode); - const { LL } = useI18nContext(); - const { from: statsFilter } = useOverviewTimeSelection(); - const { networkId } = useParams(); - const selectedNetworkId = parseInt(networkId ?? '', 10); - const location = useLocation(); - - const { - network: { getNetworks, getOverviewStats, getNetworkStats }, - } = useApi(); - - const { data: fetchNetworksData } = useQuery({ - queryKey: ['network'], - queryFn: getNetworks, - placeholderData: (perv) => perv, - select: (networks) => - orderBy(networks, (network) => network.name.toLowerCase(), ['asc']), - }); - - const { data: networkStats } = useQuery({ - queryKey: [QueryKeys.FETCH_NETWORK_STATS, statsFilter, selectedNetworkId], - queryFn: () => - getNetworkStats({ - from: statsFilter, - id: selectedNetworkId, - }), - refetchOnWindowFocus: false, - refetchInterval: STATUS_REFETCH_TIMEOUT, - enabled: !isUndefined(selectedNetworkId) && !Number.isNaN(selectedNetworkId), - }); - - const { data: overviewStats, isLoading: userStatsLoading } = useQuery({ - queryKey: [QueryKeys.FETCH_NETWORK_USERS_STATS, statsFilter, selectedNetworkId], - queryFn: () => - getOverviewStats({ - from: statsFilter, - id: selectedNetworkId, - }), - enabled: - !isUndefined(statsFilter) && - !isUndefined(selectedNetworkId) && - !Number.isNaN(selectedNetworkId), - refetchOnWindowFocus: false, - refetchInterval: STATUS_REFETCH_TIMEOUT, - }); - - const getNetworkUsers = useMemo(() => { - if (overviewStats !== undefined) { - const user = sortByDate(overviewStats.user_devices, (s) => { - const fistDevice = sortByDate(s.devices, (d) => d.connected_at, false)[0]; - return fistDevice.connected_at; - }); - const devices = sortByDate( - overviewStats.network_devices.filter((d) => d.connected_at !== undefined), - (d) => d.connected_at as string, - ); - return { - network_devices: devices, - user_devices: user, - }; - } - return undefined; - }, [overviewStats]); - - // FIXME: lock viewMode on grid for now - useEffect(() => { - if (viewMode !== OverviewLayoutType.GRID) { - setOverViewStore({ viewMode: OverviewLayoutType.GRID }); - } - }, [setOverViewStore, viewMode]); - - // biome-ignore lint/correctness/useExhaustiveDependencies: migration, checkMeLater - useEffect(() => { - if (Number.isNaN(selectedNetworkId)) { - navigate(`/admin/overview/${location.search}`, { - replace: true, - }); - } - if (fetchNetworksData) { - if (!fetchNetworksData.length) { - resetWizard(); - navigate('/admin/wizard', { replace: true }); - } else { - setOverViewStore({ networks: fetchNetworksData }); - const ids = fetchNetworksData.map((n) => n.id); - if ( - isUndefined(selectedNetworkId) || - (!isUndefined(selectedNetworkId) && !ids.includes(selectedNetworkId)) - ) { - const oldestNetwork = orderBy(fetchNetworksData, ['id'], ['asc'])[0]; - setOverViewStore({ selectedNetworkId: oldestNetwork.id }); - } - } - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [fetchNetworksData, selectedNetworkId]); - - return ( - <> - - - {breakpoint === 'desktop' && !isUndefined(selectedNetworkId) && ( - - )} - {networkStats && } -
- {userStatsLoading && ( -
- -
- )} - {!getNetworkUsers && !userStatsLoading && } - {!userStatsLoading && - getNetworkUsers && - getNetworkUsers.network_devices.length === 0 && - getNetworkUsers.user_devices.length === 0 && } - {!userStatsLoading && - getNetworkUsers && - getNetworkUsers.user_devices.length > 0 && ( - - - - )} - {!userStatsLoading && - getNetworkUsers && - getNetworkUsers.network_devices.length > 0 && ( - -
-
- {getNetworkUsers.network_devices.map((device) => ( - - ))} -
-
-
- )} -
-
- {/* Modals */} - - ); -}; diff --git a/web/src/pages/overview/OverviewStats/OverviewStats.tsx b/web/src/pages/overview/OverviewStats/OverviewStats.tsx deleted file mode 100644 index f4ebc26d8..000000000 --- a/web/src/pages/overview/OverviewStats/OverviewStats.tsx +++ /dev/null @@ -1,373 +0,0 @@ -import './style.scss'; - -import clsx from 'clsx'; -import { orderBy } from 'lodash-es'; -import millify from 'millify'; -import { forwardRef, type ReactNode, useId, useMemo } from 'react'; -import AutoSizer from 'react-virtualized-auto-sizer'; - -import { useI18nContext } from '../../../i18n/i18n-react'; -import IconPacketsIn from '../../../shared/components/svg/IconPacketsIn'; -import IconPacketsOut from '../../../shared/components/svg/IconPacketsOut'; -import { Card } from '../../../shared/defguard-ui/components/Layout/Card/Card'; -import { NetworkSpeed } from '../../../shared/defguard-ui/components/Layout/NetworkSpeed/NetworkSpeed'; -import { NetworkDirection } from '../../../shared/defguard-ui/components/Layout/NetworkSpeed/types'; -import { isPresent } from '../../../shared/defguard-ui/utils/isPresent'; -import type { WireguardNetworkStats } from '../../../shared/types'; -import { useOverviewTimeSelection } from '../../overview-index/components/hooks/useOverviewTimeSelection'; -import { NetworkUsageChart } from '../OverviewConnectedUsers/shared/components/NetworkUsageChart/NetworkUsageChart'; -import { networkTrafficToChartData } from './utils'; - -interface Props { - networkStats: WireguardNetworkStats; -} - -export const OverviewStats = forwardRef( - ({ networkStats }, ref) => { - const { from: filterValue } = useOverviewTimeSelection(); - const peakDownload = useMemo(() => { - const sorted = orderBy(networkStats.transfer_series, (stats) => stats.download, [ - 'desc', - ]); - return sorted[0]?.download ?? 0; - }, [networkStats.transfer_series]); - const peakUpload = useMemo(() => { - const sorted = orderBy(networkStats.transfer_series, ['upload'], ['desc']); - return sorted[0]?.upload ?? 0; - }, [networkStats.transfer_series]); - const { LL } = useI18nContext(); - const localLL = LL.networkOverview.stats; - - const chartData = useMemo( - () => networkTrafficToChartData(networkStats.transfer_series, filterValue), - [filterValue, networkStats.transfer_series], - ); - - const info = useMemo( - (): InfoProps[] => [ - { - key: 'currently-active-users', - count: networkStats.current_active_users, - icon: , - title: localLL.currentlyActiveUsers(), - subTitle: localLL.totalUserDevices({ - count: networkStats.current_active_users, - }), - }, - { - key: 'current-active-network-devices', - title: localLL.currentlyActiveNetworkDevices(), - icon: , - count: networkStats.current_active_network_devices, - }, - { - key: 'active-users-icon', - title: localLL.activeUsersFilter({ - hour: filterValue, - }), - count: networkStats.active_users, - icon: , - subTitle: localLL.totalUserDevices({ - count: networkStats.active_user_devices, - }), - }, - { - key: 'active-network-devices', - title: localLL.activeNetworkDevices({ - hour: filterValue, - }), - icon: , - count: networkStats.current_active_network_devices, - }, - ], - [ - filterValue, - localLL, - networkStats.active_user_devices, - networkStats.active_users, - networkStats.current_active_network_devices, - networkStats.current_active_users, - ], - ); - - return ( -
- - {info.map((info) => ( - - ))} -
- {LL.networkOverview.stats.networkUsage()} -
-
- - {LL.networkOverview.stats.in()} - - -
-
- - {LL.networkOverview.stats.out()} - - -
-
-
-
- -
-

{LL.networkOverview.stats.activityIn({ hour: filterValue })}

-
- {LL.networkOverview.stats.peak()} -
- - -
-
- - -
-
-
-
- - {({ width, height }) => ( - <> - {networkStats.transfer_series && ( - - )} - - )} - -
-
-
- ); - }, -); - -type InfoProps = { - icon: ReactNode; - title: string; - subTitle?: string; - count: number; - key: string | number; -}; - -const InfoContainer = ({ count, icon, subTitle, title }: InfoProps) => { - return ( -
-

{title}

-
- {icon} -

- {millify(count, { - precision: 0, - })} -

-
- {isPresent(subTitle) &&

{subTitle}

} -
- ); -}; - -const CurrentActiveUsersIcon = () => { - return ( - - - - - ); -}; - -const CurrentActiveNetworkDevicesIcon = () => { - const maskId = useId(); - return ( - - - - - - - - - - - - ); -}; - -const ActiveUsersIcon = () => { - return ( - - - - - - - ); -}; - -const ActiveNetworkDevicesIcon = () => { - const maskId = useId(); - const mask2Id = useId(); - return ( - - - - - - - - - - - - - - - - - - ); -}; diff --git a/web/src/pages/overview/OverviewStats/style.scss b/web/src/pages/overview/OverviewStats/style.scss deleted file mode 100644 index ffdc73c69..000000000 --- a/web/src/pages/overview/OverviewStats/style.scss +++ /dev/null @@ -1,204 +0,0 @@ -.overview-network-stats { - display: grid; - - grid-template-columns: 1fr; - grid-template-rows: 1fr 1fr; - gap: var(--spacing-s); - padding: 0 var(--spacing-xs) var(--spacing-xs); - align-items: stretch; - overflow-x: auto; - - @include media-breakpoint-up(lg) { - overflow-x: unset; - } - - @include media-breakpoint-up(xl) { - grid-template-columns: 850px 1fr; - grid-template-rows: 1fr; - } - - @include media-breakpoint-up(xxl) { - grid-template-columns: auto 1fr; - } - - & > .summary { - position: relative; - width: 100%; - box-shadow: 5px 5px 15px #00000005; - background-color: var(--white); - border-radius: 15px; - display: flex; - align-items: stretch; - align-content: center; - justify-content: flex-start; - flex-flow: row nowrap; - min-height: 120px; - min-width: 800px; - - & > .info { - display: flex; - flex-flow: column; - align-items: center; - justify-content: flex-start; - row-gap: var(--spacing-xs); - padding: var(--spacing-s) 5px; - min-width: 155px; - - &:not(:first-child) { - border-left: 1px solid var(--border-primary); - } - - &.network-usage { - row-gap: var(--spacing-m); - } - - &:not(.network-usage) { - .info-title { - min-height: 29px; - } - } - - .info-title { - color: var(--text-body-tertiary); - text-align: center; - @include typography(app-modal-1); - max-width: 120px; - } - - .info-track { - width: 100%; - display: flex; - flex-flow: row; - align-items: center; - justify-content: center; - column-gap: var(--spacing-xs); - max-height: 42px; - - .info-count { - @include typography(app-title); - } - } - - .info-sub-title { - color: var(--text-body-tertiary); - @include typography(app-modal-3); - } - - .network-usage-track { - display: flex; - flex-flow: row; - align-items: center; - justify-content: center; - column-gap: var(--spacing-xs); - - & > :nth-child(1) { - svg { - transform: rotate(-90deg); - } - } - - & > :nth-child(2) { - svg { - transform: rotate(-90deg); - } - } - - .network-speed { - &.download { - svg { - transform: rotate(90deg); - } - } - } - - .network-usage { - & > span { - @include typography(app-modal-1); - } - } - } - } - } - - & > .activity-graph { - padding: 1.5rem 2rem 1rem; - box-sizing: border-box; - border-radius: 15px; - background-color: var(--white); - box-shadow: 5px 5px 15px #00000005; - min-height: 120px; - display: grid; - grid-template-rows: 21px 1fr; - grid-template-columns: 1fr; - row-gap: 15px; - width: 100%; - - & > .chart { - grid-row: 2; - grid-column: 1 / 2; - } - - & > header { - grid-row: 1; - grid-column: 1 / 2; - display: flex; - flex-direction: row; - align-items: center; - align-content: center; - justify-content: flex-start; - - h3 { - @include typography-legacy(15px, 21px, medium, var(--text-main), 'Poppins'); - - @include media-breakpoint-down(md) { - text-transform: uppercase; - @include text-weight(semiBold); - } - - @include media-breakpoint-up(md) { - } - } - - & > .peaks { - margin-left: auto; - display: flex; - flex-direction: row; - align-items: center; - align-content: center; - justify-content: flex-start; - height: 17px; - - @include media-breakpoint-down(md) { - column-gap: 1rem; - } - - @include media-breakpoint-up(md) { - column-gap: 2rem; - } - - & > span { - @include media-breakpoint-down(md) { - &:first-of-type { - display: none; - } - } - - @include typography-legacy(12px, 17px, medium, var(--gray-light), 'Poppins'); - } - - & > .network-speed { - display: flex; - flex-direction: row; - align-items: center; - align-content: center; - justify-content: flex-start; - column-gap: 0.4rem; - } - } - } - - & > .network-speed { - margin-top: 1rem; - } - } -} diff --git a/web/src/pages/overview/OverviewStats/utils.ts b/web/src/pages/overview/OverviewStats/utils.ts deleted file mode 100644 index fe9e648be..000000000 --- a/web/src/pages/overview/OverviewStats/utils.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { groupBy, map, sortBy } from 'lodash-es'; - -import type { NetworkSpeedStats } from '../../../shared/types'; - -type AggregatedTick = { - collected_at: string; - download: number; - upload: number; - count: number; -}; - -export const networkTrafficToChartData = ( - ticks: NetworkSpeedStats[], - filter: number, -): NetworkSpeedStats[] => { - if (filter >= 2 && filter <= 5 && ticks.length > 60) { - const sorted = sortBy(ticks, (tick) => new Date(tick.collected_at).getTime()); - const first = new Date(sorted[0].collected_at).getTime(); - const last = new Date(sorted[sorted.length - 1].collected_at).getTime(); - - const totalMinutes = Math.max(1, Math.floor((last - first) / (1000 * 60))); - const minutesPerBucket = Math.ceil(totalMinutes / 60); - - const grouped = groupBy(sorted, (tick) => { - const date = new Date(tick.collected_at); - const bucketTime = new Date( - Math.floor(date.getTime() / (minutesPerBucket * 60 * 1000)) * - minutesPerBucket * - 60 * - 1000, - ); - return bucketTime.toISOString(); - }); - - return map(grouped, (group, timestamp): AggregatedTick => { - const totalDownload = group.reduce((sum, t) => sum + t.download, 0); - const totalUpload = group.reduce((sum, t) => sum + t.upload, 0); - const count = group.length; - - return { - collected_at: timestamp, - download: totalDownload, - upload: totalUpload, - count, - }; - }); - } - return ticks; -}; diff --git a/web/src/pages/overview/OverviewViewSelect/OverviewViewSelect.tsx b/web/src/pages/overview/OverviewViewSelect/OverviewViewSelect.tsx deleted file mode 100644 index 6f3ada31f..000000000 --- a/web/src/pages/overview/OverviewViewSelect/OverviewViewSelect.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import { useCallback, useEffect, useMemo } from 'react'; -import { useBreakpoint } from 'use-breakpoint'; - -import { useI18nContext } from '../../../i18n/i18n-react'; -import { deviceBreakpoints } from '../../../shared/constants'; -import { Select } from '../../../shared/defguard-ui/components/Layout/Select/Select'; -import type { - SelectOption, - SelectSelectedValue, -} from '../../../shared/defguard-ui/components/Layout/Select/types'; -import { OverviewLayoutType } from '../../../shared/types'; -import { useOverviewStore } from '../hooks/store/useOverviewStore'; - -export const OverviewViewSelect = () => { - const { breakpoint } = useBreakpoint(deviceBreakpoints); - const defaultViewMode = useOverviewStore((state) => state.defaultViewMode); - const viewMode = useOverviewStore((state) => state.viewMode); - const setOverViewStore = useOverviewStore((state) => state.setState); - const { LL } = useI18nContext(); - - useEffect(() => { - setOverViewStore({ viewMode: defaultViewMode }); - }, [defaultViewMode, setOverViewStore]); - - const getSelectOptions = useMemo((): SelectOption[] => { - if (breakpoint === 'mobile') { - return [ - { - key: 0, - value: OverviewLayoutType.GRID, - label: LL.networkOverview.filterLabels.grid(), - }, - { - key: 1, - value: OverviewLayoutType.LIST, - label: LL.networkOverview.filterLabels.list(), - disabled: true, - }, - ]; - } - if (breakpoint === 'tablet') { - return [ - { - key: 0, - value: OverviewLayoutType.GRID, - label: LL.networkOverview.filterLabels.grid(), - disabled: true, - }, - { - key: 1, - value: OverviewLayoutType.LIST, - label: LL.networkOverview.filterLabels.list(), - disabled: false, - }, - ]; - } - return [ - { - key: 0, - value: OverviewLayoutType.GRID, - label: LL.networkOverview.filterLabels.grid(), - }, - { - key: 1, - value: OverviewLayoutType.LIST, - label: LL.networkOverview.filterLabels.list(), - }, - ]; - }, [LL.networkOverview.filterLabels, breakpoint]); - - const renderSelected = useCallback( - (selected: OverviewLayoutType): SelectSelectedValue => { - const option = getSelectOptions.find((o) => o.value === selected); - if (!option) throw Error("Selected option doesn't exists"); - return { - key: selected, - displayValue: option.label, - }; - }, - [getSelectOptions], - ); - - return ( - setSelectedFilterOption(filter)} - /> - )} -
- {!isLoading && filteredProvisioners && filteredProvisioners.length > 0 && ( - - )} - {!isLoading && - (!filteredProvisioners || !filteredProvisioners.length ? ( - - ) : null)} - {isLoading && ( -
- -
- )} - -
- -
- - - ); -}; - -enum FilterOptions { - ALL = 'all', - AVAILABLE = 'available', - UNAVAILABLE = 'unavailable', -} diff --git a/web/src/pages/provisioners/components/ProvisionersList/ProvisionersList.tsx b/web/src/pages/provisioners/components/ProvisionersList/ProvisionersList.tsx deleted file mode 100644 index 4408ff6ea..000000000 --- a/web/src/pages/provisioners/components/ProvisionersList/ProvisionersList.tsx +++ /dev/null @@ -1,141 +0,0 @@ -import './style.scss'; - -import classNames from 'classnames'; -import { useMemo } from 'react'; -import { useBreakpoint } from 'use-breakpoint'; - -import { useI18nContext } from '../../../../i18n/i18n-react'; -import IconCheckmarkGreen from '../../../../shared/components/svg/IconCheckmarkGreen'; -import IconDeactivated from '../../../../shared/components/svg/IconDeactivated'; -import { deviceBreakpoints } from '../../../../shared/constants'; -import { EditButton } from '../../../../shared/defguard-ui/components/Layout/EditButton/EditButton'; -import { EditButtonOption } from '../../../../shared/defguard-ui/components/Layout/EditButton/EditButtonOption'; -import { EditButtonOptionStyleVariant } from '../../../../shared/defguard-ui/components/Layout/EditButton/types'; -import { - type ListHeader, - ListSortDirection, -} from '../../../../shared/defguard-ui/components/Layout/VirtualizedList/types'; -import { VirtualizedList } from '../../../../shared/defguard-ui/components/Layout/VirtualizedList/VirtualizedList'; -import type { Provisioner } from '../../../../shared/types'; -import { useDeleteProvisionerModal } from '../modals/useDeleteProvisionerModal'; - -interface Props { - provisioners: Provisioner[]; -} - -export const ProvisionersList = ({ provisioners }: Props) => { - const { breakpoint } = useBreakpoint(deviceBreakpoints); - const { LL } = useI18nContext(); - const openDeletionModal = useDeleteProvisionerModal((state) => state.open); - - const listCells = useMemo(() => { - const res = [ - { - key: 'name', - render: (value: Provisioner) => ( - <> - {value.id} - - ), - }, - { - key: 'status', - render: (value: Provisioner) => ( - <> - {value.connected ? ( - <> - - - {LL.provisionersOverview.list.status.available()} - - - ) : ( - <> - - - {LL.provisionersOverview.list.status.unavailable()} - - - )} - - ), - }, - { - key: 'ip', - render: (value: Provisioner) => ( - {value.ip} - ), - }, - { - key: 'edit', - render: (value: Provisioner) => ( - - openDeletionModal({ provisionerId: value.id })} - text={LL.provisionersOverview.list.editButton.delete()} - /> - - ), - }, - ]; - if (breakpoint !== 'desktop') { - res.splice(1, 1); - } - return res; - }, [ - LL.provisionersOverview.list.editButton, - LL.provisionersOverview.list.status, - openDeletionModal, - breakpoint, - ]); - - const getListHeaders = useMemo(() => { - const res: ListHeader[] = [ - { - key: 'name', - text: LL.provisionersOverview.list.headers.name(), - active: true, - sortDirection: ListSortDirection.ASC, - }, - { - key: 'status', - text: LL.provisionersOverview.list.headers.status(), - active: false, - }, - { - key: 'ip', - text: LL.provisionersOverview.list.headers.ip(), - active: false, - }, - { - key: 'actions', - text: LL.provisionersOverview.list.headers.actions(), - active: false, - sortable: false, - }, - ]; - if (breakpoint !== 'desktop') { - res.splice(1, 1); - } - return res; - }, [LL.provisionersOverview.list.headers, breakpoint]); - - return ( - - ); -}; diff --git a/web/src/pages/provisioners/components/ProvisionersList/style.scss b/web/src/pages/provisioners/components/ProvisionersList/style.scss deleted file mode 100644 index 68f32e2fd..000000000 --- a/web/src/pages/provisioners/components/ProvisionersList/style.scss +++ /dev/null @@ -1,83 +0,0 @@ -@mixin list-layout { - @include media-breakpoint-down(lg) { - grid-template-columns: 250px 1fr 60px; - @for $i from 1 through 3 { - & > :nth-child(#{$i}) { - grid-column: $i; - } - } - } - @include media-breakpoint-up(lg) { - grid-template-columns: 250px 200px 1fr 62px; - @for $i from 1 through 4 { - & > :nth-child(#{$i}) { - grid-column: $i; - } - } - } -} - -#provisioners-page { - .provisioners-list { - .headers { - @include list-layout; - - @include media-breakpoint-up(lg) { - & > :nth-child(4) { - justify-content: center; - } - } - } - - .scroll-container { - box-sizing: border-box; - padding-bottom: 1.5rem; - margin-right: 5px; - @include media-breakpoint-up(lg) { - padding-bottom: 4rem; - } - } - - .default-row { - display: inline-grid; - grid-template-rows: 60px; - align-items: center; - box-sizing: border-box; - padding: 0 2rem; - @include list-layout; - - & > * { - display: flex; - flex-flow: row nowrap; - align-items: center; - align-content: center; - justify-content: flex-start; - - & > span { - @include regular-text; - @include text-weight(medium); - - color: var(--gray-dark); - - &.connected { - color: var(--text-main); - } - } - } - - @include media-breakpoint-up(lg) { - & > :nth-child(2) { - column-gap: 5px; - } - - & > :nth-child(4) { - justify-content: center; - - & > button { - width: 100%; - } - } - } - } - } -} diff --git a/web/src/pages/provisioners/components/ProvisioningStationSetupCard/ProvisioningStationSetupCard.tsx b/web/src/pages/provisioners/components/ProvisioningStationSetupCard/ProvisioningStationSetupCard.tsx deleted file mode 100644 index 02fbd9e12..000000000 --- a/web/src/pages/provisioners/components/ProvisioningStationSetupCard/ProvisioningStationSetupCard.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import './style.scss'; - -import { useQuery } from '@tanstack/react-query'; -import { isUndefined } from 'lodash-es'; -import { type ReactNode, useMemo } from 'react'; -import Skeleton from 'react-loading-skeleton'; - -import { useI18nContext } from '../../../../i18n/i18n-react'; -import YubikeyProvisioningGraphic from '../../../../shared/components/svg/YubikeyProvisioningGraphic'; -import { ActionButton } from '../../../../shared/defguard-ui/components/Layout/ActionButton/ActionButton'; -import { ActionButtonVariant } from '../../../../shared/defguard-ui/components/Layout/ActionButton/types'; -import { Card } from '../../../../shared/defguard-ui/components/Layout/Card/Card'; -import { ExpandableCard } from '../../../../shared/defguard-ui/components/Layout/ExpandableCard/ExpandableCard'; -import useApi from '../../../../shared/hooks/useApi'; -import { useClipboard } from '../../../../shared/hooks/useClipboard'; -import { QueryKeys } from '../../../../shared/queries'; - -export const ProvisioningStationSetup = () => { - const { writeToClipboard } = useClipboard(); - const { LL } = useI18nContext(); - const { - provisioning: { getWorkerToken }, - } = useApi(); - - const { data, isLoading: tokenLoading } = useQuery({ - queryKey: [QueryKeys.FETCH_WORKER_TOKEN], - queryFn: getWorkerToken, - refetchOnWindowFocus: false, - refetchOnMount: true, - }); - - const command = useMemo( - () => - `docker run --privileged ghcr.io/defguard/yubikey-provision:main -t ${data?.token} --id --grpc `, - [data?.token], - ); - - const tokenActions = useMemo( - (): ReactNode[] => [ - { - if (data?.token) { - void writeToClipboard( - data.token, - LL.provisionersOverview.messages.copy.token(), - ); - } - }} - />, - ], - [data?.token, LL.provisionersOverview.messages.copy, writeToClipboard], - ); - - const dockerActions = useMemo( - () => [ - { - void writeToClipboard(command, LL.provisionersOverview.messages.copy.command()); - }} - />, - ], - [LL.provisionersOverview.messages, command, writeToClipboard], - ); - - return ( - -

{LL.provisionersOverview.provisioningStation.header()}

-

{LL.provisionersOverview.provisioningStation.content()}

-
- -
- {data && !isUndefined(data.token) && ( - -

{data?.token}

-
- )} - {data && !isUndefined(data.token) && ( - -

{command}

-
- )} - {tokenLoading && !data && } -
- ); -}; diff --git a/web/src/pages/provisioners/components/ProvisioningStationSetupCard/style.scss b/web/src/pages/provisioners/components/ProvisioningStationSetupCard/style.scss deleted file mode 100644 index c3e0e79a1..000000000 --- a/web/src/pages/provisioners/components/ProvisioningStationSetupCard/style.scss +++ /dev/null @@ -1,86 +0,0 @@ -@use '@scssutils' as *; - -#provisioning-setup-card { - box-sizing: border-box; - padding: 15px; - - @include media-breakpoint-down(lg) { - margin: 0 2rem; - width: calc(100% - 40px); - } - - @include media-breakpoint-up(lg) { - padding: 4rem; - margin: 0 40px 25px; - width: calc(100% - 80px); - } - - @include media-breakpoint-up(xxl) { - margin: 0; - width: 100%; - } - - & > h4 { - @include modal-header; - - text-align: center; - margin-bottom: 1.5rem; - @include media-breakpoint-up(lg) { - margin-bottom: 2rem; - } - } - - & > .image-row { - height: 75px; - display: flex; - flex-flow: row; - align-items: center; - justify-content: center; - align-content: center; - } - - & > p { - word-break: normal; - text-align: center; - @include regular-text; - - margin-bottom: 15px; - max-width: 100%; - color: var(--gray-dark); - font-size: 12px; - - @include media-breakpoint-up(lg) { - margin-bottom: 25px; - } - } - - & > .expandable-card { - width: 100%; - margin-top: 20px; - @include media-breakpoint-up(lg) { - margin-top: 40px; - } - - & > .expanded-content { - overflow: hidden; - - & > p { - max-width: 100%; - text-overflow: ellipsis; - text-align: left; - word-break: break-all; - white-space: normal; - @include small-text; - - line-height: 22px; - } - } - } - .command-skeleton { - line-height: 1; - height: 198px; - width: 100%; - margin-top: 20px; - border-radius: 12px; - } -} diff --git a/web/src/pages/provisioners/components/modals/DeleteProvisionerModal.tsx b/web/src/pages/provisioners/components/modals/DeleteProvisionerModal.tsx deleted file mode 100644 index 33da15048..000000000 --- a/web/src/pages/provisioners/components/modals/DeleteProvisionerModal.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { shallow } from 'zustand/shallow'; - -import { useI18nContext } from '../../../../i18n/i18n-react'; -import { ConfirmModal } from '../../../../shared/defguard-ui/components/Layout/modals/ConfirmModal/ConfirmModal'; -import { ConfirmModalType } from '../../../../shared/defguard-ui/components/Layout/modals/ConfirmModal/types'; -import useApi from '../../../../shared/hooks/useApi'; -import { useToaster } from '../../../../shared/hooks/useToaster'; -import { MutationKeys } from '../../../../shared/mutations'; -import { QueryKeys } from '../../../../shared/queries'; -import { useDeleteProvisionerModal } from './useDeleteProvisionerModal'; - -export const DeleteProvisionerModal = () => { - const isOpen = useDeleteProvisionerModal((state) => state.visible); - const targetId = useDeleteProvisionerModal((state) => state.provisionerId); - const [close, reset] = useDeleteProvisionerModal( - (state) => [state.close, state.reset], - shallow, - ); - - const { LL } = useI18nContext(); - const { - provisioning: { deleteWorker }, - } = useApi(); - const toaster = useToaster(); - - const queryClient = useQueryClient(); - - const { mutate, isPending: isLoading } = useMutation({ - mutationFn: deleteWorker, - mutationKey: [MutationKeys.DELETE_WORKER], - onSuccess: () => { - void queryClient.invalidateQueries({ - queryKey: [QueryKeys.FETCH_WORKERS], - }); - toaster.success( - LL.modals.deleteProvisioner.messages.success({ - provisioner: targetId ?? '', - }), - ); - close(); - }, - onError: (e) => { - toaster.error(LL.messages.error()); - console.error(e); - }, - }); - - return ( - close()} - afterClose={() => reset()} - onSubmit={() => { - if (targetId) { - mutate(targetId); - } - }} - /> - ); -}; diff --git a/web/src/pages/provisioners/components/modals/useDeleteProvisionerModal.tsx b/web/src/pages/provisioners/components/modals/useDeleteProvisionerModal.tsx deleted file mode 100644 index 90264bce4..000000000 --- a/web/src/pages/provisioners/components/modals/useDeleteProvisionerModal.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { createWithEqualityFn } from 'zustand/traditional'; - -const defaultValues: StoreValues = { - visible: false, - provisionerId: undefined, -}; - -export const useDeleteProvisionerModal = createWithEqualityFn( - (set) => ({ - ...defaultValues, - open: (values) => set({ ...defaultValues, ...values, visible: true }), - close: () => set({ visible: false }), - reset: () => set(defaultValues), - }), - Object.is, -); - -type Store = StoreValues & StoreMethods; - -type StoreValues = { - visible: boolean; - provisionerId?: string; -}; - -type StoreMethods = { - open: (values?: Partial) => void; - close: () => void; - reset: () => void; -}; diff --git a/web/src/pages/provisioners/style.scss b/web/src/pages/provisioners/style.scss deleted file mode 100644 index 58007db08..000000000 --- a/web/src/pages/provisioners/style.scss +++ /dev/null @@ -1,177 +0,0 @@ -#provisioners-page { - & > .page-content { - min-height: 100%; - max-height: 100%; - overflow-y: auto; - display: grid; - grid-template-columns: 1fr; - grid-template-rows: auto 40px 100vh; - padding: 2rem 0; - box-sizing: border-box; - row-gap: 2rem; - - @include media-breakpoint-up(lg) { - padding-top: 2rem; - row-gap: 0; - grid-template-columns: 1fr; - grid-template-rows: 104px auto 100vh; - } - - @include media-breakpoint-up(xxl) { - padding: 4rem 7rem 0 0; - grid-template-columns: minmax(880px, 1fr) minmax(400px, 700px); - grid-template-rows: 105px 1fr; - } - - & > header { - grid-row: 2; - grid-column: 1; - box-sizing: border-box; - display: flex; - flex-flow: row nowrap; - align-items: center; - align-content: center; - padding: 0 2rem; - - @include media-breakpoint-up(lg) { - padding: 0 0 0 6rem; - column-gap: 3rem; - grid-column: 1; - grid-row: 1; - } - - & > h1 { - display: none; - - @include page-header; - - @include media-breakpoint-up(lg) { - display: block; - } - } - - & > .search { - height: 40px; - width: 100%; - - @include media-breakpoint-up(lg) { - width: clamp(200px, 360px, 360px); - } - } - } - - & > .provisioners-container { - grid-column: 1; - grid-row: 3; - display: grid; - row-gap: 1.8rem; - grid-template-rows: 40px 1fr; - grid-template-columns: 1fr; - - & > .top { - grid-row: 1; - grid-column: 1; - box-sizing: border-box; - display: flex; - flex-flow: row nowrap; - width: 100%; - padding: 0 2rem; - - @include media-breakpoint-up(lg) { - padding: 0 4rem 0 6rem; - } - - & > .select { - margin-left: auto; - width: 200px; - - & > .select-container { - min-height: 40px; - } - } - - & > .provisioners-count { - display: flex; - flex-flow: row nowrap; - column-gap: 1.5rem; - align-content: center; - align-items: center; - justify-content: flex-start; - - & > span { - @include modal-header; - } - - & > .count { - display: flex; - flex-flow: row; - height: 30px; - min-width: 30px; - box-sizing: border-box; - padding: 0 5px; - align-items: center; - align-content: center; - justify-content: center; - background-color: var(--gray-light); - border-radius: 10px; - - span { - display: block; - @include typography-legacy(12px, 18px, semiBold, var(--white), Poppins); - } - } - } - } - - & > .provisioners-list, - & > .no-data, - & > .loader { - grid-row: 2; - grid-column: 1; - } - - & > .loader { - width: 100%; - height: 100%; - display: flex; - flex-flow: column; - align-items: center; - justify-content: center; - align-content: center; - } - - & > .no-data { - width: 100%; - text-align: center; - box-sizing: border-box; - padding: 0 4rem 0 6rem; - } - } - - & > .setup-container { - grid-column: 1; - grid-row: 1; - - @include media-breakpoint-up(lg) { - grid-row: 2; - } - } - - @include media-breakpoint-up(xxl) { - & > header { - grid-row: 1; - grid-column: 1/3; - } - - & > .provisioners-container { - grid-column: 1 / 2; - grid-row: 2; - } - - & > .setup-container { - grid-row: 2; - grid-column: 2 / 3; - } - } - } -} diff --git a/web/src/pages/redirect/RedirectPage.tsx b/web/src/pages/redirect/RedirectPage.tsx deleted file mode 100644 index bc3e7e186..000000000 --- a/web/src/pages/redirect/RedirectPage.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import './style.scss'; - -import { useI18nContext } from '../../i18n/i18n-react'; -import SvgIconDfgOpenidRedirect from '../../shared/components/svg/IconDfgOpenidRedirect'; -import { Card } from '../../shared/defguard-ui/components/Layout/Card/Card'; - -// used in auth flow -export const RedirectPage = () => { - const { LL } = useI18nContext(); - return ( -
- -

{LL.redirectPage.title()}

-

{LL.redirectPage.subtitle()}

-
- -
-
-
- ); -}; diff --git a/web/src/pages/redirect/style.scss b/web/src/pages/redirect/style.scss deleted file mode 100644 index 2b9ff17ac..000000000 --- a/web/src/pages/redirect/style.scss +++ /dev/null @@ -1,42 +0,0 @@ -@use '@scssutils' as *; - -#redirect-page { - width: 100%; - height: 100%; - display: flex; - flex-flow: column; - align-items: center; - justify-content: center; - box-sizing: border-box; - padding: 0 20px; - user-select: none; - - & > .card { - display: flex; - flex-flow: column; - align-items: center; - justify-content: flex-start; - box-sizing: border-box; - padding: 30px 20px; - width: 100%; - - @include media-breakpoint-up(md) { - padding: 50px 0; - width: 450px; - } - - h2 { - @include typography-legacy(20px, 30px, semiBold, var(--text-main), 'Poppins'); - margin-bottom: 20px; - width: 100%; - text-align: center; - } - - p { - @include typography-legacy(12px, 1.2, regular, var(--gray-light), 'Roboto'); - margin-bottom: 30px; - width: 100%; - text-align: center; - } - } -} diff --git a/web/src/pages/settings/SettingsClientPage/SettingsClientPage.tsx b/web/src/pages/settings/SettingsClientPage/SettingsClientPage.tsx new file mode 100644 index 000000000..8de98342c --- /dev/null +++ b/web/src/pages/settings/SettingsClientPage/SettingsClientPage.tsx @@ -0,0 +1,154 @@ +import { useMutation, useQuery } from '@tanstack/react-query'; +import { Link } from '@tanstack/react-router'; +import { ClientTrafficPolicy, type SettingsEnterprise } from '../../../shared/api/types'; +import { Breadcrumbs } from '../../../shared/components/Breadcrumbs/Breadcrumbs'; +import { DescriptionBlock } from '../../../shared/components/DescriptionBlock/DescriptionBlock'; +import { Page } from '../../../shared/components/Page/Page'; +import { SettingsCard } from '../../../shared/components/SettingsCard/SettingsCard'; +import { SettingsHeader } from '../../../shared/components/SettingsHeader/SettingsHeader'; +import { SettingsLayout } from '../../../shared/components/SettingsLayout/SettingsLayout'; +import { Divider } from '../../../shared/defguard-ui/components/Divider/Divider'; +import { InteractiveBlock } from '../../../shared/defguard-ui/components/InteractiveBlock/InteractiveBlock'; +import { MarkedSection } from '../../../shared/defguard-ui/components/MarkedSection/MarkedSection'; +import { ThemeSpacing } from '../../../shared/defguard-ui/types'; +import { isPresent } from '../../../shared/defguard-ui/utils/isPresent'; +import { getEnterpriseSettingsQueryOptions } from '../../../shared/query'; +import './style.scss'; +import { useEffect, useState } from 'react'; +import api from '../../../shared/api/api'; +import { higherPlanBadgeProps } from '../shared/consts'; + +const breadcrumbs = [ + + General + , + + Client behavior + , +]; + +export const SettingsClientPage = () => { + const { data: settings } = useQuery(getEnterpriseSettingsQueryOptions); + return ( + + + + + {isPresent(settings) && } + + + ); +}; + +const Content = ({ settings }: { settings: SettingsEnterprise }) => { + const [adminDeviceManagement, setAdminDeviceManagment] = useState( + settings.admin_device_management, + ); + const [clientActivation, setClientActivation] = useState( + settings.only_client_activation, + ); + const [clientPolicy, setClientPolicy] = useState(settings.client_traffic_policy); + + const { mutate: patchSettings } = useMutation({ + mutationFn: api.settings.patchEnterpriseSettings, + meta: { + invalidate: ['enterprise_settings'], + }, + }); + + useEffect(() => { + setAdminDeviceManagment(settings.admin_device_management); + setClientActivation(settings.only_client_activation); + setClientPolicy(settings.client_traffic_policy); + }, [settings]); + + return ( + + +

Permissions

+ +

+ Define which VPN client settings users can modify and which are restricted. +

+
+ { + const value = !adminDeviceManagement; + setAdminDeviceManagment(value); + patchSettings({ + admin_device_management: value, + }); + }} + /> + { + const value = !clientActivation; + setClientActivation(value); + patchSettings({ + only_client_activation: value, + }); + }} + /> +
+ + +

Client traffic policy

+ +

+ Specify the conditions that determine how traffic should behave in the + application. +

+
+ { + setClientPolicy(ClientTrafficPolicy.None); + patchSettings({ + client_traffic_policy: ClientTrafficPolicy.None, + }); + }} + /> + { + setClientPolicy(ClientTrafficPolicy.DisableAllTraffic); + patchSettings({ + client_traffic_policy: ClientTrafficPolicy.DisableAllTraffic, + }); + }} + /> + { + setClientPolicy(ClientTrafficPolicy.ForceAllTraffic); + patchSettings({ + client_traffic_policy: ClientTrafficPolicy.ForceAllTraffic, + }); + }} + /> +
+
+ ); +}; diff --git a/web/src/pages/settings/SettingsClientPage/style.scss b/web/src/pages/settings/SettingsClientPage/style.scss new file mode 100644 index 000000000..df76f2d17 --- /dev/null +++ b/web/src/pages/settings/SettingsClientPage/style.scss @@ -0,0 +1,11 @@ +#settings-client-behavior-card { + .marked-section > .content { + display: flex; + flex-flow: column; + row-gap: var(--spacing-xl); + + h3 { + font: var(--t-body-primary-600); + } + } +} diff --git a/web/src/pages/settings/SettingsEditOpenIdProviderPage/SettingsEditOpenIdProviderPage.tsx b/web/src/pages/settings/SettingsEditOpenIdProviderPage/SettingsEditOpenIdProviderPage.tsx new file mode 100644 index 000000000..9e7cd381c --- /dev/null +++ b/web/src/pages/settings/SettingsEditOpenIdProviderPage/SettingsEditOpenIdProviderPage.tsx @@ -0,0 +1,133 @@ +import { useMutation, useSuspenseQuery } from '@tanstack/react-query'; +import { Link, useRouter } from '@tanstack/react-router'; +import { useCallback, useMemo } from 'react'; +import api from '../../../shared/api/api'; +import type { AddOpenIdProvider } from '../../../shared/api/types'; +import { EditPage } from '../../../shared/components/EditPage/EditPage'; +import { isPresent } from '../../../shared/defguard-ui/utils/isPresent'; +import { getExternalProviderQueryOptions } from '../../../shared/query'; +import { ExternalProvider } from '../shared/types'; +import { EditCustomProviderForm } from './form/EditCustomProviderForm'; +import { EditGoogleProviderForm } from './form/EditGoogleProviderForm'; +import { EditJumpCloudProviderForm } from './form/EditJumpCloudProviderForm'; +import { EditMicrosoftProviderForm } from './form/EditMicrosoftProviderForm'; +import { EditOktaProviderForm } from './form/EditOktaProviderForm'; + +const breadcrumbs = [ + + External Identity providers + , + + Edit + , +]; + +export const SettingsEditOpenIdProviderPage = () => { + const router = useRouter(); + const { data } = useSuspenseQuery(getExternalProviderQueryOptions); + + const formData = useMemo(() => { + if (isPresent(data?.provider)) { + return { ...data.provider, ...data.settings }; + } + }, [data]); + + const { mutateAsync } = useMutation({ + mutationFn: api.openIdProvider.editOpenIdProvider, + onSuccess: () => { + router.history.back(); + }, + meta: { + invalidate: [['settings'], ['info'], ['openid']], + }, + }); + + const { mutateAsync: deleteProvider, isPending: deletePending } = useMutation({ + mutationFn: api.openIdProvider.deleteOpenIdProvider, + onSuccess: () => { + router.history.back(); + }, + meta: { + invalidate: [['settings'], ['info'], ['openid']], + }, + }); + + const handleSubmit = useCallback( + async (values: Partial) => { + if (isPresent(formData)) { + await mutateAsync({ ...formData, ...values }); + } + }, + [formData, mutateAsync], + ); + + if (!formData) return null; + + return ( + + {formData.name === ExternalProvider.Google && ( + { + deleteProvider(formData.name); + }} + loading={deletePending} + /> + )} + {formData.name === ExternalProvider.Microsoft && ( + { + deleteProvider(formData.name); + }} + loading={deletePending} + /> + )} + {formData.name === ExternalProvider.Okta && ( + { + deleteProvider(formData.name); + }} + loading={deletePending} + /> + )} + {formData.name === ExternalProvider.JumpCloud && ( + { + deleteProvider(formData.name); + }} + loading={deletePending} + /> + )} + {formData.name === ExternalProvider.Custom && ( + { + deleteProvider(formData.name); + }} + loading={deletePending} + /> + )} + + ); +}; diff --git a/web/src/pages/settings/SettingsEditOpenIdProviderPage/form/EditCustomProviderForm.tsx b/web/src/pages/settings/SettingsEditOpenIdProviderPage/form/EditCustomProviderForm.tsx new file mode 100644 index 000000000..cc80cd193 --- /dev/null +++ b/web/src/pages/settings/SettingsEditOpenIdProviderPage/form/EditCustomProviderForm.tsx @@ -0,0 +1,124 @@ +import { useMemo } from 'react'; +import type z from 'zod'; +import { m } from '../../../../paraglide/messages'; +import { EditPageControls } from '../../../../shared/components/EditPageControls/EditPageControls'; +import { EditPageFormSection } from '../../../../shared/components/EditPageFormSection/EditPageFormSection'; +import { SizedBox } from '../../../../shared/defguard-ui/components/SizedBox/SizedBox'; +import { ThemeSpacing } from '../../../../shared/defguard-ui/types'; +import { useAppForm } from '../../../../shared/form'; +import { formChangeLogic } from '../../../../shared/formLogic'; +import { providerUsernameHandlingOptions } from '../../../AddExternalOpenIdWizardPage/consts'; +import { baseExternalProviderConfigSchema } from '../../../AddExternalOpenIdWizardPage/steps/AddExternalOpenIdDirectoryStep/forms/schemas'; +import type { EditProviderFormProps } from '../types'; + +const formSchema = baseExternalProviderConfigSchema; + +type FormFields = z.infer; + +export const EditCustomProviderForm = ({ + provider, + loading, + onDelete, + onSubmit, +}: EditProviderFormProps) => { + const defaultValues = useMemo((): FormFields => { + return { + client_id: provider.client_id, + client_secret: provider.client_secret, + create_account: provider.create_account, + display_name: provider.display_name, + username_handling: provider.username_handling, + base_url: provider.base_url, + }; + }, [provider]); + + const form = useAppForm({ + defaultValues, + validationLogic: formChangeLogic, + validators: { + onSubmit: formSchema, + onChange: formSchema, + }, + onSubmit: async ({ value }) => { + await onSubmit(value); + }, + }); + + return ( +
{ + e.stopPropagation(); + e.preventDefault(); + form.handleSubmit(); + }} + > + + + + {(field) => } + + + + {(field) => } + + + + {(field) => } + + + + {(field) => ( + + )} + + + + {(field) => ( + + )} + + + + {(field) => ( + + )} + + + s.isSubmitting}> + {(submitting) => ( + { + window.history.back(); + }, + }} + submitProps={{ + text: m.controls_save_changes(), + loading: submitting || loading, + type: 'submit', + onClick: () => { + form.handleSubmit(); + }, + }} + /> + )} + + +
+ ); +}; diff --git a/web/src/pages/settings/SettingsEditOpenIdProviderPage/form/EditGoogleProviderForm.tsx b/web/src/pages/settings/SettingsEditOpenIdProviderPage/form/EditGoogleProviderForm.tsx new file mode 100644 index 000000000..9df848421 --- /dev/null +++ b/web/src/pages/settings/SettingsEditOpenIdProviderPage/form/EditGoogleProviderForm.tsx @@ -0,0 +1,227 @@ +import { omit } from 'lodash-es'; +import { useMemo } from 'react'; +import z from 'zod'; +import { m } from '../../../../paraglide/messages'; +import { DescriptionBlock } from '../../../../shared/components/DescriptionBlock/DescriptionBlock'; +import { EditPageControls } from '../../../../shared/components/EditPageControls/EditPageControls'; +import { EditPageFormSection } from '../../../../shared/components/EditPageFormSection/EditPageFormSection'; +import { Fold } from '../../../../shared/defguard-ui/components/Fold/Fold'; +import { SizedBox } from '../../../../shared/defguard-ui/components/SizedBox/SizedBox'; +import { ThemeSpacing } from '../../../../shared/defguard-ui/types'; +import { useAppForm } from '../../../../shared/form'; +import { formChangeLogic } from '../../../../shared/formLogic'; +import { + directorySyncBehaviorOptions, + directorySyncTargetOptions, + providerUsernameHandlingOptions, +} from '../../../AddExternalOpenIdWizardPage/consts'; +import { + baseExternalProviderConfigSchema, + googleProviderSyncSchema, + parseGoogleKeyFile, + providerToGoogleKeyFile, +} from '../../../AddExternalOpenIdWizardPage/steps/AddExternalOpenIdDirectoryStep/forms/schemas'; +import type { EditProviderFormProps } from '../types'; + +const basicSchema = baseExternalProviderConfigSchema.extend({ + directory_sync_enabled: z.boolean(), +}); + +const syncSchema = baseExternalProviderConfigSchema + .extend(googleProviderSyncSchema.shape) + .extend({ + directory_sync_enabled: z.boolean(), + }); + +const discriminatedSchema = z.discriminatedUnion('directory_sync_enabled', [ + basicSchema, + syncSchema, +]); + +type FormFields = z.infer; + +export const EditGoogleProviderForm = ({ + provider, + loading, + onDelete, + onSubmit, +}: EditProviderFormProps) => { + const defaultValues = useMemo((): FormFields => { + const keyFile = providerToGoogleKeyFile( + provider.google_service_account_key, + provider.google_service_account_email, + ); + + return { + base_url: provider.base_url, + client_id: provider.client_id, + client_secret: provider.client_secret, + create_account: provider.create_account, + display_name: provider.display_name, + username_handling: provider.username_handling, + admin_email: provider.admin_email ?? '', + directory_sync_admin_behavior: provider.directory_sync_admin_behavior, + directory_sync_interval: provider.directory_sync_interval, + directory_sync_target: provider.directory_sync_target, + directory_sync_user_behavior: provider.directory_sync_user_behavior, + directory_sync_enabled: provider.directory_sync_enabled, + google_service_account_file: keyFile, + }; + }, [provider]); + + const form = useAppForm({ + defaultValues, + validationLogic: formChangeLogic, + validators: { + onSubmit: syncSchema, + onChange: syncSchema, + }, + onSubmit: async ({ value }) => { + if (value.directory_sync_enabled) { + const inner = value as z.infer; + const file = await parseGoogleKeyFile(inner.google_service_account_file as File); + await onSubmit({ + ...omit(inner, ['google_service_account_file']), + google_service_account_email: file?.client_email, + google_service_account_key: file?.private_key, + }); + } else { + await onSubmit(omit(value, ['google_service_account_file'])); + } + }, + }); + + return ( +
{ + e.stopPropagation(); + e.preventDefault(); + form.handleSubmit(); + }} + > + + + + {(field) => } + + + + {(field) => } + + + + {(field) => ( + + )} + + + + {(field) => ( + + )} + + + + {(field) => ( + + )} + + + + + {(field) => } + + s.values.directory_sync_enabled}> + {(enabled) => ( + + + + {(field) => ( + + )} + + + + {(field) => ( + + )} + + + + {(field) => ( + + )} + + + + {(field) => ( + + )} + + + + {(field) => } + + + +

{`Upload a new service account key file to set the service account used for synchronization. NOTE: The uploaded file won't be visible after saving the settings and reloading the page as it's contents are sensitive and are never sent back to the dashboard.`}

+
+ + + {(field) => } + +
+ )} +
+
+ s.isSubmitting}> + {(submitting) => ( + { + window.history.back(); + }, + }} + submitProps={{ + text: m.controls_save_changes(), + loading: submitting || loading, + type: 'submit', + onClick: () => { + form.handleSubmit(); + }, + }} + /> + )} + +
+
+ ); +}; diff --git a/web/src/pages/settings/SettingsEditOpenIdProviderPage/form/EditJumpCloudProviderForm.tsx b/web/src/pages/settings/SettingsEditOpenIdProviderPage/form/EditJumpCloudProviderForm.tsx new file mode 100644 index 000000000..1d5c20a43 --- /dev/null +++ b/web/src/pages/settings/SettingsEditOpenIdProviderPage/form/EditJumpCloudProviderForm.tsx @@ -0,0 +1,199 @@ +import { omit } from 'lodash-es'; +import { useMemo } from 'react'; +import z from 'zod'; +import { m } from '../../../../paraglide/messages'; +import { EditPageControls } from '../../../../shared/components/EditPageControls/EditPageControls'; +import { EditPageFormSection } from '../../../../shared/components/EditPageFormSection/EditPageFormSection'; +import { Fold } from '../../../../shared/defguard-ui/components/Fold/Fold'; +import { SizedBox } from '../../../../shared/defguard-ui/components/SizedBox/SizedBox'; +import { ThemeSpacing } from '../../../../shared/defguard-ui/types'; +import { useAppForm } from '../../../../shared/form'; +import { formChangeLogic } from '../../../../shared/formLogic'; +import { + directorySyncBehaviorOptions, + directorySyncTargetOptions, + providerUsernameHandlingOptions, +} from '../../../AddExternalOpenIdWizardPage/consts'; +import { + baseExternalProviderConfigSchema, + jumpcloudProviderSyncSchema, +} from '../../../AddExternalOpenIdWizardPage/steps/AddExternalOpenIdDirectoryStep/forms/schemas'; +import type { EditProviderFormProps } from '../types'; + +const basicSchema = z + .object({ + directory_sync_enabled: z.boolean(), + }) + .extend(omit(baseExternalProviderConfigSchema.shape, ['base_url'])); + +const syncSchema = basicSchema.extend(jumpcloudProviderSyncSchema.shape); + +const discriminatedSchema = z.discriminatedUnion('directory_sync_enabled', [ + basicSchema, + syncSchema, +]); + +type FormFields = z.infer; + +export const EditJumpCloudProviderForm = ({ + provider, + loading, + onDelete, + onSubmit, +}: EditProviderFormProps) => { + const defaultValues = useMemo((): FormFields => { + return { + client_id: provider.client_id, + client_secret: provider.client_secret, + create_account: provider.create_account, + display_name: provider.display_name, + username_handling: provider.username_handling, + directory_sync_admin_behavior: provider.directory_sync_admin_behavior, + directory_sync_interval: provider.directory_sync_interval, + directory_sync_target: provider.directory_sync_target, + directory_sync_user_behavior: provider.directory_sync_user_behavior, + directory_sync_enabled: provider.directory_sync_enabled, + jumpcloud_api_key: provider.jumpcloud_api_key ?? '', + }; + }, [provider]); + + const form = useAppForm({ + defaultValues, + validationLogic: formChangeLogic, + validators: { + onSubmit: syncSchema, + onChange: syncSchema, + }, + onSubmit: async ({ value }) => { + await onSubmit(value); + }, + }); + + return ( +
{ + e.stopPropagation(); + e.preventDefault(); + form.handleSubmit(); + }} + > + + + + {(field) => } + + + + {(field) => } + + + + {(field) => ( + + )} + + + + {(field) => ( + + )} + + + + {(field) => ( + + )} + + + + {(field) => ( + + )} + + + + + {(field) => } + + s.values.directory_sync_enabled}> + {(enabled) => ( + + + + {(field) => ( + + )} + + + + {(field) => ( + + )} + + + + {(field) => ( + + )} + + + + {(field) => ( + + )} + + + )} + + + s.isSubmitting}> + {(submitting) => ( + { + window.history.back(); + }, + }} + submitProps={{ + text: m.controls_save_changes(), + loading: submitting || loading, + type: 'submit', + onClick: () => { + form.handleSubmit(); + }, + }} + /> + )} + + +
+ ); +}; diff --git a/web/src/pages/settings/SettingsEditOpenIdProviderPage/form/EditMicrosoftProviderForm.tsx b/web/src/pages/settings/SettingsEditOpenIdProviderPage/form/EditMicrosoftProviderForm.tsx new file mode 100644 index 000000000..7e71aee42 --- /dev/null +++ b/web/src/pages/settings/SettingsEditOpenIdProviderPage/form/EditMicrosoftProviderForm.tsx @@ -0,0 +1,212 @@ +import { omit } from 'lodash-es'; +import { useMemo } from 'react'; +import z from 'zod'; +import { m } from '../../../../paraglide/messages'; +import { EditPageControls } from '../../../../shared/components/EditPageControls/EditPageControls'; +import { EditPageFormSection } from '../../../../shared/components/EditPageFormSection/EditPageFormSection'; +import { Fold } from '../../../../shared/defguard-ui/components/Fold/Fold'; +import { SizedBox } from '../../../../shared/defguard-ui/components/SizedBox/SizedBox'; +import { ThemeSpacing } from '../../../../shared/defguard-ui/types'; +import { useAppForm } from '../../../../shared/form'; +import { formChangeLogic } from '../../../../shared/formLogic'; +import { + directorySyncBehaviorOptions, + directorySyncTargetOptions, + formatMicrosoftBaseUrl, + providerUsernameHandlingOptions, +} from '../../../AddExternalOpenIdWizardPage/consts'; +import { + baseExternalProviderConfigSchema, + microsoftProviderSyncSchema, +} from '../../../AddExternalOpenIdWizardPage/steps/AddExternalOpenIdDirectoryStep/forms/schemas'; +import type { EditProviderFormProps } from '../types'; + +const basicSchema = z + .object({ + directory_sync_enabled: z.boolean(), + microsoftTenantId: z + .string(m.form_error_required()) + .trim() + .min(1, m.form_error_required()), + }) + .extend(omit(baseExternalProviderConfigSchema.shape, ['base_url'])); + +const syncSchema = basicSchema.extend(microsoftProviderSyncSchema.shape); + +const discriminatedSchema = z.discriminatedUnion('directory_sync_enabled', [ + basicSchema, + syncSchema, +]); + +type FormFields = z.infer; + +export const EditMicrosoftProviderForm = ({ + provider, + loading, + onDelete, + onSubmit, +}: EditProviderFormProps) => { + const defaultValues = useMemo((): FormFields => { + const tenantId = provider.base_url.split('/')[provider.base_url.length - 2]; + return { + client_id: provider.client_id, + client_secret: provider.client_secret, + create_account: provider.create_account, + display_name: provider.display_name, + username_handling: provider.username_handling, + directory_sync_admin_behavior: provider.directory_sync_admin_behavior, + directory_sync_interval: provider.directory_sync_interval, + directory_sync_target: provider.directory_sync_target, + directory_sync_user_behavior: provider.directory_sync_user_behavior, + directory_sync_enabled: provider.directory_sync_enabled, + directory_sync_group_match: provider.directory_sync_group_match ?? '', + microsoftTenantId: tenantId, + }; + }, [provider]); + + const form = useAppForm({ + defaultValues, + validationLogic: formChangeLogic, + validators: { + onSubmit: syncSchema, + onChange: syncSchema, + }, + onSubmit: async ({ value }) => { + const base_url = formatMicrosoftBaseUrl(value.microsoftTenantId); + await onSubmit({ ...value, base_url }); + }, + }); + + return ( +
{ + e.stopPropagation(); + e.preventDefault(); + form.handleSubmit(); + }} + > + + + + {(field) => } + + + {(field) => } + + + + {(field) => } + + + + {(field) => ( + + )} + + + + {(field) => ( + + )} + + + + {(field) => ( + + )} + + + + + {(field) => } + + s.values.directory_sync_enabled}> + {(enabled) => ( + + + + {(field) => ( + + )} + + + + {(field) => ( + + )} + + + + {(field) => ( + + )} + + + + {(field) => ( + + )} + + + + {(field) => } + + + + {(field) => } + + + )} + + + s.isSubmitting}> + {(submitting) => ( + { + window.history.back(); + }, + }} + submitProps={{ + text: m.controls_save_changes(), + loading: submitting || loading, + type: 'submit', + onClick: () => { + form.handleSubmit(); + }, + }} + /> + )} + + +
+ ); +}; diff --git a/web/src/pages/settings/SettingsEditOpenIdProviderPage/form/EditOktaProviderForm.tsx b/web/src/pages/settings/SettingsEditOpenIdProviderPage/form/EditOktaProviderForm.tsx new file mode 100644 index 000000000..8cae09968 --- /dev/null +++ b/web/src/pages/settings/SettingsEditOpenIdProviderPage/form/EditOktaProviderForm.tsx @@ -0,0 +1,214 @@ +import { useMemo } from 'react'; +import z from 'zod'; +import { m } from '../../../../paraglide/messages'; +import { EditPageControls } from '../../../../shared/components/EditPageControls/EditPageControls'; +import { EditPageFormSection } from '../../../../shared/components/EditPageFormSection/EditPageFormSection'; +import { Fold } from '../../../../shared/defguard-ui/components/Fold/Fold'; +import { SizedBox } from '../../../../shared/defguard-ui/components/SizedBox/SizedBox'; +import { ThemeSpacing } from '../../../../shared/defguard-ui/types'; +import { useAppForm } from '../../../../shared/form'; +import { formChangeLogic } from '../../../../shared/formLogic'; +import { + directorySyncBehaviorOptions, + directorySyncTargetOptions, + providerUsernameHandlingOptions, +} from '../../../AddExternalOpenIdWizardPage/consts'; +import { + baseExternalProviderConfigSchema, + oktaProviderSyncSchema, +} from '../../../AddExternalOpenIdWizardPage/steps/AddExternalOpenIdDirectoryStep/forms/schemas'; +import type { EditProviderFormProps } from '../types'; + +const basicSchema = z + .object({ + directory_sync_enabled: z.boolean(), + }) + .extend(baseExternalProviderConfigSchema.shape); + +const syncSchema = basicSchema.extend(oktaProviderSyncSchema.shape); + +const discriminatedSchema = z.discriminatedUnion('directory_sync_enabled', [ + basicSchema, + syncSchema, +]); + +type FormFields = z.infer; + +export const EditOktaProviderForm = ({ + provider, + loading, + onDelete, + onSubmit, +}: EditProviderFormProps) => { + const defaultValues = useMemo((): FormFields => { + return { + base_url: provider.base_url, + okta_dirsync_client_id: provider.okta_dirsync_client_id ?? '', + okta_private_jwk: provider.okta_private_jwk ?? '', + client_id: provider.client_id, + client_secret: provider.client_secret, + create_account: provider.create_account, + display_name: provider.display_name, + username_handling: provider.username_handling, + directory_sync_admin_behavior: provider.directory_sync_admin_behavior, + directory_sync_interval: provider.directory_sync_interval, + directory_sync_target: provider.directory_sync_target, + directory_sync_user_behavior: provider.directory_sync_user_behavior, + directory_sync_enabled: provider.directory_sync_enabled, + }; + }, [provider]); + + const form = useAppForm({ + defaultValues, + validationLogic: formChangeLogic, + validators: { + onSubmit: syncSchema, + onChange: syncSchema, + }, + onSubmit: async ({ value }) => { + await onSubmit(value); + }, + }); + + return ( +
{ + e.stopPropagation(); + e.preventDefault(); + form.handleSubmit(); + }} + > + + + + {(field) => } + + + + {(field) => } + + + + {(field) => } + + + + {(field) => ( + + )} + + + + {(field) => ( + + )} + + + + {(field) => ( + + )} + + + + + {(field) => } + + s.values.directory_sync_enabled}> + {(enabled) => ( + + + + {(field) => ( + + )} + + + + {(field) => ( + + )} + + + + {(field) => ( + + )} + + + + {(field) => ( + + )} + + + + {(field) => ( + + )} + + + + {(field) => ( + + )} + + + )} + + + s.isSubmitting}> + {(submitting) => ( + { + window.history.back(); + }, + }} + submitProps={{ + text: m.controls_save_changes(), + loading: submitting || loading, + type: 'submit', + onClick: () => { + form.handleSubmit(); + }, + }} + /> + )} + + +
+ ); +}; diff --git a/web/src/pages/settings/SettingsEditOpenIdProviderPage/types.ts b/web/src/pages/settings/SettingsEditOpenIdProviderPage/types.ts new file mode 100644 index 000000000..d048bb816 --- /dev/null +++ b/web/src/pages/settings/SettingsEditOpenIdProviderPage/types.ts @@ -0,0 +1,12 @@ +import type { + AddOpenIdProvider, + OpenIdProvider, + OpenIdProviderSettings, +} from '../../../shared/api/types'; + +export interface EditProviderFormProps { + provider: OpenIdProvider & OpenIdProviderSettings; + onSubmit: (value: Partial) => Promise; + onDelete: () => void; + loading: boolean; +} diff --git a/web/src/pages/settings/SettingsExternalOpenIdPage/SettingsExternalOpenIdPage.tsx b/web/src/pages/settings/SettingsExternalOpenIdPage/SettingsExternalOpenIdPage.tsx new file mode 100644 index 000000000..2f712a82f --- /dev/null +++ b/web/src/pages/settings/SettingsExternalOpenIdPage/SettingsExternalOpenIdPage.tsx @@ -0,0 +1,87 @@ +import { useNavigate } from '@tanstack/react-router'; +import { SettingsHeader } from '../../../shared/components/SettingsHeader/SettingsHeader'; +import { SettingsLayout } from '../../../shared/components/SettingsLayout/SettingsLayout'; +import { higherPlanBadgeProps } from '../shared/consts'; +import { ExternalProvider, type ExternalProviderValue } from '../shared/types'; +import { ExternalProviderCard } from './components/ExternalProviderCard/ExternalProviderCard'; +import './style.scss'; +import { useQuery } from '@tanstack/react-query'; +import { useMemo } from 'react'; +import api from '../../../shared/api/api'; +import { InfoBanner } from '../../../shared/defguard-ui/components/InfoBanner/InfoBanner'; +import { SizedBox } from '../../../shared/defguard-ui/components/SizedBox/SizedBox'; +import { ThemeSpacing } from '../../../shared/defguard-ui/types'; +import { isPresent } from '../../../shared/defguard-ui/utils/isPresent'; +import { useAddExternalOpenIdStore } from '../../AddExternalOpenIdWizardPage/useAddExternalOpenIdStore'; + +export const SettingsExternalOpenIdPage = () => { + const navigate = useNavigate(); + + const { data: activeProvider } = useQuery({ + queryFn: api.openIdProvider.getOpenIdProvider, + queryKey: ['openid', 'provider'], + select: (resp) => resp.data?.provider, + }); + + const visibleProviders = useMemo(() => { + const res = Object.values(ExternalProvider).filter( + (p) => p !== ExternalProvider.Zitadel, + ); + if (activeProvider) { + return res.filter((p) => p !== activeProvider.name); + } + return res; + }, [activeProvider]); + + return ( + + + {isPresent(activeProvider) && ( + <> +

{'Active external ID Providers'}

+ + { + navigate({ to: '/settings/edit-openid' }); + }} + /> + +

{'Other external ID providers'}

+ + + + + )} +
+ {visibleProviders.map((provider) => ( + { + useAddExternalOpenIdStore.getState().initialize(provider); + navigate({ + to: '/add-external-openid', + replace: true, + }); + }} + /> + ))} +
+
+ ); +}; diff --git a/web/src/pages/settings/SettingsExternalOpenIdPage/SettingsGatewayNotificationsPage/SettingsGatewayNotificationsPage.tsx b/web/src/pages/settings/SettingsExternalOpenIdPage/SettingsGatewayNotificationsPage/SettingsGatewayNotificationsPage.tsx new file mode 100644 index 000000000..e69d6a471 --- /dev/null +++ b/web/src/pages/settings/SettingsExternalOpenIdPage/SettingsGatewayNotificationsPage/SettingsGatewayNotificationsPage.tsx @@ -0,0 +1,187 @@ +import { useMutation, useQuery } from '@tanstack/react-query'; +import { Link } from '@tanstack/react-router'; +import { useMemo } from 'react'; +import z from 'zod'; +import { m } from '../../../../paraglide/messages'; +import api from '../../../../shared/api/api'; +import type { SettingsGatewayNotifications } from '../../../../shared/api/types'; +import { Breadcrumbs } from '../../../../shared/components/Breadcrumbs/Breadcrumbs'; +import { Controls } from '../../../../shared/components/Controls/Controls'; +import { Page } from '../../../../shared/components/Page/Page'; +import { SettingsCard } from '../../../../shared/components/SettingsCard/SettingsCard'; +import { SettingsHeader } from '../../../../shared/components/SettingsHeader/SettingsHeader'; +import { SettingsLayout } from '../../../../shared/components/SettingsLayout/SettingsLayout'; +import { Button } from '../../../../shared/defguard-ui/components/Button/Button'; +import { Fold } from '../../../../shared/defguard-ui/components/Fold/Fold'; +import { InfoBanner } from '../../../../shared/defguard-ui/components/InfoBanner/InfoBanner'; +import { SizedBox } from '../../../../shared/defguard-ui/components/SizedBox/SizedBox'; +import { ThemeSpacing } from '../../../../shared/defguard-ui/types'; +import { isPresent } from '../../../../shared/defguard-ui/utils/isPresent'; +import { useAppForm } from '../../../../shared/form'; +import { formChangeLogic } from '../../../../shared/formLogic'; +import { useApp } from '../../../../shared/hooks/useApp'; +import { getSettingsQueryOptions } from '../../../../shared/query'; + +const breadcrumbsLinks = [ + + Notifications + , + + Gateway notifications + , +]; + +export const SettingsGatewayNotificationsPage = () => { + const { data: settings } = useQuery(getSettingsQueryOptions); + + return ( + + + + + {isPresent(settings) && ( + + + + )} + + + ); +}; + +const formSchema = z.object({ + gateway_disconnect_notifications_enabled: z.boolean(), + gateway_disconnect_notifications_inactivity_threshold: z + .number(m.form_error_required()) + .min(0, m.form_min_value({ value: 0 })), + gateway_disconnect_notifications_reconnect_notification_enabled: z.boolean(), +}); + +type FormFields = z.infer; + +const Content = ({ settings }: { settings: SettingsGatewayNotifications }) => { + const smtp = useApp((s) => s.appInfo.smtp_enabled); + const formDisabled = !smtp; + const defaultValues = useMemo( + (): FormFields => ({ + gateway_disconnect_notifications_enabled: + settings.gateway_disconnect_notifications_enabled ?? false, + gateway_disconnect_notifications_inactivity_threshold: + settings.gateway_disconnect_notifications_inactivity_threshold ?? 5, + gateway_disconnect_notifications_reconnect_notification_enabled: + settings.gateway_disconnect_notifications_reconnect_notification_enabled ?? false, + }), + [settings], + ); + + const { mutateAsync } = useMutation({ + mutationFn: api.settings.patchSettings, + meta: { + invalidate: ['settings'], + }, + }); + + const form = useAppForm({ + defaultValues, + validationLogic: formChangeLogic, + validators: { + onSubmit: formSchema, + onChange: formSchema, + }, + onSubmit: async ({ value }) => { + mutateAsync(value); + }, + }); + + return ( +
{ + e.stopPropagation(); + e.preventDefault(); + form.handleSubmit(); + }} + > + + {!smtp && ( + <> + + + + )} + + {(field) => ( + + )} + + + + {(field) => ( + + + s.values.gateway_disconnect_notifications_reconnect_notification_enabled + } + > + {(enabled) => ( + + + + {(field) => ( + + )} + + + )} + + + )} + + ({ + isDefault: s.isDefaultValue || s.isPristine, + isSubmitting: s.isSubmitting, + })} + > + {({ isDefault, isSubmitting }) => ( + +
+
+
+ )} +
+
+
+ ); +}; diff --git a/web/src/pages/settings/SettingsExternalOpenIdPage/components/ExternalProviderCard/ExternalProviderCard.tsx b/web/src/pages/settings/SettingsExternalOpenIdPage/components/ExternalProviderCard/ExternalProviderCard.tsx new file mode 100644 index 000000000..ba2f32342 --- /dev/null +++ b/web/src/pages/settings/SettingsExternalOpenIdPage/components/ExternalProviderCard/ExternalProviderCard.tsx @@ -0,0 +1,97 @@ +import { type ReactNode, useMemo } from 'react'; +import type { ExternalProviderValue } from '../../../shared/types'; +import './style.scss'; +import clsx from 'clsx'; +import { m } from '../../../../../paraglide/messages'; +import { externalProviderName } from '../../../../../shared/constants'; +import { Button } from '../../../../../shared/defguard-ui/components/Button/Button'; +import { Icon } from '../../../../../shared/defguard-ui/components/Icon'; +import { ThemeVariable } from '../../../../../shared/defguard-ui/types'; +import { isPresent } from '../../../../../shared/defguard-ui/utils/isPresent'; +import google from './assets/google.png'; +import jumpcloud from './assets/jumpcloud.png'; +import microsoft from './assets/microsoft.png'; +import okta from './assets/okta.png'; +import zitadel from './assets/zitadel.png'; + +type Props = { + provider: ExternalProviderValue; + displayName?: string; + onClick: () => void; + disabled?: boolean; + edit?: boolean; +}; + +const providerImage: Record = { + custom: , + google: , + jumpCloud: , + microsoft: , + okta: , + zitadel: , +}; + +const providerDescription: Record = { + custom: + 'Enter the required details to link your account securely and manage logins with your custom setup.', + zitadel: + 'Get started with a multi-tenant, API-first identity platform with comprehensive SDKs that enable security, compliance, and extensibility.', + jumpCloud: + "Enable users to log in with their JumpCloud accounts through JumpCloud's secure directory and authentication platform.", + okta: "Allow users to sign in with their Okta accounts using Okta's secure identity management service.", + microsoft: + "Enable users to log in with their Microsoft accounts through Microsoft's secure authentication platform.", + google: + "Allow users to sign in securely with their Google accounts using Google's trusted authentication service.", +}; + +export const ExternalProviderCard = ({ + provider, + displayName, + onClick, + edit = false, + disabled = false, +}: Props) => { + const name = useMemo(() => { + if (isPresent(displayName)) return displayName; + return externalProviderName[provider]; + }, [displayName, provider]); + + return ( +
+
+
+
+ {providerImage[provider]} +
+
+
+
+

{name}

+
+

{providerDescription[provider]}

+
+
+ {!edit && ( +
+
+
+ ); +}; diff --git a/web/src/pages/settings/SettingsExternalOpenIdPage/components/ExternalProviderCard/assets/google.png b/web/src/pages/settings/SettingsExternalOpenIdPage/components/ExternalProviderCard/assets/google.png new file mode 100644 index 000000000..18d768f00 Binary files /dev/null and b/web/src/pages/settings/SettingsExternalOpenIdPage/components/ExternalProviderCard/assets/google.png differ diff --git a/web/src/pages/settings/SettingsExternalOpenIdPage/components/ExternalProviderCard/assets/jumpcloud.png b/web/src/pages/settings/SettingsExternalOpenIdPage/components/ExternalProviderCard/assets/jumpcloud.png new file mode 100644 index 000000000..ec53f55a3 Binary files /dev/null and b/web/src/pages/settings/SettingsExternalOpenIdPage/components/ExternalProviderCard/assets/jumpcloud.png differ diff --git a/web/src/pages/settings/SettingsExternalOpenIdPage/components/ExternalProviderCard/assets/microsoft.png b/web/src/pages/settings/SettingsExternalOpenIdPage/components/ExternalProviderCard/assets/microsoft.png new file mode 100644 index 000000000..002e2c53a Binary files /dev/null and b/web/src/pages/settings/SettingsExternalOpenIdPage/components/ExternalProviderCard/assets/microsoft.png differ diff --git a/web/src/pages/settings/SettingsExternalOpenIdPage/components/ExternalProviderCard/assets/okta.png b/web/src/pages/settings/SettingsExternalOpenIdPage/components/ExternalProviderCard/assets/okta.png new file mode 100644 index 000000000..235c3079a Binary files /dev/null and b/web/src/pages/settings/SettingsExternalOpenIdPage/components/ExternalProviderCard/assets/okta.png differ diff --git a/web/src/pages/settings/SettingsExternalOpenIdPage/components/ExternalProviderCard/assets/zitadel.png b/web/src/pages/settings/SettingsExternalOpenIdPage/components/ExternalProviderCard/assets/zitadel.png new file mode 100644 index 000000000..719310ba0 Binary files /dev/null and b/web/src/pages/settings/SettingsExternalOpenIdPage/components/ExternalProviderCard/assets/zitadel.png differ diff --git a/web/src/pages/settings/SettingsExternalOpenIdPage/components/ExternalProviderCard/style.scss b/web/src/pages/settings/SettingsExternalOpenIdPage/components/ExternalProviderCard/style.scss new file mode 100644 index 000000000..ffb7814f9 --- /dev/null +++ b/web/src/pages/settings/SettingsExternalOpenIdPage/components/ExternalProviderCard/style.scss @@ -0,0 +1,67 @@ +.external-provider-card { + box-sizing: border-box; + padding: var(--spacing-md); + border: var(--border-1) solid var(--border-disabled); + border-radius: var(--spacing-lg); + + & > .inner { + display: grid; + grid-template-columns: 48px 1fr auto; + column-gap: var(--spacing-lg); + + & > .icon-track { + height: 48px; + width: 48px; + + & > .icon-box { + height: 48px; + width: 48px; + display: flex; + flex-flow: row; + align-items: center; + justify-content: center; + background-color: var(--bg-default); + border: 1px solid var(--border-disabled); + border-radius: var(--spacing-lg); + box-sizing: border-box; + + &:not(.variant-custom) { + background-color: var(--bg-white); + } + + &.variant-custom { + background-color: var(--bg-action-muted); + } + } + } + + & > .content-track { + max-width: 530px; + + & > .top { + display: flex; + flex-flow: row nowrap; + align-items: center; + justify-content: flex-start; + column-gap: var(--spacing-sm); + padding-bottom: var(--spacing-sm); + } + + .name { + font: var(--t-body-primary-500); + color: var(--fg-default); + } + + .description { + font: var(--t-body-sm-400); + color: var(--fg-muted); + } + } + + & > .action-track { + display: flex; + align-items: center; + justify-content: center; + } + } +} diff --git a/web/src/pages/settings/SettingsExternalOpenIdPage/style.scss b/web/src/pages/settings/SettingsExternalOpenIdPage/style.scss new file mode 100644 index 000000000..e42ec2f33 --- /dev/null +++ b/web/src/pages/settings/SettingsExternalOpenIdPage/style.scss @@ -0,0 +1,12 @@ +#settings-openid-tab { + .section-label { + font: var(--t-body-xxs-500); + color: var(--fg-muted); + } + + & > .providers { + display: flex; + flex-flow: column; + row-gap: var(--spacing-lg); + } +} diff --git a/web/src/pages/settings/SettingsIndexPage/SettingsIndexPage.tsx b/web/src/pages/settings/SettingsIndexPage/SettingsIndexPage.tsx new file mode 100644 index 000000000..014e22a31 --- /dev/null +++ b/web/src/pages/settings/SettingsIndexPage/SettingsIndexPage.tsx @@ -0,0 +1,64 @@ +import { useNavigate, useSearch } from '@tanstack/react-router'; +import { type JSX, useMemo } from 'react'; +import { Page } from '../../../shared/components/Page/Page'; +import { SizedBox } from '../../../shared/defguard-ui/components/SizedBox/SizedBox'; +import { Tabs } from '../../../shared/defguard-ui/components/Tabs/Tabs'; +import type { TabProps } from '../../../shared/defguard-ui/components/Tabs/types'; +import { ThemeSpacing } from '../../../shared/defguard-ui/types'; +import { SettingsExternalOpenIdPage } from '../SettingsExternalOpenIdPage/SettingsExternalOpenIdPage'; +import { SettingsGeneralTab } from './tabs/SettingsGeneralTab'; +import { SettingsLicenseTab } from './tabs/SettingsLicenseTab/SettingsLicenseTab'; +import { SettingsNotificationsTab } from './tabs/SettingsNotificationsTab'; +import { type SettingsTabValue, settingsTabsSchema } from './types'; + +const ActivityTab = () => null; + +const tabComponent: Record = { + general: , + notifications: , + openid: , + activity: , + license: , +}; + +const tabToTitle = (tab: SettingsTabValue): string => { + switch (tab) { + case 'general': + return 'General'; + case 'activity': + return 'Activity streaming'; + case 'license': + return 'License'; + case 'notifications': + return 'Notifications'; + case 'openid': + return 'External identity providers'; + } +}; + +export const SettingsIndexPage = () => { + const navigateTab = useNavigate({ from: '/settings' }); + const search = useSearch({ from: '/_authorized/_default/settings/' }); + + const tabs: TabProps[] = useMemo( + () => + settingsTabsSchema.options.map( + (tab): TabProps => ({ + title: tabToTitle(tab), + active: search.tab === tab, + onClick: () => { + navigateTab({ search: { tab } }); + }, + }), + ), + [navigateTab, search.tab], + ); + + return ( + + + + {tabComponent[search.tab]} + + ); +}; diff --git a/web/src/pages/settings/SettingsIndexPage/tabs/SettingsGeneralTab.tsx b/web/src/pages/settings/SettingsIndexPage/tabs/SettingsGeneralTab.tsx new file mode 100644 index 000000000..adfd7ee87 --- /dev/null +++ b/web/src/pages/settings/SettingsIndexPage/tabs/SettingsGeneralTab.tsx @@ -0,0 +1,35 @@ +import { Link } from '@tanstack/react-router'; +import { SettingsLayout } from '../../../../shared/components/SettingsLayout/SettingsLayout'; +import { SectionSelect } from '../../../../shared/defguard-ui/components/SectionSelect/SectionSelect'; +import { SizedBox } from '../../../../shared/defguard-ui/components/SizedBox/SizedBox'; +import { ThemeSpacing } from '../../../../shared/defguard-ui/types'; +import { higherPlanBadgeProps } from '../../shared/consts'; + +export const SettingsGeneralTab = () => { + return ( + + + + + + + + + + + + ); +}; diff --git a/web/src/pages/settings/SettingsIndexPage/tabs/SettingsLicenseTab/SettingsLicenseTab.tsx b/web/src/pages/settings/SettingsIndexPage/tabs/SettingsLicenseTab/SettingsLicenseTab.tsx new file mode 100644 index 000000000..0ef8e67a8 --- /dev/null +++ b/web/src/pages/settings/SettingsIndexPage/tabs/SettingsLicenseTab/SettingsLicenseTab.tsx @@ -0,0 +1,160 @@ +import './style.scss'; +import { useQuery } from '@tanstack/react-query'; +import { Fragment } from 'react/jsx-runtime'; +import { Controls } from '../../../../../shared/components/Controls/Controls'; +import { DescriptionBlock } from '../../../../../shared/components/DescriptionBlock/DescriptionBlock'; +import { SettingsCard } from '../../../../../shared/components/SettingsCard/SettingsCard'; +import { SettingsHeader } from '../../../../../shared/components/SettingsHeader/SettingsHeader'; +import { SettingsLayout } from '../../../../../shared/components/SettingsLayout/SettingsLayout'; +import { AppText } from '../../../../../shared/defguard-ui/components/AppText/AppText'; +import { Badge } from '../../../../../shared/defguard-ui/components/Badge/Badge'; +import { + type BadgeProps, + BadgeVariant, +} from '../../../../../shared/defguard-ui/components/Badge/types'; +import { Button } from '../../../../../shared/defguard-ui/components/Button/Button'; +import { Divider } from '../../../../../shared/defguard-ui/components/Divider/Divider'; +import { ExternalLink } from '../../../../../shared/defguard-ui/components/ExternalLink/ExternalLink'; +import { SizedBox } from '../../../../../shared/defguard-ui/components/SizedBox/SizedBox'; +import { + TextStyle, + ThemeSpacing, + ThemeVariable, +} from '../../../../../shared/defguard-ui/types'; +import { isPresent } from '../../../../../shared/defguard-ui/utils/isPresent'; +import { openModal } from '../../../../../shared/hooks/modalControls/modalsSubjects'; +import { ModalName } from '../../../../../shared/hooks/modalControls/modalTypes'; +import { useApp } from '../../../../../shared/hooks/useApp'; +import { + getLicenseInfoQueryOptions, + getSettingsQueryOptions, +} from '../../../../../shared/query'; +import businessImage from './assets/business.png'; +import enterpriseImage from './assets/enterprise.png'; +import starterImage from './assets/starter.png'; +import { SettingsLicenseInfoSection } from './components/SettingsLicenseInfoSection/SettingsLicenseInfoSection'; +import { LicenseModal } from './modals/LicenseModal/LicenseModal'; + +type LicenseItemData = { + imageSrc: string; + title: string; + description: string; + badges?: BadgeProps[]; +}; + +const licenses: Array = [ + { + title: 'Starter', + imageSrc: starterImage, + description: `Advanced protection, shared access controls, and centralized billing. Ideal for small to medium teams.`, + badges: [{ text: 'Free', variant: BadgeVariant.Success }], + }, + { + title: 'Business', + imageSrc: businessImage, + description: `Advanced protection, shared access controls, and centralized billing. Ideal for small to medium teams.`, + badges: [{ text: 'Most popular', variant: BadgeVariant.Plan }], + }, + { + title: 'Enterprise', + imageSrc: enterpriseImage, + description: `Custom integrations, and dedicated support tailored to your organization’s security and scalability needs.`, + }, +]; + +export const SettingsLicenseTab = () => { + const appLicenseInfo = useApp((s) => s.appInfo.license_info); + const { data: licenseInfo } = useQuery(getLicenseInfoQueryOptions); + const { data: settings } = useQuery(getSettingsQueryOptions); + + return ( + + + {isPresent(settings) && ( + + {isPresent(licenseInfo) && ( + + )} + {!isPresent(licenseInfo) && ( +
+ + {`Current plan`} + + + + +
+ )} + +

{`Enter your license key to unlock additional Defguard features. Your license key is sent by email after purchase or registration on the Plans page.`}

+
+ +
+
+
+
+ )} + + +
+
{`Expand your possibilities with advanced plans`}
+ + {`Select your plan`} + +
+ + {licenses.map((data, index) => { + const isLast = index !== licenses.length - 1; + return ( + + + {isLast && } + + ); + })} +
+ {/* modals */} + +
+ ); +}; + +const LicenseItem = ({ data }: { data: LicenseItemData }) => { + return ( +
+
+
+ +
+
+
+

{data.title}

+ {data.badges?.map((props) => ( + + ))} +
+

{data.description}

+
+
+
+ ); +}; diff --git a/web/src/pages/settings/SettingsIndexPage/tabs/SettingsLicenseTab/assets/business.png b/web/src/pages/settings/SettingsIndexPage/tabs/SettingsLicenseTab/assets/business.png new file mode 100644 index 000000000..25e066022 Binary files /dev/null and b/web/src/pages/settings/SettingsIndexPage/tabs/SettingsLicenseTab/assets/business.png differ diff --git a/web/src/pages/settings/SettingsIndexPage/tabs/SettingsLicenseTab/assets/enterprise.png b/web/src/pages/settings/SettingsIndexPage/tabs/SettingsLicenseTab/assets/enterprise.png new file mode 100644 index 000000000..5d4dce65f Binary files /dev/null and b/web/src/pages/settings/SettingsIndexPage/tabs/SettingsLicenseTab/assets/enterprise.png differ diff --git a/web/src/pages/settings/SettingsIndexPage/tabs/SettingsLicenseTab/assets/starter.png b/web/src/pages/settings/SettingsIndexPage/tabs/SettingsLicenseTab/assets/starter.png new file mode 100644 index 000000000..fab257d39 Binary files /dev/null and b/web/src/pages/settings/SettingsIndexPage/tabs/SettingsLicenseTab/assets/starter.png differ diff --git a/web/src/pages/settings/SettingsIndexPage/tabs/SettingsLicenseTab/components/SettingsLicenseInfoSection/SettingsLicenseInfoSection.tsx b/web/src/pages/settings/SettingsIndexPage/tabs/SettingsLicenseTab/components/SettingsLicenseInfoSection/SettingsLicenseInfoSection.tsx new file mode 100644 index 000000000..a54a2031a --- /dev/null +++ b/web/src/pages/settings/SettingsIndexPage/tabs/SettingsLicenseTab/components/SettingsLicenseInfoSection/SettingsLicenseInfoSection.tsx @@ -0,0 +1,133 @@ +import { type PropsWithChildren, useMemo } from 'react'; +import './style.scss'; +import dayjs from 'dayjs'; +import type { LicenseInfo } from '../../../../../../../shared/api/types'; +import { Badge } from '../../../../../../../shared/defguard-ui/components/Badge/Badge'; +import { Divider } from '../../../../../../../shared/defguard-ui/components/Divider/Divider'; +import { + Icon, + type IconKindValue, +} from '../../../../../../../shared/defguard-ui/components/Icon'; +import { ProgressionBar } from '../../../../../../../shared/defguard-ui/components/ProgressionBar/ProgressionBar'; +import { SizedBox } from '../../../../../../../shared/defguard-ui/components/SizedBox/SizedBox'; +import { ThemeSpacing } from '../../../../../../../shared/defguard-ui/types'; +import { isPresent } from '../../../../../../../shared/defguard-ui/utils/isPresent'; + +type Props = { + licenseInfo: LicenseInfo; +}; + +export const SettingsLicenseInfoSection = ({ licenseInfo: license }: Props) => { + const licenseTier = license.tier; + return ( +
+
+ + {isPresent(licenseTier) && ( + <> +

{licenseTier}

+ + + )} + {!isPresent(licenseTier) && ( +
+ +
+ )} +
+ +

{`Offline`}

+
+ +

{`Community support`}

+
+ {!license.expired && ( + + + + )} +
+ +

{`Current plan limits`}

+ + +
+ ); +}; + +type ValidUntilProps = { + validUntil: string; +}; + +const ValidUntil = ({ validUntil }: ValidUntilProps) => { + const display = useMemo((): string => { + const untilDay = dayjs.utc(validUntil).local(); + const nowDay = dayjs(); + const diff = untilDay.diff(nowDay, 'days'); + return `${untilDay.format('DD/MM/YYYY')} (${diff} ${diff !== 1 ? 'days' : 'day'} left)`; + }, [validUntil]); + + return

{display}

; +}; + +type LimitSectionProps = { + license: LicenseInfo; +}; + +const LimitsSection = (_props: LimitSectionProps) => { + return ( +
+ + +
+ ); +}; + +type PropertyInfoProps = { + title: string; +} & PropsWithChildren; + +const PropertyInfo = ({ title, children }: PropertyInfoProps) => { + return ( +
+
+

{title}

+
+
{children}
+
+ ); +}; + +type LicenseLimitProgressProps = { + title: string; + icon: IconKindValue; + value: number; + maxValue: number; +}; + +const LicenseLimitProgress = ({ + icon, + maxValue, + title, + value, +}: LicenseLimitProgressProps) => { + return ( +
+
+ +

{title}

+
+

{`${value}/${maxValue}`}

+
+
+ + + +
+ ); +}; diff --git a/web/src/pages/settings/SettingsIndexPage/tabs/SettingsLicenseTab/components/SettingsLicenseInfoSection/style.scss b/web/src/pages/settings/SettingsIndexPage/tabs/SettingsLicenseTab/components/SettingsLicenseInfoSection/style.scss new file mode 100644 index 000000000..75f35381e --- /dev/null +++ b/web/src/pages/settings/SettingsIndexPage/tabs/SettingsLicenseTab/components/SettingsLicenseInfoSection/style.scss @@ -0,0 +1,70 @@ +.license-general-info { + & > .top { + display: grid; + grid-auto-flow: column; + align-items: stretch; + } + + .license-property-info { + box-sizing: border-box; + + &:not(:first-child) { + padding-left: var(--spacing-xl); + } + + &:not(:last-child) { + border-right: var(--border-1) solid var(--bg-active); + padding-right: var(--spacing-xl); + } + + & > .top { + padding-bottom: var(--spacing-xs); + + p { + font: var(--t-body-sm-400); + color: var(--fg-neutral); + } + } + + & > .bottom { + display: flex; + flex-flow: row nowrap; + + & > p { + font: var(--t-body-sm-500); + color: var(--fg-default); + } + + & > .badge { + margin-left: var(--spacing-md); + } + } + } + + .limits-label { + font: var(--t-body-xs-400); + color: var(--fg-neutral); + } + + .license-limit-progress { + & > .info { + display: flex; + flex-flow: row nowrap; + + & > p { + font: var(--t-body-sm-500); + color: var(--fg-default); + padding-left: var(--spacing-sm); + } + + & > .counter { + margin-left: auto; + + & > p { + font: var(--t-body-sm-500); + color: var(--fg-default); + } + } + } + } +} diff --git a/web/src/pages/settings/SettingsIndexPage/tabs/SettingsLicenseTab/modals/LicenseModal/LicenseModal.tsx b/web/src/pages/settings/SettingsIndexPage/tabs/SettingsLicenseTab/modals/LicenseModal/LicenseModal.tsx new file mode 100644 index 000000000..3de59e4cd --- /dev/null +++ b/web/src/pages/settings/SettingsIndexPage/tabs/SettingsLicenseTab/modals/LicenseModal/LicenseModal.tsx @@ -0,0 +1,153 @@ +import { useStore } from '@tanstack/react-form'; +import { useMutation } from '@tanstack/react-query'; +import type { AxiosError } from 'axios'; +import { useEffect, useMemo, useState } from 'react'; +import z from 'zod'; +import { m } from '../../../../../../../paraglide/messages'; +import api from '../../../../../../../shared/api/api'; +import type { ApiError } from '../../../../../../../shared/api/types'; +import { CopyButton } from '../../../../../../../shared/components/CopyButton/CopyButton'; +import { Modal } from '../../../../../../../shared/defguard-ui/components/Modal/Modal'; +import { ModalControls } from '../../../../../../../shared/defguard-ui/components/ModalControls/ModalControls'; +import { isPresent } from '../../../../../../../shared/defguard-ui/utils/isPresent'; +import { useAppForm } from '../../../../../../../shared/form'; +import { formChangeLogic } from '../../../../../../../shared/formLogic'; +import { + closeModal, + subscribeCloseModal, + subscribeOpenModal, +} from '../../../../../../../shared/hooks/modalControls/modalsSubjects'; +import { ModalName } from '../../../../../../../shared/hooks/modalControls/modalTypes'; + +const modalNameValue = ModalName.License; + +type ModalData = { + edit: boolean; + license: string | null; +}; + +export const LicenseModal = () => { + const [isOpen, setOpen] = useState(false); + const [modalData, setModalData] = useState(null); + + useEffect(() => { + const openSub = subscribeOpenModal(modalNameValue, ({ license }) => { + setModalData({ + edit: isPresent(license) && license.length > 0, + license: license ?? null, + }); + setOpen(true); + }); + const closeSub = subscribeCloseModal(modalNameValue, () => setOpen(false)); + return () => { + openSub.unsubscribe(); + closeSub.unsubscribe(); + }; + }, []); + + return ( + setOpen(false)} + afterClose={() => { + setModalData(null); + }} + > + {isPresent(modalData) && } + + ); +}; + +const formSchema = z.object({ + license: z.string(m.form_error_required()).trim().min(1, m.form_error_required()), +}); + +type FormFields = z.infer; + +const ModalContent = ({ license: initialLicense }: ModalData) => { + const defaultValues: FormFields = useMemo( + () => ({ + license: initialLicense ?? '', + }), + [initialLicense], + ); + + const { mutateAsync: patchSettings } = useMutation({ + mutationFn: api.settings.patchSettings, + onSuccess: () => { + closeModal(modalNameValue); + }, + meta: { + invalidate: ['settings'], + }, + }); + + const form = useAppForm({ + defaultValues, + validationLogic: formChangeLogic, + validators: { + onSubmit: formSchema, + onChange: formSchema, + }, + onSubmit: async ({ value, formApi }) => { + await patchSettings({ + license: value.license, + }).catch((e: AxiosError) => { + if (e.status && e.status >= 400 && e.status < 500) { + formApi.setErrorMap({ + onSubmit: { + fields: { + license: m.form_error_license(), + }, + }, + }); + } + }); + }, + }); + + const isSubmitting = useStore(form.store, (s) => s.isSubmitting); + + return ( +
{ + e.stopPropagation(); + e.preventDefault(); + form.handleSubmit(); + }} + > + + + {(field) => ( + + )} + + { + closeModal(modalNameValue); + }, + }} + submitProps={{ + text: m.controls_submit(), + loading: isSubmitting, + onClick: () => { + form.handleSubmit(); + }, + }} + > + {isPresent(initialLicense) && initialLicense.length > 0 && ( + + )} + + +
+ ); +}; diff --git a/web/src/pages/settings/SettingsIndexPage/tabs/SettingsLicenseTab/style.scss b/web/src/pages/settings/SettingsIndexPage/tabs/SettingsLicenseTab/style.scss new file mode 100644 index 000000000..8b0703853 --- /dev/null +++ b/web/src/pages/settings/SettingsIndexPage/tabs/SettingsLicenseTab/style.scss @@ -0,0 +1,57 @@ +#license-plans { + header { + display: flex; + flex-flow: row; + align-items: center; + + & > a { + margin-left: auto; + } + } + + .license-item { + & > .track { + display: grid; + grid-template-columns: 68px 1fr; + grid-template-rows: 1fr; + column-gap: var(--spacing-xl); + + .image-track { + display: flex; + flex-flow: column; + align-items: flex-start; + justify-content: flex-start; + overflow: hidden; + + img { + width: 100%; + } + } + + & > .content { + display: flex; + flex-flow: column; + row-gap: var(--spacing-xs); + + .top { + width: 100%; + display: flex; + flex-flow: row nowrap; + align-items: center; + justify-content: flex-start; + column-gap: var(--spacing-md); + + .title { + font: var(--t-body-primary-500); + color: var(--fg-default); + } + } + + .description { + font: var(--t-body-sm-400); + color: var(--fg-muted); + } + } + } + } +} diff --git a/web/src/pages/settings/SettingsIndexPage/tabs/SettingsNotificationsTab.tsx b/web/src/pages/settings/SettingsIndexPage/tabs/SettingsNotificationsTab.tsx new file mode 100644 index 000000000..a8d45e71c --- /dev/null +++ b/web/src/pages/settings/SettingsIndexPage/tabs/SettingsNotificationsTab.tsx @@ -0,0 +1,32 @@ +import { Link } from '@tanstack/react-router'; +import { SettingsLayout } from '../../../../shared/components/SettingsLayout/SettingsLayout'; +import { SectionSelect } from '../../../../shared/defguard-ui/components/SectionSelect/SectionSelect'; +import { SizedBox } from '../../../../shared/defguard-ui/components/SizedBox/SizedBox'; +import { ThemeSpacing } from '../../../../shared/defguard-ui/types'; +import { useApp } from '../../../../shared/hooks/useApp'; +import { configuredBadge, notConfiguredBadge } from '../types'; + +export const SettingsNotificationsTab = () => { + const smtp = useApp((s) => s.appInfo.smtp_enabled); + + return ( + + + + + + + + + + ); +}; diff --git a/web/src/pages/settings/SettingsIndexPage/types.ts b/web/src/pages/settings/SettingsIndexPage/types.ts new file mode 100644 index 000000000..28f790e89 --- /dev/null +++ b/web/src/pages/settings/SettingsIndexPage/types.ts @@ -0,0 +1,25 @@ +import z from 'zod'; +import type { BadgeProps } from '../../../shared/defguard-ui/components/Badge/types'; + +export const settingsTabsSchema = z.enum([ + 'general', + 'notifications', + 'openid', + 'activity', + 'license', +]); + +export type SettingsTabValue = z.infer; + +export const configuredBadge: BadgeProps = { + text: 'Configured', + icon: 'status-available', + iconSize: 16, + variant: 'success', + showIcon: true, +}; + +export const notConfiguredBadge: BadgeProps = { + text: 'Not configured', + variant: 'critical', +}; diff --git a/web/src/pages/settings/SettingsInstancePage/SettingsInstancePage.tsx b/web/src/pages/settings/SettingsInstancePage/SettingsInstancePage.tsx new file mode 100644 index 000000000..e026c78d7 --- /dev/null +++ b/web/src/pages/settings/SettingsInstancePage/SettingsInstancePage.tsx @@ -0,0 +1,137 @@ +import { useMutation, useQuery } from '@tanstack/react-query'; +import { Link } from '@tanstack/react-router'; +import { useMemo } from 'react'; +import z from 'zod'; +import { m } from '../../../paraglide/messages'; +import api from '../../../shared/api/api'; +import type { Settings } from '../../../shared/api/types'; +import { Breadcrumbs } from '../../../shared/components/Breadcrumbs/Breadcrumbs'; +import { Controls } from '../../../shared/components/Controls/Controls'; +import { Page } from '../../../shared/components/Page/Page'; +import { SettingsCard } from '../../../shared/components/SettingsCard/SettingsCard'; +import { SettingsHeader } from '../../../shared/components/SettingsHeader/SettingsHeader'; +import { SettingsLayout } from '../../../shared/components/SettingsLayout/SettingsLayout'; +import { Button } from '../../../shared/defguard-ui/components/Button/Button'; +import { isPresent } from '../../../shared/defguard-ui/utils/isPresent'; +import { useAppForm } from '../../../shared/form'; +import { formChangeLogic } from '../../../shared/formLogic'; +import { getSettingsQueryOptions } from '../../../shared/query'; + +const breadcrumbs = [ + + General + , + + Instance settings + , +]; + +export const SettingsInstancePage = () => { + const { data: settings } = useQuery(getSettingsQueryOptions); + return ( + + + + + {isPresent(settings) && ( + + + + )} + + + ); +}; + +const formSchema = z.object({ + instance_name: z + .string(m.form_error_required()) + .trim() + .min(1, m.form_error_required()) + .min( + 3, + m.form_error_min_len({ + length: 3, + }), + ) + .max(64, m.form_error_max_len({ length: 64 })), +}); + +type FormFields = z.infer; + +const Content = ({ settings }: { settings: Settings }) => { + const { mutateAsync } = useMutation({ + mutationFn: api.settings.patchSettings, + meta: { + invalidate: ['settings'], + }, + }); + + const defaultValues = useMemo( + (): FormFields => ({ + instance_name: settings.instance_name ?? '', + }), + [settings.instance_name], + ); + + const form = useAppForm({ + defaultValues, + validationLogic: formChangeLogic, + validators: { + onSubmit: formSchema, + onChange: formSchema, + }, + onSubmit: async ({ value }) => { + await mutateAsync(value); + }, + }); + + return ( +
{ + e.stopPropagation(); + e.preventDefault(); + form.handleSubmit(); + }} + > + + + {(field) => } + + + ({ + isDefault: s.isDefaultValue || s.isPristine, + isSubmitting: s.isSubmitting, + })} + > + {({ isDefault, isSubmitting }) => ( + +
+
+
+ )} +
+
+ ); +}; diff --git a/web/src/pages/settings/SettingsPage.tsx b/web/src/pages/settings/SettingsPage.tsx deleted file mode 100644 index e7d819c03..000000000 --- a/web/src/pages/settings/SettingsPage.tsx +++ /dev/null @@ -1,182 +0,0 @@ -import './style.scss'; - -import { useQuery, useQueryClient } from '@tanstack/react-query'; -import { type ReactNode, useCallback, useEffect, useMemo, useState } from 'react'; -import { shallow } from 'zustand/shallow'; - -import { useI18nContext } from '../../i18n/i18n-react'; -import { PageContainer } from '../../shared/components/Layout/PageContainer/PageContainer'; -import { useUpgradeLicenseModal } from '../../shared/components/Layout/UpgradeLicenseModal/store'; -import { UpgradeLicenseModalVariant } from '../../shared/components/Layout/UpgradeLicenseModal/types'; -import { Card } from '../../shared/defguard-ui/components/Layout/Card/Card'; -import { CardTabs } from '../../shared/defguard-ui/components/Layout/CardTabs/CardTabs'; -import type { CardTabsData } from '../../shared/defguard-ui/components/Layout/CardTabs/types'; -import { LoaderSpinner } from '../../shared/defguard-ui/components/Layout/LoaderSpinner/LoaderSpinner'; -import { useAppStore } from '../../shared/hooks/store/useAppStore'; -import useApi from '../../shared/hooks/useApi'; -import { QueryKeys } from '../../shared/queries'; -import { ActivityLogStreamSettings } from './components/ActivityLogStreamSettings/ActivityLogStreamSettings'; -import { EnterpriseSettings } from './components/EnterpriseSettings/EnterpriseSettings'; -import { GlobalSettings } from './components/GlobalSettings/GlobalSettings'; -import { LdapSettings } from './components/LdapSettings/LdapSettings'; -import { NotificationSettings } from './components/NotificationSettings/NotificationSettings'; -import { OpenIdSettings } from './components/OpenIdSettings/OpenIdSettings'; -import { SmtpSettings } from './components/SmtpSettings/SmtpSettings'; -import { useSettingsPage } from './hooks/useSettingsPage'; - -const tabsContent: ReactNode[] = [ - , - , - , - , - , - , - , -]; - -const enterpriseTabs: number[] = [2, 3, 4, 6]; - -export const SettingsPage = () => { - const { LL } = useI18nContext(); - const { - getEnterpriseInfo, - settings: { getSettings }, - } = useApi(); - - const [activeCard, setActiveCard] = useState(0); - const queryClient = useQueryClient(); - const appInfo = useAppStore((s) => s.appInfo); - const openUpgradeLicenseModal = useUpgradeLicenseModal((s) => s.open, shallow); - - const [setPageState, resetPageState] = useSettingsPage( - (state) => [state.setState, state.reset], - shallow, - ); - - const settings = useSettingsPage((state) => state.settings); - - const { data: settingsData, isLoading: settingsLoading } = useQuery({ - queryFn: getSettings, - queryKey: [QueryKeys.FETCH_SETTINGS], - refetchOnMount: true, - refetchOnWindowFocus: false, - }); - - const { data: enterpriseInfo, isLoading: enterpriseInfoLoading } = useQuery({ - queryFn: getEnterpriseInfo, - queryKey: [QueryKeys.FETCH_ENTERPRISE_INFO], - refetchOnWindowFocus: false, - refetchOnMount: true, - }); - - const handleTabClick = useCallback( - (tabIndex: number) => { - if (appInfo) { - if (enterpriseTabs.includes(tabIndex) && !appInfo.license_info.enterprise) { - openUpgradeLicenseModal({ - modalVariant: UpgradeLicenseModalVariant.ENTERPRISE_NOTICE, - }); - } else { - setActiveCard(tabIndex); - } - } - }, - [appInfo, openUpgradeLicenseModal], - ); - - const tabs = useMemo( - (): CardTabsData[] => [ - { - key: 0, - content: LL.settingsPage.tabs.global(), - active: activeCard === 0, - onClick: () => handleTabClick(0), - }, - { - key: 1, - content: LL.settingsPage.tabs.smtp(), - active: activeCard === 1, - onClick: () => handleTabClick(1), - }, - { - key: 2, - content: LL.settingsPage.tabs.ldap(), - active: activeCard === 2, - onClick: () => handleTabClick(2), - }, - { - key: 3, - content: LL.settingsPage.tabs.openid(), - active: activeCard === 3, - onClick: () => handleTabClick(3), - }, - { - key: 4, - content: LL.settingsPage.tabs.enterprise(), - active: activeCard === 4, - onClick: () => handleTabClick(4), - }, - { - key: 5, - content: LL.settingsPage.tabs.gatewayNotifications(), - active: activeCard === 5, - onClick: () => handleTabClick(5), - }, - { - key: 6, - content: LL.settingsPage.tabs.activityLogStream(), - active: activeCard === 6, - onClick: () => handleTabClick(6), - }, - ], - [LL.settingsPage.tabs, activeCard, handleTabClick], - ); - - // set store - useEffect(() => { - setPageState({ - settings: settingsData, - enterpriseInfo: enterpriseInfo?.license_info, - }); - }, [settingsData, setPageState, enterpriseInfo?.license_info]); - - // biome-ignore lint/correctness/useExhaustiveDependencies: on mount - useEffect(() => { - void queryClient.invalidateQueries({ - queryKey: [QueryKeys.FETCH_APP_INFO], - }); - return () => { - resetPageState?.(); - }; - }, []); - - // if appinfo changes and license is not enterprise anymore then change active tab to global - // this can happen when admin is on enterprise tab but limits are exceeded in the mean time - useEffect(() => { - if ( - appInfo && - !appInfo.license_info.enterprise && - enterpriseTabs.includes(activeCard) - ) { - setActiveCard(0); - openUpgradeLicenseModal({ - modalVariant: UpgradeLicenseModalVariant.LICENSE_LIMIT, - }); - } - }, [activeCard, appInfo, openUpgradeLicenseModal]); - - return ( - -

{LL.settingsPage.title()}

- {(settingsLoading || enterpriseInfoLoading) && } - {settings && !enterpriseInfoLoading && !settingsLoading && ( - <> - - - {tabsContent[activeCard]} - - - )} -
- ); -}; diff --git a/web/src/pages/settings/SettingsSmtpPage/SendTestEmailModal.tsx b/web/src/pages/settings/SettingsSmtpPage/SendTestEmailModal.tsx new file mode 100644 index 000000000..3ce6dd74b --- /dev/null +++ b/web/src/pages/settings/SettingsSmtpPage/SendTestEmailModal.tsx @@ -0,0 +1,120 @@ +import { useEffect, useState } from 'react'; +import z from 'zod'; +import { m } from '../../../paraglide/messages'; +import api from '../../../shared/api/api'; +import { AppText } from '../../../shared/defguard-ui/components/AppText/AppText'; +import { Modal } from '../../../shared/defguard-ui/components/Modal/Modal'; +import { ModalControls } from '../../../shared/defguard-ui/components/ModalControls/ModalControls'; +import { SizedBox } from '../../../shared/defguard-ui/components/SizedBox/SizedBox'; +import { + TextStyle, + ThemeSpacing, + ThemeVariable, +} from '../../../shared/defguard-ui/types'; +import { useAppForm } from '../../../shared/form'; +import { formChangeLogic } from '../../../shared/formLogic'; +import { + closeModal, + subscribeCloseModal, + subscribeOpenModal, +} from '../../../shared/hooks/modalControls/modalsSubjects'; +import { ModalName } from '../../../shared/hooks/modalControls/modalTypes'; + +const modalNameValue = ModalName.SendTestMail; + +export const SendTestEmailModal = () => { + const [isOpen, setOpen] = useState(false); + + useEffect(() => { + const openSub = subscribeOpenModal(modalNameValue, () => { + setOpen(true); + }); + const closeSub = subscribeCloseModal(modalNameValue, () => setOpen(false)); + return () => { + openSub.unsubscribe(); + closeSub.unsubscribe(); + }; + }, []); + + return ( + setOpen(false)} + size="small" + > + + + ); +}; + +const formSchema = z.object({ + email: z.email(m.form_error_email()).min(1, m.form_error_required()), +}); + +type FormFields = z.infer; + +const defaultValues: FormFields = { + email: '', +}; + +const ModalContent = () => { + const form = useAppForm({ + defaultValues, + validationLogic: formChangeLogic, + validators: { + onSubmit: formSchema, + onChange: formSchema, + }, + onSubmit: async ({ value }) => { + await api.mail + .sendTestEmail({ + email: value.email, + }) + .finally(() => { + closeModal(modalNameValue); + }); + }, + }); + + return ( + <> + + {`Check if your SMTP configuration works by sending a test email.`} + +
{ + e.stopPropagation(); + e.preventDefault(); + form.handleSubmit(); + }} + > + + + {(field) => } + + + ({ + isDefault: s.isDefaultValue || s.isPristine, + isSubmitting: s.isSubmitting, + })} + > + {({ isSubmitting }) => ( + + )} + + +
+ + ); +}; diff --git a/web/src/pages/settings/SettingsSmtpPage/SettingsSmtpPage.tsx b/web/src/pages/settings/SettingsSmtpPage/SettingsSmtpPage.tsx new file mode 100644 index 000000000..c59cc385c --- /dev/null +++ b/web/src/pages/settings/SettingsSmtpPage/SettingsSmtpPage.tsx @@ -0,0 +1,266 @@ +import { useMutation, useQuery } from '@tanstack/react-query'; +import { Link } from '@tanstack/react-router'; +import { useMemo } from 'react'; +import z from 'zod'; +import { m } from '../../../paraglide/messages'; +import api from '../../../shared/api/api'; +import { + type Settings, + SmtpEncryption, + type SmtpEncryptionValue, +} from '../../../shared/api/types'; +import { Breadcrumbs } from '../../../shared/components/Breadcrumbs/Breadcrumbs'; +import { Controls } from '../../../shared/components/Controls/Controls'; +import { DescriptionBlock } from '../../../shared/components/DescriptionBlock/DescriptionBlock'; +import { Page } from '../../../shared/components/Page/Page'; +import { SettingsCard } from '../../../shared/components/SettingsCard/SettingsCard'; +import { SettingsHeader } from '../../../shared/components/SettingsHeader/SettingsHeader'; +import { SettingsLayout } from '../../../shared/components/SettingsLayout/SettingsLayout'; +import { Button } from '../../../shared/defguard-ui/components/Button/Button'; +import { EvenSplit } from '../../../shared/defguard-ui/components/EvenSplit/EvenSplit'; +import type { SelectOption } from '../../../shared/defguard-ui/components/Select/types'; +import { SizedBox } from '../../../shared/defguard-ui/components/SizedBox/SizedBox'; +import { ThemeSpacing } from '../../../shared/defguard-ui/types'; +import { isPresent } from '../../../shared/defguard-ui/utils/isPresent'; +import { useAppForm } from '../../../shared/form'; +import { formChangeLogic } from '../../../shared/formLogic'; +import { openModal } from '../../../shared/hooks/modalControls/modalsSubjects'; +import { ModalName } from '../../../shared/hooks/modalControls/modalTypes'; +import { useApp } from '../../../shared/hooks/useApp'; +import { patternValidEmail } from '../../../shared/patterns'; +import { getSettingsQueryOptions } from '../../../shared/query'; +import { validateIpOrDomain } from '../../../shared/validators'; +import { configuredBadge, notConfiguredBadge } from '../SettingsIndexPage/types'; +import { SendTestEmailModal } from './SendTestEmailModal'; + +const breadcrumbsLinks = [ + + Notifications + , + + SMTP Configuration + , +]; + +export const SettingsSmtpPage = () => { + const { data: settings } = useQuery(getSettingsQueryOptions); + const smtp = useApp((s) => s.appInfo.smtp_enabled); + + return ( + + + + + {isPresent(settings) && ( + + +

+ Configure the SMTP server here — it’s required for sending system messages + to users. +

+
+ + +
+ )} +
+ +
+ ); +}; + +const encryptionValueToLabel = (value: SmtpEncryptionValue): string => { + switch (value) { + case 'ImplicitTls': + return 'Implicit TLS'; + case 'StartTls': + return 'Start TLS'; + case 'None': + return 'None'; + } +}; + +const encryptionSelectOptions: SelectOption[] = Object.values( + SmtpEncryption, +).map((e) => ({ + key: e, + label: encryptionValueToLabel(e), + value: e, +})); + +const Content = ({ settings }: { settings: Settings }) => { + const smtpConfigured = useApp((s) => s.appInfo.smtp_enabled); + const formSchema = useMemo( + () => + z.object({ + smtp_server: z + .string() + .trim() + .min(1, m.form_error_required()) + .refine((val) => (!val ? true : validateIpOrDomain(val, false, true))), + smtp_port: z.number(m.form_error_required()).max(65535, m.form_error_port_max()), + smtp_password: z.string().trim(), + smtp_user: z.string().trim(), + smtp_sender: z + .string() + .trim() + .min(1, m.form_error_required()) + .regex(patternValidEmail, m.form_error_email()), + smtp_encryption: z.enum(SmtpEncryption), + }), + [], + ); + + type FormFields = z.infer; + + const emptyValues = useMemo( + (): FormFields => ({ + smtp_encryption: SmtpEncryption.StartTls, + smtp_password: '', + smtp_port: 587, + smtp_sender: '', + smtp_server: '', + smtp_user: '', + }), + [], + ); + + const defaultValues = useMemo( + (): FormFields => ({ + smtp_encryption: settings.smtp_encryption, + smtp_password: settings.smtp_password ?? '', + smtp_port: settings.smtp_port ?? 587, + smtp_sender: settings.smtp_sender ?? '', + smtp_server: settings.smtp_server ?? '', + smtp_user: settings.smtp_user ?? '', + }), + [settings], + ); + + const { mutateAsync: editSettings } = useMutation({ + mutationFn: api.settings.patchSettings, + meta: { + invalidate: [['settings'], ['info']], + }, + }); + + const { mutateAsync: deleteSmtp, isPending: deletePending } = useMutation({ + mutationFn: () => api.settings.patchSettings(emptyValues), + meta: { + invalidate: [['settings'], ['info']], + }, + onSuccess: () => { + form.reset(emptyValues); + }, + }); + + const form = useAppForm({ + defaultValues, + validationLogic: formChangeLogic, + validators: { + onSubmit: formSchema, + onChange: formSchema, + }, + onSubmit: async ({ value }) => { + await editSettings(value); + }, + }); + + return ( +
{ + e.stopPropagation(); + e.preventDefault(); + form.handleSubmit(); + }} + > + + + + {(field) => } + + + {(field) => } + + + + + + {(field) => } + + + {(field) => } + + + + + + {(field) => } + + + {(field) => ( + + )} + + + ({ + isDefaultValue: s.isDefaultValue || s.isPristine, + isSubmitting: s.isSubmitting, + })} + > + {({ isDefaultValue, isSubmitting }) => ( + + {smtpConfigured && ( +
- ); -}; diff --git a/web/src/pages/settings/components/GlobalSettings/components/GlobalSettingsForm/styles.scss b/web/src/pages/settings/components/GlobalSettings/components/GlobalSettingsForm/styles.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/web/src/pages/settings/components/GlobalSettings/components/LicenseSettings/LicenseSettings.tsx b/web/src/pages/settings/components/GlobalSettings/components/LicenseSettings/LicenseSettings.tsx deleted file mode 100644 index 3fa9acfab..000000000 --- a/web/src/pages/settings/components/GlobalSettings/components/LicenseSettings/LicenseSettings.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import './styles.scss'; - -import { useMemo } from 'react'; -import type { Control } from 'react-hook-form'; - -import { useI18nContext } from '../../../../../../i18n/i18n-react'; -import { FormInput } from '../../../../../../shared/defguard-ui/components/Form/FormInput/FormInput'; -import { ActivityIcon } from '../../../../../../shared/defguard-ui/components/icons/ActivityIcon/ActivityIcon'; -import { ActivityIconVariant } from '../../../../../../shared/defguard-ui/components/icons/ActivityIcon/types'; -import { ExpandableCard } from '../../../../../../shared/defguard-ui/components/Layout/ExpandableCard/ExpandableCard'; -import { Helper } from '../../../../../../shared/defguard-ui/components/Layout/Helper/Helper'; -import { Label } from '../../../../../../shared/defguard-ui/components/Layout/Label/Label'; -import { isPresent } from '../../../../../../shared/defguard-ui/utils/isPresent'; -import { useAppStore } from '../../../../../../shared/hooks/store/useAppStore'; -import { useSettingsPage } from '../../../../hooks/useSettingsPage'; -import type { GlobalSettingsFormFields } from '../../types'; - -export const LicenseSettings = ({ - control, -}: { - control: Control; -}) => { - const { LL } = useI18nContext(); - const appInfo = useAppStore((s) => s.appInfo); - const enterpriseInfo = useSettingsPage((s) => s.enterpriseInfo); - - const licenseIconVariant = useMemo(() => { - if ( - isPresent(enterpriseInfo) && - !enterpriseInfo.limits_exceeded && - !enterpriseInfo.expired - ) { - return ActivityIconVariant.CONNECTED; - } - return ActivityIconVariant.ERROR; - }, [enterpriseInfo]); - - const statusText = useMemo(() => { - if (!isPresent(enterpriseInfo)) { - return LL.settingsPage.license.licenseInfo.status.noLicense(); - } - if (enterpriseInfo.expired) { - return LL.settingsPage.license.licenseInfo.status.expired(); - } - if (appInfo?.license_info.any_limit_exceeded) { - return LL.settingsPage.license.licenseInfo.status.limitsExceeded(); - } - return LL.settingsPage.license.licenseInfo.status.active(); - }, [ - LL.settingsPage.license.licenseInfo.status, - appInfo?.license_info.any_limit_exceeded, - enterpriseInfo, - ]); - - return ( -
-
-

{LL.settingsPage.license.header()}

- -

{LL.settingsPage.license.helpers.enterpriseHeader.text()}

- - {LL.settingsPage.license.helpers.enterpriseHeader.link()} - -
-
-
- - - {isPresent(enterpriseInfo) ? ( -
-
- -
- -

{statusText}

- {enterpriseInfo.subscription ? ( - - {LL.settingsPage.license.licenseInfo.fields.status.subscriptionHelper()} - - ) : null} -
-
-
- -
-

- {enterpriseInfo.subscription - ? LL.settingsPage.license.licenseInfo.types.subscription.label() - : LL.settingsPage.license.licenseInfo.types.offline.label()} -

- - {enterpriseInfo.subscription - ? LL.settingsPage.license.licenseInfo.types.subscription.helper() - : LL.settingsPage.license.licenseInfo.types.offline.helper()} - -
-
-
- -

- {enterpriseInfo.valid_until - ? new Date(enterpriseInfo.valid_until).toLocaleString() - : '-'} -

-
-
- ) : ( -

- {LL.settingsPage.license.licenseInfo.status.noLicense()} -

- )} -
-
-
- ); -}; diff --git a/web/src/pages/settings/components/GlobalSettings/components/LicenseSettings/styles.scss b/web/src/pages/settings/components/GlobalSettings/components/LicenseSettings/styles.scss deleted file mode 100644 index 3d1ca3037..000000000 --- a/web/src/pages/settings/components/GlobalSettings/components/LicenseSettings/styles.scss +++ /dev/null @@ -1,56 +0,0 @@ -#license-settings { - & > .card { - display: flex; - flex-flow: column; - row-gap: 16px; - - @include media-breakpoint-up(lg) { - padding: 16px 15px; - } - - .loading-license-info { - display: flex; - justify-content: center; - align-items: center; - } - } -} - -.controls > .header { - display: flex; - justify-content: space-between; - align-items: center; - gap: 10px; -} - -#license-info { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); - gap: 16px; - - & > div { - display: flex; - flex-flow: column; - row-gap: 8px; - align-items: center; - - & > .with-helper { - display: flex; - gap: 5px; - } - } - - .license-status { - display: flex; - align-items: center; - gap: 5px; - } -} - -#no-license { - text-align: center; -} - -#license-not-required { - text-align: center; -} diff --git a/web/src/pages/settings/components/GlobalSettings/types.ts b/web/src/pages/settings/components/GlobalSettings/types.ts deleted file mode 100644 index 85875150b..000000000 --- a/web/src/pages/settings/components/GlobalSettings/types.ts +++ /dev/null @@ -1,20 +0,0 @@ -import z from 'zod'; -import type { TranslationFunctions } from '../../../../i18n/i18n-types'; - -export const globalSettingsSchema = (LL: TranslationFunctions) => - z.object({ - main_logo_url: z.string().trim(), - nav_logo_url: z.string().trim(), - instance_name: z - .string() - .trim() - .min(3, LL.form.error.minimumLength()) - .max(64, LL.form.error.maximumLength()), - openid_enabled: z.boolean(), - wireguard_enabled: z.boolean(), - worker_enabled: z.boolean(), - webhooks_enabled: z.boolean(), - license: z.string().trim().optional(), - }); - -export type GlobalSettingsFormFields = z.infer>; diff --git a/web/src/pages/settings/components/LdapSettings/LdapSettings.tsx b/web/src/pages/settings/components/LdapSettings/LdapSettings.tsx deleted file mode 100644 index 40e1109eb..000000000 --- a/web/src/pages/settings/components/LdapSettings/LdapSettings.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import './style.scss'; - -import parse from 'html-react-parser'; - -import { useI18nContext } from '../../../../i18n/i18n-react'; -import { BigInfoBox } from '../../../../shared/defguard-ui/components/Layout/BigInfoBox/BigInfoBox'; -import { useAppStore } from '../../../../shared/hooks/store/useAppStore'; -import { LdapSettingsForm } from './components/LdapSettingsForm'; - -export const LdapSettings = () => { - const { LL } = useI18nContext(); - const appInfo = useAppStore((s) => s.appInfo); - - if (!appInfo) return null; - return ( - <> - {appInfo.license_info.is_enterprise_free && ( -
- -
- )} - - - ); -}; diff --git a/web/src/pages/settings/components/LdapSettings/components/LdapConnectionTest.tsx b/web/src/pages/settings/components/LdapSettings/components/LdapConnectionTest.tsx deleted file mode 100644 index 4346999e6..000000000 --- a/web/src/pages/settings/components/LdapSettings/components/LdapConnectionTest.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { useMutation } from '@tanstack/react-query'; - -import { useI18nContext } from '../../../../../i18n/i18n-react'; -import { Button } from '../../../../../shared/defguard-ui/components/Layout/Button/Button'; -import { - ButtonSize, - ButtonStyleVariant, -} from '../../../../../shared/defguard-ui/components/Layout/Button/types'; -import useApi from '../../../../../shared/hooks/useApi'; -import { useToaster } from '../../../../../shared/hooks/useToaster'; - -export const LdapConnectionTest = () => { - const { LL } = useI18nContext(); - const localLL = LL.settingsPage.ldapSettings.test; - const { - settings: { testLdapSettings }, - } = useApi(); - - const toaster = useToaster(); - - const { isPending: isLoading, mutate } = useMutation({ - mutationFn: testLdapSettings, - onSuccess: () => { - toaster.success(localLL.messages.success()); - }, - onError: () => { - toaster.error(localLL.messages.error()); - }, - }); - - return ( - - )} - - - -
-
- {keys.map((key) => ( -
-
- -

{`${key.key_type.valueOf().toUpperCase()} Key`}

-
-
- - -
-
- ))} -
-
- - ); -}; diff --git a/web/src/pages/users/UserProfile/UserAuthenticationKeys/AuthenticationKeyList/AuthenticationKeyList.tsx b/web/src/pages/users/UserProfile/UserAuthenticationKeys/AuthenticationKeyList/AuthenticationKeyList.tsx deleted file mode 100644 index 71b9e6861..000000000 --- a/web/src/pages/users/UserProfile/UserAuthenticationKeys/AuthenticationKeyList/AuthenticationKeyList.tsx +++ /dev/null @@ -1,104 +0,0 @@ -import './style.scss'; - -import { useQuery } from '@tanstack/react-query'; -import { groupBy, isUndefined, sortBy } from 'lodash-es'; -import { Fragment, useMemo } from 'react'; - -import { useUserProfileStore } from '../../../../../shared/hooks/store/useUserProfileStore'; -import useApi from '../../../../../shared/hooks/useApi'; -import { QueryKeys } from '../../../../../shared/queries'; -import type { AuthenticationKey } from '../../../../../shared/types'; -import { AuthenticationKeyItem } from './AuthenticationKeyItem/AuthenticationKeyItem'; -import { AuthenticationKeyItemYubikey } from './AuthenticationKeyItemYubiKey/AuthenticationKeyItemYubiKey'; - -type itemData = { - yubikey?: { - yubikey_id: number; - yubikey_name: string; - yubikey_serial: string; - keys: AuthenticationKey[]; - }; - key?: AuthenticationKey; -}; - -export const AuthenticationKeyList = () => { - const user = useUserProfileStore((s) => s.userProfile?.user); - const { - user: { getAuthenticationKeysInfo: fetchAuthenticationKeys }, - } = useApi(); - - const { data: authenticationKeysInfo } = useQuery({ - queryFn: () => fetchAuthenticationKeys({ username: user?.username as string }), - queryKey: [QueryKeys.FETCH_AUTHENTICATION_KEYS_INFO, user?.username], - refetchOnMount: true, - refetchOnWindowFocus: false, - enabled: !isUndefined(user), - }); - - // parse api response then store it and return in form which can be displayed by components - const items = useMemo((): itemData[] => { - if (authenticationKeysInfo) { - const standAlone: itemData[] = authenticationKeysInfo - .filter((k) => isUndefined(k.yubikey_id)) - .map((k) => ({ - key: { - id: k.id, - name: k.name as string, - key_type: k.key_type, - key: k.key, - }, - })); - const yubikeys: itemData[] = []; - const g = groupBy( - authenticationKeysInfo.filter((k) => !isUndefined(k.yubikey_id)), - 'yubikey_id', - ); - Object.keys(g).forEach((string_id) => { - const val = g[string_id]; - const keys: AuthenticationKey[] = val.map((info) => ({ - id: info.id, - key: info.key, - key_type: info.key_type, - name: info.yubikey_name as string, - })); - yubikeys.push({ - yubikey: { - keys, - yubikey_id: val[0].yubikey_id as number, - yubikey_name: val[0].yubikey_name as string, - yubikey_serial: val[0].yubikey_serial as string, - }, - }); - }); - const res = sortBy( - [...yubikeys, ...standAlone], - (k) => - k.key?.name?.toLowerCase?.() ?? k.yubikey?.yubikey_name?.toLowerCase?.() ?? '', - ); - return res; - } - return []; - }, [authenticationKeysInfo]); - - if (items.length === 0 || !items) return null; - - return ( -
- {items.map((item, index) => ( - - {item.yubikey && ( - - )} - {item.key && } - - ))} -
- ); -}; diff --git a/web/src/pages/users/UserProfile/UserAuthenticationKeys/AuthenticationKeyList/style.scss b/web/src/pages/users/UserProfile/UserAuthenticationKeys/AuthenticationKeyList/style.scss deleted file mode 100644 index dd25ea84e..000000000 --- a/web/src/pages/users/UserProfile/UserAuthenticationKeys/AuthenticationKeyList/style.scss +++ /dev/null @@ -1,130 +0,0 @@ -@use '@scssutils' as *; - -.authentication-key-list { - display: flex; - flex-flow: column; - align-items: flex-start; - justify-content: flex-start; - row-gap: 15px; - width: 100%; - max-width: 100%; - - .authentication-key-item { - --controls-size: 40px; - background-color: var(--surface-default-modal); - border: 1px solid transparent; - border-radius: 15px; - box-sizing: border-box; - position: relative; - transition-property: border; - transition-timing-function: ease-in-out; - transition-duration: 200ms; - max-width: 100%; - width: 100%; - overflow: hidden; - - &.yubikey { - --controls-size: 90px; - } - - & > header { - .top { - padding-right: var(--controls-size); - box-sizing: border-box; - } - } - - .controls { - position: absolute; - top: 10px; - right: 15px; - display: flex; - flex-flow: row; - row-gap: 5px; - - & > * { - cursor: pointer; - width: 40px; - height: 40px; - } - - .expand-button { - border: 0px solid transparent; - background-color: transparent; - display: flex; - flex-flow: row; - align-items: center; - justify-content: center; - user-select: none; - padding: 0; - margin: 0; - } - } - - .expandable-section { - width: 100%; - display: grid; - grid-template-rows: 0fr; - - & > div { - overflow: hidden; - } - - .item-content { - border-top: 1px solid var(--border-primary); - } - } - - .item-content { - box-sizing: border-box; - padding: 20px 25px; - - .avatar-icon { - width: 40px; - height: 40px; - min-width: 40px; - max-width: 40px; - } - - .top { - padding-bottom: 18px; - display: flex; - flex-flow: row nowrap; - align-items: center; - justify-content: flex-start; - column-gap: 8px; - - p { - @include typography(app-side-bar); - } - } - - .top, - .bottom { - overflow: hidden; - width: 100%; - max-width: 100%; - } - - .bottom { - & > label { - padding-bottom: 8px; - } - - p { - @include typography(app-button-xl); - } - } - } - - &.active { - border-color: var(--border-primary); - } - - &.expanded { - .expandable-section { - grid-template-rows: auto; - } - } - } -} diff --git a/web/src/pages/users/UserProfile/UserAuthenticationKeys/DeleteAuthenticationKeyModal/DeleteAuthenticationKeyModal.tsx b/web/src/pages/users/UserProfile/UserAuthenticationKeys/DeleteAuthenticationKeyModal/DeleteAuthenticationKeyModal.tsx deleted file mode 100644 index de620e0c7..000000000 --- a/web/src/pages/users/UserProfile/UserAuthenticationKeys/DeleteAuthenticationKeyModal/DeleteAuthenticationKeyModal.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import type { AxiosError } from 'axios'; -import { shallow } from 'zustand/shallow'; - -import { useI18nContext } from '../../../../../i18n/i18n-react'; -import { ConfirmModal } from '../../../../../shared/defguard-ui/components/Layout/modals/ConfirmModal/ConfirmModal'; -import { ConfirmModalType } from '../../../../../shared/defguard-ui/components/Layout/modals/ConfirmModal/types'; -import useApi from '../../../../../shared/hooks/useApi'; -import { useToaster } from '../../../../../shared/hooks/useToaster'; -import { QueryKeys } from '../../../../../shared/queries'; -import { useDeleteAuthenticationKeyModal } from './useDeleteAuthenticationKeyModal'; - -export const DeleteAuthenticationKeyModal = () => { - const { - user: { deleteAuthenticationKey, deleteYubiKey }, - } = useApi(); - const queryClient = useQueryClient(); - const { LL } = useI18nContext(); - const toaster = useToaster(); - const isOpen = useDeleteAuthenticationKeyModal((s) => s.visible); - const [close, reset] = useDeleteAuthenticationKeyModal( - (s) => [s.close, s.reset], - shallow, - ); - const keyData = useDeleteAuthenticationKeyModal((s) => s.keyData); - - const onSuccess = () => { - toaster.success(LL.messages.success()); - void queryClient.invalidateQueries({ - queryKey: [QueryKeys.FETCH_AUTHENTICATION_KEYS_INFO], - }); - close(); - }; - - const onError = (e: AxiosError) => { - toaster.error(LL.messages.error()); - console.error(e); - }; - - const { mutate: deleteYubikeyMutation, isPending: yubikeyPending } = useMutation({ - mutationFn: deleteYubiKey, - onSuccess, - onError, - }); - - const { mutate: deleteAuthenticationKeyMutation, isPending: authKeyPending } = - useMutation({ - mutationFn: deleteAuthenticationKey, - onSuccess, - onError, - }); - - return ( - { - if (keyData) { - if (keyData.type === 'yubikey') { - deleteYubikeyMutation({ - id: keyData.id, - username: keyData.username, - }); - } else { - deleteAuthenticationKeyMutation({ - id: keyData.id, - username: keyData.username, - }); - } - } - }} - loading={authKeyPending || yubikeyPending} - /> - ); -}; diff --git a/web/src/pages/users/UserProfile/UserAuthenticationKeys/DeleteAuthenticationKeyModal/useDeleteAuthenticationKeyModal.ts b/web/src/pages/users/UserProfile/UserAuthenticationKeys/DeleteAuthenticationKeyModal/useDeleteAuthenticationKeyModal.ts deleted file mode 100644 index dc981f5d6..000000000 --- a/web/src/pages/users/UserProfile/UserAuthenticationKeys/DeleteAuthenticationKeyModal/useDeleteAuthenticationKeyModal.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { createWithEqualityFn } from 'zustand/traditional'; - -const defaultValues: StoreValues = { - visible: false, - keyData: undefined, -}; - -export const useDeleteAuthenticationKeyModal = createWithEqualityFn( - (set) => ({ - ...defaultValues, - open: (values) => set({ keyData: values, visible: true }), - close: () => set({ visible: false }), - reset: () => set(defaultValues), - }), - Object.is, -); - -type StoreValues = { - visible: boolean; - keyData?: { - id: number; - type: 'ssh' | 'gpg' | 'yubikey'; - name: string; - username: string; - }; -}; - -type StoreMethods = { - open: (init: StoreValues['keyData']) => void; - close: () => void; - reset: () => void; -}; - -type Store = StoreValues & StoreMethods; diff --git a/web/src/pages/users/UserProfile/UserAuthenticationKeys/UserAuthenticationKeys.tsx b/web/src/pages/users/UserProfile/UserAuthenticationKeys/UserAuthenticationKeys.tsx deleted file mode 100644 index 4afa782e5..000000000 --- a/web/src/pages/users/UserProfile/UserAuthenticationKeys/UserAuthenticationKeys.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import './style.scss'; - -import { useI18nContext } from '../../../../i18n/i18n-react'; -import { isPresent } from '../../../../shared/defguard-ui/utils/isPresent'; -import { useUserProfileStore } from '../../../../shared/hooks/store/useUserProfileStore'; -import { AddComponentBox } from '../../shared/components/AddComponentBox/AddComponentBox'; -import { useAddAuthorizationKeyModal } from '../../shared/modals/AddAuthenticationKeyModal/useAddAuthorizationKeyModal'; -import { RenameAuthenticationKeyModal } from '../../shared/modals/RenameAuthenticationKeyModal/RenameAuthenticationKeyModal'; -import { AuthenticationKeyList } from './AuthenticationKeyList/AuthenticationKeyList'; -import { DeleteAuthenticationKeyModal } from './DeleteAuthenticationKeyModal/DeleteAuthenticationKeyModal'; - -export const UserAuthenticationKeys = () => { - const { LL } = useI18nContext(); - const user = useUserProfileStore((state) => state.userProfile?.user); - const openAddAuthenticationKeyModal = useAddAuthorizationKeyModal((s) => s.open); - - return ( -
-
-

{LL.userPage.authenticationKeys.header()}

-
- - {isPresent(user) && ( - { - if (user) { - openAddAuthenticationKeyModal({ - user, - selectedMode: 'ssh', - }); - } - }} - /> - )} - - -
- ); -}; diff --git a/web/src/pages/users/UserProfile/UserAuthenticationKeys/style.scss b/web/src/pages/users/UserProfile/UserAuthenticationKeys/style.scss deleted file mode 100644 index 8f9e93139..000000000 --- a/web/src/pages/users/UserProfile/UserAuthenticationKeys/style.scss +++ /dev/null @@ -1,11 +0,0 @@ -@use '@scssutils' as *; - -#user-authentication-keys { - .add-component { - height: 80px; - } - - .authentication-key-list { - padding-bottom: 15px; - } -} diff --git a/web/src/pages/users/UserProfile/UserDevices/DeviceCard/DeviceCard.tsx b/web/src/pages/users/UserProfile/UserDevices/DeviceCard/DeviceCard.tsx deleted file mode 100644 index 637755637..000000000 --- a/web/src/pages/users/UserProfile/UserDevices/DeviceCard/DeviceCard.tsx +++ /dev/null @@ -1,343 +0,0 @@ -import './style.scss'; - -import classNames from 'classnames'; -import clsx from 'clsx'; -import dayjs from 'dayjs'; -import { isUndefined, orderBy } from 'lodash-es'; -import type { TargetAndTransition } from 'motion/react'; -import { useMemo, useState } from 'react'; -import { useI18nContext } from '../../../../../i18n/i18n-react'; -import { ListCellTags } from '../../../../../shared/components/Layout/ListCellTags/ListCellTags'; -import IconClip from '../../../../../shared/components/svg/IconClip'; -import SvgIconCollapse from '../../../../../shared/components/svg/IconCollapse'; -import SvgIconCopy from '../../../../../shared/components/svg/IconCopy'; -import SvgIconExpand from '../../../../../shared/components/svg/IconExpand'; -import { ColorsRGB } from '../../../../../shared/constants'; -import { Badge } from '../../../../../shared/defguard-ui/components/Layout/Badge/Badge'; -import { Card } from '../../../../../shared/defguard-ui/components/Layout/Card/Card'; -import { DeviceAvatar } from '../../../../../shared/defguard-ui/components/Layout/DeviceAvatar/DeviceAvatar'; -import { EditButton } from '../../../../../shared/defguard-ui/components/Layout/EditButton/EditButton'; -import { EditButtonOption } from '../../../../../shared/defguard-ui/components/Layout/EditButton/EditButtonOption'; -import { EditButtonOptionStyleVariant } from '../../../../../shared/defguard-ui/components/Layout/EditButton/types'; -import { Label } from '../../../../../shared/defguard-ui/components/Layout/Label/Label'; -import { LimitedText } from '../../../../../shared/defguard-ui/components/Layout/LimitedText/LimitedText'; -import { ListCellText } from '../../../../../shared/defguard-ui/components/Layout/ListCellText/ListCellText'; -import { NoData } from '../../../../../shared/defguard-ui/components/Layout/NoData/NoData'; -import { useAppStore } from '../../../../../shared/hooks/store/useAppStore'; -import { useUserProfileStore } from '../../../../../shared/hooks/store/useUserProfileStore'; -import { useClipboard } from '../../../../../shared/hooks/useClipboard'; -import type { Device, DeviceNetworkInfo } from '../../../../../shared/types'; -import { sortByDate } from '../../../../../shared/utils/sortByDate'; -import type { ListCellTag } from '../../../../acl/AclIndexPage/components/shared/types'; -import { useDeleteDeviceModal } from '../hooks/useDeleteDeviceModal'; -import { useDeviceConfigModal } from '../hooks/useDeviceConfigModal'; -import { useEditDeviceModal } from '../hooks/useEditDeviceModal'; - -const dateFormat = 'DD.MM.YYYY | HH:mm'; - -const formatDate = (date: string): string => { - return dayjs.utc(date).local().format(dateFormat); -}; - -interface Props { - device: Device; - biometricEnabled: boolean; - modifiable: boolean; -} - -export const DeviceCard = ({ device, modifiable, biometricEnabled }: Props) => { - const [hovered, setHovered] = useState(false); - const [expanded, setExpanded] = useState(false); - const { LL } = useI18nContext(); - const user = useUserProfileStore((state) => state.userProfile); - const setDeleteDeviceModal = useDeleteDeviceModal((state) => state.setState); - const setEditDeviceModal = useEditDeviceModal((state) => state.setState); - const openDeviceConfigModal = useDeviceConfigModal((state) => state.open); - const enterpriseSettings = useAppStore((state) => state.enterprise_settings); - const { writeToClipboard } = useClipboard(); - - const cn = useMemo( - () => - classNames('device-card', { - expanded, - }), - [expanded], - ); - - const getContainerAnimate = useMemo((): TargetAndTransition => { - const res: TargetAndTransition = { - borderColor: ColorsRGB.White, - }; - if (expanded || hovered) { - res.borderColor = ColorsRGB.GrayBorder; - } - return res; - }, [expanded, hovered]); - - // first, order by last_connected_at then if not preset, by network_id - const orderedLocations = useMemo((): DeviceNetworkInfo[] => { - const connected = device.networks.filter( - (network) => !isUndefined(network.last_connected_at), - ); - - const neverConnected = device.networks.filter((network) => - isUndefined(network.last_connected_at), - ); - - const connectedSorted = sortByDate( - connected, - (n) => n.last_connected_at as string, - true, - ); - const neverConnectedSorted = orderBy( - neverConnected, - (network) => network.network_name.toLowerCase(), - ['asc'], - ); - - return [...connectedSorted, ...neverConnectedSorted]; - }, [device.networks]); - - const latestLocation = orderedLocations.length ? orderedLocations[0] : undefined; - - if (!user) return null; - - return ( - setHovered(true)} - onMouseOut={() => setHovered(false)} - > -
-
- - {biometricEnabled && } - -
-
-
- - {latestLocation?.last_connected_ip && ( - { - if (latestLocation.last_connected_ip) { - void writeToClipboard(latestLocation.last_connected_ip); - } - }} - > - - - } - /> - )} - {!latestLocation?.last_connected_ip && ( - - )} -
-
- - {latestLocation?.last_connected_at && ( - { - if (latestLocation.network_name) { - void writeToClipboard(latestLocation.network_name); - } - }} - > - - - } - /> - )} - {!latestLocation?.last_connected_at && ( - - )} -
-
- - {latestLocation?.last_connected_at && ( -

{formatDate(latestLocation.last_connected_at)}

- )} - {!latestLocation?.last_connected_at && ( - - )} -
-
-
-
- {orderedLocations.map((n) => ( - - ))} -
-
- - { - setEditDeviceModal({ - visible: true, - device: device, - }); - }} - /> - {!enterpriseSettings?.only_client_activation && ( - { - openDeviceConfigModal({ - deviceName: device.name, - publicKey: device.wireguard_pubkey, - deviceId: device.id, - userId: user.user.id, - networks: device.networks.map((n) => ({ - networkId: n.network_id, - networkName: n.network_name, - })), - }); - }} - /> - )} - - setDeleteDeviceModal({ - visible: true, - device: device, - }) - } - /> - - setExpanded((state) => !state)} - /> -
-
- ); -}; - -type DeviceLocationProps = { - network_info: DeviceNetworkInfo; -}; - -const DeviceLocation = ({ - network_info: { - network_id, - network_name, - network_gateway_ip, - last_connected_ip, - last_connected_at, - device_wireguard_ips, - }, -}: DeviceLocationProps) => { - const { LL } = useI18nContext(); - const { writeToClipboard } = useClipboard(); - const ipsTags = useMemo( - (): ListCellTag[] => - device_wireguard_ips.map((ip) => ({ - key: ip, - label: ip, - displayAsTag: false, - })), - [device_wireguard_ips], - ); - return ( -
-
- -
- - {!isUndefined(network_gateway_ip) && } -
-
-
-
- - {last_connected_ip && ( - { - void writeToClipboard(last_connected_ip); - }} - > - - - } - /> - )} - {!last_connected_ip && ( - - )} -
-
- - {last_connected_at && ( -

{formatDate(last_connected_at)}

- )} - {!last_connected_at && ( - - )} -
-
- - -
-
-
- ); -}; - -type ExpandButtonProps = { - expanded: boolean; - onClick: () => void; -}; - -const ExpandButton = ({ expanded, onClick }: ExpandButtonProps) => { - return ( - - ); -}; - -const IconBiometry = () => { - return ( - - - - ); -}; diff --git a/web/src/pages/users/UserProfile/UserDevices/DeviceCard/style.scss b/web/src/pages/users/UserProfile/UserDevices/DeviceCard/style.scss deleted file mode 100644 index b30a20f76..000000000 --- a/web/src/pages/users/UserProfile/UserDevices/DeviceCard/style.scss +++ /dev/null @@ -1,190 +0,0 @@ -@use '@scssutils' as *; - -.card { - &.device-card { - overflow: hidden; - display: block; - position: relative; - display: grid; - grid-template-rows: auto 0; - grid-template-columns: 1fr; - grid-template-areas: - 'main' - 'locations'; - border: 1px solid var(--white); - - h3 { - @include small-header; - user-select: none; - } - - header { - display: grid; - align-items: center; - justify-items: start; - column-gap: 10px; - margin-bottom: 18px; - max-width: 100%; - } - - .limited { - max-width: 120px; - } - - .list-cell-text { - display: flex; - flex-flow: row; - align-items: center; - justify-content: flex-start; - - h3 { - display: inline-block; - } - } - - .main-info { - & > header { - grid-template-rows: 40px; - grid-template-columns: 40px 1fr 55px; - max-width: 100%; - overflow: hidden; - - &.biometry { - grid-template-columns: 40px 12px 1fr 55px; - } - - .biometry-icon { - width: 12px; - height: 12px; - } - - & > .avatar-icon { - grid-row: 1; - grid-column: 1 / 2; - height: 100%; - width: 100%; - - svg { - width: 30px; - height: 30px; - } - } - - & > h3 { - grid-row: 1; - grid-column: 2 / 3; - } - } - } - - .location { - max-width: 100%; - overflow: hidden; - - & > header { - grid-template-rows: 40px; - grid-template-columns: 22px 1fr; - - & > svg { - grid-row: 1; - grid-column: 1 / 2; - } - - & > .info-wrapper { - grid-row: 1; - grid-column: 2 / 3; - display: grid; - grid-template-rows: 40px; - grid-template-columns: auto 83px; - column-gap: 10px; - align-items: center; - justify-items: start; - } - } - } - - .main-info, - .location { - box-sizing: border-box; - padding: 20px 25px; - } - - & > .main-info { - grid-area: main; - } - - .section-content { - display: flex; - flex-flow: row nowrap; - justify-content: space-between; - align-items: flex-start; - - label { - display: block; - margin-bottom: 8px; - } - - p { - @include typography-legacy(15px, 18px, medium); - - &.no-data { - color: var(--text-main); - font-size: 11px; - text-align: left; - } - } - } - - & > .locations { - grid-area: locations; - display: grid; - grid-template-rows: auto; - grid-template-columns: 1fr; - grid-auto-flow: row; - - & > .location { - display: block; - box-sizing: border-box; - padding: 19px 24px; - border: 1px solid transparent; - border-top-color: var(--gray-lighter); - - &:last-child { - border-bottom-right-radius: 15px; - border-bottom-left-radius: 15px; - } - } - } - - & > .card-controls { - display: flex; - flex-flow: row nowrap; - position: absolute; - top: 10px; - right: 15px; - - .device-card-expand { - border: 0 solid transparent; - background-color: transparent; - cursor: pointer; - - svg { - width: 22px !important; - height: 22px; - } - } - } - - &.expanded { - grid-template-rows: auto auto; - } - } -} - -.copy { - background-color: transparent; - border: 0 solid transparent; - cursor: pointer; - display: flex; - align-items: center; -} diff --git a/web/src/pages/users/UserProfile/UserDevices/UserDevices.tsx b/web/src/pages/users/UserProfile/UserDevices/UserDevices.tsx deleted file mode 100644 index c5bdeb41d..000000000 --- a/web/src/pages/users/UserProfile/UserDevices/UserDevices.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import './style.scss'; - -import { sortBy } from 'lodash-es'; -import { useMemo } from 'react'; -import Skeleton from 'react-loading-skeleton'; -import { useNavigate } from 'react-router'; -import { useI18nContext } from '../../../../i18n/i18n-react'; -import { useAppStore } from '../../../../shared/hooks/store/useAppStore'; -import { useAuthStore } from '../../../../shared/hooks/store/useAuthStore'; -import { useUserProfileStore } from '../../../../shared/hooks/store/useUserProfileStore'; -import { useAddDevicePageStore } from '../../../addDevice/hooks/useAddDevicePageStore'; -import { AddComponentBox } from '../../shared/components/AddComponentBox/AddComponentBox'; -import { DeviceCard } from './DeviceCard/DeviceCard'; -import { DeleteUserDeviceModal } from './modals/DeleteUserDeviceModal/DeleteUserDeviceModal'; -import { DeviceConfigModal } from './modals/DeviceConfigModal/DeviceConfigModal'; -import { EditUserDeviceModal } from './modals/EditUserDeviceModal/EditUserDeviceModal'; - -export const UserDevices = () => { - const navigate = useNavigate(); - const appInfo = useAppStore((state) => state.appInfo); - const settings = useAppStore((state) => state.enterprise_settings); - const { LL } = useI18nContext(); - const userProfile = useUserProfileStore((state) => state.userProfile); - const initAddDevice = useAddDevicePageStore((state) => state.init); - const isAdmin = useAuthStore((state) => state.user?.is_admin); - const canManageDevices = !!( - userProfile && - (!settings?.admin_device_management || isAdmin) - ); - const sortedDevices = useMemo(() => { - if (userProfile?.devices) { - return sortBy(userProfile.devices, (device) => device.name.toLowerCase()); - } - return []; - }, [userProfile?.devices]); - - return ( -
-
-

{LL.userPage.devices.header()}

-
- {!userProfile && ( -
- - - -
- )} - {userProfile && ( - <> - {sortedDevices.length > 0 && ( -
- {sortedDevices.map((device) => ( - - ))} -
- )} - {userProfile && ( - { - initAddDevice({ - username: userProfile.user.username, - id: userProfile.user.id, - reservedDevices: userProfile.devices.map((d) => d.name), - email: userProfile.user.email, - originRoutePath: window.location.pathname, - }); - navigate('/add-device', { replace: true }); - }} - /> - )} - - )} - - - -
- ); -}; diff --git a/web/src/pages/users/UserProfile/UserDevices/hooks/useDeleteDeviceModal.ts b/web/src/pages/users/UserProfile/UserDevices/hooks/useDeleteDeviceModal.ts deleted file mode 100644 index 5b892d0aa..000000000 --- a/web/src/pages/users/UserProfile/UserDevices/hooks/useDeleteDeviceModal.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { createWithEqualityFn } from 'zustand/traditional'; - -import type { Device } from '../../../../../shared/types'; - -const defaultValues: StoreValues = { - visible: false, - device: undefined, -}; - -export const useDeleteDeviceModal = createWithEqualityFn( - (set) => ({ - ...defaultValues, - setState: (values) => set((old) => ({ ...old, ...values })), - open: (values) => set({ ...defaultValues, ...values }), - close: () => set({ visible: false }), - }), - Object.is, -); - -type StoreValues = { - visible: boolean; - device?: Device; -}; - -type StoreMethods = { - setState: (values: Partial) => void; - open: (values: Partial) => void; - close: () => void; -}; - -type Store = StoreValues & StoreMethods; diff --git a/web/src/pages/users/UserProfile/UserDevices/hooks/useDeviceConfigModal.tsx b/web/src/pages/users/UserProfile/UserDevices/hooks/useDeviceConfigModal.tsx deleted file mode 100644 index 8038753bc..000000000 --- a/web/src/pages/users/UserProfile/UserDevices/hooks/useDeviceConfigModal.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { createWithEqualityFn } from 'zustand/traditional'; - -import type { DeviceConfigsCardNetworkInfo } from '../../../../../shared/components/network/DeviceConfigsCard/types'; - -const defaultValues: StoreValues = { - isOpen: false, - userId: undefined, - publicKey: undefined, - deviceId: undefined, - networks: undefined, - deviceName: undefined, -}; - -export const useDeviceConfigModal = createWithEqualityFn( - (set) => ({ - ...defaultValues, - open: (values) => set({ ...values, isOpen: true }), - close: () => set({ isOpen: false }), - reset: () => set(defaultValues), - }), - Object.is, -); - -type Store = StoreValues & StoreMethods; - -type StoreValues = { - isOpen: boolean; - publicKey?: string; - userId?: number; - deviceId?: number; - networks?: DeviceConfigsCardNetworkInfo[]; - deviceName?: string; -}; - -type StoreMethods = { - open: (values: Partial) => void; - close: () => void; - reset: () => void; -}; diff --git a/web/src/pages/users/UserProfile/UserDevices/hooks/useEditDeviceModal.ts b/web/src/pages/users/UserProfile/UserDevices/hooks/useEditDeviceModal.ts deleted file mode 100644 index b6558d119..000000000 --- a/web/src/pages/users/UserProfile/UserDevices/hooks/useEditDeviceModal.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { createWithEqualityFn } from 'zustand/traditional'; - -import type { Device } from '../../../../../shared/types'; - -const defaultValues: StoreValues = { - visible: false, - device: undefined, -}; - -export const useEditDeviceModal = createWithEqualityFn( - (set) => ({ - ...defaultValues, - setState: (values) => set((old) => ({ ...old, ...values })), - open: (values) => set({ ...defaultValues, ...values }), - close: () => set({ visible: false }), - }), - Object.is, -); - -type StoreValues = { - visible: boolean; - device?: Device; -}; - -type StoreMethods = { - setState: (values: Partial) => void; - open: (values: Partial) => void; - close: () => void; -}; - -type Store = StoreValues & StoreMethods; diff --git a/web/src/pages/users/UserProfile/UserDevices/modals/DeleteUserDeviceModal/DeleteUserDeviceModal.tsx b/web/src/pages/users/UserProfile/UserDevices/modals/DeleteUserDeviceModal/DeleteUserDeviceModal.tsx deleted file mode 100644 index 5d6996522..000000000 --- a/web/src/pages/users/UserProfile/UserDevices/modals/DeleteUserDeviceModal/DeleteUserDeviceModal.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import type { AxiosError } from 'axios'; -import { shallow } from 'zustand/shallow'; - -import { useI18nContext } from '../../../../../../i18n/i18n-react'; -import { ConfirmModal } from '../../../../../../shared/defguard-ui/components/Layout/modals/ConfirmModal/ConfirmModal'; -import { ConfirmModalType } from '../../../../../../shared/defguard-ui/components/Layout/modals/ConfirmModal/types'; -import useApi from '../../../../../../shared/hooks/useApi'; -import { useToaster } from '../../../../../../shared/hooks/useToaster'; -import { MutationKeys } from '../../../../../../shared/mutations'; -import { QueryKeys } from '../../../../../../shared/queries'; -import { useDeleteDeviceModal } from '../../hooks/useDeleteDeviceModal'; - -export const DeleteUserDeviceModal = () => { - const { LL } = useI18nContext(); - const toaster = useToaster(); - const [device, visible] = useDeleteDeviceModal( - (state) => [state.device, state.visible], - shallow, - ); - const [setModalState, closeModal] = useDeleteDeviceModal( - (state) => [state.setState, state.close], - shallow, - ); - const { - device: { deleteDevice }, - } = useApi(); - const queryClient = useQueryClient(); - - const { mutate, isPending } = useMutation({ - mutationKey: [MutationKeys.DELETE_USER_DEVICE], - mutationFn: deleteDevice, - onSuccess: () => { - void queryClient.invalidateQueries({ - queryKey: [QueryKeys.FETCH_USER_PROFILE], - }); - toaster.success(LL.modals.deleteDevice.messages.success()); - closeModal(); - }, - onError: (err: AxiosError) => { - toaster.error(LL.messages.error()); - console.error(err); - }, - }); - - return ( - setModalState({ visible: visibility })} - onSubmit={() => { - if (device) { - mutate(device); - } - }} - /> - ); -}; diff --git a/web/src/pages/users/UserProfile/UserDevices/modals/DeviceConfigModal/DeviceConfigModal.tsx b/web/src/pages/users/UserProfile/UserDevices/modals/DeviceConfigModal/DeviceConfigModal.tsx deleted file mode 100644 index bdd45f6a9..000000000 --- a/web/src/pages/users/UserProfile/UserDevices/modals/DeviceConfigModal/DeviceConfigModal.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import './style.scss'; - -import { shallow } from 'zustand/shallow'; - -import { useI18nContext } from '../../../../../../i18n/i18n-react'; -import { DeviceConfigsCard } from '../../../../../../shared/components/network/DeviceConfigsCard/DeviceConfigsCard'; -import { ModalWithTitle } from '../../../../../../shared/defguard-ui/components/Layout/modals/ModalWithTitle/ModalWithTitle'; -import { useDeviceConfigModal } from '../../hooks/useDeviceConfigModal'; - -export const DeviceConfigModal = () => { - const isOpen = useDeviceConfigModal((state) => state.isOpen); - const [close, reset] = useDeviceConfigModal( - (state) => [state.close, state.reset], - shallow, - ); - const { LL } = useI18nContext(); - - return ( - close()} - afterClose={() => reset()} - > - - - ); -}; - -const ModalContent = () => { - const [networks, userId, deviceId, publicKey, deviceName] = useDeviceConfigModal( - (state) => [ - state.networks, - state.userId, - state.deviceId, - state.publicKey, - state.deviceName, - ], - shallow, - ); - - if (!networks || !userId || !deviceId || !publicKey || !deviceName) return null; - - return ( - - ); -}; diff --git a/web/src/pages/users/UserProfile/UserDevices/modals/DeviceConfigModal/style.scss b/web/src/pages/users/UserProfile/UserDevices/modals/DeviceConfigModal/style.scss deleted file mode 100644 index 411392885..000000000 --- a/web/src/pages/users/UserProfile/UserDevices/modals/DeviceConfigModal/style.scss +++ /dev/null @@ -1,7 +0,0 @@ -#device-config-modal { - width: 100%; - max-width: 700px; - & > .content { - padding: 25px 25px 40px; - } -} diff --git a/web/src/pages/users/UserProfile/UserDevices/modals/EditUserDeviceModal/EditUserDeviceModal.tsx b/web/src/pages/users/UserProfile/UserDevices/modals/EditUserDeviceModal/EditUserDeviceModal.tsx deleted file mode 100644 index d5c117cdc..000000000 --- a/web/src/pages/users/UserProfile/UserDevices/modals/EditUserDeviceModal/EditUserDeviceModal.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { useI18nContext } from '../../../../../../i18n/i18n-react'; -import { ModalWithTitle } from '../../../../../../shared/defguard-ui/components/Layout/modals/ModalWithTitle/ModalWithTitle'; -import { useEditDeviceModal } from '../../hooks/useEditDeviceModal'; -import { EditUserDeviceForm } from './UserDeviceEditForm'; - -export const EditUserDeviceModal = () => { - const { LL } = useI18nContext(); - const visible = useEditDeviceModal((state) => state.visible); - const closeModal = useEditDeviceModal((state) => state.close); - - return ( - closeModal()} - backdrop - > - - - ); -}; diff --git a/web/src/pages/users/UserProfile/UserDevices/modals/EditUserDeviceModal/UserDeviceEditForm.tsx b/web/src/pages/users/UserProfile/UserDevices/modals/EditUserDeviceModal/UserDeviceEditForm.tsx deleted file mode 100644 index b251ce612..000000000 --- a/web/src/pages/users/UserProfile/UserDevices/modals/EditUserDeviceModal/UserDeviceEditForm.tsx +++ /dev/null @@ -1,124 +0,0 @@ -import { zodResolver } from '@hookform/resolvers/zod'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { useMemo } from 'react'; -import { type SubmitHandler, useForm } from 'react-hook-form'; -import { z } from 'zod'; - -import { useI18nContext } from '../../../../../../i18n/i18n-react'; -import { FormInput } from '../../../../../../shared/defguard-ui/components/Form/FormInput/FormInput'; -import { Button } from '../../../../../../shared/defguard-ui/components/Layout/Button/Button'; -import { - ButtonSize, - ButtonStyleVariant, -} from '../../../../../../shared/defguard-ui/components/Layout/Button/types'; -import useApi from '../../../../../../shared/hooks/useApi'; -import { useToaster } from '../../../../../../shared/hooks/useToaster'; -import { MutationKeys } from '../../../../../../shared/mutations'; -import { - patternNoSpecialChars, - patternValidWireguardKey, -} from '../../../../../../shared/patterns'; -import { QueryKeys } from '../../../../../../shared/queries'; -import { useEditDeviceModal } from '../../hooks/useEditDeviceModal'; - -interface Inputs { - name: string; - wireguard_pubkey: string; -} - -const defaultFormValues: Inputs = { - name: '', - wireguard_pubkey: '', -}; - -export const EditUserDeviceForm = () => { - const device = useEditDeviceModal((state) => state.device); - const closeModal = useEditDeviceModal((state) => state.close); - const { LL } = useI18nContext(); - - const zodSchema = useMemo( - () => - z.object({ - name: z - .string() - .trim() - .min(4, LL.form.error.minimumLength()) - .regex(patternNoSpecialChars, LL.form.error.noSpecialChars()), - wireguard_pubkey: z - .string() - .trim() - .min(44, LL.form.error.invalidKey()) - .max(44, LL.form.error.invalidKey()) - .regex(patternValidWireguardKey, LL.form.error.invalidKey()), - }), - [LL.form.error], - ); - - const { control, handleSubmit } = useForm({ - resolver: zodResolver(zodSchema), - defaultValues: { - name: device?.name ?? defaultFormValues.name, - wireguard_pubkey: device?.wireguard_pubkey ?? defaultFormValues.wireguard_pubkey, - }, - mode: 'all', - }); - - const { - device: { editDevice }, - } = useApi(); - - const toaster = useToaster(); - const queryClient = useQueryClient(); - - const { isPending: editDeviceLoading, mutate } = useMutation({ - mutationKey: [MutationKeys.EDIT_USER_DEVICE], - mutationFn: editDevice, - onSuccess: () => { - toaster.success(LL.modals.editDevice.messages.success()); - void queryClient.invalidateQueries({ - queryKey: [QueryKeys.FETCH_USER_PROFILE], - }); - closeModal(); - }, - onError: (err) => { - toaster.error(LL.messages.error()); - console.error(err); - }, - }); - - const onSubmitSuccess: SubmitHandler = (values) => { - if (device) { - mutate({ ...device, ...values }); - } - }; - - return ( -
- - -
-
- - ); -}; diff --git a/web/src/pages/users/UserProfile/UserDevices/style.scss b/web/src/pages/users/UserProfile/UserDevices/style.scss deleted file mode 100644 index be557a3f9..000000000 --- a/web/src/pages/users/UserProfile/UserDevices/style.scss +++ /dev/null @@ -1,51 +0,0 @@ -#user-devices { - height: auto; - width: 100%; - min-width: 100%; - - & > .skeletons { - width: 100%; - display: flex; - flex-flow: column; - row-gap: 20px; - .react-loading-skeleton { - height: 70px; - } - } - - & > header { - margin-bottom: 2rem; - - & > h2 { - @include card-header; - } - } - - & > .devices { - margin-bottom: 1rem; - display: flex; - flex-flow: column nowrap; - row-gap: 1rem; - - @include media-breakpoint-up(md) { - row-gap: 1.5rem; - margin-bottom: 1.5rem; - } - - & > .device-card { - width: 100%; - } - } - - & > .add-component { - height: 80px; - - &:not(:last-of-type) { - margin-bottom: 1rem; - - @include media-breakpoint-up(lg) { - margin-bottom: 1.5rem; - } - } - } -} diff --git a/web/src/pages/users/UserProfile/UserProfile.tsx b/web/src/pages/users/UserProfile/UserProfile.tsx deleted file mode 100644 index ea0ef1c16..000000000 --- a/web/src/pages/users/UserProfile/UserProfile.tsx +++ /dev/null @@ -1,211 +0,0 @@ -import './style.scss'; - -import { useQuery } from '@tanstack/react-query'; -import classNames from 'classnames'; -import { isUndefined } from 'lodash-es'; -import { useEffect, useMemo } from 'react'; -import { useParams } from 'react-router'; -import { useBreakpoint } from 'use-breakpoint'; - -import { useI18nContext } from '../../../i18n/i18n-react'; -import IconCheckmarkWhite from '../../../shared/components/svg/IconCheckmarkWhite'; -import IconEdit from '../../../shared/components/svg/IconEdit'; -import { deviceBreakpoints } from '../../../shared/constants'; -import { Button } from '../../../shared/defguard-ui/components/Layout/Button/Button'; -import { - ButtonSize, - ButtonStyleVariant, -} from '../../../shared/defguard-ui/components/Layout/Button/types'; -import { EditButton } from '../../../shared/defguard-ui/components/Layout/EditButton/EditButton'; -import { EditButtonOption } from '../../../shared/defguard-ui/components/Layout/EditButton/EditButtonOption'; -import { EditButtonOptionStyleVariant } from '../../../shared/defguard-ui/components/Layout/EditButton/types'; -import { useAppStore } from '../../../shared/hooks/store/useAppStore'; -import { useAuthStore } from '../../../shared/hooks/store/useAuthStore'; -import { useModalStore } from '../../../shared/hooks/store/useModalStore'; -import { useUserProfileStore } from '../../../shared/hooks/store/useUserProfileStore'; -import useApi from '../../../shared/hooks/useApi'; -import { useToaster } from '../../../shared/hooks/useToaster'; -import { QueryKeys } from '../../../shared/queries'; -import { ProfileDetails } from './ProfileDetails/ProfileDetails'; -import { UserApiTokens } from './UserApiTokens/UserApiTokens'; -import { UserAuthenticationKeys } from './UserAuthenticationKeys/UserAuthenticationKeys'; -import { UserAuthInfo } from './UserAuthInfo/UserAuthInfo'; -import { UserDevices } from './UserDevices/UserDevices'; - -export const UserProfile = () => { - const toaster = useToaster(); - const { LL } = useI18nContext(); - const { breakpoint } = useBreakpoint(deviceBreakpoints); - const { username: paramsUsername } = useParams(); - const currentUser = useAuthStore((state) => state.user); - const editMode = useUserProfileStore((state) => state.editMode); - const setUserProfileState = useUserProfileStore((state) => state.setState); - const resetUserProfileState = useUserProfileStore((state) => state.reset); - const { - user: { getUser }, - } = useApi(); - - const enterpriseEnabled = useAppStore((s) => s.appInfo?.license_info.enterprise); - const showApiTokens = enterpriseEnabled && currentUser?.is_admin; - - const username = useMemo(() => { - if (paramsUsername) { - return paramsUsername; - } else { - if (currentUser?.username) { - return currentUser.username; - } - } - throw Error('No username found.'); - }, [currentUser?.username, paramsUsername]); - - const { data: userProfileData, error: fetchProfileError } = useQuery({ - queryKey: [QueryKeys.FETCH_USER_PROFILE, username], - queryFn: () => getUser(username), - refetchOnWindowFocus: true, - refetchOnMount: true, - placeholderData: (pervious) => pervious, - enabled: !isUndefined(username), - }); - - useEffect(() => { - if (userProfileData) { - setUserProfileState({ userProfile: userProfileData }); - } - }, [setUserProfileState, userProfileData]); - - useEffect(() => { - if (fetchProfileError) { - toaster.error(LL.userPage.messages.failedToFetchUserData()); - console.error(fetchProfileError); - } - }, [LL.userPage.messages, fetchProfileError, toaster]); - - // biome-ignore lint/correctness/useExhaustiveDependencies: migration, checkMeLater - useEffect(() => { - if (currentUser?.username === username) { - setUserProfileState({ isMe: true }); - } else { - setUserProfileState({ isMe: false }); - } - return () => { - resetUserProfileState(); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - return ( -
-
- {breakpoint === 'desktop' && ( -

{editMode ? LL.userPage.title.edit() : LL.userPage.title.view()}

- )} -
- {editMode ? : } -
-
-
-
- - -
-
- -
-
- -
- {showApiTokens && ( -
- -
- )} -
-
- ); -}; - -const ViewModeControls = () => { - const setUserProfileState = useUserProfileStore((state) => state.setState); - const { breakpoint } = useBreakpoint(deviceBreakpoints); - const { LL } = useI18nContext(); - return ( -
-
- ); -}; - -const EditModeControls = () => { - const { LL } = useI18nContext(); - const { breakpoint } = useBreakpoint(deviceBreakpoints); - const userProfile = useUserProfileStore((state) => state.userProfile); - const isAdmin = useAuthStore((state) => state.user?.is_admin); - const isMe = useUserProfileStore((state) => state.isMe); - const setUserProfileState = useUserProfileStore((state) => state.setState); - const setDeleteUserModalState = useModalStore((state) => state.setDeleteUserModal); - const loading = useUserProfileStore((state) => state.loading); - - const submitSubject = useUserProfileStore((state) => state.submitSubject); - - const handleDeleteUser = () => { - if (userProfile) { - setDeleteUserModalState({ visible: true, user: userProfile.user }); - } - }; - - return ( - <> - {isAdmin && !isMe && breakpoint === 'desktop' ? ( -
-
- ) : null} -
- {breakpoint !== 'desktop' && isAdmin && ( - - - - )} -
- - ); -}; diff --git a/web/src/pages/users/UserProfile/style.scss b/web/src/pages/users/UserProfile/style.scss deleted file mode 100644 index ea7567d85..000000000 --- a/web/src/pages/users/UserProfile/style.scss +++ /dev/null @@ -1,214 +0,0 @@ -#user-profile-v2 { - box-sizing: border-box; - min-height: 100%; - max-height: 100%; - overflow-x: hidden; - overflow-y: auto; - position: relative; - padding: 3rem 1.5rem; - - @include media-breakpoint-up(lg) { - padding: 39px 60px; - } - - h1, - h2, - h3 { - user-select: none; - } - - & > header { - display: block; - width: 100%; - margin-bottom: 2rem; - - @include media-breakpoint-up(lg) { - display: inline-grid; - grid-template-rows: 1fr; - grid-template-columns: auto 1fr; - grid-column-gap: 14px; - margin-bottom: 40px; - } - - &:not(.edit) { - @include media-breakpoint-down(lg) { - position: absolute; - margin: 0; - width: calc(100% - 3rem); - } - } - - & > h1 { - @include page-header; - - grid-row: 1; - grid-column: 1; - user-select: none; - } - - & > .controls { - @include media-breakpoint-down(lg) { - display: block; - width: 100%; - box-sizing: border-box; - padding: 0 0.5rem; - - & > .right { - display: flex; - flex-flow: row; - width: 100%; - - & > .btn { - margin-left: auto; - } - } - - &.edit { - & > .right { - display: inline-grid; - grid-template-columns: 1fr 1fr 40px; - grid-template-rows: 1fr; - column-gap: 1rem; - - :nth-child(1) { - grid-column: 3; - grid-row: 1; - } - - :nth-child(2) { - grid-column: 2; - grid-row: 1; - } - - :nth-child(3) { - grid-column: 1; - grid-row: 1; - } - - & > .btn { - width: 100%; - } - } - } - } - @include media-breakpoint-up(lg) { - grid-row: 1; - grid-column: 2; - width: 100%; - display: flex; - flex-flow: row nowrap; - align-items: center; - align-content: flex-start; - justify-content: flex-start; - - & > .left, - & > .right { - display: flex; - flex-flow: row nowrap; - column-gap: 1rem; - } - - & > .right { - margin-left: auto; - } - } - - .btn { - height: 40px; - } - } - } - - & > .content { - display: grid; - row-gap: 3rem; - grid-template-columns: 1fr; - grid-template-rows: repeat(3, auto); - grid-template-areas: - 'wide-cards' - 'cards-2' - 'cards-1'; - - @include media-breakpoint-up(xl) { - gap: 35px; - grid-template-columns: 1fr 440px; - grid-template-rows: auto; - grid-template-areas: - 'wide-cards cards-2' - 'wide-cards cards-1'; - } - - @include media-breakpoint-up(xxl) { - grid-template-columns: 1fr 440px 384px; - grid-template-areas: 'wide-cards cards-1 cards-2'; - } - - & > div > section > header { - box-sizing: border-box; - padding: 0 0.5rem; - margin-bottom: 1.5rem; - - @include media-breakpoint-up(lg) { - margin-bottom: 2rem; - } - - & > h2 { - @include card-header; - - user-select: none; - } - } - - & > .wide-cards, - & > .cards-1, - & > .cards-2, - & > .cards-3 { - display: flex; - flex-flow: column; - justify-content: flex-start; - row-gap: 3rem; - @include media-breakpoint-up(lg) { - row-gap: 3.5rem; - } - } - - & > .wide-cards { - grid-area: wide-cards; - } - - & > .cards-1 { - grid-area: cards-1; - } - - & > .cards-2 { - grid-area: cards-2; - } - } - - & > .content-enterprise-enabled { - & > .cards-3 { - grid-area: cards-3; - } - - grid-template-areas: - 'wide-cards' - 'cards-1' - 'cards-2' - 'cards-3'; - - @include media-breakpoint-up(xl) { - grid-template-areas: - 'wide-cards cards-1' - 'wide-cards cards-2' - 'wide-cards cards-3'; - } - - @include media-breakpoint-up(xxl) { - grid-template-columns: 1fr 440px 384px; - grid-template-rows: auto 1fr; - grid-template-areas: - 'wide-cards cards-1 cards-2' - 'wide-cards cards-1 cards-3'; - } - } -} diff --git a/web/src/pages/users/UsersOverview/UsersOverview.tsx b/web/src/pages/users/UsersOverview/UsersOverview.tsx deleted file mode 100644 index 3d74557ca..000000000 --- a/web/src/pages/users/UsersOverview/UsersOverview.tsx +++ /dev/null @@ -1,251 +0,0 @@ -import './style.scss'; - -import { useQuery } from '@tanstack/react-query'; -import { orderBy } from 'lodash-es'; -import { motion } from 'motion/react'; -import { useCallback, useEffect, useMemo, useState } from 'react'; -import { useBreakpoint } from 'use-breakpoint'; -import { shallow } from 'zustand/shallow'; - -import { useI18nContext } from '../../../i18n/i18n-react'; -import SvgIconUserAddNew from '../../../shared/components/svg/IconUserAddNew'; -import { deviceBreakpoints } from '../../../shared/constants'; -import { Button } from '../../../shared/defguard-ui/components/Layout/Button/Button'; -import { - ButtonSize, - ButtonStyleVariant, -} from '../../../shared/defguard-ui/components/Layout/Button/types'; -import { LoaderSpinner } from '../../../shared/defguard-ui/components/Layout/LoaderSpinner/LoaderSpinner'; -import { Search } from '../../../shared/defguard-ui/components/Layout/Search/Search'; -import { Select } from '../../../shared/defguard-ui/components/Layout/Select/Select'; -import { - type SelectOption, - type SelectSelectedValue, - SelectSizeVariant, -} from '../../../shared/defguard-ui/components/Layout/Select/types'; -import useApi from '../../../shared/hooks/useApi'; -import { QueryKeys } from '../../../shared/queries'; -import type { User } from '../../../shared/types'; -import { DisableMfaModal } from '../shared/modals/DisableMfaModal/DisableMfaModal'; -import { UsersList } from './components/UsersList/UsersList'; -import { AddUserModal } from './modals/AddUserModal/AddUserModal'; -import { useAddUserModal } from './modals/AddUserModal/hooks/useAddUserModal'; -import { AssignGroupsModal } from './modals/AssignGroupsModal/AssignGroupsModal'; -import { useAssignGroupsModal } from './modals/AssignGroupsModal/store'; - -enum FilterOptions { - ALL = 'all', - ADMIN = 'admin', - USERS = 'users', -} - -export const UsersOverview = () => { - const { LL, locale } = useI18nContext(); - const { breakpoint } = useBreakpoint(deviceBreakpoints); - const [selectedUsers, setSelectedUsers] = useState([]); - const openGroupsAssign = useAssignGroupsModal((s) => s.open); - const successSubject = useAssignGroupsModal((s) => s.successSubject, shallow); - - // biome-ignore lint/correctness/useExhaustiveDependencies: migration, checkMeLater - const filterSelectOptions = useMemo(() => { - const res: SelectOption[] = [ - { - label: LL.usersOverview.filterLabels.all(), - value: FilterOptions.ALL, - key: 1, - }, - { - label: LL.usersOverview.filterLabels.admin(), - value: FilterOptions.ADMIN, - key: 2, - }, - { - label: LL.usersOverview.filterLabels.users(), - value: FilterOptions.USERS, - key: 3, - }, - ]; - return res; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [locale]); - - const renderSelectedFilter = useCallback( - (selected: FilterOptions): SelectSelectedValue => { - const option = filterSelectOptions.find((o) => o.value === selected); - if (!option) throw Error("Selected value doesn't exist"); - return { - key: option.key, - displayValue: option.label, - }; - }, - [filterSelectOptions], - ); - - const [selectedFilter, setSelectedFilter] = useState(FilterOptions.ALL); - - const { - user: { getUsers }, - } = useApi(); - - const { data: users, isLoading } = useQuery({ - queryKey: [QueryKeys.FETCH_USERS_LIST], - queryFn: getUsers, - }); - - const [usersSearchValue, setUsersSearchValue] = useState(''); - - const openAddUserModal = useAddUserModal((state) => state.open); - - const filteredUsers = useMemo(() => { - if (!users || (users && !users.length)) { - return []; - } - let searched: User[] = []; - if (users) { - searched = users.filter( - (user) => - user.username - .toLocaleLowerCase() - .includes(usersSearchValue.toLocaleLowerCase()) || - user.first_name - ?.toLocaleLowerCase() - .includes(usersSearchValue.toLocaleLowerCase()) || - user.last_name - ?.toLocaleLowerCase() - .includes(usersSearchValue.toLocaleLowerCase()), - ); - } - if (searched.length) { - searched = orderBy(searched, ['username'], ['asc']); - } - switch (selectedFilter) { - case FilterOptions.ALL: - break; - case FilterOptions.ADMIN: - searched = searched.filter((user) => user.is_admin); - break; - case FilterOptions.USERS: - searched = searched.filter((user) => !user.is_admin); - break; - } - return searched; - }, [selectedFilter, users, usersSearchValue]); - - const handleUserSelect = useCallback( - (id: number) => { - if (selectedUsers.includes(id)) { - setSelectedUsers((selected) => selected.filter((i) => i !== id)); - } else { - setSelectedUsers((s) => [...s, id]); - } - }, - [selectedUsers], - ); - - const handleSelectAll = useCallback(() => { - if (users) { - if (users.length !== selectedUsers.length) { - setSelectedUsers(users.map((u) => u.id)); - } else { - setSelectedUsers([]); - } - } - }, [users, selectedUsers]); - - // biome-ignore lint/correctness/useExhaustiveDependencies: migration, checkMeLater - useEffect(() => { - if (breakpoint !== 'desktop' && selectedFilter !== FilterOptions.ALL) { - setSelectedFilter(FilterOptions.ALL); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [breakpoint]); - - useEffect(() => { - const sub = successSubject.subscribe(() => { - setSelectedUsers([]); - }); - return () => { - sub?.unsubscribe(); - }; - }, [successSubject]); - - return ( -
- {breakpoint === 'desktop' && ( -
-

{LL.usersOverview.pageTitle()}

- setUsersSearchValue(value)} - /> -
- )} - -
- {LL.usersOverview.usersCount()} -
- {users?.length ? users.length : 0} -
-
-
- {selectedUsers.length > 0 && ( -
-
- {isLoading || - (isUndefined(webhooks) && ( -
- -
- ))} - {!isLoading && filteredWebhooks && filteredWebhooks.length === 0 && ( - - )} - {!isLoading && filteredWebhooks && filteredWebhooks.length > 0 && ( - - )} - - { - if (!isUndefined(webhookToDelete)) { - deleteWebhookMutation(webhookToDelete.id); - } - }} - submitText={'Delete'} - loading={deleteWebhookIsLoading} - /> -
- ); -}; - -enum FilterOption { - ALL = 'all', - ENABLED = 'enabled', - DISABLED = 'disabled', -} diff --git a/web/src/pages/webhooks/modals/WebhookModal/WebhookForm.tsx b/web/src/pages/webhooks/modals/WebhookModal/WebhookForm.tsx deleted file mode 100644 index 49ffbebe2..000000000 --- a/web/src/pages/webhooks/modals/WebhookModal/WebhookForm.tsx +++ /dev/null @@ -1,217 +0,0 @@ -import { zodResolver } from '@hookform/resolvers/zod'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { isUndefined } from 'lodash-es'; -import { useMemo } from 'react'; -import { type SubmitHandler, useForm } from 'react-hook-form'; -import { z } from 'zod'; - -import { useI18nContext } from '../../../../i18n/i18n-react'; -import { FormCheckBox } from '../../../../shared/defguard-ui/components/Form/FormCheckBox/FormCheckBox'; -import { FormInput } from '../../../../shared/defguard-ui/components/Form/FormInput/FormInput'; -import { Button } from '../../../../shared/defguard-ui/components/Layout/Button/Button'; -import { - ButtonSize, - ButtonStyleVariant, -} from '../../../../shared/defguard-ui/components/Layout/Button/types'; -import { useModalStore } from '../../../../shared/hooks/store/useModalStore'; -import useApi from '../../../../shared/hooks/useApi'; -import { useToaster } from '../../../../shared/hooks/useToaster'; -import { MutationKeys } from '../../../../shared/mutations'; -import { QueryKeys } from '../../../../shared/queries'; - -export const WebhookForm = () => { - const { LL } = useI18nContext(); - const toaster = useToaster(); - const { - webhook: { addWebhook, editWebhook }, - } = useApi(); - const modalState = useModalStore((state) => state.webhookModal); - const setModalState = useModalStore((state) => state.setWebhookModal); - const editMode = useMemo(() => !isUndefined(modalState.webhook), [modalState.webhook]); - - const queryClient = useQueryClient(); - - const zodSchema = useMemo( - () => - z - .object({ - url: z.string().min(1, LL.modals.webhookModal.form.error.urlRequired()), - description: z - .string() - .min(1, LL.form.error.required()) - .min(4, LL.form.error.minimumLength()) - .max(65, LL.form.error.maximumLength()), - token: z - .string() - .min(1, LL.form.error.required()) - .min(3, LL.form.error.minimumLength()) - .max(250, LL.form.error.maximumLength()), - enabled: z.boolean(), - on_user_created: z.boolean(), - on_user_deleted: z.boolean(), - on_user_modified: z.boolean(), - on_hwkey_provision: z.boolean(), - }) - .superRefine((val, ctx) => { - if (val.enabled) { - if ( - !val.on_hwkey_provision && - !val.on_user_created && - !val.on_user_deleted && - !val.on_user_modified - ) { - ctx.addIssue({ - code: 'custom', - message: 'At least one event needs to be present', - }); - } - } - }), - [LL.form.error, LL.modals.webhookModal.form.error], - ); - - type FormFields = z.infer; - - const defaultFormState = useMemo((): FormFields => { - if (!isUndefined(modalState.webhook)) { - return modalState.webhook; - } - const defaultValues: FormFields = { - url: '', - description: '', - token: '', - enabled: true, - on_hwkey_provision: false, - on_user_created: false, - on_user_deleted: false, - on_user_modified: false, - }; - return defaultValues; - }, [modalState.webhook]); - - const { control, handleSubmit } = useForm({ - defaultValues: defaultFormState, - mode: 'all', - resolver: zodResolver(zodSchema), - }); - - const { mutate: addWebhookMutation, isPending: addWebhookIsLoading } = useMutation({ - mutationKey: [MutationKeys.EDIT_WEBHOOK], - mutationFn: addWebhook, - onSuccess: () => { - void queryClient.invalidateQueries({ - queryKey: [QueryKeys.FETCH_WEBHOOKS], - }); - toaster.success(LL.modals.webhookModal.form.messages.successAdd()); - setModalState({ visible: false, webhook: undefined }); - }, - onError: (err) => { - void queryClient.invalidateQueries({ - queryKey: [QueryKeys.FETCH_WEBHOOKS], - }); - toaster.error(LL.messages.error()); - setModalState({ visible: false, webhook: undefined }); - console.error(err); - }, - }); - - const { mutate: editWebhookMutation, isPending: editMutationIsLoading } = useMutation({ - mutationKey: [MutationKeys.EDIT_WEBHOOK], - mutationFn: editWebhook, - onSuccess: () => { - void queryClient.invalidateQueries({ - queryKey: [QueryKeys.FETCH_WEBHOOKS], - }); - toaster.success(LL.modals.webhookModal.form.messages.successModify()); - setModalState({ visible: false, webhook: undefined }); - }, - onError: (err) => { - void queryClient.invalidateQueries({ - queryKey: [QueryKeys.FETCH_WEBHOOKS], - }); - toaster.error(LL.messages.error()); - setModalState({ visible: false, webhook: undefined }); - console.error(err); - }, - }); - - const onValidSubmit: SubmitHandler = (values) => { - if (editMode) { - if (modalState.webhook) { - editWebhookMutation({ ...modalState.webhook, ...values }); - } - } else { - addWebhookMutation({ ...values, enabled: true }); - } - }; - - return ( -
- - - - -

{LL.modals.webhookModal.form.triggers()}

-
- - - - -
-
-
- - ); -}; diff --git a/web/src/pages/webhooks/modals/WebhookModal/WebhookModal.tsx b/web/src/pages/webhooks/modals/WebhookModal/WebhookModal.tsx deleted file mode 100644 index 9b7784828..000000000 --- a/web/src/pages/webhooks/modals/WebhookModal/WebhookModal.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import './style.scss'; - -import { isUndefined } from 'lodash-es'; -import { useMemo } from 'react'; - -import { useI18nContext } from '../../../../i18n/i18n-react'; -import { ModalWithTitle } from '../../../../shared/defguard-ui/components/Layout/modals/ModalWithTitle/ModalWithTitle'; -import { useModalStore } from '../../../../shared/hooks/store/useModalStore'; -import { WebhookForm } from './WebhookForm'; - -export const WebhookModal = () => { - const { LL } = useI18nContext(); - const modalState = useModalStore((state) => state.webhookModal); - - const getTitle = useMemo(() => { - if (!isUndefined(modalState.webhook)) { - return LL.modals.webhookModal.title.editWebhook(); - } - return LL.modals.webhookModal.title.addWebhook(); - }, [modalState.webhook, LL.modals.webhookModal.title]); - - const setModalState = useModalStore((state) => state.setWebhookModal); - - return ( - setModalState({ visible: v })} - id="webhook-modal" - backdrop - > - - - ); -}; diff --git a/web/src/pages/webhooks/modals/WebhookModal/style.scss b/web/src/pages/webhooks/modals/WebhookModal/style.scss deleted file mode 100644 index aac5e9ff4..000000000 --- a/web/src/pages/webhooks/modals/WebhookModal/style.scss +++ /dev/null @@ -1,32 +0,0 @@ -#webhook-modal { - & > .content { - & > form { - .form-checkbox { - padding-bottom: var(--spacing-s); - } - - & > h3 { - @include small-header; - - margin-bottom: 2rem; - } - - & > .events { - width: 100%; - display: grid; - grid-template-columns: 1fr 1fr; - grid-template-rows: repeat(2, 18px); - column-gap: 1rem; - row-gap: 1rem; - } - - & > .controls { - margin-top: 2.5rem; - - @include media-breakpoint-up(lg) { - margin-top: 6.5rem; - } - } - } - } -} diff --git a/web/src/pages/webhooks/style.scss b/web/src/pages/webhooks/style.scss deleted file mode 100644 index 0511e061b..000000000 --- a/web/src/pages/webhooks/style.scss +++ /dev/null @@ -1,311 +0,0 @@ -@mixin list-layout { - display: inline-grid; - grid-template-columns: 1fr 40px; - - @include media-breakpoint-up(lg) { - grid-template-columns: minmax(150px, 25%) 1fr 1fr 60px; - } - - @for $i from 1 through 4 { - & > :nth-child(#{$i}) { - grid-column: $i; - } - } - - & > * { - grid-row: 1; - } -} - -#webhooks-list-page { - & > .page-content { - position: relative; - overflow: hidden; - display: grid; - grid-template-columns: 1fr; - grid-template-rows: 40px 40px 1fr; - box-sizing: border-box; - padding-top: 1.5rem; - row-gap: 2rem; - - @include media-breakpoint-up(lg) { - row-gap: 0; - padding: 0; - align-self: stretch; - box-sizing: border-box; - user-select: none; - display: grid; - grid-template-columns: 1fr; - grid-template-rows: 147px 67px 1fr; - max-height: 100%; - overflow: hidden; - } - - header { - grid-column: 1; - grid-row: 1; - box-sizing: border-box; - width: 100%; - padding: 0 2rem; - - @include media-breakpoint-up(lg) { - display: flex; - flex-flow: row; - align-content: center; - align-items: center; - justify-content: flex-start; - width: 100%; - gap: 3rem; - padding: 4rem 6rem 4.6rem; - } - - h1 { - display: none; - - @include media-breakpoint-up(lg) { - display: block; - font-size: 4.1rem; - line-height: 6.1rem; - font-family: Poppins; - color: var(--text-main); - @include text-weight(semiBold); - } - } - - .search { - width: 100%; - height: 40px; - - @include media-breakpoint-up(lg) { - width: 400px; - } - } - } - - .actions { - grid-row: 2; - grid-column: 1; - width: 100%; - height: 100%; - display: flex; - flex-flow: row nowrap; - box-sizing: border-box; - padding: 0 2rem; - align-content: center; - align-items: center; - justify-content: flex-start; - - .select-container { - min-height: 40px; - } - - @include media-breakpoint-up(lg) { - height: auto; - padding: 0 6rem 2.7rem; - } - - .items-count { - display: flex; - flex-direction: row; - align-content: center; - align-items: center; - justify-content: flex-start; - width: auto; - height: auto; - gap: 1rem; - - span { - @include text-weight(semiBold); - @include poppins; - - color: var(--text-main); - - @include media-breakpoint-down(lg) { - font-size: 1.5rem; - line-height: 2.1rem; - text-transform: uppercase; - } - - @include media-breakpoint-up(lg) { - font-size: 2rem; - line-height: 3rem; - } - } - - .count { - display: flex; - flex-direction: row; - align-items: center; - align-content: center; - justify-content: center; - min-width: 30px; - box-sizing: border-box; - padding: 0 5px; - height: 30px; - background-color: var(--gray-light); - border-radius: 1rem; - - span { - @include text-weight(semiBold); - @include poppins; - - color: var(--white); - font-size: 1.2rem; - text-align: center; - line-height: 1.8rem; - } - } - } - - .controls { - display: flex; - flex-direction: row; - align-content: center; - align-items: center; - justify-content: center; - margin-left: auto; - - & > .select { - height: 40px; - } - - & > .add-item { - min-width: 110px; - width: auto; - - svg { - rect, - g { - fill: var(--white); - } - } - } - - @include media-breakpoint-down(lg) { - column-gap: 1rem; - - & > button { - width: 40px; - height: 40px; - - span { - display: none; - } - } - - & > .add-item { - min-width: 0; - padding: 0; - width: 40px; - height: 40px; - } - } - - @include media-breakpoint-up(lg) { - gap: 2rem; - - & > div { - width: 180px; - } - - .btn { - min-width: 110px; - } - } - } - } - - .list-loader, - .virtualized-list-container, - .no-data { - grid-column: 1; - grid-row: 3; - } - - .no-data { - width: 100%; - text-align: center; - margin-top: 20px; - } - - .list-loader { - width: 100%; - height: 100%; - display: flex; - flex-flow: column; - align-items: center; - justify-content: center; - align-content: center; - } - - .virtualized-list-container { - grid-template-columns: 1fr; - grid-template-rows: 1fr; - - @include media-breakpoint-up(lg) { - grid-template-rows: 28px 1fr; - } - - .headers { - display: none; - - @include media-breakpoint-up(lg) { - @include list-layout; - - :nth-child(4) { - justify-content: center; - } - } - } - - .scroll-container { - padding: 0; - margin-right: 5px; - grid-row: 1; - grid-column: 1; - padding-bottom: 1.5rem; - - @include media-breakpoint-up(lg) { - padding-bottom: 4rem; - grid-row: 2; - } - } - - .default-row { - @include list-layout; - - align-items: center; - grid-template-rows: 1fr; - height: 60px; - padding: 0 1.5rem; - - @include media-breakpoint-up(lg) { - padding: 0 1.5rem 0 5rem; - } - - span { - @include list-text; - } - - & > :nth-child(1) { - user-select: text; - } - - & > :nth-child(3) { - display: flex; - flex-flow: row nowrap; - align-items: center; - align-content: center; - justify-content: flex-start; - column-gap: 1rem; - } - - & > :nth-child(4) { - button { - width: 100%; - } - } - } - } - } -} diff --git a/web/src/pages/wizard/WizardPage.tsx b/web/src/pages/wizard/WizardPage.tsx deleted file mode 100644 index 6d6a96cad..000000000 --- a/web/src/pages/wizard/WizardPage.tsx +++ /dev/null @@ -1,106 +0,0 @@ -import './style.scss'; - -import { type ReactNode, useEffect, useMemo } from 'react'; -import { Navigate, Route, Routes } from 'react-router'; -import { shallow } from 'zustand/shallow'; - -import { useI18nContext } from '../../i18n/i18n-react'; -import { PageContainer } from '../../shared/components/Layout/PageContainer/PageContainer'; -import { useAppStore } from '../../shared/hooks/store/useAppStore'; -import { WizardMapDevices } from './components/WizardMapDevices/WizardMapDevices'; -import { WizardNav } from './components/WizardNav/WizardNav'; -import { WizardNetworkConfiguration } from './components/WizardNetworkConfiguration/WizardNetworkConfiguration'; -import { WizardNetworkImport } from './components/WizardNetworkImport/WizardNetworkImport'; -import { WizardType } from './components/WizardType/WizardType'; -import { WizardWelcome } from './components/WizardWelcome/WizardWelcome'; -import { useWizardStore, WizardSetupType } from './hooks/useWizardStore'; - -export const WizardPage = () => { - return ( - - - } /> - } /> - - - ); -}; - -type WizardStep = { - title: string; - element: ReactNode; - className: string; - backDisabled?: boolean; -}; - -const WizardRender = () => { - const { LL } = useI18nContext(); - const networkPresent = useAppStore((state) => state.appInfo?.network_present); - const setWizardState = useWizardStore((state) => state.setState); - const [setupType, currentStep] = useWizardStore( - (state) => [state.setupType, state.currentStep], - shallow, - ); - const getSteps = useMemo((): WizardStep[] => { - let res: WizardStep[] = [ - { - title: LL.wizard.navigation.titles.welcome(), - element: , - className: 'welcome', - }, - { - title: LL.wizard.navigation.titles.choseNetworkSetup(), - element: , - backDisabled: networkPresent, - className: 'setup-selection', - }, - ]; - switch (setupType) { - case WizardSetupType.IMPORT: - res = [ - ...res, - { - title: LL.wizard.navigation.titles.importConfig(), - element: , - className: 'import-config', - }, - { - title: LL.wizard.navigation.titles.mapDevices(), - element: , - className: 'map-devices', - backDisabled: true, - }, - ]; - break; - case WizardSetupType.MANUAL: - res = [ - ...res, - { - title: LL.wizard.navigation.titles.manualConfig(), - element: , - className: 'network-config', - }, - ]; - break; - } - return res; - }, [LL.wizard.navigation.titles, networkPresent, setupType]); - - // skip welcome step when at least one network is already present - useEffect(() => { - if (networkPresent && currentStep === 0) { - setWizardState({ currentStep: 1 }); - } - }, [currentStep, networkPresent, setWizardState]); - - return ( -
- - {getSteps[currentStep].element || null} -
- ); -}; diff --git a/web/src/pages/wizard/components/WizardMapDevices/WizardMapDevices.tsx b/web/src/pages/wizard/components/WizardMapDevices/WizardMapDevices.tsx deleted file mode 100644 index 035b64acc..000000000 --- a/web/src/pages/wizard/components/WizardMapDevices/WizardMapDevices.tsx +++ /dev/null @@ -1,183 +0,0 @@ -import './style.scss'; - -import { zodResolver } from '@hookform/resolvers/zod'; -import { useMutation, useQuery } from '@tanstack/react-query'; -import { useCallback, useEffect, useMemo, useRef } from 'react'; -import { type SubmitErrorHandler, type SubmitHandler, useForm } from 'react-hook-form'; -import { shallow } from 'zustand/shallow'; - -import { useI18nContext } from '../../../../i18n/i18n-react'; -import { Card } from '../../../../shared/defguard-ui/components/Layout/Card/Card'; -import { LoaderSpinner } from '../../../../shared/defguard-ui/components/Layout/LoaderSpinner/LoaderSpinner'; -import type { SelectOption } from '../../../../shared/defguard-ui/components/Layout/Select/types'; -import type { ListHeader } from '../../../../shared/defguard-ui/components/Layout/VirtualizedList/types'; -import { VirtualizedList } from '../../../../shared/defguard-ui/components/Layout/VirtualizedList/VirtualizedList'; -import useApi from '../../../../shared/hooks/useApi'; -import { useToaster } from '../../../../shared/hooks/useToaster'; -import { QueryKeys } from '../../../../shared/queries'; -import type { ImportedDevice, MappedDevice } from '../../../../shared/types'; -import { useWizardStore } from '../../hooks/useWizardStore'; -import { MapDeviceRow } from './components/MapDeviceRow'; -import { type WizardMapDevicesFormFields, wizardMapDevicesSchema } from './types'; - -export type WizardMapFormValues = { - devices: ImportedDevice[]; -}; - -export const WizardMapDevices = () => { - const initialized = useRef(false); - const submitElementRef = useRef(null); - const { LL } = useI18nContext(); - const { - network: { mapUserDevices }, - } = useApi(); - const toaster = useToaster(); - const setWizardState = useWizardStore((state) => state.setState); - const setImportedDevices = useWizardStore((state) => state.setImportedDevices); - const [submitSubject, nextStepSubject] = useWizardStore( - (state) => [state.submitSubject, state.nextStepSubject], - shallow, - ); - const importedDevices = useWizardStore((state) => state.importedNetworkDevices); - const importedNetwork = useWizardStore((state) => state.importedNetworkConfig); - const { - user: { getUsers }, - } = useApi(); - - const zodSchema = useMemo(() => wizardMapDevicesSchema(LL), [LL]); - - const { isLoading, data: users } = useQuery({ - queryKey: [QueryKeys.FETCH_USERS_LIST], - queryFn: getUsers, - refetchOnMount: false, - }); - - const { isPending: createLoading, mutate } = useMutation({ - mutationFn: mapUserDevices, - onSuccess: () => { - setWizardState({ loading: false }); - toaster.success(LL.wizard.deviceMap.messages.crateSuccess()); - nextStepSubject.next(); - }, - onError: (err) => { - setWizardState({ loading: false }); - toaster.error(LL.messages.error()); - console.error(err); - }, - }); - - const { handleSubmit, control, reset, getValues } = useForm( - { - defaultValues: { devices: importedDevices ?? [] }, - mode: 'onSubmit', - resolver: zodResolver(zodSchema), - }, - ); - - const getUsersOptions = useMemo( - (): SelectOption[] => - users?.map((user) => ({ - value: user.id, - label: `${user.first_name} ${user.last_name}`, - key: user.id, - meta: ``, - })) ?? [], - [users], - ); - - const getHeaders = useMemo( - (): ListHeader[] => [ - { text: 'Device Name', key: 0, sortable: false }, - { text: 'IPs', key: 1, sortable: false }, - { text: 'User', key: 2, sortable: false }, - ], - [], - ); - - const renderRow = useCallback( - (data: DeviceRowData) => ( - - ), - [control, getUsersOptions], - ); - - const handleValidSubmit: SubmitHandler = (values) => { - if (importedNetwork) { - setWizardState({ loading: true }); - mutate({ - devices: values.devices as MappedDevice[], - networkId: importedNetwork.id, - }); - } - }; - - const handleInvalidSubmit: SubmitErrorHandler = () => { - toaster.error(LL.wizard.deviceMap.messages.errorsInForm()); - }; - const devicesList = useMemo((): DeviceRowData[] => { - if (importedDevices) { - return importedDevices.map((_, index) => ({ - itemIndex: index, - })); - } - - return []; - }, [importedDevices]); - - // allows to submit form from WizardNav - useEffect(() => { - const sub = submitSubject.subscribe(() => { - if (submitElementRef.current) { - submitElementRef.current.click(); - } - }); - return () => sub?.unsubscribe(); - }, [submitSubject]); - - // init form with values from imported config - useEffect(() => { - if (importedDevices && !initialized.current) { - initialized.current = true; - reset({ devices: importedDevices }); - } - }, [importedDevices, reset]); - - // save form state so progress won't be lost - useEffect(() => { - const interval = setInterval(() => { - const values = getValues(); - setImportedDevices(values.devices); - }, 5000); - - return () => { - clearInterval(interval); - }; - }, [getValues, setImportedDevices]); - - if (isLoading || !importedDevices || createLoading) return ; - return ( - -
- - customRowRender={renderRow} - data={devicesList} - rowSize={70} - headers={getHeaders} - headerPadding={{ - left: 20, - right: 20, - }} - padding={{ - left: 47, - right: 47, - }} - /> - - -
- ); -}; - -type DeviceRowData = { - itemIndex: number; -}; diff --git a/web/src/pages/wizard/components/WizardMapDevices/components/MapDeviceRow.tsx b/web/src/pages/wizard/components/WizardMapDevices/components/MapDeviceRow.tsx deleted file mode 100644 index 06480d133..000000000 --- a/web/src/pages/wizard/components/WizardMapDevices/components/MapDeviceRow.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import type { TargetAndTransition } from 'motion/react'; -import { useCallback, useMemo } from 'react'; -import { type Control, useController } from 'react-hook-form'; - -import { ColorsRGB } from '../../../../../shared/constants'; -import { RowBox } from '../../../../../shared/defguard-ui/components/Layout/RowBox/RowBox'; -import { Select } from '../../../../../shared/defguard-ui/components/Layout/Select/Select'; -import { - type SelectOption, - type SelectSelectedValue, - SelectSizeVariant, -} from '../../../../../shared/defguard-ui/components/Layout/Select/types'; -import type { WizardMapDevicesFormFields } from '../types'; - -type Props = { - options: SelectOption[]; - control: Control; - index: number; -}; - -export const MapDeviceRow = ({ options, control, index }: Props) => { - const nameController = useController({ - control, - name: `devices.${index}.name`, - }); - - const userController = useController({ - control, - name: `devices.${index}.user_id`, - }); - - const ipController = useController({ - control, - name: `devices.${index}.wireguard_ips`, - }); - - const hasErrors = useMemo(() => { - return nameController.fieldState.invalid || userController.fieldState.invalid; - }, [nameController.fieldState.invalid, userController.fieldState.invalid]); - - const getAnimate = useMemo(() => { - const res: TargetAndTransition = { - borderColor: ColorsRGB.GrayBorder, - }; - if (hasErrors) { - res.borderColor = ColorsRGB.Error; - } - return res; - }, [hasErrors]); - - const renderSelected = useCallback( - (selected: number): SelectSelectedValue => { - const option = options.find((o) => o.value === selected); - if (!option) throw Error("Selected value doesn't exist"); - return { - key: option.key, - displayValue: option.label, - }; - }, - [options], - ); - - return ( - - - {ipController.field.value.join(' ')} - - - - ); -}; diff --git a/web/src/pages/wizard/components/WizardNetworkConfiguration/components/DividerHeader.tsx b/web/src/pages/wizard/components/WizardNetworkConfiguration/components/DividerHeader.tsx deleted file mode 100644 index 86243ea08..000000000 --- a/web/src/pages/wizard/components/WizardNetworkConfiguration/components/DividerHeader.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import type { PropsWithChildren } from 'react'; - -type DividerHeaderProps = { - text: string; -} & PropsWithChildren; - -export const DividerHeader = ({ text, children }: DividerHeaderProps) => { - return ( -
-
-

{text}

- {children} -
-
- ); -}; diff --git a/web/src/pages/wizard/components/WizardNetworkConfiguration/style.scss b/web/src/pages/wizard/components/WizardNetworkConfiguration/style.scss deleted file mode 100644 index 675509ec1..000000000 --- a/web/src/pages/wizard/components/WizardNetworkConfiguration/style.scss +++ /dev/null @@ -1,55 +0,0 @@ -#wizard-manual-network-configuration { - box-shadow: none; - - @include media-breakpoint-up(xl) { - box-shadow: var(--card-shadow); - } - - & > form { - @include media-breakpoint-up(xl) { - box-sizing: border-box; - padding: 47px 55px; - } - - & > * { - width: 100%; - } - - & > .message-box-spacer { - padding-bottom: 25px; - } - - & > .form-checkbox { - margin-bottom: 25px; - } - } - - #location-mfa-mode-explain-message-box { - ul { - list-style-position: inside; - margin-top: 8px; - - li { - p { - display: inline; - } - } - } - } - - .divider-header { - padding-bottom: var(--spacing-s); - - .inner { - display: flex; - flex-flow: row; - align-items: center; - justify-content: flex-start; - border-bottom: 1px solid var(--border-primary); - } - - .header { - @include typography(app-side-bar); - } - } -} diff --git a/web/src/pages/wizard/components/WizardNetworkImport/WizardNetworkImport.tsx b/web/src/pages/wizard/components/WizardNetworkImport/WizardNetworkImport.tsx deleted file mode 100644 index 52c798c0e..000000000 --- a/web/src/pages/wizard/components/WizardNetworkImport/WizardNetworkImport.tsx +++ /dev/null @@ -1,246 +0,0 @@ -import './style.scss'; - -import { zodResolver } from '@hookform/resolvers/zod'; -import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; -import { isUndefined } from 'lodash-es'; -import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; -import { type SubmitHandler, useForm } from 'react-hook-form'; -import { useNavigate } from 'react-router'; -import { z } from 'zod'; -import { shallow } from 'zustand/shallow'; - -import { useI18nContext } from '../../../../i18n/i18n-react'; -import { FormInput } from '../../../../shared/defguard-ui/components/Form/FormInput/FormInput'; -import { FormSelect } from '../../../../shared/defguard-ui/components/Form/FormSelect/FormSelect'; -import { Button } from '../../../../shared/defguard-ui/components/Layout/Button/Button'; -import { - ButtonSize, - ButtonStyleVariant, -} from '../../../../shared/defguard-ui/components/Layout/Button/types'; -import { Card } from '../../../../shared/defguard-ui/components/Layout/Card/Card'; -import { MessageBox } from '../../../../shared/defguard-ui/components/Layout/MessageBox/MessageBox'; -import type { SelectOption } from '../../../../shared/defguard-ui/components/Layout/Select/types'; -import useApi from '../../../../shared/hooks/useApi'; -import { useToaster } from '../../../../shared/hooks/useToaster'; -import { MutationKeys } from '../../../../shared/mutations'; -import { QueryKeys } from '../../../../shared/queries'; -import type { ImportNetworkRequest } from '../../../../shared/types'; -import { invalidateMultipleQueries } from '../../../../shared/utils/invalidateMultipleQueries'; -import { titleCase } from '../../../../shared/utils/titleCase'; -import { validateIpOrDomain } from '../../../../shared/validators'; -import { useWizardStore } from '../../hooks/useWizardStore'; - -interface FormInputs extends Omit { - fileName: string; - allowed_groups: string[]; -} -const defaultValues: FormInputs = { - name: '', - endpoint: '', - fileName: '', - config: '', - allowed_groups: [], -}; -export const WizardNetworkImport = () => { - const submitRef = useRef(null); - const queryClient = useQueryClient(); - const { LL } = useI18nContext(); - const navigate = useNavigate(); - const { - network: { importNetwork }, - groups: { getGroups }, - } = useApi(); - const toaster = useToaster(); - const [setWizardState, nextStepSubject, submitSubject, resetWizard] = useWizardStore( - (state) => [ - state.setState, - state.nextStepSubject, - state.submitSubject, - state.resetState, - ], - shallow, - ); - const [groupOptions, setGroupOptions] = useState[]>([]); - - const zodSchema = useMemo( - () => - z.object({ - name: z.string().trim().min(1, LL.form.error.required()), - endpoint: z - .string() - .trim() - .min(1, LL.form.error.required()) - .refine((val) => validateIpOrDomain(val), LL.form.error.endpoint()), - fileName: z.string().trim().min(1, LL.form.error.required()), - config: z.string().trim().min(1, LL.form.error.required()), - allowed_groups: z.array(z.string().min(1, LL.form.error.minimumLength())), - }), - [LL.form.error], - ); - - const { control, handleSubmit, setValue, setError, resetField } = useForm({ - defaultValues, - mode: 'all', - reValidateMode: 'onChange', - resolver: zodResolver(zodSchema), - }); - - const { - mutate: importNetworkMutation, - isPending, - data, - } = useMutation({ - mutationFn: importNetwork, - mutationKey: [MutationKeys.IMPORT_NETWORK], - onSuccess: (response) => { - toaster.success(LL.networkConfiguration.form.messages.networkCreated()); - // complete wizard if there is no devices to map - if (response.devices.length === 0) { - toaster.success(LL.wizard.completed()); - resetWizard(); - invalidateMultipleQueries(queryClient, [ - [QueryKeys.FETCH_NETWORKS], - [QueryKeys.FETCH_APP_INFO], - ]); - navigate('/admin/overview', { replace: true }); - } else { - setWizardState({ - importedNetworkDevices: response.devices, - importedNetworkConfig: response.network, - loading: false, - }); - nextStepSubject.next(); - } - }, - onError: (err) => { - setWizardState({ loading: false }); - toaster.error(LL.messages.error()); - resetField('fileName'); - resetField('config'); - console.error(err); - }, - }); - - const onValidSubmit: SubmitHandler = useCallback( - (data) => { - if (!isPending) { - setWizardState({ loading: true }); - importNetworkMutation(data); - } - }, - [importNetworkMutation, isPending, setWizardState], - ); - - const handleConfigUpload = () => { - const input = document.createElement('input'); - input.type = 'file'; - input.multiple = false; - input.style.display = 'none'; - input.onchange = () => { - if (input.files && input.files.length === 1) { - const reader = new FileReader(); - reader.onload = () => { - if (reader.result && input.files) { - const res = reader.result; - setValue('config', res as string); - setValue('fileName', input.files[0].name); - } - }; - reader.onerror = () => { - toaster.error('Error while reading file.'); - setError('fileName', { - message: 'Please try again', - }); - }; - reader.readAsText(input.files[0]); - } - }; - input.click(); - }; - - useEffect(() => { - const sub = submitSubject.subscribe(() => { - submitRef.current?.click(); - }); - return () => sub?.unsubscribe(); - }, [submitSubject]); - - const { - isLoading: groupsLoading, - data: fetchGroupsData, - error: fetchGroupsError, - } = useQuery({ - queryKey: [QueryKeys.FETCH_GROUPS], - queryFn: getGroups, - }); - - // biome-ignore lint/correctness/useExhaustiveDependencies: migration, checkMeLater - useEffect(() => { - if (fetchGroupsError) { - toaster.error(LL.messages.error()); - console.error(fetchGroupsError); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [fetchGroupsError]); - - useEffect(() => { - if (fetchGroupsData) { - setGroupOptions( - fetchGroupsData.groups.map((g) => ({ - key: g, - value: g, - label: titleCase(g), - })), - ); - } - }, [fetchGroupsData]); - - return ( - -
- - -

{LL.networkConfiguration.form.helpers.gateway()}

-
- - -

{LL.networkConfiguration.form.helpers.allowedGroups()}

-
- ({ - key: group, - displayValue: titleCase(group), - })} - /> - - -

{displayDate()}

- - - ); -}; - -type DisplayProps = { - selected?: string | null; - displayFormat?: string; - clearable?: boolean; - onClear?: () => void; -} & HTMLAttributes; - -const DisplayField = forwardRef( - ({ selected, className, displayFormat, onClear, clearable = false, ...rest }, ref) => { - return ( -
- - {clearable && isPresent(onClear) && selected !== null && ( - { - onClear?.(); - }} - > - - - )} -
- ); - }, -); diff --git a/web/src/shared/components/Layout/DateInput/FormDateInput.tsx b/web/src/shared/components/Layout/DateInput/FormDateInput.tsx deleted file mode 100644 index 7163f889d..000000000 --- a/web/src/shared/components/Layout/DateInput/FormDateInput.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { isUndefined } from 'lodash-es'; -import { useMemo } from 'react'; -import { - type FieldValues, - type UseControllerProps, - useController, -} from 'react-hook-form'; - -import { DateInput } from './DateInput'; -import type { DateInputProps } from './types'; - -type Props = { - onChange?: (value: string | null) => void; - controller: UseControllerProps; - label?: string; - disabled?: boolean; -} & Pick; - -export const FormDateInput = ({ - onChange, - controller, - label, - disabled = false, - ...dateInputProps -}: Props) => { - const { - field: { value, onChange: fieldChange }, - fieldState: { isDirty, isTouched, error }, - formState: { isSubmitted }, - } = useController(controller); - - const errorMessage = useMemo(() => { - if ( - (!isUndefined(error) && (isDirty || isTouched)) || - (!isUndefined(error) && isSubmitted) - ) { - return error.message; - } - return undefined; - }, [error, isDirty, isSubmitted, isTouched]); - - return ( - { - fieldChange(val); - onChange?.(val); - }} - label={label} - errorMessage={errorMessage} - disabled={disabled} - {...dateInputProps} - /> - ); -}; diff --git a/web/src/shared/components/Layout/DateInput/style.scss b/web/src/shared/components/Layout/DateInput/style.scss deleted file mode 100644 index cb8657511..000000000 --- a/web/src/shared/components/Layout/DateInput/style.scss +++ /dev/null @@ -1,276 +0,0 @@ -.date-input-spacer { - & > .inner { - position: relative; - - &.with-time { - & > .fields-track { - display: grid; - grid-template-columns: 1fr 80px; - column-gap: var(--spacing-s); - - & > .react-datepicker-wrapper { - &:first-child { - grid-row: 1; - grid-column: 1 / 2; - } - - &:last-child { - grid-row: 1; - grid-column: 2 / 3; - } - } - } - } - - & > .label { - user-select: none; - @include typography(app-wizard-1); - color: var(--text-body-tertiary); - padding-bottom: var(--spacing-xs); - } - } -} - -.date-input-container { - position: relative; - - &.clearable { - & > .date-input { - padding: 8px 42px 8px 20px; - } - - &:hover { - & > .interaction-box { - visibility: visible; - } - } - } - - .interaction-box { - visibility: hidden; - width: 24px; - height: 24px; - position: absolute; - right: 10px; - top: 50%; - transform: translateY(-50%); - - &:hover { - svg path { - fill: var(--surface-alert-primary); - } - } - } - - .date-input { - display: flex; - flex-flow: row; - align-items: center; - justify-content: flex-start; - background-color: var(--surface-frame-bg); - border: 1px solid var(--border-primary); - border-radius: 10px; - box-sizing: border-box; - width: 100%; - min-height: 50px; - margin: 0; - padding: 8px 20px; - cursor: pointer; - opacity: 1; - - @include animate-field; - transition-property: border-color, opacity; - - span { - width: 100%; - text-align: left; - @include typography(app-input); - } - } - - &.disabled { - cursor: not-allowed; - - & > .date-input { - cursor: not-allowed; - opacity: var(--disabled-opacity); - } - } - - &.error { - & > .date-input { - border-color: var(--border-alert); - } - } -} - -.react-datepicker-popper { - z-index: 2; -} - -.react-datepicker { - border-radius: 10px; - background-color: var(--surface-default-modal); - border: 1px solid var(--border-primary); - box-sizing: border-box; - padding: var(--spacing-s); - width: 100%; - max-width: 360px; -} - -.react-datepicker__triangle { - color: var(--border-primary); -} - -.react-datepicker__day-names { - display: grid; - grid-template-columns: repeat(7, 1fr); - grid-template-rows: auto; - column-gap: var(--spacing-s); - align-items: center; - border-bottom: 1px solid var(--border-primary); -} - -.react-datepicker__aria-live { - display: none; -} - -.react-datepicker-wrapper { - width: 100%; -} - -.react-datepicker__month { - display: flex; - flex-flow: column; - row-gap: var(--spacing-xs); -} - -.react-datepicker__week { - display: grid; - grid-template-columns: repeat(7, 1fr); - column-gap: var(--spacing-s); -} - -.react-datepicker__day { - width: 100%; - cursor: pointer; - - .custom-day { - display: flex; - flex-flow: row; - align-items: center; - justify-content: center; - width: 20px; - height: 20px; - border-radius: 5px; - - @include typography(app-modal-1); - - &:hover { - background-color: var(--surface-main-primary); - - span { - color: var(--white); - } - } - } -} - -.react-datepicker__day--selected { - & > .custom-day { - background-color: var(--surface-main-primary); - - span { - color: var(--white); - } - } -} - -.react-datepicker__header { - padding: 0 0 var(--spacing-xs) 0; - - .date-picker-custom-header { - display: grid; - width: 100%; - grid-template-rows: auto; - grid-template-columns: 22px 1fr 22px; - align-items: center; - justify-items: start; - column-gap: 10px; - border: 0; - padding-bottom: var(--spacing-xs); - - button { - display: flex; - flex-flow: row; - align-items: center; - justify-content: center; - margin: 0; - padding: 0; - background: none; - border: none; - width: 22px; - height: 22px; - cursor: pointer; - - svg { - width: 100%; - height: 100%; - } - - &:last-child { - svg { - transform: rotateZ(-180deg); - } - } - } - - p { - width: 100%; - text-align: center; - @include typography(modal-title); - user-select: none; - } - - .icon-container { - svg { - path { - fill: var(--surface-icon-primary); - } - } - } - } -} - -.react-datepicker-time__header { - color: var(--text-body-primary); - @include typography(modal-title); - border-bottom: 1px solid var(--border-primary); - padding-bottom: var(--spacing-xs); -} - -.react-datepicker__time-list { - display: flex; - flex-flow: column; - row-gap: var(--spacing-xs); - list-style: none; - padding-right: 10px; - scrollbar-gutter: stable; - max-height: min(250px, 75dvh); - overflow: auto; - - .react-datepicker__time-list-item { - @include typography(app-modal-1); - color: var(--text-body-primary); - padding: 3px; - border-radius: 5px; - cursor: pointer; - background-color: transparent; - - &:hover, - &.react-datepicker__time-list-item--selected { - color: var(--white); - background-color: var(--surface-main-primary); - } - } -} diff --git a/web/src/shared/components/Layout/DateInput/types.ts b/web/src/shared/components/Layout/DateInput/types.ts deleted file mode 100644 index 1fda27111..000000000 --- a/web/src/shared/components/Layout/DateInput/types.ts +++ /dev/null @@ -1,9 +0,0 @@ -export type DateInputProps = { - selected: string; - label?: string; - errorMessage?: string; - disabled?: boolean; - showTimeSelection?: boolean; - clearable?: boolean; - onChange: (value: string | null) => void; -}; diff --git a/web/src/shared/components/Layout/EnterpriseUpgradeToast/EnterpriseUpgradeToast.tsx b/web/src/shared/components/Layout/EnterpriseUpgradeToast/EnterpriseUpgradeToast.tsx deleted file mode 100644 index 5e557bbe9..000000000 --- a/web/src/shared/components/Layout/EnterpriseUpgradeToast/EnterpriseUpgradeToast.tsx +++ /dev/null @@ -1,75 +0,0 @@ -import './style.scss'; - -import { useCallback } from 'react'; - -import { useI18nContext } from '../../../../i18n/i18n-react'; -import { Badge } from '../../../defguard-ui/components/Layout/Badge/Badge'; -import { BadgeStyleVariant } from '../../../defguard-ui/components/Layout/Badge/types'; -import type { ToastOptions } from '../../../defguard-ui/components/Layout/ToastManager/Toast/types'; -import { useToastsStore } from '../../../defguard-ui/hooks/toasts/useToastStore'; -import SvgIconX from '../../svg/IconX'; - -export const EnterpriseUpgradeToast = ({ id }: ToastOptions) => { - const removeToast = useToastsStore((s) => s.removeToast); - const { LL } = useI18nContext(); - - const closeToast = useCallback(() => { - removeToast(id); - }, [id, removeToast]); - - const handleDismiss = () => { - closeToast(); - }; - - return ( -
-
-
- - - - - - } - className="toaster-badge" - /> -

{LL.modals.enterpriseUpgradeToaster.title()}

-
- -
-
-

{LL.modals.enterpriseUpgradeToaster.message()}

- -
-
- ); -}; diff --git a/web/src/shared/components/Layout/EnterpriseUpgradeToast/style.scss b/web/src/shared/components/Layout/EnterpriseUpgradeToast/style.scss deleted file mode 100644 index 1e282663c..000000000 --- a/web/src/shared/components/Layout/EnterpriseUpgradeToast/style.scss +++ /dev/null @@ -1,99 +0,0 @@ -.enterprise-upgrade-toaster { - box-sizing: border-box; - padding: 15px; - box-shadow: 0px 6px 10px 0px rgba(0, 0, 0, 0.1); - border-radius: 10px; - background-color: var(--surface-nav-bg); - border-radius: 15px; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - gap: 8px; - - min-width: 270px; - max-width: 400px; - - .toaster-badge { - border-radius: 100%; - width: 18px; - height: 18px; - } - - .dismiss { - display: flex; - flex-flow: row; - align-items: center; - justify-content: center; - border: 0 solid transparent; - background-color: transparent; - cursor: pointer; - padding: 2px; - - svg { - & > g { - & > g { - fill: var(--surface-icon-primary); - } - } - height: 14px; - width: 14px; - } - } - - .top { - display: flex; - align-items: center; - flex-flow: row nowrap; - column-gap: 8px; - width: 100%; - justify-content: space-between; - - .heading { - display: flex; - align-items: center; - gap: 5px; - } - - p { - color: var(--text-body-primary); - text-wrap: nowrap; - white-space: none; - @include typography(app-modal-1); - } - - & > a { - margin-left: auto; - border: none; - background-color: transparent; - cursor: pointer; - width: 22px; - height: 22px; - display: inline-flex; - flex-flow: row; - align-content: center; - align-items: center; - justify-content: center; - padding: 4px; - box-sizing: border-box; - text-decoration: none; - } - } - - .bottom { - min-width: 230px; - display: flex; - flex-flow: column; - align-items: left; - justify-content: space-between; - width: 100%; - @include typography(app-copyright); - - .upgrade-link-container { - display: flex; - width: 100%; - margin-top: 8px; - justify-content: right; - } - } -} diff --git a/web/src/shared/components/Layout/EnterpriseUpgradeToast/types.ts b/web/src/shared/components/Layout/EnterpriseUpgradeToast/types.ts deleted file mode 100644 index 5e111a0ca..000000000 --- a/web/src/shared/components/Layout/EnterpriseUpgradeToast/types.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { z } from 'zod'; - -export const enterpriseUpgradeToastMetaSchema = z.object({ - customId: z.string().trim().min(1), -}); - -export type EnterpriseUpgradeToastMeta = z.infer; diff --git a/web/src/shared/components/Layout/ExpandableSection/ExpandableSection.tsx b/web/src/shared/components/Layout/ExpandableSection/ExpandableSection.tsx deleted file mode 100644 index 4178b75c1..000000000 --- a/web/src/shared/components/Layout/ExpandableSection/ExpandableSection.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import './style.scss'; - -import clsx from 'clsx'; -import { type PropsWithChildren, useState } from 'react'; - -import { ArrowSingle } from '../../../defguard-ui/components/icons/ArrowSingle/ArrowSingle'; -import { ArrowSingleDirection } from '../../../defguard-ui/components/icons/ArrowSingle/types'; - -type Props = { - text: string; - initOpen?: boolean; - textAs?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p'; - className?: string; - id?: string; -} & PropsWithChildren; - -export const ExpandableSection = ({ - children, - text, - className, - id, - textAs: Tag = 'p', - initOpen = true, -}: Props) => { - const [expanded, setExpanded] = useState(initOpen); - - return ( -
-
{ - setExpanded((s) => !s); - }} - > - {text} - -
-
-
{children}
-
-
- ); -}; diff --git a/web/src/shared/components/Layout/ExpandableSection/style.scss b/web/src/shared/components/Layout/ExpandableSection/style.scss deleted file mode 100644 index e2cd65435..000000000 --- a/web/src/shared/components/Layout/ExpandableSection/style.scss +++ /dev/null @@ -1,59 +0,0 @@ -.expandable-section { - & > .track { - width: 100%; - user-select: none; - cursor: pointer; - display: grid; - grid-template-columns: 1fr auto; - column-gap: var(--spacing-xs); - align-items: center; - justify-items: start; - border-bottom: 1px solid var(--border-primary); - - p { - width: 100%; - user-select: none; - } - - .arrow-single { - align-self: end; - width: 22px; - height: 22px; - - svg { - transition-property: transform; - - @include animate-standard; - } - } - } - - & > .expandable { - display: grid; - width: 100%; - transition-property: grid-template-rows; - - @include animate-standard(); - - &:not(.open) { - grid-template-rows: 0fr; - } - - &.open { - grid-template-rows: 1fr; - } - - & > div { - overflow: hidden; - padding-top: var(--spacing-s); - } - } -} - -.expandable-section .track p { - @include typography(app-side-bar); -} - -.expandable-section .track h2 { - @include typography(app-body-1); -} diff --git a/web/src/shared/components/Layout/ListCellTags/ListCellTags.tsx b/web/src/shared/components/Layout/ListCellTags/ListCellTags.tsx deleted file mode 100644 index 2fcbabc29..000000000 --- a/web/src/shared/components/Layout/ListCellTags/ListCellTags.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import './style.scss'; - -import useResizeObserver from '@react-hook/resize-observer'; -import clsx from 'clsx'; -import { useCallback, useRef, useState } from 'react'; - -import type { ListCellTag } from '../../../../pages/acl/AclIndexPage/components/shared/types'; -import { FloatingMenu } from '../../../defguard-ui/components/Layout/FloatingMenu/FloatingMenu'; -import { FloatingMenuProvider } from '../../../defguard-ui/components/Layout/FloatingMenu/FloatingMenuProvider'; -import { FloatingMenuTrigger } from '../../../defguard-ui/components/Layout/FloatingMenu/FloatingMenuTrigger'; -import { Tag } from '../../../defguard-ui/components/Layout/Tag/Tag'; -import { isPresent } from '../../../defguard-ui/utils/isPresent'; - -type RenderTagsProps = { - data: ListCellTag[]; - placeholder?: string; -}; - -export const ListCellTags = ({ data, placeholder }: RenderTagsProps) => { - const containerRef = useRef(null); - const [overflows, setOverflows] = useState(false); - - const handleResize = useCallback(() => { - if (containerRef.current) { - setOverflows(containerRef.current.scrollWidth > containerRef.current.clientWidth); - } - }, []); - - useResizeObserver(containerRef, handleResize); - return ( - - -
- - {data.length === 0 && isPresent(placeholder) && ( -

{placeholder}

- )} -
-
- - - -
- ); -}; - -const FloatingContent = ({ data }: RenderTagsProps) => { - return ( -
    - {data.map((d) => ( -
  • {d.label}
  • - ))} -
- ); -}; - -const TagContent = ({ data }: RenderTagsProps) => { - return ( -
- {data.map((d) => { - if (d.displayAsTag) { - return ; - } - return {d.label}; - })} -
- ); -}; diff --git a/web/src/shared/components/Layout/ListCellTags/style.scss b/web/src/shared/components/Layout/ListCellTags/style.scss deleted file mode 100644 index 90633bb6b..000000000 --- a/web/src/shared/components/Layout/ListCellTags/style.scss +++ /dev/null @@ -1,72 +0,0 @@ -.list-cell-tags-floating { - list-style: none; - display: flex; - flex-flow: column; - row-gap: var(--spacing-xs); - max-height: 250px; - max-width: 100dvh; - min-height: 35px; - overflow: auto; - padding-right: var(--spacing-xs); - min-width: 201px; - - li { - @include typography(app-modal-1); - color: var(--text-body-secondary); - } -} - -.list-cell-tags { - position: relative; - overflow: hidden; - max-width: 100%; - width: 100%; - - &:not(.empty) { - cursor: help; - } - - span, - p { - @include typography(app-modal-2); - } - - & > .track { - display: flex; - flex-flow: row nowrap; - align-items: center; - justify-content: flex-start; - column-gap: var(--spacing-xs); - overflow: visible; - - .tag { - padding: 3px 6px; - height: unset; - border-radius: 6px; - } - } - - &.overflows { - &::after { - position: absolute; - top: 0; - right: 0; - width: 65px; - height: 100%; - content: ' '; - background: linear-gradient( - 90deg, - rgba(0, 0, 0, 0) 0%, - var(--surface-default-modal) 100% - ); - } - } - - .no-data { - cursor: default; - } - - .tag { - user-select: none; - } -} diff --git a/web/src/shared/components/Layout/ListHeader/ListHeader.tsx b/web/src/shared/components/Layout/ListHeader/ListHeader.tsx deleted file mode 100644 index 54a28ab92..000000000 --- a/web/src/shared/components/Layout/ListHeader/ListHeader.tsx +++ /dev/null @@ -1,158 +0,0 @@ -import './style.scss'; - -import clsx from 'clsx'; -import { uniqBy } from 'lodash-es'; -import { useEffect } from 'react'; - -import { CheckBox } from '../../../defguard-ui/components/Layout/Checkbox/CheckBox'; -import { InteractionBox } from '../../../defguard-ui/components/Layout/InteractionBox/InteractionBox'; -import { ListSortDirection } from '../../../defguard-ui/components/Layout/VirtualizedList/types'; -import { isPresent } from '../../../defguard-ui/utils/isPresent'; -import type { ListHeaderColumnConfig } from './types'; - -type ListHeaderColumnProps = { - active: boolean; - sortDirection?: ListSortDirection; - onClick?: () => void; - columnKey?: string; -} & Omit, 'key'>; - -type Props = { - headers: ListHeaderColumnConfig[]; - activeKey?: keyof T; - sortDirection?: ListSortDirection; - className?: string; - id?: string; - selectAll?: boolean; - onSelectAll?: (value: boolean) => void; - onChange?: (key: keyof T, direction: ListSortDirection) => void; -}; - -const ListHeaderColumn = ({ - onClick, - sortDirection, - label, - active, - enabled, - ...props -}: ListHeaderColumnProps) => { - const disabled = !enabled || !isPresent(props.sortKey); - const key = (props.sortKey ?? props.columnKey) as string; - - useEffect(() => { - if (!props.sortKey && !props.columnKey) { - throw Error('ListHeader needs either key or sortKey!'); - } - }, [props.columnKey, props.sortKey]); - - return ( -
- -
- ); -}; - -export const ListHeader = ({ - headers, - activeKey, - sortDirection, - className, - id, - selectAll, - onChange, - onSelectAll, -}: Props) => { - useEffect(() => { - const unq = uniqBy(headers, (h) => h.sortKey ?? h.key); - if (unq.length !== headers.length) { - throw Error('ListHeader component given headers with duplicate identifiers'); - } - }, [headers]); - - const selectEnabled = isPresent(selectAll) && isPresent(onSelectAll); - - return ( -
- {!selectEnabled &&
} - {selectEnabled && ( -
- { - onSelectAll?.(!selectAll); - }} - > - - -
- )} - {headers.map(({ label, sortKey, enabled, key }) => { - const isActive = activeKey === sortKey; - const direction = isActive ? sortDirection : ListSortDirection.ASC; - const componentKey: string = (sortKey ?? key) as string; - return ( - { - if (enabled && isPresent(onChange) && isPresent(sortKey)) { - if (isActive) { - const newDirection = - sortDirection === ListSortDirection.ASC - ? ListSortDirection.DESC - : ListSortDirection.ASC; - onChange(sortKey, newDirection); - } else { - onChange(sortKey, ListSortDirection.ASC); - } - } - }} - /> - ); - })} -
- ); -}; diff --git a/web/src/shared/components/Layout/ListHeader/style.scss b/web/src/shared/components/Layout/ListHeader/style.scss deleted file mode 100644 index ccafe59af..000000000 --- a/web/src/shared/components/Layout/ListHeader/style.scss +++ /dev/null @@ -1,70 +0,0 @@ -.list-headers { - width: 100%; - display: inline-grid; - grid-template-rows: 1fr; - - .list-header-column { - button { - display: flex; - flex-flow: row; - align-items: center; - justify-content: flex-start; - background-color: transparent; - padding: 0; - margin: 0; - border: none; - - & > .label { - @include typography(app-modal-1); - @include animate-standard; - transition-property: font-weight color; - color: var(--text-body-tertiary); - } - - & > .sort-icon { - display: flex; - flex-flow: row; - align-items: center; - justify-content: center; - padding: 0; - margin: 0; - width: 22px; - height: 22px; - user-select: none; - visibility: visible; - @include animate-standard; - transition-property: transform; - transform: rotate(180deg); - - &.asc { - transform: rotate(180deg); - } - - &.desc { - transform: rotate(0deg); - } - } - } - - &.disabled { - & > button { - & > .sort-icon { - visibility: hidden; - } - } - } - - &:not(.disabled) { - &.active { - & > button { - cursor: pointer; - - & > .label { - font-weight: 500; - color: var(--text-body-primary); - } - } - } - } - } -} diff --git a/web/src/shared/components/Layout/ListHeader/types.ts b/web/src/shared/components/Layout/ListHeader/types.ts deleted file mode 100644 index 8a7281522..000000000 --- a/web/src/shared/components/Layout/ListHeader/types.ts +++ /dev/null @@ -1,6 +0,0 @@ -export type ListHeaderColumnConfig = { - label: string; - enabled?: boolean; - sortKey?: keyof T; - key?: string; -}; diff --git a/web/src/shared/components/Layout/ManagementPageLayout/ManagementPageLayout.tsx b/web/src/shared/components/Layout/ManagementPageLayout/ManagementPageLayout.tsx deleted file mode 100644 index 6a8ec0217..000000000 --- a/web/src/shared/components/Layout/ManagementPageLayout/ManagementPageLayout.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import './style.scss'; - -import { clsx } from 'clsx'; - -import { useNavigationStore } from '../../../../components/Navigation/hooks/useNavigationStore'; -import { Search } from '../../../defguard-ui/components/Layout/Search/Search'; -import type { ManagementPageProps } from './types'; - -export const ManagementPageLayout = ({ - children, - title, - actions, - itemsCount, - search, - id, -}: ManagementPageProps) => { - const navOpen = useNavigationStore((s) => s.isOpen); - return ( -
-
-
-

{title}

- {search && ( - - )} -
-
- {itemsCount && ( -
-

{itemsCount.label}

- {itemsCount.itemsCount !== undefined && ( -
- {itemsCount.itemsCount} -
- )} -
- )} -
{actions}
-
-
{children}
-
-
- ); -}; diff --git a/web/src/shared/components/Layout/ManagementPageLayout/style.scss b/web/src/shared/components/Layout/ManagementPageLayout/style.scss deleted file mode 100644 index becdb95a1..000000000 --- a/web/src/shared/components/Layout/ManagementPageLayout/style.scss +++ /dev/null @@ -1,80 +0,0 @@ -.management-page { - box-sizing: border-box; - width: 100%; - padding-top: 64px; - padding-left: 88px; - max-height: 100dvh; - overflow-y: auto; - - &.nav-open { - padding-left: 230px; - } - - & > .page-content { - & > header { - display: flex; - flex-flow: row wrap; - align-items: center; - justify-content: flex-start; - gap: 30px; - width: 100%; - box-sizing: border-box; - padding: 0 70px 48px; - - h1 { - @include typography(app-title); - } - - .search { - min-width: 360px; - max-width: 100%; - } - } - - & > .top-bar { - flex-flow: row; - display: flex; - box-sizing: border-box; - padding: 0 70px 30px; - width: 100%; - - .items-count { - display: flex; - flex-flow: row; - flex-grow: 0; - align-items: center; - justify-content: flex-start; - column-gap: 10px; - - & > p { - @include typography(app-body-1); - } - - .count-box { - display: flex; - align-items: center; - flex-flow: row; - align-items: center; - justify-content: center; - flex-grow: 0; - box-sizing: border-box; - padding: 0 8px; - height: 30px; - min-width: 30px; - background-color: var(--text-body-tertiary); - user-select: none; - border-radius: 35%; - - span { - color: var(--surface-frame-bg); - @include typography(app-number); - } - } - } - - & > .actions { - margin-left: auto; - } - } - } -} diff --git a/web/src/shared/components/Layout/ManagementPageLayout/types.ts b/web/src/shared/components/Layout/ManagementPageLayout/types.ts deleted file mode 100644 index 51d0ac054..000000000 --- a/web/src/shared/components/Layout/ManagementPageLayout/types.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type { ReactNode } from 'react'; - -export type ManagementPageProps = { - children: ReactNode; - title: string; - search?: ManagementPageSearch; - actions?: ReactNode; - itemsCount?: ManagementPageItemsCount; - id?: string; -}; - -export type ManagementPageItemsCount = { - itemsCount?: number; - label: string; -}; - -export type ManagementPageSearch = { - onSearch: (searchValue: string) => void; - placeholder?: string; - loading?: boolean; -}; diff --git a/web/src/shared/components/Layout/PageContainer/PageContainer.tsx b/web/src/shared/components/Layout/PageContainer/PageContainer.tsx deleted file mode 100644 index 523f60366..000000000 --- a/web/src/shared/components/Layout/PageContainer/PageContainer.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import './style.scss'; - -import clsx from 'clsx'; -import type { ComponentProps } from 'react'; -import { useNavigationStore } from '../../../../components/Navigation/hooks/useNavigationStore'; - -type Props = { - withDefaultPadding?: boolean; -} & ComponentProps<'div'>; - -export const PageContainer = ({ - children, - className, - ref, - withDefaultPadding = false, - ...rest -}: Props) => { - const isNavOpen = useNavigationStore((state) => state.isOpen); - return ( -
-
- {children} -
-
- ); -}; diff --git a/web/src/shared/components/Layout/PageContainer/style.scss b/web/src/shared/components/Layout/PageContainer/style.scss deleted file mode 100644 index 2c1b17be2..000000000 --- a/web/src/shared/components/Layout/PageContainer/style.scss +++ /dev/null @@ -1,38 +0,0 @@ -@use '@scssutils' as *; - -.page-container { - min-height: inherit; - - @include media-breakpoint-down(lg) { - height: 100%; - max-height: inherit; - box-sizing: border-box; - position: relative; - } - - @include media-breakpoint-up(lg) { - margin-left: 0; - width: 100%; - height: inherit; - max-height: inherit; - - & > .page-content { - width: calc(100% - 87px); - margin-left: 87px; - - &.nav-open { - margin-left: 230px; - width: calc(100% - 230px); - } - } - } -} - -.page-container > .page-content.default-padding { - box-sizing: border-box; - padding: var(--spacing-m) var(--spacing-s) var(--spacing-m); - - @include media-breakpoint-up(lg) { - padding: var(--spacing-xl) var(--spacing-xl) var(--spacing-m); - } -} diff --git a/web/src/shared/components/Layout/PageLayout/PageLayout.tsx b/web/src/shared/components/Layout/PageLayout/PageLayout.tsx deleted file mode 100644 index b8c4880a5..000000000 --- a/web/src/shared/components/Layout/PageLayout/PageLayout.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import './style.scss'; - -import clsx from 'clsx'; -import type { PropsWithChildren } from 'react'; - -import { PageContainer } from '../PageContainer/PageContainer'; - -type Props = { - id: string; - className?: string; - withDefaultPadding?: boolean; -} & PropsWithChildren; - -export const PageLayout = ({ - id, - className, - children, - withDefaultPadding = false, -}: Props) => { - return ( - - {children} - - ); -}; diff --git a/web/src/shared/components/Layout/PageLayout/style.scss b/web/src/shared/components/Layout/PageLayout/style.scss deleted file mode 100644 index 550dbdeb6..000000000 --- a/web/src/shared/components/Layout/PageLayout/style.scss +++ /dev/null @@ -1,26 +0,0 @@ -.page-layout { - & > .page-content { - box-sizing: border-box; - padding: var(--spacing-s); - - @include media-breakpoint-up(md) { - padding: var(--spacing-m); - } - - @include media-breakpoint-up(lg) { - padding: var(--spacing-xl); - } - - h1 { - @include typography(app-title); - } - - h2 { - @include typography(app-body-1); - } - - h3 { - @include typography(app-side-bar); - } - } -} diff --git a/web/src/shared/components/Layout/PageLimiter/PageLimiter.tsx b/web/src/shared/components/Layout/PageLimiter/PageLimiter.tsx deleted file mode 100644 index c34347a1f..000000000 --- a/web/src/shared/components/Layout/PageLimiter/PageLimiter.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import './style.scss'; - -import clsx from 'clsx'; -import type { HTMLAttributes, PropsWithChildren } from 'react'; - -import { useNavigationStore } from '../../../../components/Navigation/hooks/useNavigationStore'; - -type Props = PropsWithChildren & HTMLAttributes; - -export const PageLimiter = ({ children, className, ...divProps }: Props) => { - const navOpen = useNavigationStore((s) => s.isOpen); - - return ( -
-
{children}
-
- ); -}; diff --git a/web/src/shared/components/Layout/PageLimiter/style.scss b/web/src/shared/components/Layout/PageLimiter/style.scss deleted file mode 100644 index a4ffd1161..000000000 --- a/web/src/shared/components/Layout/PageLimiter/style.scss +++ /dev/null @@ -1,20 +0,0 @@ -.page-limiter { - width: 100%; - display: flex; - flex-flow: row; - align-items: center; - justify-content: center; - box-sizing: border-box; - padding: var(--spacing-l) var(--spacing-s); - - & > .page-limited-content { - width: 100%; - max-width: calc(var(--page-content-limit) + 142px); - } - - &.nav-open { - & > .page-limited-content { - max-width: var(--page-content-limit); - } - } -} diff --git a/web/src/shared/components/Layout/RenderMarkdown/RenderMarkdown.tsx b/web/src/shared/components/Layout/RenderMarkdown/RenderMarkdown.tsx deleted file mode 100644 index 5c9aaa6e8..000000000 --- a/web/src/shared/components/Layout/RenderMarkdown/RenderMarkdown.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import Markdown from 'react-markdown'; -import rehypeExternalLinks from 'rehype-external-links'; -import rehypeRaw from 'rehype-raw'; -import rehypeSanitize, { defaultSchema } from 'rehype-sanitize'; - -type Props = { - content: string; -}; - -const sanitizeSchema = { - ...defaultSchema, - tagNames: [...(defaultSchema.tagNames ?? []), 'div', 'br'], // Allow
tags - attributes: { - ...defaultSchema.attributes, - div: ['style'], // Allow `style` attribute on
- a: ['href', 'target'], // Allow `href` and `target` on - }, -}; - -export const RenderMarkdown = ({ content }: Props) => { - const parse = (): string => { - const lines = content.split(/\r?\n/); - - // Trim all lines and handle empty lines - const processedLines = lines.map((line) => { - const trimmedLine = line.trim(); - return trimmedLine === '' ? '' : trimmedLine; // Replace empty lines with empty strings - }); - - // Remove the first line if it's empty - if (processedLines.length > 0 && processedLines[0] === '') { - processedLines.shift(); - } - - // Remove the last line if it's empty - if (processedLines.length > 0 && processedLines[processedLines.length - 1] === '') { - processedLines.pop(); - } - - return processedLines.join('\n'); - }; - return ( - - {parse()} - - ); -}; diff --git a/web/src/shared/components/Layout/SectionWithCard/SectionWithCard.tsx b/web/src/shared/components/Layout/SectionWithCard/SectionWithCard.tsx deleted file mode 100644 index b49170f7a..000000000 --- a/web/src/shared/components/Layout/SectionWithCard/SectionWithCard.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import './style.scss'; - -import clsx from 'clsx'; -import type { HTMLAttributes, PropsWithChildren } from 'react'; - -import { Card } from '../../../defguard-ui/components/Layout/Card/Card'; - -type Props = { title: string } & HTMLAttributes & PropsWithChildren; - -export const SectionWithCard = ({ - title, - className, - children, - ...containerProps -}: Props) => { - return ( -
-

{title}

- {children} -
- ); -}; diff --git a/web/src/shared/components/Layout/SectionWithCard/style.scss b/web/src/shared/components/Layout/SectionWithCard/style.scss deleted file mode 100644 index e8d45a77b..000000000 --- a/web/src/shared/components/Layout/SectionWithCard/style.scss +++ /dev/null @@ -1,12 +0,0 @@ -.section-with-title { - & > h2 { - @include typography(body-1); - padding-bottom: var(--spacing-s); - } - - & > .card { - box-sizing: border-box; - width: 100%; - padding: var(--spacing-m); - } -} diff --git a/web/src/shared/components/Layout/UpgradeLicenseModal/UpgradeLicenseModal.tsx b/web/src/shared/components/Layout/UpgradeLicenseModal/UpgradeLicenseModal.tsx deleted file mode 100644 index b1347d232..000000000 --- a/web/src/shared/components/Layout/UpgradeLicenseModal/UpgradeLicenseModal.tsx +++ /dev/null @@ -1,135 +0,0 @@ -import './style.scss'; - -import { useId, useMemo } from 'react'; -import { shallow } from 'zustand/shallow'; - -import { useI18nContext } from '../../../../i18n/i18n-react'; -import { Button } from '../../../defguard-ui/components/Layout/Button/Button'; -import { - ButtonSize, - ButtonStyleVariant, -} from '../../../defguard-ui/components/Layout/Button/types'; -import { Modal } from '../../../defguard-ui/components/Layout/modals/Modal/Modal'; -import { useAuthStore } from '../../../hooks/store/useAuthStore'; -import { externalLink } from '../../../links'; -import { RenderMarkdown } from '../RenderMarkdown/RenderMarkdown'; -import checkboxUrl from './checkbox.svg?url'; -import { useUpgradeLicenseModal } from './store'; -import { UpgradeLicenseModalVariant } from './types'; - -export const UpgradeLicenseModal = () => { - const isAdmin = useAuthStore((s) => s.user?.is_admin ?? false); - const isOpen = useUpgradeLicenseModal((s) => s.visible); - const [close, reset] = useUpgradeLicenseModal((s) => [s.close, s.reset], shallow); - - return ( - - - - ); -}; - -const ModalContent = () => { - const variant = useUpgradeLicenseModal((s) => s.modalVariant); - const { LL } = useI18nContext(); - const localLL = LL.modals.upgradeLicenseModal; - const close = useUpgradeLicenseModal((s) => s.close, shallow); - - const [title, subtitle] = useMemo(() => { - switch (variant) { - case UpgradeLicenseModalVariant.ENTERPRISE_NOTICE: - return [localLL.enterprise.title(), localLL.enterprise.subTitle()]; - case UpgradeLicenseModalVariant.LICENSE_LIMIT: - return [localLL.limit.title(), localLL.limit.subTitle()]; - } - }, [localLL.enterprise, localLL.limit, variant]); - - return ( - <> -
-

{title}

-
-
- -
-
- - -
-
- - ); -}; - -const DecoratorSvg = () => { - const id = useId(); - return ( - - - - - - - - - - - - ); -}; diff --git a/web/src/shared/components/Layout/UpgradeLicenseModal/checkbox.svg b/web/src/shared/components/Layout/UpgradeLicenseModal/checkbox.svg deleted file mode 100644 index dfdbc53ab..000000000 --- a/web/src/shared/components/Layout/UpgradeLicenseModal/checkbox.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/web/src/shared/components/Layout/UpgradeLicenseModal/store.tsx b/web/src/shared/components/Layout/UpgradeLicenseModal/store.tsx deleted file mode 100644 index cc6d8fc83..000000000 --- a/web/src/shared/components/Layout/UpgradeLicenseModal/store.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import dayjs from 'dayjs'; -import { persist } from 'zustand/middleware'; -import { createWithEqualityFn } from 'zustand/traditional'; - -import { UpgradeLicenseModalVariant } from './types'; - -//minutes -const modalTimeout = 30; - -const defaults: StoreValues = { - visible: false, - modalVariant: UpgradeLicenseModalVariant.ENTERPRISE_NOTICE, - lastClosed: undefined, -}; - -export const useUpgradeLicenseModal = createWithEqualityFn()( - persist( - (set, get) => ({ - ...defaults, - open: ({ modalVariant }) => { - const { lastClosed } = get(); - if ( - lastClosed !== undefined && - modalVariant === UpgradeLicenseModalVariant.LICENSE_LIMIT - ) { - const past = dayjs(lastClosed).utc(); - const now = dayjs().utc(); - const diff = now.diff(past, 'minutes'); - if (diff >= modalTimeout) { - set({ visible: true, modalVariant }); - } - } else { - set({ visible: true, modalVariant }); - } - }, - close: () => { - const { modalVariant } = get(); - if (modalVariant === UpgradeLicenseModalVariant.LICENSE_LIMIT) { - set({ visible: false, lastClosed: dayjs().utc().toISOString() }); - } else { - set({ visible: false }); - } - }, - reset: () => - set({ visible: defaults.visible, modalVariant: defaults.modalVariant }), - }), - { - name: 'upgrade-license-modal', - version: 1, - partialize: (s) => ({ lastOpened: s.lastClosed }), - }, - ), - Object.is, -); - -type Store = StoreValues & StoreMethods; -type StoreValues = { - visible: boolean; - modalVariant: UpgradeLicenseModalVariant; - lastClosed?: string; -}; -type Open = Pick; - -type StoreMethods = { - open: (modalVariant: Open) => void; - close: () => void; - reset: () => void; -}; diff --git a/web/src/shared/components/Layout/UpgradeLicenseModal/style.scss b/web/src/shared/components/Layout/UpgradeLicenseModal/style.scss deleted file mode 100644 index 0c767f6ee..000000000 --- a/web/src/shared/components/Layout/UpgradeLicenseModal/style.scss +++ /dev/null @@ -1,130 +0,0 @@ -.modal { - &.upgrade-license-modal { - @include media-breakpoint-up(lg) { - padding: 154px 0 40px !important; - align-items: center; - justify-items: center; - } - } -} - -#upgrade-license-modal-content { - background-color: var(--surface-nav-bg); - max-width: 1026px; - width: 100%; - overflow: hidden; - - @include media-breakpoint-down(lg) { - min-height: 100dvh; - } - - @include media-breakpoint-up(lg) { - box-shadow: 0px 12px 24px 0px rgba(0, 0, 0, 0.08); - border-radius: 15px; - } - - & > .title { - display: flex; - flex-flow: row; - align-content: center; - align-items: center; - justify-content: center; - box-sizing: border-box; - - padding: 25px 20px 10px; - - @include media-breakpoint-up(lg) { - padding: 56px 20px 10px; - } - - p { - @include typography(app-title); - } - } - - & > .image-container { - align-items: center; - display: flex; - flex-flow: row; - justify-content: center; - - padding-bottom: 20px; - - @include media-breakpoint-up(lg) { - padding-bottom: 45px; - } - } - - .controls { - width: 100%; - box-sizing: border-box; - display: flex; - align-items: center; - justify-content: center; - padding: 0 20px 20px; - flex-flow: column-reverse; - gap: 18px; - - @include media-breakpoint-up(lg) { - padding: 0 50px 50px; - flex-flow: row; - } - - .btn, - a { - width: 100%; - } - - a { - text-decoration: none; - } - } - - & > .content { - box-sizing: border-box; - padding: 0 20px 40px; - - & > :nth-child(1) { - padding-bottom: 20px; - } - - @include media-breakpoint-up(lg) { - padding: 0 50px 40px; - - & > :nth-child(1) { - padding-bottom: 40px; - } - } - - p { - @include typography(app-welcome-2); - } - - ul { - padding-left: 28px; - list-style: none; - padding-bottom: 32px; - - @include media-breakpoint-up(lg) { - padding-bottom: 40px; - } - - li { - position: relative; - @include typography(app-welcome-2); - - &::before { - user-select: none; - position: absolute; - left: -24px; - top: 50%; - transform: translateY(-50%); - content: ' '; - width: 18px; - height: 18px; - background: var(--list-image); - } - } - } - } -} diff --git a/web/src/shared/components/Layout/UpgradeLicenseModal/types.ts b/web/src/shared/components/Layout/UpgradeLicenseModal/types.ts deleted file mode 100644 index 0f52624b3..000000000 --- a/web/src/shared/components/Layout/UpgradeLicenseModal/types.ts +++ /dev/null @@ -1,6 +0,0 @@ -export enum UpgradeLicenseModalVariant { - // when user try to use feature locked behind enterprise - ENTERPRISE_NOTICE, - // when current limits - LICENSE_LIMIT, -} diff --git a/web/src/shared/components/Layout/VersionUpdateToast/VersionUpdateToast.tsx b/web/src/shared/components/Layout/VersionUpdateToast/VersionUpdateToast.tsx deleted file mode 100644 index 79d40db00..000000000 --- a/web/src/shared/components/Layout/VersionUpdateToast/VersionUpdateToast.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import './style.scss'; - -import dayjs from 'dayjs'; -import { useCallback, useEffect } from 'react'; - -import { useI18nContext } from '../../../../i18n/i18n-react'; -import type { ToastOptions } from '../../../defguard-ui/components/Layout/ToastManager/Toast/types'; -import { useToastsStore } from '../../../defguard-ui/hooks/toasts/useToastStore'; -import { useUpdatesStore } from '../../../hooks/store/useUpdatesStore'; - -export const VersionUpdateToast = ({ id }: ToastOptions) => { - const removeToast = useToastsStore((s) => s.removeToast); - const updateData = useUpdatesStore((s) => s.update); - const dismissal = useUpdatesStore((s) => s.dismissal); - const setUpdateStore = useUpdatesStore((s) => s.setStore); - const { LL } = useI18nContext(); - - const closeToast = useCallback(() => { - removeToast(id); - }, [id, removeToast]); - - const handleOpenModal = () => { - setUpdateStore({ modalVisible: true }); - closeToast(); - }; - - const handleDismiss = () => { - if (updateData) { - setUpdateStore({ - dismissal: { - dismissedAt: dayjs.utc().toISOString(), - version: updateData.version, - }, - }); - closeToast(); - } - }; - - useEffect(() => { - if (dismissal && dismissal.version === updateData?.version) { - closeToast(); - } - }, [closeToast, dismissal, updateData?.version]); - - if (!updateData) return null; - - return ( -
-
-

- {LL.modals.updatesNotificationToaster.title({ - version: updateData.version, - })} -

- {updateData.critical && ( - - - - - )} - - - - - - -
-
- - -
-
- ); -}; diff --git a/web/src/shared/components/Layout/VersionUpdateToast/style.scss b/web/src/shared/components/Layout/VersionUpdateToast/style.scss deleted file mode 100644 index a8f777be7..000000000 --- a/web/src/shared/components/Layout/VersionUpdateToast/style.scss +++ /dev/null @@ -1,63 +0,0 @@ -.update-toaster { - box-sizing: border-box; - padding: 10px 20px 15px; - box-shadow: 0px 6px 10px 0px rgba(0, 0, 0, 0.1); - border-radius: 10px; - background-color: var(--surface-nav-bg); - border-radius: 15px; - - min-width: 270px; - max-width: 100%; - - .top { - padding-bottom: 8px; - display: flex; - align-items: center; - flex-flow: row nowrap; - min-width: 230px; - column-gap: 8px; - - p { - color: var(--text-body-primary); - text-wrap: nowrap; - white-space: none; - @include typography(app-number); - } - - & > a { - margin-left: auto; - border: none; - background-color: transparent; - cursor: pointer; - width: 22px; - height: 22px; - display: inline-flex; - flex-flow: row; - align-content: center; - align-items: center; - justify-content: center; - padding: 4px; - box-sizing: border-box; - text-decoration: none; - } - } - - .bottom { - min-width: 230px; - display: flex; - flex-flow: row; - align-items: center; - justify-content: space-between; - - & > * { - cursor: pointer; - } - - button { - border: none; - background: transparent; - color: var(--text-body-primary); - @include typography(app-copyright); - } - } -} diff --git a/web/src/shared/components/Layout/VersionUpdateToast/types.ts b/web/src/shared/components/Layout/VersionUpdateToast/types.ts deleted file mode 100644 index 8ea128bf9..000000000 --- a/web/src/shared/components/Layout/VersionUpdateToast/types.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { z } from 'zod'; - -export const versionUpdateToastMetaSchema = z.object({ - customId: z.string().trim().min(1), -}); - -export type VersionUpdateToastMeta = z.infer; diff --git a/web/src/shared/components/Layout/WireguardConfigExpandable/WireguardConfigExpandable.tsx b/web/src/shared/components/Layout/WireguardConfigExpandable/WireguardConfigExpandable.tsx deleted file mode 100644 index 998b026f1..000000000 --- a/web/src/shared/components/Layout/WireguardConfigExpandable/WireguardConfigExpandable.tsx +++ /dev/null @@ -1,113 +0,0 @@ -import './style.scss'; - -import { Fragment, type ReactNode, useCallback, useMemo, useState } from 'react'; -import QRCode from 'react-qr-code'; - -import { useI18nContext } from '../../../../i18n/i18n-react'; -import { ActionButton } from '../../../defguard-ui/components/Layout/ActionButton/ActionButton'; -import { ActionButtonVariant } from '../../../defguard-ui/components/Layout/ActionButton/types'; -import { ExpandableCard } from '../../../defguard-ui/components/Layout/ExpandableCard/ExpandableCard'; -import { useClipboard } from '../../../hooks/useClipboard'; -import { downloadWGConfig } from '../../../utils/downloadWGConfig'; - -type Props = { - config: string; - publicKey: string; - deviceName: string; - privateKey?: string; -}; - -enum ConfigCardView { - FILE, - QR, -} - -export const WireguardConfigExpandable = ({ - config, - deviceName, - publicKey, - privateKey, -}: Props) => { - const { LL } = useI18nContext(); - const { writeToClipboard } = useClipboard(); - const localLL = LL.modals.addStandaloneDevice.steps.manual.finish; - const [view, setView] = useState(ConfigCardView.FILE); - - const configForExport = useMemo(() => { - if (privateKey) { - return config.replace('YOUR_PRIVATE_KEY', privateKey); - } - return config; - }, [config, privateKey]); - - const getQRConfig = useMemo((): string => { - if (privateKey) { - return config.replace('YOUR_PRIVATE_KEY', privateKey); - } - return config.replace('YOUR_PRIVATE_KEY', publicKey); - }, [config, privateKey, publicKey]); - - const renderTextConfig = () => { - const content = configForExport.split('\n'); - return ( -

- {content.map((text, index) => ( - - {text} - {index !== content.length - 1 &&
} -
- ))} -

- ); - }; - - const handleConfigCopy = useCallback(() => { - void writeToClipboard( - configForExport, - LL.components.deviceConfigsCard.messages.copyConfig(), - ); - }, [LL.components.deviceConfigsCard.messages, configForExport, writeToClipboard]); - - const handleConfigDownload = useCallback(() => { - downloadWGConfig(configForExport, deviceName.toLowerCase().replace(' ', '-')); - }, [configForExport, deviceName]); - - const actions = useMemo( - (): ReactNode[] => [ - setView(ConfigCardView.FILE)} - />, - setView(ConfigCardView.QR)} - />, - , - , - ], - [handleConfigCopy, handleConfigDownload, view], - ); - return ( - - {view === ConfigCardView.FILE && renderTextConfig()} - {view === ConfigCardView.QR && } - - ); -}; diff --git a/web/src/shared/components/Layout/WireguardConfigExpandable/style.scss b/web/src/shared/components/Layout/WireguardConfigExpandable/style.scss deleted file mode 100644 index 62957360e..000000000 --- a/web/src/shared/components/Layout/WireguardConfigExpandable/style.scss +++ /dev/null @@ -1,18 +0,0 @@ -.wireguard-config-card { - .expanded-content { - display: flex; - flex-flow: column; - align-items: center; - - .config { - width: 100%; - @include typography(app-code); - color: var(--text-button-primary); - - span { - @include typography(app-code); - color: var(--text-button-primary); - } - } - } -} diff --git a/web/src/shared/components/Layout/buttons/AddButton/AddButton.tsx b/web/src/shared/components/Layout/buttons/AddButton/AddButton.tsx deleted file mode 100644 index 66152a268..000000000 --- a/web/src/shared/components/Layout/buttons/AddButton/AddButton.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import './style.scss'; - -import clsx from 'clsx'; - -import { useI18nContext } from '../../../../../i18n/i18n-react'; -import { Button } from '../../../../defguard-ui/components/Layout/Button/Button'; -import { - ButtonSize, - ButtonStyleVariant, -} from '../../../../defguard-ui/components/Layout/Button/types'; -import SvgIconPlusWhite from '../../../svg/IconPlusWhite'; - -type Props = { - onClick?: () => void; - loading?: boolean; - text?: string; - className?: string; -}; - -export const AddButton = ({ loading, onClick, text, className }: Props) => { - const { LL } = useI18nContext(); - const defaultText = LL.common.controls.addNew(); - - return ( - + +
+ ); +}; diff --git a/web/src/shared/components/SelectMultiple/style.scss b/web/src/shared/components/SelectMultiple/style.scss new file mode 100644 index 000000000..9a99ad836 --- /dev/null +++ b/web/src/shared/components/SelectMultiple/style.scss @@ -0,0 +1,17 @@ +.select-multiple .fold-content { + .selected { + display: flex; + flex-flow: row wrap; + align-items: center; + justify-content: flex-start; + gap: var(--spacing-sm); + } + + & > button { + background-color: transparent; + border: 0; + font: var(--t-body-sm-400); + color: var(--fg-action); + cursor: pointer; + } +} diff --git a/web/src/shared/components/SelectMultiple/types.ts b/web/src/shared/components/SelectMultiple/types.ts new file mode 100644 index 000000000..0663bd219 --- /dev/null +++ b/web/src/shared/components/SelectMultiple/types.ts @@ -0,0 +1,11 @@ +import type { SelectionKey, SelectionOption } from '../SelectionSection/type'; + +export type SelectMultipleProps = { + options: SelectionOption[]; + selected: Set; + modalTitle: string; + editText: string; + toggleText: string; + counterText: (count: number) => string; + onChange: (value: Array) => void; +}; diff --git a/web/src/shared/components/SelectionSection/SelectionSection.tsx b/web/src/shared/components/SelectionSection/SelectionSection.tsx new file mode 100644 index 000000000..cab190856 --- /dev/null +++ b/web/src/shared/components/SelectionSection/SelectionSection.tsx @@ -0,0 +1,154 @@ +import { useCallback, useMemo, useState } from 'react'; +import './style.scss'; +import clsx from 'clsx'; +import { orderBy } from 'lodash-es'; +import { m } from '../../../paraglide/messages'; +import { Checkbox } from '../../defguard-ui/components/Checkbox/Checkbox'; +import { Divider } from '../../defguard-ui/components/Divider/Divider'; +import { EmptyState } from '../../defguard-ui/components/EmptyState/EmptyState'; +import { Search } from '../../defguard-ui/components/Search/Search'; +import { SizedBox } from '../../defguard-ui/components/SizedBox/SizedBox'; +import { Toggle } from '../../defguard-ui/components/Toggle/Toggle'; +import { ThemeSpacing } from '../../defguard-ui/types'; +import type { SelectionKey, SelectionOption, SelectionSectionProps } from './type'; + +//TODO: virtualize items +export const SelectionSection = ({ + onChange, + options, + selection, + className, + id, + itemGap = 8, + itemHeight = 24, +}: SelectionSectionProps) => { + const [onlySelected, setOnlySelected] = useState(false); + const [search, setSearch] = useState(''); + const searching = search.trim().length > 0; + + const visibleOptions = useMemo(() => { + let res = options; + if (onlySelected) { + res = res.filter((o) => selection.has(o.id)); + } + const trimmedSearch = search.trim().toLowerCase(); + if (trimmedSearch) { + res = res.filter((option) => { + if (option.searchFields) { + return option.searchFields.some((val) => + val.toLowerCase().includes(trimmedSearch), + ); + } + return option.label.toLowerCase().includes(trimmedSearch); + }); + } + return res; + }, [options, onlySelected, selection, search.trim]); + + const handleSelectAll = useCallback(() => { + const allSelected = selection.size === options.length; + if (allSelected) { + onChange(new Set()); + } else { + const all = options.map((o) => o.id); + onChange(new Set(all)); + } + }, [selection.size, onChange, options]); + + const handleSelect = useCallback( + (option: SelectionOption, selected: boolean, selection: Set) => { + const clone = new Set(selection); + if (selected) { + clone.delete(option.id); + if (!clone.size && onlySelected) { + setOnlySelected(false); + } + } else { + clone.add(option.id); + } + onChange(clone); + }, + [onChange, onlySelected], + ); + + const maxHeight = useMemo(() => itemHeight * 10 + itemGap * 9, [itemGap, itemHeight]); + + return ( +
+ + +
+ +
+ setOnlySelected((s) => !s)} + disabled={selection.size === 0} + /> +
+
+ + {searching && visibleOptions.length === 0 && ( + <> + + + + + )} + {visibleOptions.length > 0 && ( +
+
+ {orderBy( + visibleOptions, + (item) => item.label.toLowerCase().replaceAll(' ', ''), + ['asc'], + ).map((option) => { + const selected = selection.has(option.id); + return ( +
+ { + handleSelect(option, selected, selection); + }} + /> +
+ ); + })} +
+
+ )} +
+ ); +}; diff --git a/web/src/shared/components/SelectionSection/style.scss b/web/src/shared/components/SelectionSection/style.scss new file mode 100644 index 000000000..91ec2880c --- /dev/null +++ b/web/src/shared/components/SelectionSection/style.scss @@ -0,0 +1,43 @@ +.selection-section { + .actions { + display: flex; + flex-flow: row nowrap; + align-items: center; + justify-content: flex-start; + column-gap: var(--spacing-xl); + + & > .right { + margin-left: auto; + + .selected-filter { + display: flex; + flex-flow: row; + align-items: center; + justify-content: flex-end; + flex: none; + column-gap: var(--spacing-sm); + cursor: pointer; + + & > span { + font: var(--t-body-sm-400); + color: var(--fg-neutral); + } + } + } + } + + .items-box { + overflow: hidden auto; + + & > .inner { + display: flex; + flex-flow: column; + justify-content: flex-start; + + & > .item { + display: flex; + flex: none; + } + } + } +} diff --git a/web/src/shared/components/SelectionSection/type.ts b/web/src/shared/components/SelectionSection/type.ts new file mode 100644 index 000000000..ebf2828f6 --- /dev/null +++ b/web/src/shared/components/SelectionSection/type.ts @@ -0,0 +1,19 @@ +export type SelectionKey = string | number; + +export type SelectionOption = { + id: T; + label: string; + meta?: unknown; + // if there is a need to search in more then label itself + searchFields?: string[]; +}; + +export interface SelectionSectionProps { + selection: Set; + onChange: (value: Set) => void; + options: SelectionOption[]; + itemHeight?: number; + itemGap?: number; + className?: string; + id?: string; +} diff --git a/web/src/shared/components/SettingsCard/SettingsCard.tsx b/web/src/shared/components/SettingsCard/SettingsCard.tsx new file mode 100644 index 000000000..43a5cce80 --- /dev/null +++ b/web/src/shared/components/SettingsCard/SettingsCard.tsx @@ -0,0 +1,13 @@ +import type { HTMLProps } from 'react'; +import './style.scss'; +import clsx from 'clsx'; + +interface Props extends HTMLProps {} + +export const SettingsCard = ({ className, children, ...props }: Props) => { + return ( +
+ {children} +
+ ); +}; diff --git a/web/src/shared/components/SettingsCard/style.scss b/web/src/shared/components/SettingsCard/style.scss new file mode 100644 index 000000000..ef4ad7fbb --- /dev/null +++ b/web/src/shared/components/SettingsCard/style.scss @@ -0,0 +1,10 @@ +.settings-card { + border-radius: var(--radius-lg); + border: var(--border-1) solid var(--border-disabled); + padding: var(--spacing-xl); + box-sizing: border-box; + + .controls { + padding-top: var(--spacing-2xl); + } +} diff --git a/web/src/shared/components/SettingsHeader/SettingsHeader.tsx b/web/src/shared/components/SettingsHeader/SettingsHeader.tsx new file mode 100644 index 000000000..99a8f4938 --- /dev/null +++ b/web/src/shared/components/SettingsHeader/SettingsHeader.tsx @@ -0,0 +1,41 @@ +import { Badge } from '../../defguard-ui/components/Badge/Badge'; +import type { BadgeProps } from '../../defguard-ui/components/Badge/types'; +import { Icon } from '../../defguard-ui/components/Icon'; +import type { IconKindValue } from '../../defguard-ui/components/Icon/icon-types'; +import { ThemeVariable } from '../../defguard-ui/types'; +import { isPresent } from '../../defguard-ui/utils/isPresent'; +import './style.scss'; + +interface SettingsHeaderProps { + icon: IconKindValue; + badgeProps?: BadgeProps; + title: string; + subtitle: string; +} + +export const SettingsHeader = ({ + icon, + badgeProps, + title, + subtitle, +}: SettingsHeaderProps) => { + return ( +
+
+
+
+
+ +
+
+
+
+

{title}

+ {isPresent(badgeProps) && } +
+

{subtitle}

+
+
+
+ ); +}; diff --git a/web/src/shared/components/SettingsHeader/style.scss b/web/src/shared/components/SettingsHeader/style.scss new file mode 100644 index 000000000..c12c39508 --- /dev/null +++ b/web/src/shared/components/SettingsHeader/style.scss @@ -0,0 +1,61 @@ +.settings-header { + padding-bottom: var(--spacing-2xl); + + & > .inner-track { + display: grid; + grid-template-columns: 32px 1fr; + grid-template-rows: 1fr; + column-gap: var(--spacing-xl); + align-items: start; + justify-content: start; + + & > .icon-track { + .icon-wrap { + display: grid; + grid-template-columns: 32px; + grid-template-rows: 32px; + place-items: center center; + + & > * { + grid-row: 1; + grid-column: 1 / 2; + } + + .bg { + display: block; + content: ' '; + pointer-events: none; + background-color: var(--bg-action-muted); + border-radius: var(--radius-full); + width: 100%; + height: 100%; + } + + .icon path { + fill: var(--fg-action); + } + } + } + + & > .content-track { + & > .top { + display: flex; + flex-flow: row; + align-items: center; + justify-content: flex-start; + gap: var(--spacing-md); + + h4 { + font: var(--t-title-h4); + color: var(--fg-default); + } + } + + & > p { + padding-top: var(--spacing-xs); + font: var(--t-body-sm-400); + color: var(--fg-faded); + } + } + } +} diff --git a/web/src/shared/components/SettingsLayout/SettingsLayout.tsx b/web/src/shared/components/SettingsLayout/SettingsLayout.tsx new file mode 100644 index 000000000..4dbeb0f6a --- /dev/null +++ b/web/src/shared/components/SettingsLayout/SettingsLayout.tsx @@ -0,0 +1,20 @@ +import type { HTMLProps, PropsWithChildren } from 'react'; +import './style.scss'; +import clsx from 'clsx'; +import { LayoutGrid } from '../LayoutGrid/LayoutGrid'; + +export const SettingsLayout = ({ + children, + className, + ...props +}: PropsWithChildren & HTMLProps) => { + return ( +
+ +
+ {children} +
+
+
+ ); +}; diff --git a/web/src/shared/components/SettingsLayout/style.scss b/web/src/shared/components/SettingsLayout/style.scss new file mode 100644 index 000000000..83dac2a49 --- /dev/null +++ b/web/src/shared/components/SettingsLayout/style.scss @@ -0,0 +1,11 @@ +.settings-layout { + & > .layout-grid { + box-sizing: border-box; + padding: var(--spacing-3xl) var(--spacing-xl); + + & > .main-content { + grid-column: 1 / 7; + grid-row: 1; + } + } +} diff --git a/web/src/shared/components/TableValuesListCell/TableValuesListCell.tsx b/web/src/shared/components/TableValuesListCell/TableValuesListCell.tsx new file mode 100644 index 000000000..55683c685 --- /dev/null +++ b/web/src/shared/components/TableValuesListCell/TableValuesListCell.tsx @@ -0,0 +1,34 @@ +import './style.scss'; +import clsx from 'clsx'; +import { TableCell } from '../../defguard-ui/components/table/TableCell/TableCell'; +import type { TableCellProps } from '../../defguard-ui/components/table/TableCell/types'; +import { openModal } from '../../hooks/modalControls/modalsSubjects'; +import { ModalName } from '../../hooks/modalControls/modalTypes'; + +interface Props extends TableCellProps { + values: string[]; +} + +export const TableValuesListCell = ({ values, className, ...cellProps }: Props) => { + const clickable = values.length > 2; + + return ( + { + e.preventDefault(); + e.stopPropagation(); + if (clickable) { + openModal(ModalName.DisplayList, { + data: values, + }); + } + }} + > + {values.join(', ')} + + ); +}; diff --git a/web/src/shared/components/TableValuesListCell/style.scss b/web/src/shared/components/TableValuesListCell/style.scss new file mode 100644 index 000000000..045552028 --- /dev/null +++ b/web/src/shared/components/TableValuesListCell/style.scss @@ -0,0 +1,5 @@ +.table-cell.values-list { + &.clickable { + cursor: help; + } +} diff --git a/web/src/shared/components/TransferChart/TransferChart.tsx b/web/src/shared/components/TransferChart/TransferChart.tsx new file mode 100644 index 000000000..f091a8f67 --- /dev/null +++ b/web/src/shared/components/TransferChart/TransferChart.tsx @@ -0,0 +1,45 @@ +import { Bar, BarChart, ResponsiveContainer, XAxis, YAxis } from 'recharts'; +import type { TransferChartData } from '../../utils/stats'; +import './style.scss'; +import dayjs from 'dayjs'; +import { ThemeVariable } from '../../defguard-ui/types'; + +type Props = { + data: TransferChartData[]; + height?: number; + showX?: boolean; + barGap?: number; +}; + +export const TransferChart = ({ data, showX, height = 50, barGap = 4 }: Props) => { + return ( +
+ + + {showX && ( + dayjs.unix(timestamp).format('HH:mm')} + tickLine={false} + axisLine={false} + padding={{ + left: 0, + right: 0, + }} + tickMargin={8} + height={23} + /> + )} + + + + + +
+ ); +}; diff --git a/web/src/shared/components/TransferChart/style.scss b/web/src/shared/components/TransferChart/style.scss new file mode 100644 index 000000000..8c9c9d242 --- /dev/null +++ b/web/src/shared/components/TransferChart/style.scss @@ -0,0 +1,6 @@ +.transfer-chart { + .recharts-cartesian-axis-tick-value tspan { + font: var(--t-body-xxs-400); + color: var(--fg-neutral); + } +} diff --git a/web/src/shared/components/TransferText/TransferText.tsx b/web/src/shared/components/TransferText/TransferText.tsx new file mode 100644 index 000000000..f11ec47b7 --- /dev/null +++ b/web/src/shared/components/TransferText/TransferText.tsx @@ -0,0 +1,27 @@ +import byteSize from 'byte-size'; +import clsx from 'clsx'; +import './style.scss'; +import { Icon } from '../../defguard-ui/components/Icon'; + +type Props = { + variant?: 'upload' | 'download'; + data: number; + icon?: boolean; +}; + +export const TransferText = ({ data, variant, icon }: Props) => { + const size = byteSize(data, { precision: 1 }); + + return ( +
+ {icon && ( + + )} + {`${size.value} ${size.unit}`} +
+ ); +}; diff --git a/web/src/shared/components/TransferText/style.scss b/web/src/shared/components/TransferText/style.scss new file mode 100644 index 000000000..49e73cf88 --- /dev/null +++ b/web/src/shared/components/TransferText/style.scss @@ -0,0 +1,25 @@ +.transfer-text { + --color: var(--fg-action); + + display: flex; + flex-flow: row nowrap; + align-items: center; + column-gap: var(--spacing-xs); + + &.upload { + --color: var(--fg-critical); + } + + &.download { + --color: var(--fg-action); + } + + .icon path { + fill: var(--color); + } + + span { + font: var(--t-body-xs-500); + color: var(--color); + } +} diff --git a/web/src/shared/components/UploadField/UploadField.tsx b/web/src/shared/components/UploadField/UploadField.tsx new file mode 100644 index 000000000..8396cb338 --- /dev/null +++ b/web/src/shared/components/UploadField/UploadField.tsx @@ -0,0 +1,68 @@ +import clsx from 'clsx'; +import './style.scss'; +import { useMutation } from '@tanstack/react-query'; +import { getFile } from 'easy-file-picker'; +import { m } from '../../../paraglide/messages'; +import { Button } from '../../defguard-ui/components/Button/Button'; +import { FieldError } from '../../defguard-ui/components/FieldError/FieldError'; +import { Icon, IconKind } from '../../defguard-ui/components/Icon'; +import { ThemeVariable } from '../../defguard-ui/types'; +import { isPresent } from '../../defguard-ui/utils/isPresent'; +import type { UploadFieldProps } from './types'; + +export const UploadField = ({ + value, + className, + error, + id, + acceptedExtensions, + loading, + disabled, + testId, + onChange, +}: UploadFieldProps) => { + const valuePresent = isPresent(value); + const { mutate, isPending } = useMutation({ + mutationFn: getFile, + onSuccess: (result) => { + onChange?.(result); + }, + }); + + return ( +
+
+ {valuePresent && ( +
+ +

{value.name}

+ +
+ )} + {!valuePresent && ( +
+ +
+ ); +}; diff --git a/web/src/shared/components/UploadField/style.scss b/web/src/shared/components/UploadField/style.scss new file mode 100644 index 000000000..5a2e68d4c --- /dev/null +++ b/web/src/shared/components/UploadField/style.scss @@ -0,0 +1,57 @@ +.upload-field { + & > .inner-track { + display: flex; + flex-flow: column; + align-items: flex-start; + justify-content: flex-start; + padding: 1px; + + & > .btn { + flex: none; + } + + .file-row { + box-sizing: border-box; + padding: var(--spacing-sm); + display: grid; + column-gap: var(--spacing-md); + grid-template-columns: 20px 1fr 20px; + overflow: hidden; + border-radius: var(--radius-md); + background-color: var(--bg-muted); + + & > p { + font: var(--t-body-sm-500); + color: var(--fg-default); + text-wrap: nowrap; + text-overflow: ellipsis; + overflow: hidden; + max-width: 100%; + text-align: left; + } + + & > button { + --fill: var(--fg-muted); + + width: 20px; + height: 20px; + cursor: pointer; + display: inline-block; + margin: 0; + padding: 0; + border: 0; + background: transparent; + + &:hover { + --fill: var(--fg-critical); + } + + svg path { + fill: var(--fill); + + @include animate(fill); + } + } + } + } +} diff --git a/web/src/shared/components/UploadField/types.ts b/web/src/shared/components/UploadField/types.ts new file mode 100644 index 000000000..1637a5056 --- /dev/null +++ b/web/src/shared/components/UploadField/types.ts @@ -0,0 +1,11 @@ +export interface UploadFieldProps { + value?: File | null; + className?: string; + id?: string; + error?: string; + loading?: boolean; + disabled?: boolean; + acceptedExtensions?: string[]; + testId?: string; + onChange?: (value: File | null) => void; +} diff --git a/web/src/shared/components/i18n/RenderTranslation/RenderTranslation.tsx b/web/src/shared/components/i18n/RenderTranslation/RenderTranslation.tsx deleted file mode 100644 index 9e7e1b14f..000000000 --- a/web/src/shared/components/i18n/RenderTranslation/RenderTranslation.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import { Fragment, type ReactNode, useMemo } from 'react'; - -type Props = { - translation: string; - components: ReactNode[]; -}; -/** - * Renders string and replace every instance of `` with given component in order. - */ -export const RenderTranslation = ({ translation, components }: Props) => { - const segments = useMemo(() => { - const res = translation.split(''); - if (res.length === 1) { - throw Error('Translation is missing "" keyword.'); - } - return res; - }, [translation]); - return ( - <> - {segments.map((val, index) => ( - - {val} - {components[index] ?? null} - - ))} - - ); -}; diff --git a/web/src/shared/components/modals/AddAuthKeyModal/AddAuthKeyModal.tsx b/web/src/shared/components/modals/AddAuthKeyModal/AddAuthKeyModal.tsx new file mode 100644 index 000000000..d5d2861ed --- /dev/null +++ b/web/src/shared/components/modals/AddAuthKeyModal/AddAuthKeyModal.tsx @@ -0,0 +1,178 @@ +import z from 'zod'; +import { m } from '../../../../paraglide/messages'; +import { Modal } from '../../../defguard-ui/components/Modal/Modal'; +import { ModalControls } from '../../../defguard-ui/components/ModalControls/ModalControls'; +import { useAppForm } from '../../../form'; +import { formChangeLogic } from '../../../formLogic'; +import { + closeModal, + subscribeCloseModal, + subscribeOpenModal, +} from '../../../hooks/modalControls/modalsSubjects'; +import { ModalName } from '../../../hooks/modalControls/modalTypes'; +import './style.scss'; +import { useStore } from '@tanstack/react-form'; +import { useMutation } from '@tanstack/react-query'; +import type { AxiosError } from 'axios'; +import { useEffect, useMemo, useState } from 'react'; +import api from '../../../api/api'; +import { type ApiError, AuthKeyType, type AuthKeyTypeValue } from '../../../api/types'; +import { Select } from '../../../defguard-ui/components/Select/Select'; +import type { SelectOption } from '../../../defguard-ui/components/Select/types'; +import { isPresent } from '../../../defguard-ui/utils/isPresent'; + +const modalNameKey = ModalName.AddAuthKey; + +export const AddAuthKeyModal = () => { + const [isOpen, setOpen] = useState(false); + const [username, setUsername] = useState(null); + + useEffect(() => { + const openSub = subscribeOpenModal(modalNameKey, (data) => { + setUsername(data.username); + setOpen(true); + }); + const closeSub = subscribeCloseModal(modalNameKey, () => setOpen(false)); + return () => { + openSub.unsubscribe(); + closeSub.unsubscribe(); + }; + }, []); + + return ( + setOpen(false)} + afterClose={() => { + setUsername(null); + }} + > + {isPresent(username) && } + + ); +}; + +const selectOptions: SelectOption[] = [ + { + key: AuthKeyType.SSH, + value: AuthKeyType.SSH, + label: 'SSH', + }, + { + key: AuthKeyType.GPG, + value: AuthKeyType.GPG, + label: 'GPG', + }, +] as const; + +const getFormSchema = () => + z.object({ + name: z.string().trim().min(1, m.form_error_required()), + key: z.string().trim().min(1, m.form_error_required()), + }); + +type FormFields = z.infer>; + +const defaultValues: FormFields = { + key: '', + name: '', +}; + +const ModalContent = ({ username }: { username: string }) => { + const [selected, setSelected] = useState(selectOptions[0]); + const formSchema = useMemo(() => getFormSchema(), []); + + const { mutateAsync: addKey } = useMutation({ + mutationFn: api.user.addAuthKey, + meta: { + invalidate: [['user', username], ['user']], + }, + onSuccess: () => { + closeModal(modalNameKey); + }, + }); + + const form = useAppForm({ + defaultValues, + validationLogic: formChangeLogic, + validators: { + onSubmit: formSchema, + onChange: formSchema, + }, + onSubmit: async ({ value, formApi }) => { + await addKey({ + ...value, + key_type: selected.value, + username, + }).catch((e: AxiosError) => { + const msg = e.response?.data.msg; + if (msg?.includes('verification')) { + formApi.setErrorMap({ + onSubmit: { + fields: { + key: m.form_error_invalid_key(), + }, + }, + }); + } + }); + }, + }); + + const isSubmitting = useStore(form.store, (s) => s.isSubmitting); + + // biome-ignore lint/correctness/useExhaustiveDependencies: side effect + useEffect(() => { + if (!form.state.isPristine) { + form.validateAllFields('change'); + } + }, [selected]); + + return ( + <> +
{ + e.stopPropagation(); + e.preventDefault(); + form.handleSubmit(); + }} + > + + { - setSelectedNetwork(networkId); - }} - /> - ); - }, [loadingConfig, getSelectOptions, selectedNetwork, renderSelected]); - - const getQrConfig = useMemo((): string => { - if (selectedConfig) { - if (privateKey) { - return selectedConfig.replace('YOUR_PRIVATE_KEY', privateKey); - } - return selectedConfig.replace('YOUR_PRIVATE_KEY', publicKey); - } - return ''; - }, [selectedConfig, publicKey, privateKey]); - - const getConfigExport = useMemo((): string | undefined => { - if (selectedConfig) { - if (privateKey) { - return selectedConfig.replace('YOUR_PRIVATE_KEY', privateKey); - } - return selectedConfig; - } - return undefined; - }, [selectedConfig, privateKey]); - - const expandableCardActions = useMemo(() => { - return [ - , - { - if (getConfigExport) { - void writeToClipboard( - getConfigExport, - LL.components.deviceConfigsCard.messages.copyConfig(), - ); - } - }} - />, - { - if (getConfigExport) { - downloadWGConfig(getConfigExport, deviceName.toLowerCase().replace(' ', '-')); - } - }} - />, - ]; - }, [ - deviceName, - LL.components.deviceConfigsCard.messages, - getConfigExport, - writeToClipboard, - ]); - - return ( - - {getQrConfig && !loadingConfig && } - {(isUndefined(selectedConfig) || loadingConfig) && } - - ); -}; diff --git a/web/src/shared/components/network/DeviceConfigsCard/style.scss b/web/src/shared/components/network/DeviceConfigsCard/style.scss deleted file mode 100644 index 96644d81a..000000000 --- a/web/src/shared/components/network/DeviceConfigsCard/style.scss +++ /dev/null @@ -1,18 +0,0 @@ -.device-configs-card { - width: 100%; - - .top { - .extras { - .select { - width: 140px; - } - } - } - - .expanded-content { - display: flex; - flex-flow: row; - align-items: center; - justify-content: center; - } -} diff --git a/web/src/shared/components/network/DeviceConfigsCard/types.ts b/web/src/shared/components/network/DeviceConfigsCard/types.ts deleted file mode 100644 index af96401ce..000000000 --- a/web/src/shared/components/network/DeviceConfigsCard/types.ts +++ /dev/null @@ -1,4 +0,0 @@ -export type DeviceConfigsCardNetworkInfo = { - networkId: number; - networkName: string; -}; diff --git a/web/src/shared/components/network/GatewaysStatus/AllNetworksGatewaysStatus/AllNetworksGatewaysStatus.tsx b/web/src/shared/components/network/GatewaysStatus/AllNetworksGatewaysStatus/AllNetworksGatewaysStatus.tsx deleted file mode 100644 index bac392540..000000000 --- a/web/src/shared/components/network/GatewaysStatus/AllNetworksGatewaysStatus/AllNetworksGatewaysStatus.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import './style.scss'; - -import { useQuery } from '@tanstack/react-query'; -import { flatten } from 'lodash-es'; -import { useEffect, useMemo } from 'react'; - -import { isPresent } from '../../../../defguard-ui/utils/isPresent'; -import useApi from '../../../../hooks/useApi'; -import { useToaster } from '../../../../hooks/useToaster'; -import type { GatewayStatus } from '../../../../types'; -import { GatewaysFloatingStatus } from '../GatewaysFloatingStatus/GatewaysFloatingStatus'; -import { GatewaysStatusInfo } from '../GatewaysStatusInfo/GatewaysStatusInfo'; - -type MappedStats = { - id: number; - name: string; - gateways: GatewayStatus[]; -}; - -export const AllNetworksGatewaysStatus = () => { - const { - network: { getAllGatewaysStatus }, - } = useApi(); - - const toaster = useToaster(); - - const { data, isLoading, isError, error } = useQuery({ - queryKey: ['network', 'gateways'], - queryFn: getAllGatewaysStatus, - placeholderData: (perv) => perv, - refetchInterval: 60 * 1000, - }); - - const [totalConnections, connectedCount] = useMemo(() => { - if (data) { - const flat = flatten(Object.values(data)); - const totalCount = flat.length; - const connectedCount = flat.reduce( - (ac, current) => ac + (current.connected ? 1 : 0), - 0, - ); - return [totalCount, connectedCount]; - } - return [0, 0]; - }, [data]); - - const listData = useMemo(() => { - if (data) { - const res: MappedStats[] = []; - for (const networkId of Object.keys(data)) { - const gateways = data[networkId]; - if (gateways.length > 0) { - const name = gateways[0].network_name; - res.push({ - id: Number(networkId), - name, - gateways, - }); - } - } - return res; - } - return []; - }, [data]); - - useEffect(() => { - if (isPresent(error)) { - toaster.error('Failed to check full gateways status.'); - console.error(error); - } - }, [error, toaster]); - - return ( - -
- {listData.map((stats) => ( -
-
-

{stats.name}

-
- {stats.gateways.map((gateway) => ( - - ))} -
- ))} -
-
- ); -}; diff --git a/web/src/shared/components/network/GatewaysStatus/AllNetworksGatewaysStatus/style.scss b/web/src/shared/components/network/GatewaysStatus/AllNetworksGatewaysStatus/style.scss deleted file mode 100644 index d8996a799..000000000 --- a/web/src/shared/components/network/GatewaysStatus/AllNetworksGatewaysStatus/style.scss +++ /dev/null @@ -1,21 +0,0 @@ -.gateways-status-floating-menu { - .all-networks-gateways { - display: flex; - flex-flow: column; - row-gap: var(--spacing-s); - } - - .network-gateways { - display: flex; - flex-flow: column; - row-gap: var(--spacing-xs); - - & > .network { - border-bottom: 1px solid var(--border-primary); - - p { - @include typography(app-modal-1); - } - } - } -} diff --git a/web/src/shared/components/network/GatewaysStatus/GatewayStatusIcon.tsx b/web/src/shared/components/network/GatewaysStatus/GatewayStatusIcon.tsx deleted file mode 100644 index 88c2e9a11..000000000 --- a/web/src/shared/components/network/GatewaysStatus/GatewayStatusIcon.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { motion, type TargetAndTransition } from 'motion/react'; -import { useMemo } from 'react'; - -import { ColorsRGB } from '../../../constants'; -import { GatewayConnectionStatus } from './types'; - -type Props = { - status: GatewayConnectionStatus; - customColor?: ColorsRGB; -}; - -export const GatewayStatusIcon = ({ status, customColor }: Props) => { - const getAnimate = useMemo((): TargetAndTransition => { - const res: TargetAndTransition = { - fill: ColorsRGB.Error, - }; - if (customColor) { - res.fill = customColor; - return res; - } - switch (status) { - case GatewayConnectionStatus.CONNECTED: - res.fill = ColorsRGB.Success; - break; - case GatewayConnectionStatus.ERROR: - res.fill = ColorsRGB.Error; - break; - case GatewayConnectionStatus.PARTIAL: - res.fill = ColorsRGB.Warning; - break; - case GatewayConnectionStatus.DISCONNECTED: - res.fill = ColorsRGB.Error; - break; - } - return res; - }, [customColor, status]); - - return ( - - - - ); -}; diff --git a/web/src/shared/components/network/GatewaysStatus/GatewaysFloatingStatus/GatewaysFloatingStatus.tsx b/web/src/shared/components/network/GatewaysStatus/GatewaysFloatingStatus/GatewaysFloatingStatus.tsx deleted file mode 100644 index aff48c464..000000000 --- a/web/src/shared/components/network/GatewaysStatus/GatewaysFloatingStatus/GatewaysFloatingStatus.tsx +++ /dev/null @@ -1,125 +0,0 @@ -import './style.scss'; - -import { useMutation, useQueryClient } from '@tanstack/react-query'; - -import { InteractionBox } from '../../../../defguard-ui/components/Layout/InteractionBox/InteractionBox'; -import useApi from '../../../../hooks/useApi'; -import { useToaster } from '../../../../hooks/useToaster'; -import type { GatewayStatus } from '../../../../types'; - -type Props = { - status: GatewayStatus; -}; - -export const GatewaysFloatingStatus = ({ status }: Props) => { - const { - network: { deleteGateway }, - } = useApi(); - const toaster = useToaster(); - - const queryClient = useQueryClient(); - - const { mutate, isPending } = useMutation({ - mutationFn: deleteGateway, - onError: (err) => { - toaster.error('Failed to remove gateway'); - console.error(err); - }, - onSuccess: () => { - void queryClient.invalidateQueries({ - queryKey: ['network', 'gateways'], - }); - void queryClient.invalidateQueries({ - queryKey: ['network', status.network_id, 'gateways'], - }); - }, - }); - - return ( -
- {status.connected && } - {!status.connected && } -
- {status.name &&

{status.name}

} - {status.hostname &&

{status.hostname}

} -
-
- {!status.connected && !isPending && ( - { - mutate({ - gatewayId: status.uid, - networkId: status.network_id, - }); - }} - > - - - )} -
-
- ); -}; - -const IconDismiss = () => { - return ( - - - - - ); -}; - -const IconConnected = () => { - return ( - - - - - ); -}; - -const IconDisconnected = () => { - return ( - - - - - ); -}; diff --git a/web/src/shared/components/network/GatewaysStatus/GatewaysFloatingStatus/style.scss b/web/src/shared/components/network/GatewaysStatus/GatewaysFloatingStatus/style.scss deleted file mode 100644 index 2acece9b6..000000000 --- a/web/src/shared/components/network/GatewaysStatus/GatewaysFloatingStatus/style.scss +++ /dev/null @@ -1,60 +0,0 @@ -.gateway-floating-status-info { - display: flex; - flex-flow: row; - align-items: center; - justify-content: center; - column-gap: 5px; - - & > .info { - display: flex; - flex-flow: column; - align-items: flex-start; - justify-content: center; - row-gap: 5px; - - .name, - .hostname { - color: var(--text-body-secondary); - @include typography(app-modal-2); - } - - & > .name { - font-weight: 700; - } - - & > .hostname { - font-weight: 400; - } - } - - & > .dismiss { - .interaction-box { - width: 18px; - height: 18px; - - button { - width: 24px; - height: 24px; - } - } - - svg { - width: 18px; - height: 18px; - - path { - fill: var(--surface-icon-primary); - transition-property: fill; - @include animate-standard; - } - } - - &:hover { - svg { - path { - fill: var(--surface-alert-primary); - } - } - } - } -} diff --git a/web/src/shared/components/network/GatewaysStatus/GatewaysStatusInfo/GatewaysStatusInfo.tsx b/web/src/shared/components/network/GatewaysStatus/GatewaysStatusInfo/GatewaysStatusInfo.tsx deleted file mode 100644 index ab42200e1..000000000 --- a/web/src/shared/components/network/GatewaysStatus/GatewaysStatusInfo/GatewaysStatusInfo.tsx +++ /dev/null @@ -1,111 +0,0 @@ -import './style.scss'; - -import clsx from 'clsx'; -import { type PropsWithChildren, useMemo, useState } from 'react'; -import Skeleton from 'react-loading-skeleton'; - -import { useI18nContext } from '../../../../../i18n/i18n-react'; -import { ArrowSingle } from '../../../../defguard-ui/components/icons/ArrowSingle/ArrowSingle'; -import { ArrowSingleDirection } from '../../../../defguard-ui/components/icons/ArrowSingle/types'; -import { FloatingMenu } from '../../../../defguard-ui/components/Layout/FloatingMenu/FloatingMenu'; -import { FloatingMenuProvider } from '../../../../defguard-ui/components/Layout/FloatingMenu/FloatingMenuProvider'; -import { FloatingMenuTrigger } from '../../../../defguard-ui/components/Layout/FloatingMenu/FloatingMenuTrigger'; -import { Label } from '../../../../defguard-ui/components/Layout/Label/Label'; -import { GatewayStatusIcon } from '../GatewayStatusIcon'; -import { GatewayConnectionStatus } from '../types'; - -type Props = { - totalCount: number; - connectionCount: number; - isLoading?: boolean; - isError?: boolean; - forceStatus?: GatewayConnectionStatus; -} & PropsWithChildren; - -export const GatewaysStatusInfo = ({ - children, - connectionCount, - totalCount, - forceStatus, - isLoading = false, - isError = false, -}: Props) => { - const { LL } = useI18nContext(); - const localLL = LL.components.gatewaysStatus; - const [floatingOpen, setOpen] = useState(false); - - const status = useMemo((): GatewayConnectionStatus => { - if (forceStatus) { - return forceStatus; - } - if (isError) { - return GatewayConnectionStatus.ERROR; - } - if (isLoading) { - return GatewayConnectionStatus.LOADING; - } - if (totalCount === 0 || connectionCount === 0) { - return GatewayConnectionStatus.DISCONNECTED; - } - if (totalCount !== connectionCount) { - return GatewayConnectionStatus.PARTIAL; - } - return GatewayConnectionStatus.CONNECTED; - }, [connectionCount, forceStatus, isError, isLoading, totalCount]); - - const getInfoText = () => { - switch (status) { - case GatewayConnectionStatus.LOADING: - return ''; - case GatewayConnectionStatus.ERROR: - return localLL.states.error(); - case GatewayConnectionStatus.DISCONNECTED: - return localLL.states.none(); - case GatewayConnectionStatus.PARTIAL: - return localLL.states.some({ - count: connectionCount, - }); - case GatewayConnectionStatus.CONNECTED: - return localLL.states.all({ - count: connectionCount, - }); - } - }; - - return ( -
- - - -
{ - if (totalCount > 0) { - setOpen(true); - } - }} - > - {isLoading && } - {!isLoading && ( -
-

{getInfoText()}

- -
- )} - {totalCount > 0 && } -
-
- - {children} - -
-
- ); -}; diff --git a/web/src/shared/components/network/GatewaysStatus/GatewaysStatusInfo/style.scss b/web/src/shared/components/network/GatewaysStatus/GatewaysStatusInfo/style.scss deleted file mode 100644 index 0b7b08280..000000000 --- a/web/src/shared/components/network/GatewaysStatus/GatewaysStatusInfo/style.scss +++ /dev/null @@ -1,62 +0,0 @@ -.gateways-status-info { - width: 100%; - display: flex; - flex-flow: row; - align-items: center; - justify-content: flex-start; - column-gap: var(--spacing-xs); - min-height: 18px; - - & > label { - user-select: none; - - @include typography(app-modal-3); - } - - .info-track { - display: flex; - flex-flow: row; - align-items: center; - justify-content: flex-start; - - .react-loading-skeleton { - height: 18px; - width: 120px; - border-radius: 2px; - } - - & > .info { - display: flex; - flex-flow: row; - align-items: center; - justify-content: flex-start; - user-select: none; - cursor: pointer; - - &.connected { - color: var(--text-positive); - } - - &.disconnected { - cursor: default; - color: var(--text-alert); - } - - &.partial { - color: var(--text-important); - } - - p { - color: inherit; - padding-right: 5px; - text-wrap: nowrap; - @include typography(app-modal-3); - } - - svg { - width: 8px; - height: 8px; - } - } - } -} diff --git a/web/src/shared/components/network/GatewaysStatus/NetworkGatewaysStatus/NetworkGatewaysStatus.tsx b/web/src/shared/components/network/GatewaysStatus/NetworkGatewaysStatus/NetworkGatewaysStatus.tsx deleted file mode 100644 index 345d47d3d..000000000 --- a/web/src/shared/components/network/GatewaysStatus/NetworkGatewaysStatus/NetworkGatewaysStatus.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { useQuery } from '@tanstack/react-query'; -import { useMemo } from 'react'; - -import useApi from '../../../../hooks/useApi'; -import { GatewaysFloatingStatus } from '../GatewaysFloatingStatus/GatewaysFloatingStatus'; -import { GatewaysStatusInfo } from '../GatewaysStatusInfo/GatewaysStatusInfo'; - -type Props = { - networkId?: number; -}; -export const NetworkGatewaysStatus = ({ networkId }: Props) => { - const { - network: { getGatewaysStatus }, - } = useApi(); - - const { data, isLoading, isError } = useQuery({ - queryKey: ['network', networkId, 'gateways'], - queryFn: () => getGatewaysStatus(networkId as number), - enabled: Boolean(networkId), - }); - - const [totalConnections, connectedCount] = useMemo(() => { - if (!data) return [0, 0]; - const total = data.length; - const connected = data.reduce( - (count, status) => count + (status.connected ? 1 : 0), - 0, - ); - return [total, connected]; - }, [data]); - - return ( - - {data?.map((status) => ( - - ))} - - ); -}; diff --git a/web/src/shared/components/network/GatewaysStatus/style.scss b/web/src/shared/components/network/GatewaysStatus/style.scss deleted file mode 100644 index b0c3d0f89..000000000 --- a/web/src/shared/components/network/GatewaysStatus/style.scss +++ /dev/null @@ -1,120 +0,0 @@ -@use '@scssutils' as *; - -.network-gateways-connection { - width: 100%; - height: 20px; - display: flex; - flex-flow: row nowrap; - column-gap: 20px; - align-items: center; - justify-content: flex-start; - - & > label { - display: block; - font-size: 10px; - } - - & > .status-container { - height: 100%; - display: flex; - align-items: center; - justify-content: flex-start; - gap: 0; - cursor: pointer; - - & > .status { - height: 100%; - display: flex; - flex-flow: row nowrap; - gap: 5px; - align-items: center; - justify-content: flex-start; - - & > span { - @include typography-legacy(10px, 1.2, medium, var(--gray-light), 'Roboto'); - user-select: none; - } - - & > svg { - width: 8px; - height: 8px; - } - } - } - - &.status-loading { - & > .status-container { - gap: 10px; - } - } -} - -.floating-ui-gateways-status { - display: flex; - flex-flow: column; - align-items: flex-start; - justify-content: flex-start; - box-sizing: border-box; - row-gap: 20px; - z-index: 1; - border: 1px solid var(--gray-lighter); - border-radius: 10px; - padding: 18px 10px; - width: min-content; - min-width: 120px; - background-color: var(--white); - - & > .gateway-status-row { - display: flex; - flex-flow: row nowrap; - gap: 5px; - align-items: center; - justify-items: center; - position: relative; - - & > .icon-container { - & > svg { - width: 12px; - height: 12px; - } - } - - & > .info-container { - @include typography-legacy(12px, 1.2, medium, unset); - - .location { - color: var(--gray-dark); - white-space: nowrap; - } - - .hostname { - color: var(--gray-light); - white-space: nowrap; - @include text-weight(regular); - } - } - - &.disconnected { - & > .info-container { - .location, - .hostname { - color: var(--error); - } - } - } - - & > .gateway-dismiss { - display: flex; - flex-flow: row nowrap; - align-items: center; - justify-content: center; - background-color: transparent; - border: 0 solid transparent; - cursor: pointer; - width: 22px; - height: 22px; - padding: 0; - margin: 0; - } - } -} diff --git a/web/src/shared/components/network/GatewaysStatus/types.ts b/web/src/shared/components/network/GatewaysStatus/types.ts deleted file mode 100644 index a1b342acc..000000000 --- a/web/src/shared/components/network/GatewaysStatus/types.ts +++ /dev/null @@ -1,7 +0,0 @@ -export enum GatewayConnectionStatus { - CONNECTED = 'CONNECTED', - PARTIAL = 'PARTIAL', - DISCONNECTED = 'DISCONNECTED', - ERROR = 'ERROR', - LOADING = 'LOADING', -} diff --git a/web/src/shared/components/svg/.eslintrc b/web/src/shared/components/svg/.eslintrc deleted file mode 100644 index 2255300e5..000000000 --- a/web/src/shared/components/svg/.eslintrc +++ /dev/null @@ -1,7 +0,0 @@ -{ - "root": false, - "rules": { - "@typescript-eslint/explicit-module-boundary-types": "off", - "max-len": "off" - } -} diff --git a/web/src/shared/components/svg/Avatar01Blue.tsx b/web/src/shared/components/svg/Avatar01Blue.tsx deleted file mode 100644 index 3d91b5afa..000000000 --- a/web/src/shared/components/svg/Avatar01Blue.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgAvatar01Blue = (props: SVGProps) => ( - - - -); -export default SvgAvatar01Blue; diff --git a/web/src/shared/components/svg/Avatar01Gray.tsx b/web/src/shared/components/svg/Avatar01Gray.tsx deleted file mode 100644 index 6a15abe73..000000000 --- a/web/src/shared/components/svg/Avatar01Gray.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgAvatar01Gray = (props: SVGProps) => ( - - - -); -export default SvgAvatar01Gray; diff --git a/web/src/shared/components/svg/Avatar02Blue.tsx b/web/src/shared/components/svg/Avatar02Blue.tsx deleted file mode 100644 index fd380698b..000000000 --- a/web/src/shared/components/svg/Avatar02Blue.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgAvatar02Blue = (props: SVGProps) => ( - - - -); -export default SvgAvatar02Blue; diff --git a/web/src/shared/components/svg/Avatar02Gray.tsx b/web/src/shared/components/svg/Avatar02Gray.tsx deleted file mode 100644 index 70383066f..000000000 --- a/web/src/shared/components/svg/Avatar02Gray.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgAvatar02Gray = (props: SVGProps) => ( - - - -); -export default SvgAvatar02Gray; diff --git a/web/src/shared/components/svg/Avatar03Blue.tsx b/web/src/shared/components/svg/Avatar03Blue.tsx deleted file mode 100644 index ccc0d1f0c..000000000 --- a/web/src/shared/components/svg/Avatar03Blue.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgAvatar03Blue = (props: SVGProps) => ( - - - -); -export default SvgAvatar03Blue; diff --git a/web/src/shared/components/svg/Avatar03Gray.tsx b/web/src/shared/components/svg/Avatar03Gray.tsx deleted file mode 100644 index d9eb70306..000000000 --- a/web/src/shared/components/svg/Avatar03Gray.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgAvatar03Gray = (props: SVGProps) => ( - - - -); -export default SvgAvatar03Gray; diff --git a/web/src/shared/components/svg/Avatar04Blue.tsx b/web/src/shared/components/svg/Avatar04Blue.tsx deleted file mode 100644 index 5b65b6fb1..000000000 --- a/web/src/shared/components/svg/Avatar04Blue.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgAvatar04Blue = (props: SVGProps) => ( - - - -); -export default SvgAvatar04Blue; diff --git a/web/src/shared/components/svg/Avatar04Gray.tsx b/web/src/shared/components/svg/Avatar04Gray.tsx deleted file mode 100644 index f21612ca3..000000000 --- a/web/src/shared/components/svg/Avatar04Gray.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgAvatar04Gray = (props: SVGProps) => ( - - - -); -export default SvgAvatar04Gray; diff --git a/web/src/shared/components/svg/Avatar05Blue.tsx b/web/src/shared/components/svg/Avatar05Blue.tsx deleted file mode 100644 index dcf0c5731..000000000 --- a/web/src/shared/components/svg/Avatar05Blue.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgAvatar05Blue = (props: SVGProps) => ( - - - -); -export default SvgAvatar05Blue; diff --git a/web/src/shared/components/svg/Avatar05Gray.tsx b/web/src/shared/components/svg/Avatar05Gray.tsx deleted file mode 100644 index ec1c9de85..000000000 --- a/web/src/shared/components/svg/Avatar05Gray.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgAvatar05Gray = (props: SVGProps) => ( - - - -); -export default SvgAvatar05Gray; diff --git a/web/src/shared/components/svg/Avatar06Blue.tsx b/web/src/shared/components/svg/Avatar06Blue.tsx deleted file mode 100644 index 34083cc9e..000000000 --- a/web/src/shared/components/svg/Avatar06Blue.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgAvatar06Blue = (props: SVGProps) => ( - - - -); -export default SvgAvatar06Blue; diff --git a/web/src/shared/components/svg/Avatar06Gray.tsx b/web/src/shared/components/svg/Avatar06Gray.tsx deleted file mode 100644 index 3f2193e85..000000000 --- a/web/src/shared/components/svg/Avatar06Gray.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgAvatar06Gray = (props: SVGProps) => ( - - - -); -export default SvgAvatar06Gray; diff --git a/web/src/shared/components/svg/Avatar07Blue.tsx b/web/src/shared/components/svg/Avatar07Blue.tsx deleted file mode 100644 index 2e72b42c6..000000000 --- a/web/src/shared/components/svg/Avatar07Blue.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgAvatar07Blue = (props: SVGProps) => ( - - - -); -export default SvgAvatar07Blue; diff --git a/web/src/shared/components/svg/Avatar07Gray.tsx b/web/src/shared/components/svg/Avatar07Gray.tsx deleted file mode 100644 index 8c34b8292..000000000 --- a/web/src/shared/components/svg/Avatar07Gray.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgAvatar07Gray = (props: SVGProps) => ( - - - -); -export default SvgAvatar07Gray; diff --git a/web/src/shared/components/svg/Avatar08Blue.tsx b/web/src/shared/components/svg/Avatar08Blue.tsx deleted file mode 100644 index c11a0bd0d..000000000 --- a/web/src/shared/components/svg/Avatar08Blue.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgAvatar08Blue = (props: SVGProps) => ( - - - -); -export default SvgAvatar08Blue; diff --git a/web/src/shared/components/svg/Avatar08Gray.tsx b/web/src/shared/components/svg/Avatar08Gray.tsx deleted file mode 100644 index 75eb7b393..000000000 --- a/web/src/shared/components/svg/Avatar08Gray.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgAvatar08Gray = (props: SVGProps) => ( - - - -); -export default SvgAvatar08Gray; diff --git a/web/src/shared/components/svg/Avatar09Blue.tsx b/web/src/shared/components/svg/Avatar09Blue.tsx deleted file mode 100644 index 68a12c072..000000000 --- a/web/src/shared/components/svg/Avatar09Blue.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgAvatar09Blue = (props: SVGProps) => ( - - - -); -export default SvgAvatar09Blue; diff --git a/web/src/shared/components/svg/Avatar09Gray.tsx b/web/src/shared/components/svg/Avatar09Gray.tsx deleted file mode 100644 index 07f610260..000000000 --- a/web/src/shared/components/svg/Avatar09Gray.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgAvatar09Gray = (props: SVGProps) => ( - - - -); -export default SvgAvatar09Gray; diff --git a/web/src/shared/components/svg/Avatar10Blue.tsx b/web/src/shared/components/svg/Avatar10Blue.tsx deleted file mode 100644 index 14a2b4347..000000000 --- a/web/src/shared/components/svg/Avatar10Blue.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgAvatar10Blue = (props: SVGProps) => ( - - - -); -export default SvgAvatar10Blue; diff --git a/web/src/shared/components/svg/Avatar10Gray.tsx b/web/src/shared/components/svg/Avatar10Gray.tsx deleted file mode 100644 index 6dd2c88e8..000000000 --- a/web/src/shared/components/svg/Avatar10Gray.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgAvatar10Gray = (props: SVGProps) => ( - - - -); -export default SvgAvatar10Gray; diff --git a/web/src/shared/components/svg/Avatar11Blue.tsx b/web/src/shared/components/svg/Avatar11Blue.tsx deleted file mode 100644 index 1c94964a0..000000000 --- a/web/src/shared/components/svg/Avatar11Blue.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgAvatar11Blue = (props: SVGProps) => ( - - - -); -export default SvgAvatar11Blue; diff --git a/web/src/shared/components/svg/Avatar11Gray.tsx b/web/src/shared/components/svg/Avatar11Gray.tsx deleted file mode 100644 index 1099af095..000000000 --- a/web/src/shared/components/svg/Avatar11Gray.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgAvatar11Gray = (props: SVGProps) => ( - - - -); -export default SvgAvatar11Gray; diff --git a/web/src/shared/components/svg/Avatar12Blue.tsx b/web/src/shared/components/svg/Avatar12Blue.tsx deleted file mode 100644 index 127f9d5c9..000000000 --- a/web/src/shared/components/svg/Avatar12Blue.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgAvatar12Blue = (props: SVGProps) => ( - - - -); -export default SvgAvatar12Blue; diff --git a/web/src/shared/components/svg/Avatar12Gray.tsx b/web/src/shared/components/svg/Avatar12Gray.tsx deleted file mode 100644 index 12a3dd709..000000000 --- a/web/src/shared/components/svg/Avatar12Gray.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgAvatar12Gray = (props: SVGProps) => ( - - - -); -export default SvgAvatar12Gray; diff --git a/web/src/shared/components/svg/DefguardLogo.tsx b/web/src/shared/components/svg/DefguardLogo.tsx deleted file mode 100644 index 78768b7e2..000000000 --- a/web/src/shared/components/svg/DefguardLogo.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgDefguardLogo = (props: SVGProps) => ( - - - - - - - - - - -); -export default SvgDefguardLogo; diff --git a/web/src/shared/components/svg/DefguardLogoLogin.tsx b/web/src/shared/components/svg/DefguardLogoLogin.tsx deleted file mode 100644 index 1ed3011f0..000000000 --- a/web/src/shared/components/svg/DefguardLogoLogin.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgDefguardLogoLogin = (props: SVGProps) => ( - - - - - - - - - - -); -export default SvgDefguardLogoLogin; diff --git a/web/src/shared/components/svg/DefguardNavLogo.tsx b/web/src/shared/components/svg/DefguardNavLogo.tsx deleted file mode 100644 index 47ce92f07..000000000 --- a/web/src/shared/components/svg/DefguardNavLogo.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgDefguardNavLogo = (props: SVGProps) => ( - - - - - - - - - - - -); -export default SvgDefguardNavLogo; diff --git a/web/src/shared/components/svg/DefguardNavLogoCollapsed.tsx b/web/src/shared/components/svg/DefguardNavLogoCollapsed.tsx deleted file mode 100644 index e2b0904ac..000000000 --- a/web/src/shared/components/svg/DefguardNavLogoCollapsed.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgDefguardNavLogoCollapsed = (props: SVGProps) => ( - - - - - - - - - - -); -export default SvgDefguardNavLogoCollapsed; diff --git a/web/src/shared/components/svg/DefguardNoIcon.tsx b/web/src/shared/components/svg/DefguardNoIcon.tsx deleted file mode 100644 index 07b86bee9..000000000 --- a/web/src/shared/components/svg/DefguardNoIcon.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgDefguardNoIcon = (props: SVGProps) => ( - - - -); -export default SvgDefguardNoIcon; diff --git a/web/src/shared/components/svg/GlowIcon.tsx b/web/src/shared/components/svg/GlowIcon.tsx deleted file mode 100644 index d3a9a10b1..000000000 --- a/web/src/shared/components/svg/GlowIcon.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgGlowIcon = (props: SVGProps) => ( - - - - - - - - - - - - - - - - - - - - - -); -export default SvgGlowIcon; diff --git a/web/src/shared/components/svg/Icon24HConnections.tsx b/web/src/shared/components/svg/Icon24HConnections.tsx deleted file mode 100644 index 7f85e6de5..000000000 --- a/web/src/shared/components/svg/Icon24HConnections.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIcon24HConnections = (props: SVGProps) => ( - - - - - - - -); -export default SvgIcon24HConnections; diff --git a/web/src/shared/components/svg/IconActiveConnections.tsx b/web/src/shared/components/svg/IconActiveConnections.tsx deleted file mode 100644 index bace3e279..000000000 --- a/web/src/shared/components/svg/IconActiveConnections.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconActiveConnections = (props: SVGProps) => ( - - - -); -export default SvgIconActiveConnections; diff --git a/web/src/shared/components/svg/IconActivityAdd.tsx b/web/src/shared/components/svg/IconActivityAdd.tsx deleted file mode 100644 index a0010b060..000000000 --- a/web/src/shared/components/svg/IconActivityAdd.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconActivityAdd = (props: SVGProps) => ( - - - - - - - - - - - - -); -export default SvgIconActivityAdd; diff --git a/web/src/shared/components/svg/IconActivityRemoved.tsx b/web/src/shared/components/svg/IconActivityRemoved.tsx deleted file mode 100644 index 89023fbd7..000000000 --- a/web/src/shared/components/svg/IconActivityRemoved.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconActivityRemoved = (props: SVGProps) => ( - - - - - - - - - - - - - - - -); -export default SvgIconActivityRemoved; diff --git a/web/src/shared/components/svg/IconActivityWarning.tsx b/web/src/shared/components/svg/IconActivityWarning.tsx deleted file mode 100644 index 11c99de8f..000000000 --- a/web/src/shared/components/svg/IconActivityWarning.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconActivityWarning = (props: SVGProps) => ( - - - - - - - - - - - - -); -export default SvgIconActivityWarning; diff --git a/web/src/shared/components/svg/IconArrowDouble.tsx b/web/src/shared/components/svg/IconArrowDouble.tsx deleted file mode 100644 index bf54f029c..000000000 --- a/web/src/shared/components/svg/IconArrowDouble.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconArrowDouble = (props: SVGProps) => ( - - - - - - -); -export default SvgIconArrowDouble; diff --git a/web/src/shared/components/svg/IconArrowDoubleGrayLeft.tsx b/web/src/shared/components/svg/IconArrowDoubleGrayLeft.tsx deleted file mode 100644 index cf21f7794..000000000 --- a/web/src/shared/components/svg/IconArrowDoubleGrayLeft.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconArrowDoubleGrayLeft = (props: SVGProps) => ( - - - - - - - - - - - - - -); -export default SvgIconArrowDoubleGrayLeft; diff --git a/web/src/shared/components/svg/IconArrowGrayDown.tsx b/web/src/shared/components/svg/IconArrowGrayDown.tsx deleted file mode 100644 index 880a92df0..000000000 --- a/web/src/shared/components/svg/IconArrowGrayDown.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconArrowGrayDown = (props: SVGProps) => ( - - - - - - - - - - - - -); -export default SvgIconArrowGrayDown; diff --git a/web/src/shared/components/svg/IconArrowGrayDown1.tsx b/web/src/shared/components/svg/IconArrowGrayDown1.tsx deleted file mode 100644 index 8a19b3812..000000000 --- a/web/src/shared/components/svg/IconArrowGrayDown1.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconArrowGrayDown1 = (props: SVGProps) => ( - - - - - - - - - - - - -); -export default SvgIconArrowGrayDown1; diff --git a/web/src/shared/components/svg/IconArrowGrayLeft.tsx b/web/src/shared/components/svg/IconArrowGrayLeft.tsx deleted file mode 100644 index 23c3768b2..000000000 --- a/web/src/shared/components/svg/IconArrowGrayLeft.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconArrowGrayLeft = (props: SVGProps) => ( - - - - - - - - - - - - -); -export default SvgIconArrowGrayLeft; diff --git a/web/src/shared/components/svg/IconArrowGrayRight.tsx b/web/src/shared/components/svg/IconArrowGrayRight.tsx deleted file mode 100644 index 613e5b8a5..000000000 --- a/web/src/shared/components/svg/IconArrowGrayRight.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconArrowGrayRight = (props: SVGProps) => ( - - - - - - - - - - - - -); -export default SvgIconArrowGrayRight; diff --git a/web/src/shared/components/svg/IconArrowGraySmall.tsx b/web/src/shared/components/svg/IconArrowGraySmall.tsx deleted file mode 100644 index 5ba0d9bf8..000000000 --- a/web/src/shared/components/svg/IconArrowGraySmall.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconArrowGraySmall = (props: SVGProps) => ( - - - - - - - - - - - -); -export default SvgIconArrowGraySmall; diff --git a/web/src/shared/components/svg/IconArrowGrayUp.tsx b/web/src/shared/components/svg/IconArrowGrayUp.tsx deleted file mode 100644 index 113318375..000000000 --- a/web/src/shared/components/svg/IconArrowGrayUp.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconArrowGrayUp = (props: SVGProps) => ( - - - - - - - - - - - - -); -export default SvgIconArrowGrayUp; diff --git a/web/src/shared/components/svg/IconArrowGrayUp1.tsx b/web/src/shared/components/svg/IconArrowGrayUp1.tsx deleted file mode 100644 index 17b5cf285..000000000 --- a/web/src/shared/components/svg/IconArrowGrayUp1.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconArrowGrayUp1 = (props: SVGProps) => ( - - - - - - - - - - - - -); -export default SvgIconArrowGrayUp1; diff --git a/web/src/shared/components/svg/IconArrowSingle.tsx b/web/src/shared/components/svg/IconArrowSingle.tsx deleted file mode 100644 index 1850ba72a..000000000 --- a/web/src/shared/components/svg/IconArrowSingle.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconArrowSingle = (props: SVGProps) => ( - - - - - - - - - -); -export default SvgIconArrowSingle; diff --git a/web/src/shared/components/svg/IconArrowSingle2.tsx b/web/src/shared/components/svg/IconArrowSingle2.tsx deleted file mode 100644 index b07f4fca9..000000000 --- a/web/src/shared/components/svg/IconArrowSingle2.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconArrowSingle2 = (props: SVGProps) => ( - - - - - - - - - -); -export default SvgIconArrowSingle2; diff --git a/web/src/shared/components/svg/IconArrowWhiteLeft.tsx b/web/src/shared/components/svg/IconArrowWhiteLeft.tsx deleted file mode 100644 index 90c14acd2..000000000 --- a/web/src/shared/components/svg/IconArrowWhiteLeft.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconArrowWhiteLeft = (props: SVGProps) => ( - - - - - - - - - - - - -); -export default SvgIconArrowWhiteLeft; diff --git a/web/src/shared/components/svg/IconAsterix.tsx b/web/src/shared/components/svg/IconAsterix.tsx deleted file mode 100644 index f9cfe6fa8..000000000 --- a/web/src/shared/components/svg/IconAsterix.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconAsterix = (props: SVGProps) => ( - - - - - - - - - - - - - -); -export default SvgIconAsterix; diff --git a/web/src/shared/components/svg/IconAuthenticationKey.tsx b/web/src/shared/components/svg/IconAuthenticationKey.tsx deleted file mode 100644 index 7040e3b15..000000000 --- a/web/src/shared/components/svg/IconAuthenticationKey.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import type { SVGProps } from 'react'; - -const IconAuthenticationKey = (props: SVGProps) => ( - - - - - -); - -export default IconAuthenticationKey; diff --git a/web/src/shared/components/svg/IconCancel.tsx b/web/src/shared/components/svg/IconCancel.tsx deleted file mode 100644 index c120692f7..000000000 --- a/web/src/shared/components/svg/IconCancel.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconCancel = (props: SVGProps) => ( - - - - -); -export default SvgIconCancel; diff --git a/web/src/shared/components/svg/IconCancelAlt.tsx b/web/src/shared/components/svg/IconCancelAlt.tsx deleted file mode 100644 index 640d3c93e..000000000 --- a/web/src/shared/components/svg/IconCancelAlt.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconCancelAlt = (props: SVGProps) => ( - - - - - - - - - - - - -); -export default SvgIconCancelAlt; diff --git a/web/src/shared/components/svg/IconCheckmark.tsx b/web/src/shared/components/svg/IconCheckmark.tsx deleted file mode 100644 index 2791acb59..000000000 --- a/web/src/shared/components/svg/IconCheckmark.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconCheckmark = (props: SVGProps) => ( - - - - -); -export default SvgIconCheckmark; diff --git a/web/src/shared/components/svg/IconCheckmarkGreen.tsx b/web/src/shared/components/svg/IconCheckmarkGreen.tsx deleted file mode 100644 index f1b183c4e..000000000 --- a/web/src/shared/components/svg/IconCheckmarkGreen.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconCheckmarkGreen = (props: SVGProps) => ( - - - - - - - - - - - - -); -export default SvgIconCheckmarkGreen; diff --git a/web/src/shared/components/svg/IconCheckmarkWhite.tsx b/web/src/shared/components/svg/IconCheckmarkWhite.tsx deleted file mode 100644 index 9dc0773be..000000000 --- a/web/src/shared/components/svg/IconCheckmarkWhite.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconCheckmarkWhite = (props: SVGProps) => ( - - - - - - - - - - - - -); -export default SvgIconCheckmarkWhite; diff --git a/web/src/shared/components/svg/IconCheckmarkWhite1.tsx b/web/src/shared/components/svg/IconCheckmarkWhite1.tsx deleted file mode 100644 index 6226c0b53..000000000 --- a/web/src/shared/components/svg/IconCheckmarkWhite1.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconCheckmarkWhite1 = (props: SVGProps) => ( - - - - - - - - - - - - -); -export default SvgIconCheckmarkWhite1; diff --git a/web/src/shared/components/svg/IconCheckmarkWhiteBig.tsx b/web/src/shared/components/svg/IconCheckmarkWhiteBig.tsx deleted file mode 100644 index d6c697e52..000000000 --- a/web/src/shared/components/svg/IconCheckmarkWhiteBig.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconCheckmarkWhiteBig = (props: SVGProps) => ( - - - - - - - - - - - - -); -export default SvgIconCheckmarkWhiteBig; diff --git a/web/src/shared/components/svg/IconClip.tsx b/web/src/shared/components/svg/IconClip.tsx deleted file mode 100644 index 4ac8252ca..000000000 --- a/web/src/shared/components/svg/IconClip.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconClip = (props: SVGProps) => ( - - - - - - - - - - -); -export default SvgIconClip; diff --git a/web/src/shared/components/svg/IconCloseGray.tsx b/web/src/shared/components/svg/IconCloseGray.tsx deleted file mode 100644 index f7f857f7d..000000000 --- a/web/src/shared/components/svg/IconCloseGray.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconCloseGray = (props: SVGProps) => ( - - - - - - - - - - - - -); -export default SvgIconCloseGray; diff --git a/web/src/shared/components/svg/IconCollapse.tsx b/web/src/shared/components/svg/IconCollapse.tsx deleted file mode 100644 index 31c7eacff..000000000 --- a/web/src/shared/components/svg/IconCollapse.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconCollapse = (props: SVGProps) => ( - - - - - - -); -export default SvgIconCollapse; diff --git a/web/src/shared/components/svg/IconConnected.tsx b/web/src/shared/components/svg/IconConnected.tsx deleted file mode 100644 index 575f7d77d..000000000 --- a/web/src/shared/components/svg/IconConnected.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconConnected = (props: SVGProps) => ( - - - - - - - - - - - - - -); -export default SvgIconConnected; diff --git a/web/src/shared/components/svg/IconCopy.tsx b/web/src/shared/components/svg/IconCopy.tsx deleted file mode 100644 index 854066bb8..000000000 --- a/web/src/shared/components/svg/IconCopy.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconCopy = (props: SVGProps) => ( - - - - - - - - - - - - - - - - - -); -export default SvgIconCopy; diff --git a/web/src/shared/components/svg/IconDeactivated.tsx b/web/src/shared/components/svg/IconDeactivated.tsx deleted file mode 100644 index ace4a9e6e..000000000 --- a/web/src/shared/components/svg/IconDeactivated.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconDeactivated = (props: SVGProps) => ( - - - - - - - - - - - - - - - -); -export default SvgIconDeactivated; diff --git a/web/src/shared/components/svg/IconDelete.tsx b/web/src/shared/components/svg/IconDelete.tsx deleted file mode 100644 index 04ea62533..000000000 --- a/web/src/shared/components/svg/IconDelete.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconDelete = (props: SVGProps) => ( - - - - - - - - - - - - - - - - -); -export default SvgIconDelete; diff --git a/web/src/shared/components/svg/IconDfgOpenidRedirect.tsx b/web/src/shared/components/svg/IconDfgOpenidRedirect.tsx deleted file mode 100644 index 3b94b97aa..000000000 --- a/web/src/shared/components/svg/IconDfgOpenidRedirect.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconDfgOpenidRedirect = (props: SVGProps) => ( - - - - - - - - - - - - - - - - - - -); -export default SvgIconDfgOpenidRedirect; diff --git a/web/src/shared/components/svg/IconDisconnected.tsx b/web/src/shared/components/svg/IconDisconnected.tsx deleted file mode 100644 index 049690fba..000000000 --- a/web/src/shared/components/svg/IconDisconnected.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconDisconnected = (props: SVGProps) => ( - - - - - - - - - - -); -export default SvgIconDisconnected; diff --git a/web/src/shared/components/svg/IconDownload.tsx b/web/src/shared/components/svg/IconDownload.tsx deleted file mode 100644 index 288790588..000000000 --- a/web/src/shared/components/svg/IconDownload.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconDownload = (props: SVGProps) => ( - - - - -); -export default SvgIconDownload; diff --git a/web/src/shared/components/svg/IconEdit.tsx b/web/src/shared/components/svg/IconEdit.tsx deleted file mode 100644 index a35002a65..000000000 --- a/web/src/shared/components/svg/IconEdit.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconEdit = (props: SVGProps) => ( - - - -); -export default SvgIconEdit; diff --git a/web/src/shared/components/svg/IconEditAlt.tsx b/web/src/shared/components/svg/IconEditAlt.tsx deleted file mode 100644 index 16faa5eab..000000000 --- a/web/src/shared/components/svg/IconEditAlt.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconEditAlt = (props: SVGProps) => ( - - - - - - - - - - -); -export default SvgIconEditAlt; diff --git a/web/src/shared/components/svg/IconEditAltHover.tsx b/web/src/shared/components/svg/IconEditAltHover.tsx deleted file mode 100644 index 467818b9e..000000000 --- a/web/src/shared/components/svg/IconEditAltHover.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconEditAltHover = (props: SVGProps) => ( - - - - - - - - - - -); -export default SvgIconEditAltHover; diff --git a/web/src/shared/components/svg/IconEditHover.tsx b/web/src/shared/components/svg/IconEditHover.tsx deleted file mode 100644 index cf6b5770e..000000000 --- a/web/src/shared/components/svg/IconEditHover.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconEditHover = (props: SVGProps) => ( - - - - - - - - - - -); -export default SvgIconEditHover; diff --git a/web/src/shared/components/svg/IconEditNetwork.tsx b/web/src/shared/components/svg/IconEditNetwork.tsx deleted file mode 100644 index f36b3d961..000000000 --- a/web/src/shared/components/svg/IconEditNetwork.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconEditNetwork = (props: SVGProps) => ( - - - - - - - - - - - -); -export default SvgIconEditNetwork; diff --git a/web/src/shared/components/svg/IconEth.tsx b/web/src/shared/components/svg/IconEth.tsx deleted file mode 100644 index 5ed99e401..000000000 --- a/web/src/shared/components/svg/IconEth.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconEth = (props: SVGProps) => ( - - - - - - - - - - - - - -); -export default SvgIconEth; diff --git a/web/src/shared/components/svg/IconExpand.tsx b/web/src/shared/components/svg/IconExpand.tsx deleted file mode 100644 index b5afd9976..000000000 --- a/web/src/shared/components/svg/IconExpand.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconExpand = (props: SVGProps) => ( - - - - - - -); -export default SvgIconExpand; diff --git a/web/src/shared/components/svg/IconFilter.tsx b/web/src/shared/components/svg/IconFilter.tsx deleted file mode 100644 index bf21cae36..000000000 --- a/web/src/shared/components/svg/IconFilter.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconFilter = (props: SVGProps) => ( - - - - - - - - - - -); -export default SvgIconFilter; diff --git a/web/src/shared/components/svg/IconHamburgerClose.tsx b/web/src/shared/components/svg/IconHamburgerClose.tsx deleted file mode 100644 index 636ee05ef..000000000 --- a/web/src/shared/components/svg/IconHamburgerClose.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconHamburgerClose = (props: SVGProps) => ( - - - - - - - - - - - - - - - - - -); -export default SvgIconHamburgerClose; diff --git a/web/src/shared/components/svg/IconHamburgerMenu.tsx b/web/src/shared/components/svg/IconHamburgerMenu.tsx deleted file mode 100644 index f7f523906..000000000 --- a/web/src/shared/components/svg/IconHamburgerMenu.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconHamburgerMenu = (props: SVGProps) => ( - - - - - - - - - - - - - -); -export default SvgIconHamburgerMenu; diff --git a/web/src/shared/components/svg/IconHamburgerMenu1.tsx b/web/src/shared/components/svg/IconHamburgerMenu1.tsx deleted file mode 100644 index 4a481cd4a..000000000 --- a/web/src/shared/components/svg/IconHamburgerMenu1.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconHamburgerMenu1 = (props: SVGProps) => ( - - - - - - - - - - - - - -); -export default SvgIconHamburgerMenu1; diff --git a/web/src/shared/components/svg/IconHourglass.tsx b/web/src/shared/components/svg/IconHourglass.tsx deleted file mode 100644 index c72cbc5af..000000000 --- a/web/src/shared/components/svg/IconHourglass.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconHourglass = (props: SVGProps) => ( - - - - - - - - - -); -export default SvgIconHourglass; diff --git a/web/src/shared/components/svg/IconHourglassHover.tsx b/web/src/shared/components/svg/IconHourglassHover.tsx deleted file mode 100644 index 984cdd9d4..000000000 --- a/web/src/shared/components/svg/IconHourglassHover.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconHourglassHover = (props: SVGProps) => ( - - - - - - - - - -); -export default SvgIconHourglassHover; diff --git a/web/src/shared/components/svg/IconInfo.tsx b/web/src/shared/components/svg/IconInfo.tsx deleted file mode 100644 index a86d9397d..000000000 --- a/web/src/shared/components/svg/IconInfo.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconInfo = (props: SVGProps) => ( - - - - - - - - - - -); -export default SvgIconInfo; diff --git a/web/src/shared/components/svg/IconInfoError.tsx b/web/src/shared/components/svg/IconInfoError.tsx deleted file mode 100644 index a0e493ce9..000000000 --- a/web/src/shared/components/svg/IconInfoError.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconInfoError = (props: SVGProps) => ( - - - - - - -); -export default SvgIconInfoError; diff --git a/web/src/shared/components/svg/IconInfoNormal.tsx b/web/src/shared/components/svg/IconInfoNormal.tsx deleted file mode 100644 index ae9e605b0..000000000 --- a/web/src/shared/components/svg/IconInfoNormal.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconInfoNormal = (props: SVGProps) => ( - - - - - - - - - - -); -export default SvgIconInfoNormal; diff --git a/web/src/shared/components/svg/IconInfoSuccess.tsx b/web/src/shared/components/svg/IconInfoSuccess.tsx deleted file mode 100644 index 186328d38..000000000 --- a/web/src/shared/components/svg/IconInfoSuccess.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconInfoSuccess = (props: SVGProps) => ( - - - - - - - - - - - - -); -export default SvgIconInfoSuccess; diff --git a/web/src/shared/components/svg/IconInfoSuccess1.tsx b/web/src/shared/components/svg/IconInfoSuccess1.tsx deleted file mode 100644 index 414743cda..000000000 --- a/web/src/shared/components/svg/IconInfoSuccess1.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconInfoSuccess1 = (props: SVGProps) => ( - - - - - - - - - - - - -); -export default SvgIconInfoSuccess1; diff --git a/web/src/shared/components/svg/IconInfoWarning.tsx b/web/src/shared/components/svg/IconInfoWarning.tsx deleted file mode 100644 index 76aae4041..000000000 --- a/web/src/shared/components/svg/IconInfoWarning.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconInfoWarning = (props: SVGProps) => ( - - - - - - - -); -export default SvgIconInfoWarning; diff --git a/web/src/shared/components/svg/IconKey.tsx b/web/src/shared/components/svg/IconKey.tsx deleted file mode 100644 index 5576b62b8..000000000 --- a/web/src/shared/components/svg/IconKey.tsx +++ /dev/null @@ -1,41 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconKey = (props: SVGProps) => ( - - - - - - - - - - - - - - - - - -); -export default SvgIconKey; diff --git a/web/src/shared/components/svg/IconListOrderDown.tsx b/web/src/shared/components/svg/IconListOrderDown.tsx deleted file mode 100644 index b62c76dc1..000000000 --- a/web/src/shared/components/svg/IconListOrderDown.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconListOrderDown = (props: SVGProps) => ( - - - - - - - - - - - - - - - - - - - - - - -); -export default SvgIconListOrderDown; diff --git a/web/src/shared/components/svg/IconListOrderDownHover.tsx b/web/src/shared/components/svg/IconListOrderDownHover.tsx deleted file mode 100644 index a2fcd091c..000000000 --- a/web/src/shared/components/svg/IconListOrderDownHover.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconListOrderDownHover = (props: SVGProps) => ( - - - - - - - - - - - - - - - - - - - - - - -); -export default SvgIconListOrderDownHover; diff --git a/web/src/shared/components/svg/IconListOrderUp.tsx b/web/src/shared/components/svg/IconListOrderUp.tsx deleted file mode 100644 index 1d9c3b4e6..000000000 --- a/web/src/shared/components/svg/IconListOrderUp.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconListOrderUp = (props: SVGProps) => ( - - - - - - - - - - - - - - - - - - - - - - -); -export default SvgIconListOrderUp; diff --git a/web/src/shared/components/svg/IconListOrderUpHover.tsx b/web/src/shared/components/svg/IconListOrderUpHover.tsx deleted file mode 100644 index edd42a218..000000000 --- a/web/src/shared/components/svg/IconListOrderUpHover.tsx +++ /dev/null @@ -1,79 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconListOrderUpHover = (props: SVGProps) => ( - - - - - - - - - - - - - - - - - - - - - - -); -export default SvgIconListOrderUpHover; diff --git a/web/src/shared/components/svg/IconNavGroups.tsx b/web/src/shared/components/svg/IconNavGroups.tsx deleted file mode 100644 index 8d8274584..000000000 --- a/web/src/shared/components/svg/IconNavGroups.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconNavGroups = (props: SVGProps) => ( - - - -); -export default SvgIconNavGroups; diff --git a/web/src/shared/components/svg/IconNavHamburger.tsx b/web/src/shared/components/svg/IconNavHamburger.tsx deleted file mode 100644 index e717788d6..000000000 --- a/web/src/shared/components/svg/IconNavHamburger.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconNavHamburger = (props: SVGProps) => ( - - - -); -export default SvgIconNavHamburger; diff --git a/web/src/shared/components/svg/IconNavKey.tsx b/web/src/shared/components/svg/IconNavKey.tsx deleted file mode 100644 index 275f935aa..000000000 --- a/web/src/shared/components/svg/IconNavKey.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconNavKey = (props: SVGProps) => ( - - - - -); -export default SvgIconNavKey; diff --git a/web/src/shared/components/svg/IconNavLocations.tsx b/web/src/shared/components/svg/IconNavLocations.tsx deleted file mode 100644 index 784d86fe0..000000000 --- a/web/src/shared/components/svg/IconNavLocations.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconNavLocations = (props: SVGProps) => ( - - - -); -export default SvgIconNavLocations; diff --git a/web/src/shared/components/svg/IconNavLogout.tsx b/web/src/shared/components/svg/IconNavLogout.tsx deleted file mode 100644 index abaca1441..000000000 --- a/web/src/shared/components/svg/IconNavLogout.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconNavLogout = (props: SVGProps) => ( - - - -); -export default SvgIconNavLogout; diff --git a/web/src/shared/components/svg/IconNavOpenid.tsx b/web/src/shared/components/svg/IconNavOpenid.tsx deleted file mode 100644 index 0a132405c..000000000 --- a/web/src/shared/components/svg/IconNavOpenid.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconNavOpenid = (props: SVGProps) => ( - - - -); -export default SvgIconNavOpenid; diff --git a/web/src/shared/components/svg/IconNavOverview.tsx b/web/src/shared/components/svg/IconNavOverview.tsx deleted file mode 100644 index 1f25cd21f..000000000 --- a/web/src/shared/components/svg/IconNavOverview.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconNavOverview = (props: SVGProps) => ( - - - -); -export default SvgIconNavOverview; diff --git a/web/src/shared/components/svg/IconNavProfile.tsx b/web/src/shared/components/svg/IconNavProfile.tsx deleted file mode 100644 index f877fe2d6..000000000 --- a/web/src/shared/components/svg/IconNavProfile.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconNavProfile = (props: SVGProps) => ( - - - -); -export default SvgIconNavProfile; diff --git a/web/src/shared/components/svg/IconNavProfile1.tsx b/web/src/shared/components/svg/IconNavProfile1.tsx deleted file mode 100644 index c06fa829c..000000000 --- a/web/src/shared/components/svg/IconNavProfile1.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconNavProfile1 = (props: SVGProps) => ( - - - - - - -); -export default SvgIconNavProfile1; diff --git a/web/src/shared/components/svg/IconNavProvisioners.tsx b/web/src/shared/components/svg/IconNavProvisioners.tsx deleted file mode 100644 index 442b2df23..000000000 --- a/web/src/shared/components/svg/IconNavProvisioners.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconNavProvisioners = (props: SVGProps) => ( - - - - -); -export default SvgIconNavProvisioners; diff --git a/web/src/shared/components/svg/IconNavSettings.tsx b/web/src/shared/components/svg/IconNavSettings.tsx deleted file mode 100644 index 3db043d78..000000000 --- a/web/src/shared/components/svg/IconNavSettings.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconNavSettings = (props: SVGProps) => ( - - - -); -export default SvgIconNavSettings; diff --git a/web/src/shared/components/svg/IconNavSupport.tsx b/web/src/shared/components/svg/IconNavSupport.tsx deleted file mode 100644 index cbfbfe855..000000000 --- a/web/src/shared/components/svg/IconNavSupport.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconNavSupport = (props: SVGProps) => ( - - - - -); -export default SvgIconNavSupport; diff --git a/web/src/shared/components/svg/IconNavUsers.tsx b/web/src/shared/components/svg/IconNavUsers.tsx deleted file mode 100644 index ac8ebfbff..000000000 --- a/web/src/shared/components/svg/IconNavUsers.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconNavUsers = (props: SVGProps) => ( - - - -); -export default SvgIconNavUsers; diff --git a/web/src/shared/components/svg/IconNavVpn.tsx b/web/src/shared/components/svg/IconNavVpn.tsx deleted file mode 100644 index 3cf82d4a6..000000000 --- a/web/src/shared/components/svg/IconNavVpn.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconNavVpn = (props: SVGProps) => ( - - - - - - -); -export default SvgIconNavVpn; diff --git a/web/src/shared/components/svg/IconNavWebhook.tsx b/web/src/shared/components/svg/IconNavWebhook.tsx deleted file mode 100644 index e9fdade7e..000000000 --- a/web/src/shared/components/svg/IconNavWebhook.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconNavWebhook = (props: SVGProps) => ( - - - - -); -export default SvgIconNavWebhook; diff --git a/web/src/shared/components/svg/IconNavWebhooks.tsx b/web/src/shared/components/svg/IconNavWebhooks.tsx deleted file mode 100644 index a391baa50..000000000 --- a/web/src/shared/components/svg/IconNavWebhooks.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconNavWebhooks = (props: SVGProps) => ( - - - - - - - - - - - - -); -export default SvgIconNavWebhooks; diff --git a/web/src/shared/components/svg/IconNavYubikey.tsx b/web/src/shared/components/svg/IconNavYubikey.tsx deleted file mode 100644 index 2c10c8923..000000000 --- a/web/src/shared/components/svg/IconNavYubikey.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconNavYubikey = (props: SVGProps) => ( - - - - -); -export default SvgIconNavYubikey; diff --git a/web/src/shared/components/svg/IconNetworkLoad.tsx b/web/src/shared/components/svg/IconNetworkLoad.tsx deleted file mode 100644 index a3f50dfc7..000000000 --- a/web/src/shared/components/svg/IconNetworkLoad.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconNetworkLoad = (props: SVGProps) => ( - - - -); -export default SvgIconNetworkLoad; diff --git a/web/src/shared/components/svg/IconOpenModal.tsx b/web/src/shared/components/svg/IconOpenModal.tsx deleted file mode 100644 index a448fda11..000000000 --- a/web/src/shared/components/svg/IconOpenModal.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconOpenModal = (props: SVGProps) => ( - - - - - - - - - - - - - - - - - - -); -export default SvgIconOpenModal; diff --git a/web/src/shared/components/svg/IconPacketsIn.tsx b/web/src/shared/components/svg/IconPacketsIn.tsx deleted file mode 100644 index 6aa735278..000000000 --- a/web/src/shared/components/svg/IconPacketsIn.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconPacketsIn = (props: SVGProps) => ( - - - - - - - - - - - - - - - - -); -export default SvgIconPacketsIn; diff --git a/web/src/shared/components/svg/IconPacketsOut.tsx b/web/src/shared/components/svg/IconPacketsOut.tsx deleted file mode 100644 index 63e77cecd..000000000 --- a/web/src/shared/components/svg/IconPacketsOut.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconPacketsOut = (props: SVGProps) => ( - - - - - - - - - - - - - - - - -); -export default SvgIconPacketsOut; diff --git a/web/src/shared/components/svg/IconPlusGray.tsx b/web/src/shared/components/svg/IconPlusGray.tsx deleted file mode 100644 index 154a6002a..000000000 --- a/web/src/shared/components/svg/IconPlusGray.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconPlusGray = (props: SVGProps) => ( - - - - - - - - - - - - -); -export default SvgIconPlusGray; diff --git a/web/src/shared/components/svg/IconPlusWhite.tsx b/web/src/shared/components/svg/IconPlusWhite.tsx deleted file mode 100644 index 718ed0d22..000000000 --- a/web/src/shared/components/svg/IconPlusWhite.tsx +++ /dev/null @@ -1,45 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconPlusWhite = (props: SVGProps) => ( - - - - - - - - - - - - -); -export default SvgIconPlusWhite; diff --git a/web/src/shared/components/svg/IconPopupClose.tsx b/web/src/shared/components/svg/IconPopupClose.tsx deleted file mode 100644 index bd938d4ff..000000000 --- a/web/src/shared/components/svg/IconPopupClose.tsx +++ /dev/null @@ -1,46 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconPopupClose = (props: SVGProps) => ( - - - - - - - - - - - - -); -export default SvgIconPopupClose; diff --git a/web/src/shared/components/svg/IconReadMore.tsx b/web/src/shared/components/svg/IconReadMore.tsx deleted file mode 100644 index 4f8176028..000000000 --- a/web/src/shared/components/svg/IconReadMore.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconReadMore = (props: SVGProps) => ( - - - - - - - - - - - - - -); -export default SvgIconReadMore; diff --git a/web/src/shared/components/svg/IconRedirect.tsx b/web/src/shared/components/svg/IconRedirect.tsx deleted file mode 100644 index 4752372d1..000000000 --- a/web/src/shared/components/svg/IconRedirect.tsx +++ /dev/null @@ -1,164 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconRedirect = (props: SVGProps) => ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -); -export default SvgIconRedirect; diff --git a/web/src/shared/components/svg/IconSearch.tsx b/web/src/shared/components/svg/IconSearch.tsx deleted file mode 100644 index 8383671ee..000000000 --- a/web/src/shared/components/svg/IconSearch.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconSearch = (props: SVGProps) => ( - - - - - - - - - -); -export default SvgIconSearch; diff --git a/web/src/shared/components/svg/IconSearchHover.tsx b/web/src/shared/components/svg/IconSearchHover.tsx deleted file mode 100644 index 92fef84bb..000000000 --- a/web/src/shared/components/svg/IconSearchHover.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconSearchHover = (props: SVGProps) => ( - - - - - - - - - -); -export default SvgIconSearchHover; diff --git a/web/src/shared/components/svg/IconSettings.tsx b/web/src/shared/components/svg/IconSettings.tsx deleted file mode 100644 index cea59aed9..000000000 --- a/web/src/shared/components/svg/IconSettings.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconSettings = (props: SVGProps) => ( - - - -); -export default SvgIconSettings; diff --git a/web/src/shared/components/svg/IconSuccessLarge.tsx b/web/src/shared/components/svg/IconSuccessLarge.tsx deleted file mode 100644 index 63ef6c588..000000000 --- a/web/src/shared/components/svg/IconSuccessLarge.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconSuccessLarge = (props: SVGProps) => ( - - - - - - -); -export default SvgIconSuccessLarge; diff --git a/web/src/shared/components/svg/IconTagDismiss.tsx b/web/src/shared/components/svg/IconTagDismiss.tsx deleted file mode 100644 index 9a8b5c809..000000000 --- a/web/src/shared/components/svg/IconTagDismiss.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { type SVGProps, useId } from 'react'; - -const SvgIconTagDismiss = (props: SVGProps) => { - const maskId = useId(); - return ( - - - - - - - - - - - - ); -}; -export default SvgIconTagDismiss; diff --git a/web/src/shared/components/svg/IconTrash.tsx b/web/src/shared/components/svg/IconTrash.tsx deleted file mode 100644 index d3c093696..000000000 --- a/web/src/shared/components/svg/IconTrash.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconTrash = (props: SVGProps) => ( - - - - - - - - - - - - - -); -export default SvgIconTrash; diff --git a/web/src/shared/components/svg/IconUpgrade.tsx b/web/src/shared/components/svg/IconUpgrade.tsx deleted file mode 100644 index c0cf9fc29..000000000 --- a/web/src/shared/components/svg/IconUpgrade.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconUpgrade = (props: SVGProps) => ( - - - - - - - - - - - -); -export default SvgIconUpgrade; diff --git a/web/src/shared/components/svg/IconUserAddNew.tsx b/web/src/shared/components/svg/IconUserAddNew.tsx deleted file mode 100644 index 9a285b185..000000000 --- a/web/src/shared/components/svg/IconUserAddNew.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconUserAddNew = (props: SVGProps) => ( - - - -); -export default SvgIconUserAddNew; diff --git a/web/src/shared/components/svg/IconUserList.tsx b/web/src/shared/components/svg/IconUserList.tsx deleted file mode 100644 index 659c6b96b..000000000 --- a/web/src/shared/components/svg/IconUserList.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconUserList = (props: SVGProps) => ( - - - - - - - - - - - - - -); -export default SvgIconUserList; diff --git a/web/src/shared/components/svg/IconUserListElement.tsx b/web/src/shared/components/svg/IconUserListElement.tsx deleted file mode 100644 index 36305fdc2..000000000 --- a/web/src/shared/components/svg/IconUserListElement.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconUserListElement = (props: SVGProps) => ( - - - - - - - - - - - - - - -); -export default SvgIconUserListElement; diff --git a/web/src/shared/components/svg/IconUserListExpanded.tsx b/web/src/shared/components/svg/IconUserListExpanded.tsx deleted file mode 100644 index 16e35f574..000000000 --- a/web/src/shared/components/svg/IconUserListExpanded.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconUserListExpanded = (props: SVGProps) => ( - - - - - - - - - - - - - - - - - - - - - - - - - -); -export default SvgIconUserListExpanded; diff --git a/web/src/shared/components/svg/IconUserListHover.tsx b/web/src/shared/components/svg/IconUserListHover.tsx deleted file mode 100644 index f5906314b..000000000 --- a/web/src/shared/components/svg/IconUserListHover.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconUserListHover = (props: SVGProps) => ( - - - - - - - - - - - - - -); -export default SvgIconUserListHover; diff --git a/web/src/shared/components/svg/IconWaiting.tsx b/web/src/shared/components/svg/IconWaiting.tsx deleted file mode 100644 index b08b28e05..000000000 --- a/web/src/shared/components/svg/IconWaiting.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconWaiting = (props: SVGProps) => ( - - - - - - - - - - - - -); -export default SvgIconWaiting; diff --git a/web/src/shared/components/svg/IconWaitingHover.tsx b/web/src/shared/components/svg/IconWaitingHover.tsx deleted file mode 100644 index 9526ae6b5..000000000 --- a/web/src/shared/components/svg/IconWaitingHover.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconWaitingHover = (props: SVGProps) => ( - - - - - - - - - - - - -); -export default SvgIconWaitingHover; diff --git a/web/src/shared/components/svg/IconWallet.tsx b/web/src/shared/components/svg/IconWallet.tsx deleted file mode 100644 index bb2b1b2f9..000000000 --- a/web/src/shared/components/svg/IconWallet.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconWallet = (props: SVGProps) => ( - - - -); -export default SvgIconWallet; diff --git a/web/src/shared/components/svg/IconWarning.tsx b/web/src/shared/components/svg/IconWarning.tsx deleted file mode 100644 index 26ed48b02..000000000 --- a/web/src/shared/components/svg/IconWarning.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconWarning = (props: SVGProps) => ( - - - - - - -); -export default SvgIconWarning; diff --git a/web/src/shared/components/svg/IconX.tsx b/web/src/shared/components/svg/IconX.tsx deleted file mode 100644 index aa76fe576..000000000 --- a/web/src/shared/components/svg/IconX.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgIconX = (props: SVGProps) => ( - - - - - - - - - - - -); -export default SvgIconX; diff --git a/web/src/shared/components/svg/ImageMeshNetwork.tsx b/web/src/shared/components/svg/ImageMeshNetwork.tsx deleted file mode 100644 index bda85528a..000000000 --- a/web/src/shared/components/svg/ImageMeshNetwork.tsx +++ /dev/null @@ -1,157 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgImageMeshNetwork = (props: SVGProps) => ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -); -export default SvgImageMeshNetwork; diff --git a/web/src/shared/components/svg/ImageRegularNetwork.tsx b/web/src/shared/components/svg/ImageRegularNetwork.tsx deleted file mode 100644 index cb7a8bc2a..000000000 --- a/web/src/shared/components/svg/ImageRegularNetwork.tsx +++ /dev/null @@ -1,165 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgImageRegularNetwork = (props: SVGProps) => ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -); -export default SvgImageRegularNetwork; diff --git a/web/src/shared/components/svg/ImportConfig.tsx b/web/src/shared/components/svg/ImportConfig.tsx deleted file mode 100644 index bb40ae38c..000000000 --- a/web/src/shared/components/svg/ImportConfig.tsx +++ /dev/null @@ -1,69 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgImportConfig = (props: SVGProps) => ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -); -export default SvgImportConfig; diff --git a/web/src/shared/components/svg/LogoDefguardWhite.tsx b/web/src/shared/components/svg/LogoDefguardWhite.tsx deleted file mode 100644 index 51dc3b95c..000000000 --- a/web/src/shared/components/svg/LogoDefguardWhite.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgLogoDefguardWhite = (props: SVGProps) => ( - - - - - - - - - - -); -export default SvgLogoDefguardWhite; diff --git a/web/src/shared/components/svg/ManualConfig.tsx b/web/src/shared/components/svg/ManualConfig.tsx deleted file mode 100644 index 88828f711..000000000 --- a/web/src/shared/components/svg/ManualConfig.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgManualConfig = (props: SVGProps) => ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -); -export default SvgManualConfig; diff --git a/web/src/shared/components/svg/MetamaskIcon.tsx b/web/src/shared/components/svg/MetamaskIcon.tsx deleted file mode 100644 index 14ff42f23..000000000 --- a/web/src/shared/components/svg/MetamaskIcon.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgMetamaskIcon = (props: SVGProps) => ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -); -export default SvgMetamaskIcon; diff --git a/web/src/shared/components/svg/PhantomIcon.tsx b/web/src/shared/components/svg/PhantomIcon.tsx deleted file mode 100644 index abb6683c8..000000000 --- a/web/src/shared/components/svg/PhantomIcon.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgPhantomIcon = (props: SVGProps) => ( - - - - - - - - - - - - - - -); -export default SvgPhantomIcon; diff --git a/web/src/shared/components/svg/QrIconWhite.tsx b/web/src/shared/components/svg/QrIconWhite.tsx deleted file mode 100644 index 30424c4fb..000000000 --- a/web/src/shared/components/svg/QrIconWhite.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgQrIconWhite = (props: SVGProps) => ( - - - - - - - - - - - - - - - - - - - - - - -); -export default SvgQrIconWhite; diff --git a/web/src/shared/components/svg/Subtract.tsx b/web/src/shared/components/svg/Subtract.tsx deleted file mode 100644 index 340e023f5..000000000 --- a/web/src/shared/components/svg/Subtract.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgSubtract = (props: SVGProps) => ( - - - -); -export default SvgSubtract; diff --git a/web/src/shared/components/svg/WireguardLogo.tsx b/web/src/shared/components/svg/WireguardLogo.tsx deleted file mode 100644 index 2799fd73a..000000000 --- a/web/src/shared/components/svg/WireguardLogo.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgWireguardLogo = (props: SVGProps) => ( - - - - - - - - - - -); -export default SvgWireguardLogo; diff --git a/web/src/shared/components/svg/YubikeyProvisioningGraphic.tsx b/web/src/shared/components/svg/YubikeyProvisioningGraphic.tsx deleted file mode 100644 index c9aac1bb7..000000000 --- a/web/src/shared/components/svg/YubikeyProvisioningGraphic.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import type { SVGProps } from 'react'; - -const SvgYubikeyProvisioningGraphic = (props: SVGProps) => ( - - - - - - - - - - - - - - - - - - - - - -); -export default SvgYubikeyProvisioningGraphic; diff --git a/web/src/shared/components/utils/DelayRender/DelayRender.tsx b/web/src/shared/components/utils/DelayRender/DelayRender.tsx deleted file mode 100644 index 1da5d11aa..000000000 --- a/web/src/shared/components/utils/DelayRender/DelayRender.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { type ReactNode, useEffect, useState } from 'react'; - -interface Props { - delay: number; - fallback: ReactNode | null; - children?: ReactNode; -} - -export const DelayRender = ({ delay, fallback, children }: Props) => { - const [isShown, setIsShown] = useState(false); - - useEffect(() => { - setTimeout(() => { - setIsShown(true); - }, delay); - }, [delay]); - - return ( - <> - {isShown && children ? children : null} - {!isShown && fallback ? fallback : null} - - ); -}; diff --git a/web/src/shared/components/wizard/WizardCard/WizardCard.tsx b/web/src/shared/components/wizard/WizardCard/WizardCard.tsx new file mode 100644 index 000000000..57671ac4b --- /dev/null +++ b/web/src/shared/components/wizard/WizardCard/WizardCard.tsx @@ -0,0 +1,13 @@ +import type { HTMLProps } from 'react'; +import './style.scss'; +import clsx from 'clsx'; + +type Props = HTMLProps; + +export const WizardCard = ({ className, children, ...props }: Props) => { + return ( +
+ {children} +
+ ); +}; diff --git a/web/src/shared/components/wizard/WizardCard/style.scss b/web/src/shared/components/wizard/WizardCard/style.scss new file mode 100644 index 000000000..27e3d7019 --- /dev/null +++ b/web/src/shared/components/wizard/WizardCard/style.scss @@ -0,0 +1,16 @@ +.wizard-card { + box-sizing: border-box; + padding: var(--spacing-2xl); + border: var(--border-1) solid var(--border-disabled); + border-radius: var(--radius-xl); + width: 100%; + + & > * { + width: 100%; + } + + .modal-controls, + .controls { + padding-top: var(--spacing-3xl); + } +} diff --git a/web/src/shared/components/wizard/WizardPage/WizardPage.tsx b/web/src/shared/components/wizard/WizardPage/WizardPage.tsx new file mode 100644 index 000000000..91fecae8b --- /dev/null +++ b/web/src/shared/components/wizard/WizardPage/WizardPage.tsx @@ -0,0 +1,86 @@ +import { type HTMLProps, type PropsWithChildren, useEffect, useMemo } from 'react'; +import './style.scss'; +import clsx from 'clsx'; +import { orderBy } from 'lodash-es'; +import { Badge } from '../../../defguard-ui/components/Badge/Badge'; +import { SizedBox } from '../../../defguard-ui/components/SizedBox/SizedBox'; +import { ThemeSpacing } from '../../../defguard-ui/types'; +import { isPresent } from '../../../defguard-ui/utils/isPresent'; +import { LayoutGrid } from '../../LayoutGrid/LayoutGrid'; +import type { WizardPageConfig } from '../types'; +import { WizardStepsCard } from '../WizardStepsCard/WizardStepsCard'; +import { WizardTop } from '../WizardTop/WizardTop'; + +type Props = HTMLProps & + PropsWithChildren & + WizardPageConfig & { + onClose: () => void; + }; + +export const WizardPage = ({ + className, + activeStep: activeStepId, + steps, + subtitle, + title, + children, + onClose, + ...containerProps +}: Props) => { + const activeStep = steps[activeStepId]; + + const visibleSteps = useMemo( + () => + orderBy( + Object.values(steps).filter((step) => !step.hidden), + (s) => s.order, + ['asc'], + ), + [steps], + ); + + const activeStepIndex = useMemo( + () => visibleSteps.findIndex((s) => s.id === activeStepId), + [visibleSteps, activeStepId], + ); + + // Warn user that if the tab with wizard is closed he's progress can be not recoverable + useEffect(() => { + if (!import.meta.env.DEV) { + window.onbeforeunload = () => 'All unsaved changes will be lost!'; + + return () => { + window.onbeforeunload = null; + }; + } + }, []); + + return ( +
+ +
+
+ +
+

{title}

+ +

{subtitle}

+ + +
+
+ + +

{activeStep.label}

+ {isPresent(activeStep.description) && ( +

{activeStep.description}

+ )} + + {children} +
+
+
+
+
+ ); +}; diff --git a/web/src/shared/components/wizard/WizardPage/style.scss b/web/src/shared/components/wizard/WizardPage/style.scss new file mode 100644 index 000000000..db7e173f5 --- /dev/null +++ b/web/src/shared/components/wizard/WizardPage/style.scss @@ -0,0 +1,52 @@ +.wizard-page { + --page-content-limit: 1120px; + + & > .limiter { + display: flex; + flex-flow: row; + align-items: center; + justify-content: center; + + & > .content-tack { + width: 100%; + max-width: var(--page-content-limit); + + & > .layout-grid { + & > .side { + grid-row: 1; + grid-column: 1 / 5; + } + + & > .main { + grid-row: 1; + grid-column: 5 / 13; + + & > .step-title { + font: var(--t-title-h5); + } + + & > .step-description { + font: var(--t-body-sm-400); + color: var(--fg-neutral); + } + } + } + } + } + + .layout-grid { + padding-top: var(--spacing-4xl); + } + + .layout-grid > .side { + .title { + font: var(--t-title-h3); + color: var(--fg-default); + } + + .description { + font: var(--t-body-sm-400); + color: var(--fg-muted); + } + } +} diff --git a/web/src/shared/components/wizard/WizardStepsCard/WizardStepsCard.tsx b/web/src/shared/components/wizard/WizardStepsCard/WizardStepsCard.tsx new file mode 100644 index 000000000..29e5367d6 --- /dev/null +++ b/web/src/shared/components/wizard/WizardStepsCard/WizardStepsCard.tsx @@ -0,0 +1,43 @@ +import { Fragment } from 'react/jsx-runtime'; +import type { WizardPageStep } from '../types'; +import './style.scss'; +import clsx from 'clsx'; +import { Icon } from '../../../defguard-ui/components/Icon'; + +interface Props { + activeStep: WizardPageStep; + steps: WizardPageStep[]; +} + +export const WizardStepsCard = ({ steps, activeStep }: Props) => { + return ( +
+
    + {steps.map((step, index) => ( + +
  • activeStep.order, + active: step.id === activeStep.id, + success: step.order < activeStep.order, + })} + > +
    +
    + {step.order < activeStep.order && } + {step.order >= activeStep.order && {index + 1}} +
    + {step.label} +
  • + {index !== steps.length - 1 && ( +
  • +
    +
  • + )} +
    + ))} +
+
+ ); +}; diff --git a/web/src/shared/components/wizard/WizardStepsCard/style.scss b/web/src/shared/components/wizard/WizardStepsCard/style.scss new file mode 100644 index 000000000..3504e9269 --- /dev/null +++ b/web/src/shared/components/wizard/WizardStepsCard/style.scss @@ -0,0 +1,107 @@ +.wizard-steps-card { + box-sizing: border-box; + padding: var(--spacing-xl); + border-radius: var(--radius-xl); + background-color: var(--bg-disabled); + + ul { + list-style: none; + display: flex; + flex-flow: column; + row-gap: var(--spacing-sm); + + li:not(.spacer) { + --bg: var(--bg-faded); + --indicator-color: var(--fg-white); + --color: var(--fg-muted); + + display: flex; + flex-flow: row nowrap; + column-gap: var(--spacing-md); + + &.muted { + --bg: var(--bg-faded); + } + + &.active { + --bg: var(--bg-action); + --color: var(--fg-action); + } + + &.success { + --bg: var(--bg-success); + --color: var(--fg-success); + } + + & > span { + font: var(--t-body-sm-400); + color: var(--color); + + @include animate(color); + } + + .step-indicator { + display: grid; + grid-template-columns: 20px; + grid-template-rows: 20px; + place-items: center center; + + .icon { + display: flex; + } + + .icon path { + fill: var(--indicator-color); + } + + .icon, + span, + div { + grid-row: 1; + grid-column: 1 / 2; + } + + span { + font: var(--t-body-xs-500); + color: var(--indicator-color); + width: 100%; + max-width: 100%; + text-align: center; + + @include animate(color); + } + + .circle { + display: block; + content: ' '; + width: 100%; + height: 100%; + border-radius: var(--radius-full); + background-color: var(--bg); + + @include animate(background-color); + } + } + } + + li.spacer { + user-select: none; + display: inline-block; + content: ' '; + position: relative; + width: 10px; + height: 12px; + + & > .line { + display: block; + content: ' '; + position: absolute; + right: -0.5px; + top: 0; + width: 1px; + height: 100%; + background-color: var(--border-muted); + } + } + } +} diff --git a/web/src/shared/components/wizard/WizardTop/WizardTop.tsx b/web/src/shared/components/wizard/WizardTop/WizardTop.tsx new file mode 100644 index 000000000..dbdcf0a6a --- /dev/null +++ b/web/src/shared/components/wizard/WizardTop/WizardTop.tsx @@ -0,0 +1,18 @@ +import { IconButton } from '../../../defguard-ui/components/IconButton/IconButton'; +import { NavLogo } from '../../Navigation/assets/NavLogo'; +import './style.scss'; + +type Props = { + onClick: () => void; +}; + +export const WizardTop = ({ onClick }: Props) => { + return ( +
+
+ + +
+
+ ); +}; diff --git a/web/src/shared/components/wizard/WizardTop/style.scss b/web/src/shared/components/wizard/WizardTop/style.scss new file mode 100644 index 000000000..afdad58d4 --- /dev/null +++ b/web/src/shared/components/wizard/WizardTop/style.scss @@ -0,0 +1,18 @@ +.wizard-top { + width: 100%; + border-bottom: var(--border-1) solid var(--border-disabled); + min-height: 60px; + display: flex; + flex-flow: row; + align-items: center; + justify-content: center; + + .content-track { + display: flex; + flex-flow: row; + align-items: center; + justify-content: space-between; + width: 100%; + max-width: var(--page-content-limit); + } +} diff --git a/web/src/shared/components/wizard/types.ts b/web/src/shared/components/wizard/types.ts new file mode 100644 index 000000000..cb0976455 --- /dev/null +++ b/web/src/shared/components/wizard/types.ts @@ -0,0 +1,22 @@ +export interface WizardPageConfig { + title: string; + subtitle: string; + activeStep: string | number; + steps: WizardPageStepsConfig; + relatedDocs?: WizardDocsLink[]; +} + +export interface WizardDocsLink { + link: string; + label: string; +} + +export interface WizardPageStep { + id: number | string; + order: number; + label: string; + description?: string; + hidden?: boolean; +} + +export type WizardPageStepsConfig = Record; diff --git a/web/src/shared/const/overviewPeriodOptions.ts b/web/src/shared/const/overviewPeriodOptions.ts new file mode 100644 index 000000000..17b3e1c68 --- /dev/null +++ b/web/src/shared/const/overviewPeriodOptions.ts @@ -0,0 +1,11 @@ +import type { SelectOption } from '../defguard-ui/components/Select/types'; + +export const overviewPeriodOptions: SelectOption[] = [ + { key: 1, label: '1h period', value: 1 }, + { key: 2, label: '2h period', value: 2 }, + { key: 6, label: '6h period', value: 6 }, + { key: 8, label: '8h period', value: 8 }, + { key: 12, label: '12h period', value: 12 }, + { key: 16, label: '16h period', value: 16 }, + { key: 24, label: '24h period', value: 24 }, +]; diff --git a/web/src/shared/constants.ts b/web/src/shared/constants.ts index b3885eb78..d40741216 100644 --- a/web/src/shared/constants.ts +++ b/web/src/shared/constants.ts @@ -1,46 +1,41 @@ -// Mirror of colors.scss -export enum ColorsRGB { - InputSelect = '#77c9ff', - InputWarning = '#dc8787', - White = '#ffffff', - GrayBorderDark = '#617684', - GrayBorder = '#e8e8e8', - BgLight = '#f9f9f9', - TextMain = '#222222', - GrayLighter = '#cbd3d8', - GrayLight = '#899CA8', - // eslint-disable-next-line - GrayDark = '#617684', - GrayDarker = '#485964', - Success = '#14bc6e', - SuccessDark = '#10A15E', - Error = '#cb3f3f', - ErrorDark = '#B53030', - Primary = '#0c8ce0', - PrimaryDark = '#0876BE', - LightGreenBg = '#eefff7', - LightRedBg = '#fff5f5', - LightGrayBg = '#F1F1F1', - Warning = '#c8a043', - SeparatorStroke = '#c4c4c4', - Transparent = '#00000000', - DefaultBoxShadow = '#0000001A', -} +import { + ExternalProvider, + type ExternalProviderValue, +} from '../pages/settings/shared/types'; -export const StandardBoxShadow = `5px 10px 20px rgba(0, 0, 0, 0.1)`; +export const externalLink = { + defguard: { + download: 'https://defguard.net/download', + }, + client: { + desktop: { + linux: { + arch: 'https://aur.archlinux.org/packages/defguard-client', + }, + }, + mobile: { + apple: 'https://apps.apple.com/us/app/defguard-vpn-client/id6748068630', + google: 'https://play.google.com/store/apps/details?id=net.defguard.mobile', + }, + }, +} as const; -export const inactiveBoxShadow = '0px 6px 10px rgba(0, 0, 0, 0)'; - -export const buttonsBoxShadow = `0px 6px 10px rgba(0,0,0,0.1)`; +export const externalProviderName: Record = { + custom: 'Custom provider', + google: 'Google', + jumpCloud: 'JumpCloud', + microsoft: 'Microsoft', + okta: 'Okta', + zitadel: 'Zitadel', +}; -export const cardsShadow = '5px 5px 15px rgba(0,0,0,0.02)'; +export const SUPPORTED_SYNC_PROVIDERS: Set = new Set([ + ExternalProvider.Google, + ExternalProvider.Microsoft, + ExternalProvider.Okta, + ExternalProvider.JumpCloud, +]); -export enum FramerMotionID { - UserProfileTabUnderline = 'UserProfileTabUnderline', -} +export const googleProviderBaseUrl = 'https://accounts.google.com'; -export const deviceBreakpoints = { - mobile: 0, - tablet: 768, - desktop: 992, -}; +export const jumpcloudProviderBaseUrl = 'https://oauth.id.jumpcloud.com'; diff --git a/web/src/shared/defguard-ui b/web/src/shared/defguard-ui index 41f090911..c328669db 160000 --- a/web/src/shared/defguard-ui +++ b/web/src/shared/defguard-ui @@ -1 +1 @@ -Subproject commit 41f0909110f8358e5225f3a9c19bd6590d5e905c +Subproject commit c328669db363dd91bb31a58b2bcb7990dded13f3 diff --git a/web/src/shared/form.tsx b/web/src/shared/form.tsx new file mode 100644 index 000000000..e47ee00f9 --- /dev/null +++ b/web/src/shared/form.tsx @@ -0,0 +1,35 @@ +import { createFormHook, createFormHookContexts } from '@tanstack/react-form'; +import { FormSelectMultiple } from './components/FormSelectMultiple/FormSelectMultiple'; +import { FormUploadField } from './components/FormUploadField/FormUploadField'; +import { FormCheckbox } from './defguard-ui/components/form/FormCheckbox/FormCheckbox'; +import { FormInput } from './defguard-ui/components/form/FormInput/FormInput'; +import { FormInteractiveBlock } from './defguard-ui/components/form/FormInteractiveBlock/FormInteractiveBlock'; +import { FormRadio } from './defguard-ui/components/form/FormRadio/FormRadio'; +import { FormSelect } from './defguard-ui/components/form/FormSelect/FormSelect'; +import { FormSubmitButton } from './defguard-ui/components/form/FormSubmitButton/FormSubmitButton'; +import { FormSuggestedIPInput } from './defguard-ui/components/form/FormSuggestedIPInput/FormSuggestedIPInput'; +import { FormTextarea } from './defguard-ui/components/form/FormTextarea/FormTextarea'; +import { FormToggle } from './defguard-ui/components/form/FormToggle/FormToggle'; + +export const { fieldContext, formContext, useFieldContext, useFormContext } = + createFormHookContexts(); + +export const { useAppForm, withFieldGroup, withForm } = createFormHook({ + fieldContext, + formContext, + fieldComponents: { + FormTextarea, + FormInput, + FormSelect, + FormCheckbox, + FormRadio, + FormToggle, + FormSuggestedIPInput, + FormSelectMultiple, + FormInteractiveBlock, + FormUploadField, + }, + formComponents: { + FormSubmitButton, + }, +}); diff --git a/web/src/shared/formLogic.ts b/web/src/shared/formLogic.ts new file mode 100644 index 000000000..11aa1d6d3 --- /dev/null +++ b/web/src/shared/formLogic.ts @@ -0,0 +1,6 @@ +import { revalidateLogic } from '@tanstack/react-form'; + +export const formChangeLogic = revalidateLogic({ + mode: 'change', + modeAfterSubmission: 'change', +}); diff --git a/web/src/shared/helpers/displayDate.ts b/web/src/shared/helpers/displayDate.ts deleted file mode 100644 index fbd5e549c..000000000 --- a/web/src/shared/helpers/displayDate.ts +++ /dev/null @@ -1,8 +0,0 @@ -import dayjs from 'dayjs'; - -/** - * Parse date from Core API to readable standarized date to display for user to see. - * **/ -export const displayDate = (dateFromApi: string): string => { - return dayjs.utc(dateFromApi).format('DD.MM.YYYY'); -}; diff --git a/web/src/shared/helpers/getUserFullName.ts b/web/src/shared/helpers/getUserFullName.ts deleted file mode 100644 index 5ef789529..000000000 --- a/web/src/shared/helpers/getUserFullName.ts +++ /dev/null @@ -1,4 +0,0 @@ -import type { User } from '../types'; - -export const getUserFullName = (user: User): string => - `${user.first_name} ${user.last_name}`; diff --git a/web/src/shared/hooks/api/api-client.ts b/web/src/shared/hooks/api/api-client.ts deleted file mode 100644 index 31a0a6c21..000000000 --- a/web/src/shared/hooks/api/api-client.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { buildApi } from './api'; -import axiosClient from './axios-client'; - -const apiEndpoints = buildApi(axiosClient); - -export default apiEndpoints; diff --git a/web/src/shared/hooks/api/api.ts b/web/src/shared/hooks/api/api.ts deleted file mode 100644 index 86605260a..000000000 --- a/web/src/shared/hooks/api/api.ts +++ /dev/null @@ -1,741 +0,0 @@ -import type { Axios, AxiosResponse } from 'axios'; - -import { getNetworkStatsFilterValue } from '../../../pages/overview/helpers/stats'; -import type { - AddDeviceResponse, - AddOpenidClientRequest, - AddUserRequest, - Api, - AuthorizedClient, - ChangeOpenidClientStateRequest, - ChangePasswordRequest, - changeWebhookStateRequest, - Device, - EditOpenidClientRequest, - EmptyApiResponse, - GetNetworkStatsRequest, - GroupsResponse, - LoginData, - LoginResponse, - MFALoginResponse, - Network, - NetworkToken, - OpenIdInfo, - OpenidClient, - Provisioner, - RemoveUserClientRequest, - ResetPasswordRequest, - Settings, - StartEnrollmentRequest, - StartEnrollmentResponse, - User, - UserEditRequest, - UserGroupRequest, - UserProfile, - VerifyOpenidClientRequest, - WireguardNetworkStats, - WorkerJobRequest, - WorkerJobResponse, - WorkerJobStatus, - WorkerToken, -} from '../../types'; -import type { UpdateInfo } from '../store/useUpdatesStore'; - -const unpackRequest = (res: AxiosResponse): T => res.data; - -export const buildApi = (client: Axios): Api => { - const getOutdatedInfo = () => client.get(`/outdated`).then(unpackRequest); - - const addUser = (data: AddUserRequest) => - client.post(`/user`, data).then(unpackRequest); - - const getMe = () => client.get(`/me`).then(unpackRequest); - - const getUser: Api['user']['getUser'] = (username) => - client.get(`/user/${username}`).then(unpackRequest); - - const editUser = ({ username, data }: UserEditRequest) => - client.put(`/user/${username}`, data).then(unpackRequest); - - const deleteUser = (user: User) => - client.delete(`/user/${user.username}`).then(unpackRequest); - - const fetchDevices = () => client.get(`/device`).then(unpackRequest); - - const fetchDevice = (id: string) => - client.get(`/device/${id}`).then(unpackRequest); - - const getUsers = () => client.get('/user').then(unpackRequest); - - const downloadDeviceConfig: Api['device']['downloadDeviceConfig'] = (data) => - client - .get(`/network/${data.network_id}/device/${data.device_id}/config`) - .then(unpackRequest); - - const modifyDevice = (device: Device) => - client.put(`/device/${device.id}`, device).then(unpackRequest); - - const deleteDevice = (device: Device) => - client.delete(`/device/${device.id}`); - - const addDevice: Api['device']['addDevice'] = ({ username, ...rest }) => - client.post(`/device/${username}`, rest).then(unpackRequest); - - const fetchUserDevices = (username: string) => - client.get(`/device/user/${username}`).then(unpackRequest); - - const fetchNetworks = () => client.get(`/network`).then(unpackRequest); - - const fetchNetwork = (id: number) => - client.get(`/network/${id}`).then(unpackRequest); - - // For now there is only one network - const modifyNetwork: Api['network']['editNetwork'] = (data) => - client.put(`/network/${data.id}`, data.network).then(unpackRequest); - - const deleteNetwork: Api['network']['deleteNetwork'] = (id) => - client.delete(`/network/${id}`); - - const addNetwork: Api['network']['addNetwork'] = (network) => - client.post(`/network`, network).then(unpackRequest); - - const importNetwork: Api['network']['importNetwork'] = (network) => - client.post(`/network/import`, network).then(unpackRequest); - - const mapUserDevices: Api['network']['mapUserDevices'] = (data) => - client - .post(`/network/${data.networkId}/devices`, { devices: data.devices }) - .then(unpackRequest); - - const login: Api['auth']['login'] = (data: LoginData) => - client.post('/auth', data).then((response) => { - if (response.status === 200) { - return response.data as LoginResponse; - } - if (response.status === 201) { - return { - mfa: response.data as MFALoginResponse, - }; - } - return {}; - }); - - const logout = () => client.post('/auth/logout').then(unpackRequest); - - const getOpenidInfo: Api['auth']['openid']['getOpenIdInfo'] = () => - client.get(`/openid/auth_info`).then(unpackRequest); - - const usernameAvailable = (username: string) => - client.post('/user/available', { username }); - - const getWorkers: Api['provisioning']['getWorkers'] = () => - client.get('/worker').then(unpackRequest); - - const provisionYubiKey = (data: WorkerJobRequest) => - client.post(`/worker/job`, data).then((response) => response.data); - - const getJobStatus = (id?: number) => - client.get(`/worker/${id}`).then(unpackRequest); - - const changePassword = ({ username, ...rest }: ChangePasswordRequest) => - client.put(`/user/${username}/password`, rest); - - const resetPassword = ({ username }: ResetPasswordRequest) => - client.post(`/user/${username}/reset_password`); - - const startEnrollment = ({ username, ...rest }: StartEnrollmentRequest) => - client - .post(`/user/${username}/start_enrollment`, rest) - .then((response) => response.data); - - const getGroups = () => client.get('/group').then(unpackRequest); - - const addToGroup = ({ group, ...rest }: UserGroupRequest) => - client.post(`/group/${group}`, rest); - - const removeFromGroup = ({ group, username }: UserGroupRequest) => - client.delete(`/group/${group}/user/${username}`); - - const createGroup: Api['groups']['createGroup'] = (data) => - client.post(`/group`, data).then(unpackRequest); - - const editGroup: Api['groups']['editGroup'] = ({ originalName, ...rest }) => - client.put(`/group/${originalName}`, rest).then(unpackRequest); - - const deleteWorker = (id: string) => - client.delete(`/worker/${id}`).then(unpackRequest); - - const getWebhooks = () => client.get('/webhook').then(unpackRequest); - - const deleteWebhook = (id: string) => - client.delete(`/webhook/${id}`).then(unpackRequest); - - const changeWebhookState = ({ id, ...rest }: changeWebhookStateRequest) => - client.post(`/webhook/${id}`, rest); - - const addWebhook: Api['webhook']['addWebhook'] = (data) => { - return client.post('/webhook', data); - }; - const editWebhook: Api['webhook']['editWebhook'] = ({ id, ...rest }) => { - return client.put(`/webhook/${id}`, rest); - }; - const getOpenidClients = () => client.get('/oauth').then(unpackRequest); - - const getOpenidClient = (client_id: string) => - client.get(`/oauth/${client_id}`).then(unpackRequest); - - const addOpenidClient = (data: AddOpenidClientRequest) => { - return client.post('/oauth', data); - }; - const editOpenidClient = ({ client_id, ...rest }: EditOpenidClientRequest) => { - return client.put(`/oauth/${client_id}`, rest); - }; - const changeOpenidClientState = ({ - clientId, - ...rest - }: ChangeOpenidClientStateRequest) => { - return client.post(`/oauth/${clientId}`, rest); - }; - const deleteOpenidClient = (id: string) => - client.delete(`/oauth/${id}`).then(unpackRequest); - - const verifyOpenidClient = (data: VerifyOpenidClientRequest) => - client.post('openid/verify', data); - - const getUserClients = (username: string) => - client.get(`/oauth/apps/${username}`).then(unpackRequest); - - const removeUserClient = (data: RemoveUserClientRequest) => - client - .delete(`/user/${data.username}/oauth_app/${data.client_id}`) - .then(unpackRequest); - - const oAuthConsent = (params: unknown) => - client - .post('/oauth/authorize', null, { - params: params, - }) - .then(unpackRequest); - - const getOverviewStats: Api['network']['getOverviewStats'] = ( - data: GetNetworkStatsRequest, - ) => { - const from = getNetworkStatsFilterValue(data.from ?? 1); - return client - .get(`/network/${data.id}/stats/users`, { - params: { - from, - }, - }) - .then(unpackRequest); - }; - - const getNetworkToken: Api['network']['getNetworkToken'] = (networkId) => - client.get(`/network/${networkId}/token`).then(unpackRequest); - - const getNetworkStats: Api['network']['getNetworkStats'] = (data) => { - const fromParam = getNetworkStatsFilterValue(data.from ?? 1); - return client - .get(`/network/${data.id}/stats`, { - params: { - from: fromParam, - }, - }) - .then(unpackRequest); - }; - - const getWorkerToken = () => - client.get('/worker/token').then(unpackRequest); - - const mfaDisable = () => client.delete('/auth/mfa').then(unpackRequest); - - const mfaWebauthnRegisterStart: Api['auth']['mfa']['webauthn']['register']['start'] = - () => client.post('/auth/webauthn/init').then(unpackRequest); - - const mfaWebauthnRegisterFinish: Api['auth']['mfa']['webauthn']['register']['finish'] = - (data) => client.post('/auth/webauthn/finish', data).then(unpackRequest); - - const mfaWebauthnStart = () => client.post('/auth/webauthn/start').then(unpackRequest); - - const mfaWebautnFinish: Api['auth']['mfa']['webauthn']['finish'] = (data) => - client.post('/auth/webauthn', data).then(unpackRequest); - - const mfaTOTPInit = () => client.post('/auth/totp/init').then(unpackRequest); - - const mfaTOTPEnable: Api['auth']['mfa']['totp']['enable'] = (data) => - client.post('/auth/totp', data).then(unpackRequest); - - const mfaTOTPDisable = () => client.delete('/auth/totp').then(unpackRequest); - - const mfaTOTPVerify: Api['auth']['mfa']['totp']['verify'] = (data) => - client.post('/auth/totp/verify', data).then(unpackRequest); - - const mfaEmailMFAInit: Api['auth']['mfa']['email']['register']['start'] = () => - client.post('/auth/email/init').then(unpackRequest); - - const mfaEmailMFAEnable: Api['auth']['mfa']['email']['register']['finish'] = (data) => - client.post('/auth/email', data).then(unpackRequest); - - const mfaEmailMFADisable = () => client.delete('/auth/email').then(unpackRequest); - - const mfaEmailMFASendCode: Api['auth']['mfa']['email']['sendCode'] = () => - client.get('/auth/email').then(unpackRequest); - - const mfaEmailMFAVerify: Api['auth']['mfa']['email']['verify'] = (data) => - client.post('/auth/email/verify', data).then(unpackRequest); - - const mfaWebauthnDeleteKey: Api['auth']['mfa']['webauthn']['deleteKey'] = ({ - keyId, - username, - }) => client.delete(`/user/${username}/security_key/${keyId}`); - - const getSettings = () => client.get('/settings').then(unpackRequest); - - const editSettings = (settings: Settings) => - client.put('/settings', settings).then(unpackRequest); - - const getEnterpriseInfo = () => client.get('/enterprise_info').then(unpackRequest); - - const mfaEnable = () => client.put('/auth/mfa').then(unpackRequest); - - const recovery: Api['auth']['mfa']['recovery'] = (data) => - client.post('/auth/recovery', data).then(unpackRequest); - - const getAppInfo: Api['getAppInfo'] = () => client.get('/info').then(unpackRequest); - - const setDefaultBranding: Api['settings']['setDefaultBranding'] = (id: string) => - client.put(`/settings/${id}`).then(unpackRequest); - - const downloadSupportData: Api['support']['downloadSupportData'] = () => - client.get(`/support/configuration`).then(unpackRequest); - - const downloadLogs: Api['support']['downloadLogs'] = () => - client.get(`/support/logs`).then(unpackRequest); - - const getGatewaysStatus: Api['network']['getGatewaysStatus'] = (networkId) => - client.get(`/network/${networkId}/gateways`).then(unpackRequest); - - const deleteGateway: Api['network']['deleteGateway'] = (data) => - client.delete(`/network/${data.networkId}/gateways/${data.gatewayId}`); - - const changePasswordSelf: Api['changePasswordSelf'] = (data) => - client.put('/user/change_password', data).then(unpackRequest); - - const sendTestMail: Api['mail']['sendTestMail'] = (data) => - client.post('/mail/test', data).then(unpackRequest); - - const sendSupportMail: Api['mail']['sendSupportMail'] = () => - client.post('/mail/support', {}).then(unpackRequest); - - const startDesktopActivation: Api['user']['startDesktopActivation'] = (data) => - client.post(`/user/${data.username}/start_desktop`, data).then(unpackRequest); - - const getAuthenticationKeysInfo: Api['user']['getAuthenticationKeysInfo'] = (data) => - client.get(`/user/${data.username}/auth_key`).then(unpackRequest); - - const addAuthenticationKey: Api['user']['addAuthenticationKey'] = (data) => - client.post(`/user/${data.username}/auth_key`, data).then(unpackRequest); - - const renameAuthenticationKey: Api['user']['renameAuthenticationKey'] = (data) => - client - .post(`/user/${data.username}/auth_key/${data.id}/rename`, { - name: data.name, - }) - .then(unpackRequest); - - const deleteAuthenticationKey: Api['user']['deleteAuthenticationKey'] = (data) => - client.delete(`/user/${data.username}/auth_key/${data.id}`).then(unpackRequest); - - const renameYubikey: Api['user']['renameYubikey'] = (data) => - client - .post(`/user/${data.username}/yubikey/${data.id}/rename`, { - name: data.name, - }) - .then(unpackRequest); - - const deleteYubiKey: Api['user']['deleteYubiKey'] = (data) => - client.delete(`/user/${data.username}/yubikey/${data.id}`).then(unpackRequest); - - const getApiTokensInfo: Api['user']['getApiTokensInfo'] = (data) => - client.get(`/user/${data.username}/api_token`).then(unpackRequest); - - const addApiToken: Api['user']['addApiToken'] = (data) => - client.post(`/user/${data.username}/api_token`, data).then(unpackRequest); - - const renameApiToken: Api['user']['renameApiToken'] = (data) => - client - .post(`/user/${data.username}/api_token/${data.id}/rename`, { - name: data.name, - }) - .then(unpackRequest); - - const deleteApiToken: Api['user']['deleteApiToken'] = (data) => - client.delete(`/user/${data.username}/api_token/${data.id}`).then(unpackRequest); - - const disableUserMfa: Api['user']['disableUserMfa'] = (username) => - client.delete(`/user/${username}/mfa`).then(unpackRequest); - - const patchSettings: Api['settings']['patchSettings'] = (data) => - client.patch('/settings', data).then(unpackRequest); - - const getEssentialSettings: Api['settings']['getEssentialSettings'] = () => - client.get('/settings_essentials').then(unpackRequest); - - const getEnterpriseSettings: Api['settings']['getEnterpriseSettings'] = () => - client.get('/settings_enterprise').then(unpackRequest); - - const patchEnterpriseSettings: Api['settings']['patchEnterpriseSettings'] = (data) => - client.patch('/settings_enterprise', data).then(unpackRequest); - - const testLdapSettings: Api['settings']['testLdapSettings'] = () => - client.get('/ldap/test').then(unpackRequest); - - const getGroupsInfo: Api['groups']['getGroupsInfo'] = () => - client.get('/group-info').then(unpackRequest); - - const deleteGroup: Api['groups']['deleteGroup'] = (group) => - client.delete(`/group/${group}`); - - const addUsersToGroups: Api['groups']['addUsersToGroups'] = (data) => - client.post('/groups-assign', data).then(unpackRequest); - - const fetchOpenIdProvider: Api['settings']['fetchOpenIdProviders'] = () => - client.get(`/openid/provider`).then(unpackRequest); - - const addOpenIdProvider: Api['settings']['addOpenIdProvider'] = (data) => - client.post(`/openid/provider`, data).then(unpackRequest); - - const deleteOpenIdProvider: Api['settings']['deleteOpenIdProvider'] = (name) => - client.delete(`/openid/provider/${name}`).then(unpackRequest); - - const editOpenIdProvider: Api['settings']['editOpenIdProvider'] = (data) => - client.put(`/openid/provider/${data.name}`, data).then(unpackRequest); - - const openIdCallback: Api['auth']['openid']['callback'] = (data) => - client.post('/openid/callback', data).then((response) => { - if (response.status === 200) { - return response.data as LoginResponse; - } - if (response.status === 201) { - const mfa = response.data as MFALoginResponse; - return { - mfa, - } as LoginResponse; - } - return {}; - }); - - const getNewVersion: Api['getNewVersion'] = () => - client.get('/updates').then((res) => { - if (res.data === null) { - return null; - } - return res.data as UpdateInfo; - }); - - const testDirsync: Api['settings']['testDirsync'] = () => - client.get('/test_directory_sync').then(unpackRequest); - - const createStandaloneDevice: Api['standaloneDevice']['createManualDevice'] = (data) => - client.post('/device/network', data).then(unpackRequest); - - const deleteStandaloneDevice: Api['standaloneDevice']['deleteDevice'] = (deviceId) => - client.delete(`/device/network/${deviceId}`); - const editStandaloneDevice: Api['standaloneDevice']['editDevice'] = ({ id, ...data }) => - client.put(`/device/network/${id}`, data).then(unpackRequest); - - const getStandaloneDevice: Api['standaloneDevice']['getDevice'] = (deviceId) => - client.get(`/device/network/${deviceId}`).then(unpackRequest); - - const getAvailableLocationIp: Api['standaloneDevice']['getAvailableIp'] = (data) => - client.get(`/device/network/ip/${data.locationId}`).then(unpackRequest); - - const validateLocationIp: Api['standaloneDevice']['validateLocationIp'] = ({ - location, - ...rest - }) => client.post(`/device/network/ip/${location}`, rest).then(unpackRequest); - - const getStandaloneDevicesList: Api['standaloneDevice']['getDevicesList'] = () => - client.get('/device/network').then(unpackRequest); - - const createStandaloneCliDevice: Api['standaloneDevice']['createCliDevice'] = (data) => - client.post('/device/network/start_cli', data).then(unpackRequest); - - const getStandaloneDeviceConfig: Api['standaloneDevice']['getDeviceConfig'] = (id) => - client.get(`/device/network/${id}/config`).then(unpackRequest); - - const generateStandaloneDeviceAuthToken: Api['standaloneDevice']['generateAuthToken'] = - (id) => client.post(`/device/network/start_cli/${id}`).then(unpackRequest); - - const createAclRule: Api['acl']['rules']['createRule'] = (data) => - client.post('/acl/rule', data).then(unpackRequest); - - const editAclRule: Api['acl']['rules']['editRule'] = ({ id, ...rest }) => - client.put(`/acl/rule/${id}`, rest).then(unpackRequest); - - const getAclRules: Api['acl']['rules']['getRules'] = () => - client.get('/acl/rule').then(unpackRequest); - - const getAclRule: Api['acl']['rules']['getRule'] = (id: number) => - client.get(`/acl/rule/${id}`).then(unpackRequest); - - const deleteAclRule: Api['acl']['rules']['deleteRule'] = (id) => - client.delete(`/acl/rule/${id}`).then(unpackRequest); - - const getAliases: Api['acl']['aliases']['getAliases'] = () => - client.get(`/acl/alias`).then(unpackRequest); - - const getAlias: Api['acl']['aliases']['getAlias'] = (id) => - client.get(`/acl/alias/${id}`).then(unpackRequest); - - const createAlias: Api['acl']['aliases']['createAlias'] = (data) => - client.post(`/acl/alias`, data).then(unpackRequest); - - const editAlias: Api['acl']['aliases']['editAlias'] = (data) => - client.put(`/acl/alias/${data.id}`, data).then(unpackRequest); - - const deleteAlias: Api['acl']['aliases']['deleteAlias'] = (id) => - client.delete(`/acl/alias/${id}`).then(unpackRequest); - - const applyAclRules: Api['acl']['rules']['applyRules'] = (rules) => - client - .put('/acl/rule/apply', { - rules: rules, - }) - .then(unpackRequest); - - const applyAclAliases: Api['acl']['aliases']['applyAliases'] = (aliases) => - client - .put(`/acl/alias/apply`, { - aliases: aliases, - }) - .then(unpackRequest); - - const getActivityLog: Api['activityLog']['getActivityLog'] = (params) => - client - .get(`/activity_log`, { - params, - }) - .then(unpackRequest); - - const getAllNetworksStats: Api['network']['getAllNetworksStats'] = (params) => { - const fromParam = getNetworkStatsFilterValue(params.from ?? 1); - return client - .get('/network/stats', { - params: { - from: fromParam, - }, - }) - .then(unpackRequest); - }; - - const getAllGatewaysStatus: Api['network']['getAllGatewaysStatus'] = () => - client.get('/network/gateways').then(unpackRequest); - - const getActivityLogStreams: Api['activityLogStream']['getActivityLogStreams'] = () => - client.get('/activity_log_stream').then(unpackRequest); - const createActivityLogStream: Api['activityLogStream']['createActivityLogStream'] = ( - data, - ) => client.post('/activity_log_stream', data).then(unpackRequest); - const modifyActivityLogStream: Api['activityLogStream']['modifyActivityLogStream'] = ({ - id, - ...rest - }) => client.put(`/activity_log_stream/${id}`, rest).then(unpackRequest); - const deleteActivityLogStream: Api['activityLogStream']['deleteActivityLogStream'] = ( - id, - ) => client.delete(`/activity_log_stream/${id}`).then(unpackRequest); - - return { - getOutdatedInfo, - getAppInfo, - getNewVersion, - changePasswordSelf, - getEnterpriseInfo, - activityLogStream: { - createActivityLogStream, - deleteActivityLogStream, - getActivityLogStreams, - modifyActivityLogStream, - }, - activityLog: { - getActivityLog, - }, - acl: { - aliases: { - createAlias, - deleteAlias, - editAlias, - getAlias, - getAliases, - applyAliases: applyAclAliases, - }, - rules: { - createRule: createAclRule, - getRules: getAclRules, - getRule: getAclRule, - editRule: editAclRule, - deleteRule: deleteAclRule, - applyRules: applyAclRules, - }, - }, - oAuth: { - consent: oAuthConsent, - }, - groups: { - deleteGroup, - getGroupsInfo, - getGroups, - createGroup, - editGroup, - addUsersToGroups, - }, - standaloneDevice: { - createManualDevice: createStandaloneDevice, - deleteDevice: deleteStandaloneDevice, - editDevice: editStandaloneDevice, - getDevice: getStandaloneDevice, - getAvailableIp: getAvailableLocationIp, - validateLocationIp: validateLocationIp, - getDevicesList: getStandaloneDevicesList, - createCliDevice: createStandaloneCliDevice, - getDeviceConfig: getStandaloneDeviceConfig, - generateAuthToken: generateStandaloneDeviceAuthToken, - }, - user: { - getMe, - addUser, - getUser, - getUsers, - editUser, - deleteUser, - usernameAvailable, - changePassword, - resetPassword, - addToGroup, - removeFromGroup, - startEnrollment, - startDesktopActivation, - getAuthenticationKeysInfo, - addAuthenticationKey, - deleteAuthenticationKey, - renameAuthenticationKey, - deleteYubiKey, - renameYubikey, - getApiTokensInfo, - addApiToken, - deleteApiToken, - renameApiToken, - disableUserMfa, - }, - device: { - addDevice: addDevice, - getDevice: fetchDevice, - getDevices: fetchDevices, - getUserDevices: fetchUserDevices, - editDevice: modifyDevice, - deleteDevice, - downloadDeviceConfig, - }, - network: { - getAllNetworksStats, - getAllGatewaysStatus, - addNetwork, - importNetwork, - mapUserDevices: mapUserDevices, - getNetwork: fetchNetwork, - getNetworks: fetchNetworks, - editNetwork: modifyNetwork, - deleteNetwork, - getNetworkToken, - getNetworkStats, - getGatewaysStatus, - deleteGateway, - getOverviewStats: getOverviewStats, - }, - auth: { - login, - logout, - openid: { - getOpenIdInfo: getOpenidInfo, - callback: openIdCallback, - }, - mfa: { - disable: mfaDisable, - enable: mfaEnable, - recovery: recovery, - webauthn: { - register: { - start: mfaWebauthnRegisterStart, - finish: mfaWebauthnRegisterFinish, - }, - start: mfaWebauthnStart, - finish: mfaWebautnFinish, - deleteKey: mfaWebauthnDeleteKey, - }, - totp: { - init: mfaTOTPInit, - enable: mfaTOTPEnable, - disable: mfaTOTPDisable, - verify: mfaTOTPVerify, - }, - email: { - register: { - start: mfaEmailMFAInit, - finish: mfaEmailMFAEnable, - }, - disable: mfaEmailMFADisable, - sendCode: mfaEmailMFASendCode, - verify: mfaEmailMFAVerify, - }, - }, - }, - provisioning: { - provisionYubiKey: provisionYubiKey, - getWorkers: getWorkers, - getJobStatus: getJobStatus, - getWorkerToken: getWorkerToken, - deleteWorker, - }, - webhook: { - getWebhooks: getWebhooks, - deleteWebhook: deleteWebhook, - addWebhook: addWebhook, - changeWebhookState: changeWebhookState, - editWebhook: editWebhook, - }, - openid: { - getOpenidClients: getOpenidClients, - addOpenidClient: addOpenidClient, - getOpenidClient: getOpenidClient, - editOpenidClient: editOpenidClient, - deleteOpenidClient: deleteOpenidClient, - changeOpenidClientState: changeOpenidClientState, - verifyOpenidClient: verifyOpenidClient, - getUserClients: getUserClients, - removeUserClient: removeUserClient, - }, - settings: { - getSettings: getSettings, - editSettings: editSettings, - setDefaultBranding: setDefaultBranding, - patchSettings, - getEssentialSettings, - getEnterpriseSettings, - patchEnterpriseSettings, - testLdapSettings, - fetchOpenIdProviders: fetchOpenIdProvider, - addOpenIdProvider, - deleteOpenIdProvider, - editOpenIdProvider, - testDirsync, - }, - support: { - downloadSupportData, - downloadLogs, - }, - mail: { - sendTestMail: sendTestMail, - sendSupportMail: sendSupportMail, - }, - }; -}; diff --git a/web/src/shared/hooks/api/axios-client.ts b/web/src/shared/hooks/api/axios-client.ts deleted file mode 100644 index 441ac15f7..000000000 --- a/web/src/shared/hooks/api/axios-client.ts +++ /dev/null @@ -1,19 +0,0 @@ -import axios from 'axios'; -import qs from 'qs'; - -const envBaseUrl: string | undefined = import.meta.env.VITE_API_BASE_URL; - -const axiosClient = axios.create({ - baseURL: envBaseUrl && String(envBaseUrl).length > 0 ? envBaseUrl : '/api/v1', -}); - -axiosClient.defaults.headers.common['Content-Type'] = 'application/json'; - -axiosClient.defaults.paramsSerializer = { - serialize: (params) => - qs.stringify(params, { - arrayFormat: 'repeat', - }), -}; - -export default axiosClient; diff --git a/web/src/shared/hooks/api/provider.tsx b/web/src/shared/hooks/api/provider.tsx deleted file mode 100644 index a3c6560fd..000000000 --- a/web/src/shared/hooks/api/provider.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { type PropsWithChildren, useEffect } from 'react'; -import { shallow } from 'zustand/shallow'; - -import { useI18nContext } from '../../../i18n/i18n-react'; -import { removeNulls } from '../../utils/removeNulls'; -import { useApiStore } from './store'; - -const ApiContextManager = ({ children }: PropsWithChildren) => { - const [client, endpoints] = useApiStore((s) => [s.client, s.endpoints], shallow); - - const { LL } = useI18nContext(); - - // biome-ignore lint/correctness/useExhaustiveDependencies: migration, checkMeLater - useEffect(() => { - if (client && LL && LL.messages) { - const defaultResponseInterceptor = client.interceptors.response.use( - (res) => { - // API returns null in optional fields. - if (res.data) { - res.data = removeNulls(res.data); - } - return res; - }, - (error) => { - console.error('Axios Error ', error); - throw error; - }, - ); - return () => { - client.interceptors.response.eject(defaultResponseInterceptor); - }; - } - }, [LL?.messages, client]); - - if (!client || !endpoints) return null; - - return <>{children}; -}; - -export const ApiProvider = ({ children }: PropsWithChildren) => { - return {children}; -}; diff --git a/web/src/shared/hooks/api/store.ts b/web/src/shared/hooks/api/store.ts deleted file mode 100644 index 35be22919..000000000 --- a/web/src/shared/hooks/api/store.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { Axios } from 'axios'; -import { createWithEqualityFn } from 'zustand/traditional'; - -import type { Api } from '../../types'; -import { buildApi } from './api'; -import apiEndpoints from './api-client'; -import axiosClient from './axios-client'; - -const defaults: StoreValues = { - client: axiosClient, - endpoints: apiEndpoints, -}; - -export const useApiStore = createWithEqualityFn( - (set) => ({ - ...defaults, - init: (client) => { - const endpoints = buildApi(client); - set({ - client, - endpoints, - }); - }, - }), - Object.is, -); - -type Store = StoreMethods & StoreValues; - -type StoreValues = { - client?: Axios; - endpoints?: Api; -}; - -type StoreMethods = { - init: (client: Axios) => void; -}; diff --git a/web/src/shared/hooks/modalControls/modalTypes.ts b/web/src/shared/hooks/modalControls/modalTypes.ts new file mode 100644 index 000000000..815d4a3e1 --- /dev/null +++ b/web/src/shared/hooks/modalControls/modalTypes.ts @@ -0,0 +1,150 @@ +import z from 'zod'; +import type { AddDeviceResponse, User } from '../../api/types'; +import type { + OpenAddApiTokenModal, + OpenAddNetworkDeviceModal, + OpenAssignUsersToGroupsModal, + OpenAuthKeyRenameModal, + OpenCEGroupModal, + OpenCEOpenIdClientModal, + OpenCEWebhookModal, + OpenDisplayListModal, + OpenEditDeviceModal, + OpenEditNetworkDeviceModal, + OpenEditUserModal, + OpenGatewaySetupModal, + OpenLicenseModal, + OpenNetworkDeviceConfigModal, + OpenNetworkDeviceTokenModal, + OpenRenameApiTokenModal, +} from './types'; + +export const ModalName = { + License: 'license', + SendTestMail: 'sendTestMail', + GatewaySetup: 'gatewaySetup', + DisplayList: 'displayList', + ChangePassword: 'changePassword', + TotpSetup: 'totpSetup', + RecoveryCodes: 'recoveryCodes', + EmailMfaSetup: 'emailMfaSetup', + WebauthnSetup: 'webauthnSetup', + EditUserDevice: 'editUserDevice', + UserDeviceConfig: 'userDeviceConfig', + AddAuthKey: 'addAuthKey', + RenameAuthKey: 'renameAuthKey', + AddApiToken: 'addApiToken', + RenameApiToken: 'renameApiToken', + CreateEditGroup: 'createEditGroup', + EditUserModal: 'editUserModal', + CEOpenIdClient: 'createEditOpenIdClient', + CEWebhook: 'createEditWebhook', + AssignGroupsToUsers: 'assignGroupsToUsers', + AddNetworkDevice: 'addNetworkDevice', + EditNetworkDevice: 'editNetworkDevice', + NetworkDeviceConfig: 'networkDeviceConfig', + NetworkDeviceToken: 'networkDeviceToken', + AddLocation: 'addLocation', +} as const; + +export type ModalNameValue = (typeof ModalName)[keyof typeof ModalName]; + +const modalOpenArgsSchema = z.discriminatedUnion('name', [ + z.object({ + name: z.literal(ModalName.ChangePassword), + data: z.object({ + user: z.custom(), + adminForm: z.boolean(), + }), + }), + z.object({ name: z.literal(ModalName.TotpSetup) }), + z.object({ name: z.literal(ModalName.RecoveryCodes), data: z.array(z.string()) }), + z.object({ name: z.literal(ModalName.EmailMfaSetup) }), + z.object({ name: z.literal(ModalName.WebauthnSetup) }), + z.object({ + name: z.literal(ModalName.EditUserDevice), + data: z.custom(), + }), + z.object({ + name: z.literal(ModalName.UserDeviceConfig), + data: z.custom(), + }), + z.object({ + name: z.literal(ModalName.AddAuthKey), + data: z.object({ + username: z.string(), + }), + }), + z.object({ + name: z.literal(ModalName.RenameAuthKey), + data: z.custom(), + }), + z.object({ + name: z.literal(ModalName.AddApiToken), + data: z.custom(), + }), + z.object({ + name: z.literal(ModalName.RenameApiToken), + data: z.custom(), + }), + z.object({ + name: z.literal(ModalName.CreateEditGroup), + data: z.custom(), + }), + z.object({ + name: z.literal(ModalName.EditUserModal), + data: z.custom(), + }), + z.object({ + name: z.literal(ModalName.EditUserModal), + data: z.custom(), + }), + z.object({ + name: z.literal(ModalName.CEOpenIdClient), + data: z.custom(), + }), + z.object({ + name: z.literal(ModalName.CEWebhook), + data: z.custom(), + }), + z.object({ + name: z.literal(ModalName.AssignGroupsToUsers), + data: z.custom(), + }), + z.object({ + name: z.literal(ModalName.AddNetworkDevice), + data: z.custom(), + }), + z.object({ + name: z.literal(ModalName.EditNetworkDevice), + data: z.custom(), + }), + z.object({ + name: z.literal(ModalName.NetworkDeviceConfig), + data: z.custom(), + }), + z.object({ + name: z.literal(ModalName.NetworkDeviceToken), + data: z.custom(), + }), + z.object({ + name: z.literal(ModalName.DisplayList), + data: z.custom(), + }), + z.object({ + name: z.literal(ModalName.GatewaySetup), + data: z.custom(), + }), + z.object({ + name: z.literal(ModalName.AddLocation), + }), + z.object({ + name: z.literal(ModalName.SendTestMail), + }), + z.object({ + name: z.literal(ModalName.License), + data: z.custom(), + }), +]); + +export type ModalOpenEvent = z.infer; diff --git a/web/src/shared/hooks/modalControls/modalsSubjects.ts b/web/src/shared/hooks/modalControls/modalsSubjects.ts new file mode 100644 index 000000000..bc4984a32 --- /dev/null +++ b/web/src/shared/hooks/modalControls/modalsSubjects.ts @@ -0,0 +1,48 @@ +import { filter, Subject, type Subscription } from 'rxjs'; +import type { ModalNameValue, ModalOpenEvent } from './modalTypes'; + +export const openModalSubject = new Subject(); + +export const closeModalSubject = new Subject(); + +export function subscribeOpenModal( + name: N, + handler: Extract extends { data: infer D } + ? (data: D) => void + : () => void, +): Subscription { + return openModalSubject + .pipe(filter((e): e is Extract => e.name === name)) + .subscribe((e) => { + if ('data' in e) (handler as (d: unknown) => void)(e.data); + else (handler as () => void)(); + }); +} + +export const subscribeCloseModal = ( + name: N, + handler: () => void, +) => { + return closeModalSubject.pipe(filter((e) => e === name)).subscribe(() => handler()); +}; + +export const closeModal = (name: T) => { + closeModalSubject.next(name); +}; + +type PayloadOf = + Extract extends { + data: infer D; + } + ? D + : undefined; +export function openModal( + name: N, + ...args: PayloadOf extends undefined ? [] : [PayloadOf] +) { + const event = ( + args.length === 0 ? { name } : { name, data: args[0] } + ) as ModalOpenEvent; + + openModalSubject.next(event); +} diff --git a/web/src/shared/hooks/modalControls/types.ts b/web/src/shared/hooks/modalControls/types.ts new file mode 100644 index 000000000..0eaa883d2 --- /dev/null +++ b/web/src/shared/hooks/modalControls/types.ts @@ -0,0 +1,97 @@ +import type { + AvailableLocationIpResponse, + Device, + GatewayTokenResponse, + GroupInfo, + NetworkDevice, + NetworkLocation, + OpenIdClient, + StartEnrollmentResponse, + User, + Webhook, +} from '../../api/types'; + +export interface OpenEditDeviceModal { + device: Device; + reservedNames: string[]; + username: string; +} + +export interface OpenAuthKeyRenameModal { + id: number; + name: string; + username: string; +} + +export interface OpenAddApiTokenModal { + username: string; +} + +export interface OpenRenameApiTokenModal { + id: number; + name: string; + username: string; +} + +export interface OpenDeleteApiTokenModal { + id: number; + username: string; +} + +export interface OpenCEGroupModal { + groupInfo?: GroupInfo; + reservedNames: string[]; + users: User[]; +} + +export interface OpenEditUserModal { + user: User; + reservedUsernames: string[]; + reservedEmails: string[]; +} + +export interface OpenCEOpenIdClientModal { + openIdClient?: OpenIdClient; + reservedNames: string[]; +} + +export interface OpenCEWebhookModal { + webhook?: Webhook; +} + +export interface OpenAssignUsersToGroupsModal { + users: number[]; + groups: GroupInfo[]; +} + +export interface OpenAddNetworkDeviceModal { + locations: NetworkLocation[]; + availableIps: AvailableLocationIpResponse; + reservedNames: string[]; +} +export interface OpenEditNetworkDeviceModal { + device: NetworkDevice; + reservedNames: string[]; +} +export interface OpenNetworkDeviceConfigModal { + device: NetworkDevice; + config: string; +} +export interface OpenNetworkDeviceTokenModal { + device: NetworkDevice; + enrollment: StartEnrollmentResponse; +} + +export interface OpenDisplayListModal { + title?: string; + data: string[]; +} + +export interface OpenGatewaySetupModal { + networkId: number; + data: GatewayTokenResponse; +} + +export interface OpenLicenseModal { + license?: string | null; +} diff --git a/web/src/shared/hooks/store/useAppStore.ts b/web/src/shared/hooks/store/useAppStore.ts deleted file mode 100644 index 4fded31c3..000000000 --- a/web/src/shared/hooks/store/useAppStore.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { pick } from 'lodash-es'; -import { createJSONStorage, persist } from 'zustand/middleware'; -import { createWithEqualityFn } from 'zustand/traditional'; - -import type { Locales } from '../../../i18n/i18n-types'; -import type { AppInfo, SettingsEnterprise, SettingsEssentials } from '../../types'; - -const defaultValues: StoreValues = { - settings: undefined, - language: undefined, - appInfo: undefined, - enterprise_settings: undefined, -}; - -const persistKeys: Array = ['language']; - -export const useAppStore = createWithEqualityFn()( - persist( - (set) => ({ - ...defaultValues, - setState: (data) => set(data), - }), - { - name: 'app-store', - version: 1, - partialize: (store) => pick(store, persistKeys), - storage: createJSONStorage(() => sessionStorage), - }, - ), - Object.is, -); - -type Store = StoreValues & StoreMethods; - -type StoreValues = { - settings?: SettingsEssentials; - language?: Locales; - appInfo?: AppInfo; - enterprise_settings?: SettingsEnterprise; -}; - -type StoreMethods = { - setState: (values: Partial) => void; -}; diff --git a/web/src/shared/hooks/store/useAuthStore.ts b/web/src/shared/hooks/store/useAuthStore.ts deleted file mode 100644 index 8463f035e..000000000 --- a/web/src/shared/hooks/store/useAuthStore.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { pick } from 'lodash-es'; -import { Subject } from 'rxjs'; -import { createJSONStorage, persist } from 'zustand/middleware'; -import { createWithEqualityFn } from 'zustand/traditional'; - -import type { LoginSubjectData, User } from '../../types'; - -export const useAuthStore = createWithEqualityFn()( - persist( - (set, get) => ({ - user: undefined, - openIdParams: undefined, - loginSubject: new Subject(), - setState: (newState) => set({ ...get(), ...newState }), - resetState: () => - set({ - user: undefined, - openIdParams: undefined, - }), - }), - { - name: 'auth-store', - partialize: (store) => pick(store, ['user', 'authLocation']), - storage: createJSONStorage(() => sessionStorage), - }, - ), - Object.is, -); -export interface AuthStore { - loginSubject: Subject; - user?: User; - // If this is set, redirect user to allow page and nowhere else - openIdParams?: URLSearchParams; - setState: (newState: Partial) => void; - resetState: () => void; -} diff --git a/web/src/shared/hooks/store/useEnterpriseUpgradeStore.tsx b/web/src/shared/hooks/store/useEnterpriseUpgradeStore.tsx deleted file mode 100644 index aa4e9598f..000000000 --- a/web/src/shared/hooks/store/useEnterpriseUpgradeStore.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { isObject } from 'lodash-es'; -import { persist } from 'zustand/middleware'; -import { createWithEqualityFn } from 'zustand/traditional'; - -import { EnterpriseUpgradeToast } from '../../components/Layout/EnterpriseUpgradeToast/EnterpriseUpgradeToast'; -import type { EnterpriseUpgradeToastMeta } from '../../components/Layout/EnterpriseUpgradeToast/types'; -import { versionUpdateToastMetaSchema } from '../../components/Layout/VersionUpdateToast/types'; -import { ToastType } from '../../defguard-ui/components/Layout/ToastManager/Toast/types'; -import { useToastsStore } from '../../defguard-ui/hooks/toasts/useToastStore'; - -const upgradeToastCustomId = 'enterprise-upgrade-toast'; - -export const useEnterpriseUpgradeStore = createWithEqualityFn()( - persist( - () => ({ - show: () => { - const { addToast, toasts } = useToastsStore.getState(); - const isIn = toasts.find((t) => { - const meta = t.meta; - if (meta) { - const parseResult = versionUpdateToastMetaSchema.safeParse(meta); - if (parseResult.success) { - return parseResult.data.customId === upgradeToastCustomId; - } - } - return false; - }); - if (!isIn) { - const meta: EnterpriseUpgradeToastMeta = { - customId: upgradeToastCustomId, - }; - addToast({ - customComponent: EnterpriseUpgradeToast, - message: '', - type: ToastType.INFO, - meta, - }); - } - }, - }), - { - name: 'enterprise-upgrade-store', - version: 1, - }, - ), - isObject, -); - -type Store = { - show: () => void; -}; diff --git a/web/src/shared/hooks/store/useModalStore.ts b/web/src/shared/hooks/store/useModalStore.ts deleted file mode 100644 index ff8491e67..000000000 --- a/web/src/shared/hooks/store/useModalStore.ts +++ /dev/null @@ -1,146 +0,0 @@ -import { createWithEqualityFn } from 'zustand/traditional'; - -import type { UseModalStore } from '../../types'; - -/** - * This approach is deprecated, please use separate stores for each modal to keep things clean - */ -export const useModalStore = createWithEqualityFn( - (set) => ({ - // DO NOT EXTEND THIS STORE - openIdClientModal: { - client: undefined, - viewMode: false, - visible: false, - }, - // DO NOT EXTEND THIS STORE - setOpenIdClientModal: (newState) => - set((oldState) => ({ - openIdClientModal: { ...oldState.openIdClientModal, ...newState }, - })), - // DO NOT EXTEND THIS STORE - recoveryCodesModal: { - visible: false, - codes: undefined, - }, - // DO NOT EXTEND THIS STORE - registerTOTP: { - visible: false, - }, - // DO NOT EXTEND THIS STORE - provisionKeyModal: { - visible: false, - user: undefined, - }, - // DO NOT EXTEND THIS STORE - deleteUserModal: { - visible: false, - user: undefined, - }, - // DO NOT EXTEND THIS STORE - toggleUserModal: { - visible: false, - user: undefined, - }, - // DO NOT EXTEND THIS STORE - changePasswordModal: { - visible: false, - user: undefined, - }, - // DO NOT EXTEND THIS STORE - keyDetailModal: { - visible: false, - }, - // DO NOT EXTEND THIS STORE - keyDeleteModal: { - visible: false, - }, - // DO NOT EXTEND THIS STORE - webhookModal: { - visible: false, - webhook: undefined, - }, - // DO NOT EXTEND THIS STORE - setWebhookModal: (newState) => - set((oldState) => ({ - webhookModal: { ...oldState.webhookModal, ...newState }, - })), - // DO NOT EXTEND THIS STORE - deleteOpenidClientModal: { - visible: false, - client: undefined, - onSuccess: undefined, - }, - // DO NOT EXTEND THIS STORE - enableOpenidClientModal: { - visible: false, - client: undefined, - onSuccess: undefined, - }, - // DO NOT EXTEND THIS STORE - addOpenidClientModal: { - visible: false, - }, - // DO NOT EXTEND THIS STORE - manageWebAuthNKeysModal: { - visible: false, - }, - // DO NOT EXTEND THIS STORE - addSecurityKeyModal: { - visible: false, - }, - // DO NOT EXTEND THIS STORE - licenseModal: { - visible: false, - }, - // DO NOT EXTEND THIS STORE - setState: (newState) => set((oldState) => ({ ...oldState, ...newState })), - // DO NOT EXTEND THIS STORE - setRecoveryCodesModal: (newState) => - set((oldState) => ({ - recoveryCodesModal: { ...oldState.recoveryCodesModal, ...newState }, - })), - // DO NOT EXTEND THIS STORE - setKeyDeleteModal: (v) => - set((state) => ({ keyDeleteModal: { ...state.keyDeleteModal, ...v } })), - // DO NOT EXTEND THIS STORE - setKeyDetailModal: (v) => - set((state) => ({ keyDetailModal: { ...state.keyDetailModal, ...v } })), - // DO NOT EXTEND THIS STORE - setChangePasswordModal: (data) => - set((state) => ({ - changePasswordModal: { ...state.changePasswordModal, ...data }, - })), - // DO NOT EXTEND THIS STORE - setDeleteUserModal: (data) => - set((state) => ({ - deleteUserModal: { ...state.deleteUserModal, ...data }, - })), - // DO NOT EXTEND THIS STORE - setToggleUserModal: (data) => - set((state) => ({ - toggleUserModal: { ...state.toggleUserModal, ...data }, - })), - // DO NOT EXTEND THIS STORE - setProvisionKeyModal: (data) => - set((state) => ({ - provisionKeyModal: { ...state.provisionKeyModal, ...data }, - })), - // DO NOT EXTEND THIS STORE - setAddOpenidClientModal: (v) => - set((state) => ({ - addOpenidClientModal: { ...state.addOpenidClientModal, ...v }, - })), - // DO NOT EXTEND THIS STORE - setDeleteOpenidClientModal: (data) => - set((state) => ({ - deleteOpenidClientModal: { ...state.deleteOpenidClientModal, ...data }, - })), - // DO NOT EXTEND THIS STORE - setEnableOpenidClientModal: (data) => - set((state) => ({ - enableOpenidClientModal: { ...state.enableOpenidClientModal, ...data }, - })), - }), - Object.is, -); diff --git a/web/src/shared/hooks/store/useOpenidClientStore.tsx b/web/src/shared/hooks/store/useOpenidClientStore.tsx deleted file mode 100644 index aef345c43..000000000 --- a/web/src/shared/hooks/store/useOpenidClientStore.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { createWithEqualityFn } from 'zustand/traditional'; - -import type { OpenidClientStore } from '../../types'; - -export const useOpenidClientStore = createWithEqualityFn( - (set) => ({ - editMode: false, - setEditMode: (v) => set(() => ({ editMode: v })), - }), - Object.is, -); diff --git a/web/src/shared/hooks/store/useUpdatesStore.tsx b/web/src/shared/hooks/store/useUpdatesStore.tsx deleted file mode 100644 index 5aa30501a..000000000 --- a/web/src/shared/hooks/store/useUpdatesStore.tsx +++ /dev/null @@ -1,97 +0,0 @@ -import { isObject, pick } from 'lodash-es'; -import { persist } from 'zustand/middleware'; -import { createWithEqualityFn } from 'zustand/traditional'; - -import { - type VersionUpdateToastMeta, - versionUpdateToastMetaSchema, -} from '../../components/Layout/VersionUpdateToast/types'; -import { VersionUpdateToast } from '../../components/Layout/VersionUpdateToast/VersionUpdateToast'; -import { ToastType } from '../../defguard-ui/components/Layout/ToastManager/Toast/types'; -import { useToastsStore } from '../../defguard-ui/hooks/toasts/useToastStore'; - -const keysToPersist: Array = ['dismissal']; - -const defaultState: StoreValues = { - modalVisible: false, - dismissal: undefined, - update: undefined, -}; - -const updateToastCustomId = 'version-update-toast'; - -export const useUpdatesStore = createWithEqualityFn()( - persist( - (set, get) => ({ - ...defaultState, - setStore: (vals) => set(vals), - openModal: () => set({ modalVisible: true }), - closeModal: () => set({ modalVisible: false }), - setUpdate: (update) => { - const state = get(); - if (!state.dismissal || state.dismissal.version !== update.version) { - const { addToast, toasts } = useToastsStore.getState(); - // this is needed in order to not duplicate the version update toast upon page reload because toast is not dismissible and will otherwise appear again when update is checked for. - const isIn = toasts.find((t) => { - const meta = t.meta; - if (meta) { - const parseResult = versionUpdateToastMetaSchema.safeParse(meta); - if (parseResult.success) { - return parseResult.data.customId === updateToastCustomId; - } - } - return false; - }); - if (!isIn) { - const meta: VersionUpdateToastMeta = { - customId: updateToastCustomId, - }; - addToast({ - customComponent: VersionUpdateToast, - message: '', - type: ToastType.INFO, - meta, - }); - } - } - set({ update: update }); - }, - clearUpdate: () => set({ update: undefined }), - }), - { - name: 'updates-store', - version: 1, - partialize: (s) => pick(s, keysToPersist), - }, - ), - isObject, -); - -type Store = StoreValues & StoreMethods; - -type Dismissal = { - version: string; - dismissedAt: string; -}; - -export type UpdateInfo = { - version: string; - critical: boolean; - // Markdown - notes: string; - release_notes_url: string; -}; - -type StoreValues = { - modalVisible: boolean; - dismissal?: Dismissal; - update?: UpdateInfo; -}; - -type StoreMethods = { - setStore: (values: Partial) => void; - openModal: () => void; - closeModal: () => void; - setUpdate: (value: NonNullable) => void; - clearUpdate: () => void; -}; diff --git a/web/src/shared/hooks/store/useUserProfileStore.ts b/web/src/shared/hooks/store/useUserProfileStore.ts deleted file mode 100644 index 2c0a79be1..000000000 --- a/web/src/shared/hooks/store/useUserProfileStore.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Subject } from 'rxjs'; -import { createWithEqualityFn } from 'zustand/traditional'; - -import type { UserProfile } from '../../types'; - -const defaultValues: StoreValues = { - editMode: false, - loading: false, - isMe: false, - submitSubject: new Subject(), - userProfile: undefined, -}; - -export const useUserProfileStore = createWithEqualityFn( - (set) => ({ - ...defaultValues, - setState: (newState) => set((oldState) => ({ ...oldState, ...newState })), - reset: () => set(defaultValues), - }), - Object.is, -); - -type Store = StoreValues & StoreMethods; - -type StoreValues = { - editMode: boolean; - isMe: boolean; - userProfile?: UserProfile; - submitSubject: Subject; - loading: boolean; -}; - -type StoreMethods = { - setState: (state: Partial) => void; - reset: () => void; -}; diff --git a/web/src/shared/hooks/theme/types.ts b/web/src/shared/hooks/theme/types.ts new file mode 100644 index 000000000..64eea86b7 --- /dev/null +++ b/web/src/shared/hooks/theme/types.ts @@ -0,0 +1,5 @@ +import z from 'zod'; + +export const themeSchema = z.enum(['light', 'dark']); + +export type ThemeKey = z.infer; diff --git a/web/src/shared/hooks/theme/useTheme.tsx b/web/src/shared/hooks/theme/useTheme.tsx new file mode 100644 index 000000000..21001868a --- /dev/null +++ b/web/src/shared/hooks/theme/useTheme.tsx @@ -0,0 +1,18 @@ +import { useCallback, useState } from 'react'; +import type { ThemeKey } from './types'; + +const storageKey = 'dg-theme'; + +export const useTheme = () => { + const [theme, setTheme] = useState( + document.documentElement.dataset.theme as ThemeKey, + ); + + const changeTheme = useCallback((newTheme: ThemeKey) => { + document.documentElement.dataset.theme = newTheme; + localStorage.setItem(storageKey, newTheme); + setTheme(newTheme); + }, []); + + return { changeTheme, theme }; +}; diff --git a/web/src/shared/hooks/useApi.tsx b/web/src/shared/hooks/useApi.tsx deleted file mode 100644 index 35ebb111f..000000000 --- a/web/src/shared/hooks/useApi.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { useApiStore } from './api/store'; - -export const useApi = () => { - const endpoints = useApiStore((s) => s.endpoints); - - if (!endpoints) { - throw Error('Used API hook before it was initialized.'); - } - - return endpoints; -}; - -export default useApi; diff --git a/web/src/shared/hooks/useApp.tsx b/web/src/shared/hooks/useApp.tsx new file mode 100644 index 000000000..60c222ad8 --- /dev/null +++ b/web/src/shared/hooks/useApp.tsx @@ -0,0 +1,35 @@ +import { create } from 'zustand'; +import type { ApplicationInfo } from '../api/types'; + +type StoreValues = { + navigationOpen: boolean; + appInfo: ApplicationInfo; +}; + +type Store = StoreValues; + +const defaults: StoreValues = { + navigationOpen: true, + appInfo: { + external_openid_enabled: false, + ldap_info: { + ad: false, + enabled: false, + }, + license_info: { + any_limit_exceeded: false, + enterprise: true, + is_enterprise_free: true, + limits_exceeded: { + device: false, + user: false, + wireguard_network: false, + }, + }, + network_present: false, + smtp_enabled: false, + version: '', + }, +}; + +export const useApp = create(() => ({ ...defaults })); diff --git a/web/src/shared/hooks/useAuth.tsx b/web/src/shared/hooks/useAuth.tsx new file mode 100644 index 000000000..98b22e5c7 --- /dev/null +++ b/web/src/shared/hooks/useAuth.tsx @@ -0,0 +1,56 @@ +import { omit } from 'lodash-es'; +import { Subject } from 'rxjs'; +import { create } from 'zustand'; +import { createJSONStorage, persist } from 'zustand/middleware'; +import type { LoginMfaResponse, LoginResponse, User } from '../api/types'; + +type Store = Values & Methods; + +type Values = { + isAdmin: boolean; + isAuthenticated: boolean; + user?: User; + mfaLogin?: LoginMfaResponse; + consentData?: unknown; + authSubject: Subject; +}; + +type Methods = { + setUser: (values?: User) => void; + reset: () => void; +}; + +const defaults: Values = { + isAdmin: false, + isAuthenticated: false, + user: undefined, + mfaLogin: undefined, + authSubject: new Subject(), +}; + +export const useAuth = create()( + persist( + (set) => ({ + ...defaults, + setUser: (user) => { + if (user) { + set({ + isAdmin: user.is_admin, + isAuthenticated: true, + user: user, + mfaLogin: undefined, + }); + } else { + set(defaults); + } + }, + reset: () => set(defaults), + }), + { + name: 'auth-store', + version: 1, + storage: createJSONStorage(() => sessionStorage), + partialize: (state) => omit(state, ['setUser', 'reset', 'authSubject']), + }, + ), +); diff --git a/web/src/shared/hooks/useClipboard.tsx b/web/src/shared/hooks/useClipboard.tsx deleted file mode 100644 index 809c80caf..000000000 --- a/web/src/shared/hooks/useClipboard.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { useCallback } from 'react'; - -import { useI18nContext } from '../../i18n/i18n-react'; -import { useToaster } from '../defguard-ui/hooks/toasts/useToaster'; - -export const useClipboard = () => { - const { LL } = useI18nContext(); - - const toaster = useToaster(); - - const writeToClipboard = useCallback( - async (content: string, customMessage?: string) => { - if (window.isSecureContext) { - try { - await navigator.clipboard.writeText(content); - if (customMessage) { - toaster.success(customMessage); - } else { - toaster.success(LL.messages.clipboard.success()); - } - } catch (e) { - toaster.error(LL.messages.clipboard.error()); - console.error(e); - } - } else { - toaster.warning(LL.messages.insecureContext()); - } - }, - [LL.messages, toaster], - ); - - return { - writeToClipboard, - }; -}; diff --git a/web/src/shared/hooks/useToaster.tsx b/web/src/shared/hooks/useToaster.tsx deleted file mode 100644 index 8e42f8247..000000000 --- a/web/src/shared/hooks/useToaster.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import { useToaster } from '../defguard-ui/hooks/toasts/useToaster'; - -export { useToaster }; diff --git a/web/src/shared/images/gif/tnt-built.gif b/web/src/shared/images/gif/tnt-built.gif deleted file mode 100644 index 65b1ebf7f..000000000 Binary files a/web/src/shared/images/gif/tnt-built.gif and /dev/null differ diff --git a/web/src/shared/images/png/auto-config-1.png b/web/src/shared/images/png/auto-config-1.png deleted file mode 100644 index 27abd10a0..000000000 Binary files a/web/src/shared/images/png/auto-config-1.png and /dev/null differ diff --git a/web/src/shared/images/png/auto-config-2.png b/web/src/shared/images/png/auto-config-2.png deleted file mode 100644 index 12def4c66..000000000 Binary files a/web/src/shared/images/png/auto-config-2.png and /dev/null differ diff --git a/web/src/shared/images/png/manual-config-1.png b/web/src/shared/images/png/manual-config-1.png deleted file mode 100644 index 90f6e741b..000000000 Binary files a/web/src/shared/images/png/manual-config-1.png and /dev/null differ diff --git a/web/src/shared/images/svg/avatar_01_blue.svg b/web/src/shared/images/svg/avatar_01_blue.svg deleted file mode 100644 index 592b3f0ac..000000000 --- a/web/src/shared/images/svg/avatar_01_blue.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/web/src/shared/images/svg/avatar_01_gray.svg b/web/src/shared/images/svg/avatar_01_gray.svg deleted file mode 100644 index 84dd6a646..000000000 --- a/web/src/shared/images/svg/avatar_01_gray.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/web/src/shared/images/svg/avatar_02_blue.svg b/web/src/shared/images/svg/avatar_02_blue.svg deleted file mode 100644 index 94aaed03a..000000000 --- a/web/src/shared/images/svg/avatar_02_blue.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/web/src/shared/images/svg/avatar_02_gray.svg b/web/src/shared/images/svg/avatar_02_gray.svg deleted file mode 100644 index f5677a788..000000000 --- a/web/src/shared/images/svg/avatar_02_gray.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/web/src/shared/images/svg/avatar_03_blue.svg b/web/src/shared/images/svg/avatar_03_blue.svg deleted file mode 100644 index db95a5847..000000000 --- a/web/src/shared/images/svg/avatar_03_blue.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/web/src/shared/images/svg/avatar_03_gray.svg b/web/src/shared/images/svg/avatar_03_gray.svg deleted file mode 100644 index 66fd2adeb..000000000 --- a/web/src/shared/images/svg/avatar_03_gray.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/web/src/shared/images/svg/avatar_04_blue.svg b/web/src/shared/images/svg/avatar_04_blue.svg deleted file mode 100644 index f01041d76..000000000 --- a/web/src/shared/images/svg/avatar_04_blue.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/web/src/shared/images/svg/avatar_04_gray.svg b/web/src/shared/images/svg/avatar_04_gray.svg deleted file mode 100644 index 29b2f55ac..000000000 --- a/web/src/shared/images/svg/avatar_04_gray.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/web/src/shared/images/svg/avatar_05_blue.svg b/web/src/shared/images/svg/avatar_05_blue.svg deleted file mode 100644 index fa0a54e55..000000000 --- a/web/src/shared/images/svg/avatar_05_blue.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/web/src/shared/images/svg/avatar_05_gray.svg b/web/src/shared/images/svg/avatar_05_gray.svg deleted file mode 100644 index a7dc25707..000000000 --- a/web/src/shared/images/svg/avatar_05_gray.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/web/src/shared/images/svg/avatar_06_blue.svg b/web/src/shared/images/svg/avatar_06_blue.svg deleted file mode 100644 index 704e076ac..000000000 --- a/web/src/shared/images/svg/avatar_06_blue.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/web/src/shared/images/svg/avatar_06_gray.svg b/web/src/shared/images/svg/avatar_06_gray.svg deleted file mode 100644 index dca541868..000000000 --- a/web/src/shared/images/svg/avatar_06_gray.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/web/src/shared/images/svg/avatar_07_blue.svg b/web/src/shared/images/svg/avatar_07_blue.svg deleted file mode 100644 index 01780f431..000000000 --- a/web/src/shared/images/svg/avatar_07_blue.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/web/src/shared/images/svg/avatar_07_gray.svg b/web/src/shared/images/svg/avatar_07_gray.svg deleted file mode 100644 index f61d88db8..000000000 --- a/web/src/shared/images/svg/avatar_07_gray.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/web/src/shared/images/svg/avatar_08_blue.svg b/web/src/shared/images/svg/avatar_08_blue.svg deleted file mode 100644 index b73799724..000000000 --- a/web/src/shared/images/svg/avatar_08_blue.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/web/src/shared/images/svg/avatar_08_gray.svg b/web/src/shared/images/svg/avatar_08_gray.svg deleted file mode 100644 index da2e88691..000000000 --- a/web/src/shared/images/svg/avatar_08_gray.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/web/src/shared/images/svg/avatar_09_blue.svg b/web/src/shared/images/svg/avatar_09_blue.svg deleted file mode 100644 index a0cd65547..000000000 --- a/web/src/shared/images/svg/avatar_09_blue.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/web/src/shared/images/svg/avatar_09_gray.svg b/web/src/shared/images/svg/avatar_09_gray.svg deleted file mode 100644 index 3e88abfe9..000000000 --- a/web/src/shared/images/svg/avatar_09_gray.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/web/src/shared/images/svg/avatar_10_blue.svg b/web/src/shared/images/svg/avatar_10_blue.svg deleted file mode 100644 index 78021e42c..000000000 --- a/web/src/shared/images/svg/avatar_10_blue.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/web/src/shared/images/svg/avatar_10_gray.svg b/web/src/shared/images/svg/avatar_10_gray.svg deleted file mode 100644 index a0d8d9f17..000000000 --- a/web/src/shared/images/svg/avatar_10_gray.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/web/src/shared/images/svg/avatar_11_blue.svg b/web/src/shared/images/svg/avatar_11_blue.svg deleted file mode 100644 index 08fae6132..000000000 --- a/web/src/shared/images/svg/avatar_11_blue.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/web/src/shared/images/svg/avatar_11_gray.svg b/web/src/shared/images/svg/avatar_11_gray.svg deleted file mode 100644 index e6857d453..000000000 --- a/web/src/shared/images/svg/avatar_11_gray.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/web/src/shared/images/svg/avatar_12_blue.svg b/web/src/shared/images/svg/avatar_12_blue.svg deleted file mode 100644 index 9ecc5218c..000000000 --- a/web/src/shared/images/svg/avatar_12_blue.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/web/src/shared/images/svg/avatar_12_gray.svg b/web/src/shared/images/svg/avatar_12_gray.svg deleted file mode 100644 index d5331aa02..000000000 --- a/web/src/shared/images/svg/avatar_12_gray.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/web/src/shared/images/svg/defguard-logo-login.svg b/web/src/shared/images/svg/defguard-logo-login.svg deleted file mode 100644 index 1b11dbe5e..000000000 --- a/web/src/shared/images/svg/defguard-logo-login.svg +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/web/src/shared/images/svg/defguard-logo.svg b/web/src/shared/images/svg/defguard-logo.svg deleted file mode 100644 index 757497f79..000000000 --- a/web/src/shared/images/svg/defguard-logo.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/web/src/shared/images/svg/defguard-nav-logo-collapsed.svg b/web/src/shared/images/svg/defguard-nav-logo-collapsed.svg deleted file mode 100644 index e13c498ec..000000000 --- a/web/src/shared/images/svg/defguard-nav-logo-collapsed.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/web/src/shared/images/svg/defguard-nav-logo.svg b/web/src/shared/images/svg/defguard-nav-logo.svg deleted file mode 100644 index 2073f3065..000000000 --- a/web/src/shared/images/svg/defguard-nav-logo.svg +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/web/src/shared/images/svg/defguard-no-icon.svg b/web/src/shared/images/svg/defguard-no-icon.svg deleted file mode 100644 index 54668bc44..000000000 --- a/web/src/shared/images/svg/defguard-no-icon.svg +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/web/src/shared/images/svg/glow-icon.svg b/web/src/shared/images/svg/glow-icon.svg deleted file mode 100644 index 8c09e8d07..000000000 --- a/web/src/shared/images/svg/glow-icon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-24h-connections.svg b/web/src/shared/images/svg/icon-24h-connections.svg deleted file mode 100644 index b7c16a132..000000000 --- a/web/src/shared/images/svg/icon-24h-connections.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-X.svg b/web/src/shared/images/svg/icon-X.svg deleted file mode 100644 index 182fd291f..000000000 --- a/web/src/shared/images/svg/icon-X.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/web/src/shared/images/svg/icon-active-connections.svg b/web/src/shared/images/svg/icon-active-connections.svg deleted file mode 100644 index 2faf7a9f7..000000000 --- a/web/src/shared/images/svg/icon-active-connections.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-activity-add.svg b/web/src/shared/images/svg/icon-activity-add.svg deleted file mode 100644 index e88c7f966..000000000 --- a/web/src/shared/images/svg/icon-activity-add.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-activity-removed.svg b/web/src/shared/images/svg/icon-activity-removed.svg deleted file mode 100644 index ddeef064c..000000000 --- a/web/src/shared/images/svg/icon-activity-removed.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-activity-warning.svg b/web/src/shared/images/svg/icon-activity-warning.svg deleted file mode 100644 index d4a93cfe6..000000000 --- a/web/src/shared/images/svg/icon-activity-warning.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-arrow-double-gray-left.svg b/web/src/shared/images/svg/icon-arrow-double-gray-left.svg deleted file mode 100644 index 24fb0c6a2..000000000 --- a/web/src/shared/images/svg/icon-arrow-double-gray-left.svg +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/web/src/shared/images/svg/icon-arrow-double.svg b/web/src/shared/images/svg/icon-arrow-double.svg deleted file mode 100644 index 50407cc3b..000000000 --- a/web/src/shared/images/svg/icon-arrow-double.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/web/src/shared/images/svg/icon-arrow-gray-down-1.svg b/web/src/shared/images/svg/icon-arrow-gray-down-1.svg deleted file mode 100644 index 9145d18fa..000000000 --- a/web/src/shared/images/svg/icon-arrow-gray-down-1.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-arrow-gray-down.svg b/web/src/shared/images/svg/icon-arrow-gray-down.svg deleted file mode 100644 index 9a40a5a6e..000000000 --- a/web/src/shared/images/svg/icon-arrow-gray-down.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-arrow-gray-left.svg b/web/src/shared/images/svg/icon-arrow-gray-left.svg deleted file mode 100644 index 1e32319ab..000000000 --- a/web/src/shared/images/svg/icon-arrow-gray-left.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-arrow-gray-right.svg b/web/src/shared/images/svg/icon-arrow-gray-right.svg deleted file mode 100644 index 7051ebf99..000000000 --- a/web/src/shared/images/svg/icon-arrow-gray-right.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-arrow-gray-small.svg b/web/src/shared/images/svg/icon-arrow-gray-small.svg deleted file mode 100644 index cd8f32e0f..000000000 --- a/web/src/shared/images/svg/icon-arrow-gray-small.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/web/src/shared/images/svg/icon-arrow-gray-up-1.svg b/web/src/shared/images/svg/icon-arrow-gray-up-1.svg deleted file mode 100644 index 9145d18fa..000000000 --- a/web/src/shared/images/svg/icon-arrow-gray-up-1.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-arrow-gray-up.svg b/web/src/shared/images/svg/icon-arrow-gray-up.svg deleted file mode 100644 index 79e689a5c..000000000 --- a/web/src/shared/images/svg/icon-arrow-gray-up.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-arrow-single-2.svg b/web/src/shared/images/svg/icon-arrow-single-2.svg deleted file mode 100644 index 134bf16f9..000000000 --- a/web/src/shared/images/svg/icon-arrow-single-2.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/web/src/shared/images/svg/icon-arrow-single.svg b/web/src/shared/images/svg/icon-arrow-single.svg deleted file mode 100644 index 615e0c33d..000000000 --- a/web/src/shared/images/svg/icon-arrow-single.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/web/src/shared/images/svg/icon-arrow-white-left.svg b/web/src/shared/images/svg/icon-arrow-white-left.svg deleted file mode 100644 index 58cb7f8a2..000000000 --- a/web/src/shared/images/svg/icon-arrow-white-left.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-asterix.svg b/web/src/shared/images/svg/icon-asterix.svg deleted file mode 100644 index 32ccc70d6..000000000 --- a/web/src/shared/images/svg/icon-asterix.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-cancel-alt.svg b/web/src/shared/images/svg/icon-cancel-alt.svg deleted file mode 100644 index f12840ec7..000000000 --- a/web/src/shared/images/svg/icon-cancel-alt.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-cancel.svg b/web/src/shared/images/svg/icon-cancel.svg deleted file mode 100644 index e896f6ff5..000000000 --- a/web/src/shared/images/svg/icon-cancel.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/web/src/shared/images/svg/icon-checkmark-green.svg b/web/src/shared/images/svg/icon-checkmark-green.svg deleted file mode 100644 index 693f6c676..000000000 --- a/web/src/shared/images/svg/icon-checkmark-green.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-checkmark-white-1.svg b/web/src/shared/images/svg/icon-checkmark-white-1.svg deleted file mode 100644 index 7eb02e89e..000000000 --- a/web/src/shared/images/svg/icon-checkmark-white-1.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-checkmark-white-big.svg b/web/src/shared/images/svg/icon-checkmark-white-big.svg deleted file mode 100644 index c82e449c4..000000000 --- a/web/src/shared/images/svg/icon-checkmark-white-big.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-checkmark-white.svg b/web/src/shared/images/svg/icon-checkmark-white.svg deleted file mode 100644 index 7eb02e89e..000000000 --- a/web/src/shared/images/svg/icon-checkmark-white.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-checkmark.svg b/web/src/shared/images/svg/icon-checkmark.svg deleted file mode 100644 index e89207942..000000000 --- a/web/src/shared/images/svg/icon-checkmark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/web/src/shared/images/svg/icon-clip.svg b/web/src/shared/images/svg/icon-clip.svg deleted file mode 100644 index a6c73cb5f..000000000 --- a/web/src/shared/images/svg/icon-clip.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/web/src/shared/images/svg/icon-close-gray.svg b/web/src/shared/images/svg/icon-close-gray.svg deleted file mode 100644 index 00d2ae115..000000000 --- a/web/src/shared/images/svg/icon-close-gray.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-collapse.svg b/web/src/shared/images/svg/icon-collapse.svg deleted file mode 100644 index a77032155..000000000 --- a/web/src/shared/images/svg/icon-collapse.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/web/src/shared/images/svg/icon-connected.svg b/web/src/shared/images/svg/icon-connected.svg deleted file mode 100644 index 2fcf1a1b0..000000000 --- a/web/src/shared/images/svg/icon-connected.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-copy.svg b/web/src/shared/images/svg/icon-copy.svg deleted file mode 100644 index dea93e94a..000000000 --- a/web/src/shared/images/svg/icon-copy.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/web/src/shared/images/svg/icon-deactivated.svg b/web/src/shared/images/svg/icon-deactivated.svg deleted file mode 100644 index d265f00bf..000000000 --- a/web/src/shared/images/svg/icon-deactivated.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-delete.svg b/web/src/shared/images/svg/icon-delete.svg deleted file mode 100644 index 21f67bd21..000000000 --- a/web/src/shared/images/svg/icon-delete.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-dfg-openid-redirect.svg b/web/src/shared/images/svg/icon-dfg-openid-redirect.svg deleted file mode 100644 index 2ad7d7b14..000000000 --- a/web/src/shared/images/svg/icon-dfg-openid-redirect.svg +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/web/src/shared/images/svg/icon-disconnected.svg b/web/src/shared/images/svg/icon-disconnected.svg deleted file mode 100644 index 264eb5ab1..000000000 --- a/web/src/shared/images/svg/icon-disconnected.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-download.svg b/web/src/shared/images/svg/icon-download.svg deleted file mode 100644 index ac250bcd4..000000000 --- a/web/src/shared/images/svg/icon-download.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/web/src/shared/images/svg/icon-edit.svg b/web/src/shared/images/svg/icon-edit.svg deleted file mode 100644 index 0bfed6c3c..000000000 --- a/web/src/shared/images/svg/icon-edit.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/web/src/shared/images/svg/icon-eth.svg b/web/src/shared/images/svg/icon-eth.svg deleted file mode 100644 index b5224357d..000000000 --- a/web/src/shared/images/svg/icon-eth.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-expand.svg b/web/src/shared/images/svg/icon-expand.svg deleted file mode 100644 index cb4cb70ff..000000000 --- a/web/src/shared/images/svg/icon-expand.svg +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/web/src/shared/images/svg/icon-filter.svg b/web/src/shared/images/svg/icon-filter.svg deleted file mode 100644 index ba93d3115..000000000 --- a/web/src/shared/images/svg/icon-filter.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-hamburger-close.svg b/web/src/shared/images/svg/icon-hamburger-close.svg deleted file mode 100644 index b475b8678..000000000 --- a/web/src/shared/images/svg/icon-hamburger-close.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-hamburger-menu-1.svg b/web/src/shared/images/svg/icon-hamburger-menu-1.svg deleted file mode 100644 index 7c60537f6..000000000 --- a/web/src/shared/images/svg/icon-hamburger-menu-1.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-hamburger-menu.svg b/web/src/shared/images/svg/icon-hamburger-menu.svg deleted file mode 100644 index 7c60537f6..000000000 --- a/web/src/shared/images/svg/icon-hamburger-menu.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-hourglass-hover.svg b/web/src/shared/images/svg/icon-hourglass-hover.svg deleted file mode 100644 index cf7c83cc9..000000000 --- a/web/src/shared/images/svg/icon-hourglass-hover.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-hourglass.svg b/web/src/shared/images/svg/icon-hourglass.svg deleted file mode 100644 index c095b4c37..000000000 --- a/web/src/shared/images/svg/icon-hourglass.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-info-error.svg b/web/src/shared/images/svg/icon-info-error.svg deleted file mode 100644 index e9652c6a2..000000000 --- a/web/src/shared/images/svg/icon-info-error.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/web/src/shared/images/svg/icon-info-normal.svg b/web/src/shared/images/svg/icon-info-normal.svg deleted file mode 100644 index b1201f85c..000000000 --- a/web/src/shared/images/svg/icon-info-normal.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-info-success-1.svg b/web/src/shared/images/svg/icon-info-success-1.svg deleted file mode 100644 index 33ff498f0..000000000 --- a/web/src/shared/images/svg/icon-info-success-1.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-info-success.svg b/web/src/shared/images/svg/icon-info-success.svg deleted file mode 100644 index 18d47c8f5..000000000 --- a/web/src/shared/images/svg/icon-info-success.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-info-warning.svg b/web/src/shared/images/svg/icon-info-warning.svg deleted file mode 100644 index 0255cb595..000000000 --- a/web/src/shared/images/svg/icon-info-warning.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/web/src/shared/images/svg/icon-info.svg b/web/src/shared/images/svg/icon-info.svg deleted file mode 100644 index ae6aea2fd..000000000 --- a/web/src/shared/images/svg/icon-info.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-key.svg b/web/src/shared/images/svg/icon-key.svg deleted file mode 100644 index f307f0aa7..000000000 --- a/web/src/shared/images/svg/icon-key.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/web/src/shared/images/svg/icon-list-order-down-hover.svg b/web/src/shared/images/svg/icon-list-order-down-hover.svg deleted file mode 100644 index 5002aef97..000000000 --- a/web/src/shared/images/svg/icon-list-order-down-hover.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-list-order-down.svg b/web/src/shared/images/svg/icon-list-order-down.svg deleted file mode 100644 index 5002aef97..000000000 --- a/web/src/shared/images/svg/icon-list-order-down.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-list-order-up-hover.svg b/web/src/shared/images/svg/icon-list-order-up-hover.svg deleted file mode 100644 index d203dff5a..000000000 --- a/web/src/shared/images/svg/icon-list-order-up-hover.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-list-order-up.svg b/web/src/shared/images/svg/icon-list-order-up.svg deleted file mode 100644 index d203dff5a..000000000 --- a/web/src/shared/images/svg/icon-list-order-up.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-nav-groups.svg b/web/src/shared/images/svg/icon-nav-groups.svg deleted file mode 100644 index f443cb3cd..000000000 --- a/web/src/shared/images/svg/icon-nav-groups.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/web/src/shared/images/svg/icon-nav-hamburger.svg b/web/src/shared/images/svg/icon-nav-hamburger.svg deleted file mode 100644 index df7c3630e..000000000 --- a/web/src/shared/images/svg/icon-nav-hamburger.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/web/src/shared/images/svg/icon-nav-key.svg b/web/src/shared/images/svg/icon-nav-key.svg deleted file mode 100644 index aa8191987..000000000 --- a/web/src/shared/images/svg/icon-nav-key.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/web/src/shared/images/svg/icon-nav-logout.svg b/web/src/shared/images/svg/icon-nav-logout.svg deleted file mode 100644 index c552562e3..000000000 --- a/web/src/shared/images/svg/icon-nav-logout.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/web/src/shared/images/svg/icon-nav-openid.svg b/web/src/shared/images/svg/icon-nav-openid.svg deleted file mode 100644 index 7f5b9c2e6..000000000 --- a/web/src/shared/images/svg/icon-nav-openid.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/web/src/shared/images/svg/icon-nav-overview.svg b/web/src/shared/images/svg/icon-nav-overview.svg deleted file mode 100644 index 0df2f3dbc..000000000 --- a/web/src/shared/images/svg/icon-nav-overview.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/web/src/shared/images/svg/icon-nav-profile.svg b/web/src/shared/images/svg/icon-nav-profile.svg deleted file mode 100644 index 34dcc915a..000000000 --- a/web/src/shared/images/svg/icon-nav-profile.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/web/src/shared/images/svg/icon-nav-settings.svg b/web/src/shared/images/svg/icon-nav-settings.svg deleted file mode 100644 index 931643726..000000000 --- a/web/src/shared/images/svg/icon-nav-settings.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/web/src/shared/images/svg/icon-nav-support.svg b/web/src/shared/images/svg/icon-nav-support.svg deleted file mode 100644 index 047bf1951..000000000 --- a/web/src/shared/images/svg/icon-nav-support.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/web/src/shared/images/svg/icon-nav-users.svg b/web/src/shared/images/svg/icon-nav-users.svg deleted file mode 100644 index 4b14750d0..000000000 --- a/web/src/shared/images/svg/icon-nav-users.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/web/src/shared/images/svg/icon-nav-webhook.svg b/web/src/shared/images/svg/icon-nav-webhook.svg deleted file mode 100644 index 1be94ef85..000000000 --- a/web/src/shared/images/svg/icon-nav-webhook.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/web/src/shared/images/svg/icon-nav-yubikey.svg b/web/src/shared/images/svg/icon-nav-yubikey.svg deleted file mode 100644 index d5c26c32c..000000000 --- a/web/src/shared/images/svg/icon-nav-yubikey.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/web/src/shared/images/svg/icon-network-load.svg b/web/src/shared/images/svg/icon-network-load.svg deleted file mode 100644 index fd8a9492d..000000000 --- a/web/src/shared/images/svg/icon-network-load.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-open-modal.svg b/web/src/shared/images/svg/icon-open-modal.svg deleted file mode 100644 index bfdee988e..000000000 --- a/web/src/shared/images/svg/icon-open-modal.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/web/src/shared/images/svg/icon-packets-in.svg b/web/src/shared/images/svg/icon-packets-in.svg deleted file mode 100644 index a9d52d1e1..000000000 --- a/web/src/shared/images/svg/icon-packets-in.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-packets-out.svg b/web/src/shared/images/svg/icon-packets-out.svg deleted file mode 100644 index 208c6ee15..000000000 --- a/web/src/shared/images/svg/icon-packets-out.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-plus-gray.svg b/web/src/shared/images/svg/icon-plus-gray.svg deleted file mode 100644 index 60b39855f..000000000 --- a/web/src/shared/images/svg/icon-plus-gray.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-plus-white.svg b/web/src/shared/images/svg/icon-plus-white.svg deleted file mode 100644 index 4fc0d8a36..000000000 --- a/web/src/shared/images/svg/icon-plus-white.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-popup-close.svg b/web/src/shared/images/svg/icon-popup-close.svg deleted file mode 100644 index 9710d425c..000000000 --- a/web/src/shared/images/svg/icon-popup-close.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-read-more.svg b/web/src/shared/images/svg/icon-read-more.svg deleted file mode 100644 index 44e030bc4..000000000 --- a/web/src/shared/images/svg/icon-read-more.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-redirect.svg b/web/src/shared/images/svg/icon-redirect.svg deleted file mode 100644 index 01d3be3cf..000000000 --- a/web/src/shared/images/svg/icon-redirect.svg +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/web/src/shared/images/svg/icon-search-hover.svg b/web/src/shared/images/svg/icon-search-hover.svg deleted file mode 100644 index 8b0447365..000000000 --- a/web/src/shared/images/svg/icon-search-hover.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-search.svg b/web/src/shared/images/svg/icon-search.svg deleted file mode 100644 index 017f8af5d..000000000 --- a/web/src/shared/images/svg/icon-search.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-settings.svg b/web/src/shared/images/svg/icon-settings.svg deleted file mode 100644 index 68a608e30..000000000 --- a/web/src/shared/images/svg/icon-settings.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/web/src/shared/images/svg/icon-success-large.svg b/web/src/shared/images/svg/icon-success-large.svg deleted file mode 100644 index 603541629..000000000 --- a/web/src/shared/images/svg/icon-success-large.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/web/src/shared/images/svg/icon-tag-dismiss.svg b/web/src/shared/images/svg/icon-tag-dismiss.svg deleted file mode 100644 index 809bf3e3e..000000000 --- a/web/src/shared/images/svg/icon-tag-dismiss.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/web/src/shared/images/svg/icon-trash.svg b/web/src/shared/images/svg/icon-trash.svg deleted file mode 100644 index 5ba7414b8..000000000 --- a/web/src/shared/images/svg/icon-trash.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/web/src/shared/images/svg/icon-user-add-new.svg b/web/src/shared/images/svg/icon-user-add-new.svg deleted file mode 100644 index 511dce10b..000000000 --- a/web/src/shared/images/svg/icon-user-add-new.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/web/src/shared/images/svg/icon-user-list-element.svg b/web/src/shared/images/svg/icon-user-list-element.svg deleted file mode 100644 index ddc3cd435..000000000 --- a/web/src/shared/images/svg/icon-user-list-element.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-user-list-expanded.svg b/web/src/shared/images/svg/icon-user-list-expanded.svg deleted file mode 100644 index 1a069d4d1..000000000 --- a/web/src/shared/images/svg/icon-user-list-expanded.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-user-list-hover.svg b/web/src/shared/images/svg/icon-user-list-hover.svg deleted file mode 100644 index 53d1f3139..000000000 --- a/web/src/shared/images/svg/icon-user-list-hover.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-user-list.svg b/web/src/shared/images/svg/icon-user-list.svg deleted file mode 100644 index 4247585f9..000000000 --- a/web/src/shared/images/svg/icon-user-list.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-waiting-hover.svg b/web/src/shared/images/svg/icon-waiting-hover.svg deleted file mode 100644 index df7ecccdf..000000000 --- a/web/src/shared/images/svg/icon-waiting-hover.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-waiting.svg b/web/src/shared/images/svg/icon-waiting.svg deleted file mode 100644 index 3fd11ef83..000000000 --- a/web/src/shared/images/svg/icon-waiting.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/icon-wallet.svg b/web/src/shared/images/svg/icon-wallet.svg deleted file mode 100644 index a4c14d3be..000000000 --- a/web/src/shared/images/svg/icon-wallet.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/web/src/shared/images/svg/icon-warning.svg b/web/src/shared/images/svg/icon-warning.svg deleted file mode 100644 index e9652c6a2..000000000 --- a/web/src/shared/images/svg/icon-warning.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/web/src/shared/images/svg/image-mesh-network.svg b/web/src/shared/images/svg/image-mesh-network.svg deleted file mode 100644 index ca353409a..000000000 --- a/web/src/shared/images/svg/image-mesh-network.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/image-regular-network.svg b/web/src/shared/images/svg/image-regular-network.svg deleted file mode 100644 index 79dd62eed..000000000 --- a/web/src/shared/images/svg/image-regular-network.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/import-config.svg b/web/src/shared/images/svg/import-config.svg deleted file mode 100644 index 4c771a5e6..000000000 --- a/web/src/shared/images/svg/import-config.svg +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/web/src/shared/images/svg/logo-defguard-white.svg b/web/src/shared/images/svg/logo-defguard-white.svg deleted file mode 100644 index 1b11dbe5e..000000000 --- a/web/src/shared/images/svg/logo-defguard-white.svg +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/web/src/shared/images/svg/manual-config.svg b/web/src/shared/images/svg/manual-config.svg deleted file mode 100644 index 2b8a71025..000000000 --- a/web/src/shared/images/svg/manual-config.svg +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/web/src/shared/images/svg/metamask-icon.svg b/web/src/shared/images/svg/metamask-icon.svg deleted file mode 100644 index de358e637..000000000 --- a/web/src/shared/images/svg/metamask-icon.svg +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/web/src/shared/images/svg/phantom-icon.svg b/web/src/shared/images/svg/phantom-icon.svg deleted file mode 100644 index dd1537ae0..000000000 --- a/web/src/shared/images/svg/phantom-icon.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/web/src/shared/images/svg/qr-icon-white.svg b/web/src/shared/images/svg/qr-icon-white.svg deleted file mode 100644 index 5832392b1..000000000 --- a/web/src/shared/images/svg/qr-icon-white.svg +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - diff --git a/web/src/shared/images/svg/subtract.svg b/web/src/shared/images/svg/subtract.svg deleted file mode 100644 index 49f4d0012..000000000 --- a/web/src/shared/images/svg/subtract.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/web/src/shared/images/svg/wireguard-logo.svg b/web/src/shared/images/svg/wireguard-logo.svg deleted file mode 100644 index b083a26c9..000000000 --- a/web/src/shared/images/svg/wireguard-logo.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/web/src/shared/images/svg/yubikey-provisioning-graphic.svg b/web/src/shared/images/svg/yubikey-provisioning-graphic.svg deleted file mode 100644 index 1cb7331e6..000000000 --- a/web/src/shared/images/svg/yubikey-provisioning-graphic.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/web/src/shared/links.ts b/web/src/shared/links.ts deleted file mode 100644 index a60f7206d..000000000 --- a/web/src/shared/links.ts +++ /dev/null @@ -1,29 +0,0 @@ -// Static links to other services - -export const externalLink = { - defguardReleases: `https://defguard.net/download/`, - gitbook: { - base: 'https://docs.defguard.net/', - setup: { - gateway: - 'https://docs.defguard.net/deployment-strategies/gateway', - }, - wireguard: { - addDevices: - 'https://docs.defguard.net/using-defguard-for-end-users/adding-wireguard-devices', - }, - }, - defguardSite: 'https://defguard.net', - defguardPricing: 'https://defguard.net/pricing', - wireguard: { - download: 'https://www.wireguard.com/install/', - }, - defguardCliDocs: 'https://docs.defguard.net/using-defguard-for-end-users/cli-client', - clientApp: { - download: { - android: 'https://play.google.com/store/apps/details?id=net.defguard.mobile', - ios: 'https://apps.apple.com/us/app/defguard-vpn-client/id6748068630', - desktop: 'https://defguard.net/download/', - }, - }, -}; diff --git a/web/src/shared/messageIds.ts b/web/src/shared/messageIds.ts deleted file mode 100644 index 0b90b4296..000000000 --- a/web/src/shared/messageIds.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const messageIds = { - ProvisioningWipeWarning: 'ProvisioningWipeWarning', -}; diff --git a/web/src/shared/mutations.ts b/web/src/shared/mutations.ts deleted file mode 100644 index 62f8023f1..000000000 --- a/web/src/shared/mutations.ts +++ /dev/null @@ -1,48 +0,0 @@ -export const MutationKeys = { - DELETE_OPENID_CLIENT: 'DELETE_OPENID_CLIENT', - LOG_IN: 'LOG_IN', - OPENID_CALLBACK: 'OPENID_CALLBACK', - ADD_OPENID_CLIENT: 'ADD_OPENID_CLIENT', - EDIT_OPENID_CLIENT: 'EDIT_OPENID_CLIENT', - REGISTER_SECURITY_KEY_START: 'REGISTER_SECURITY_KEY_START', - REGISTER_SECURITY_KEY_FINISH: 'REGISTER_SECURITY_KEY_FINISH', - CREATE_WORKER_JOB: 'CREATE_WORKER_JOB', - CHANGE_PASSWORD: 'CHANGE_PASSWORD', - RESET_PASSWORD: 'RESET_PASSWORD', - ADD_USER_TO_GROUP: 'ADD_USER_TO_GROUP', - ADD_DEVICE: 'ADD_DEVICE', - REMOVE_USER_FROM_GROUP: 'REMOVE_USER_FROM_GROUP', - CHANGE_USER_PASSWORD: 'CHANGE_USER_PASSWORD', - DELETE_WORKER: 'DELETE_WORKER', - OAUTH_CONSENT: 'OAUTH_CONSENT', - DELETE_WEBHOOK: 'DELETE_WEBHOOK', - CHANGE_WEBHOOK_STATE: 'CHANGE_WEBHOOK_STATE', - EDIT_WEBHOOK: 'EDIT_WEBHOOK', - CHANGE_CLIENT_STATE: 'CHANGE_CLIENT_STATE', - REMOVE_USER_CLIENT: 'REMOVE_USER_CLIENT', - EDIT_USER_DEVICE: 'EDIT_USER_DEVICE', - DELETE_USER_DEVICE: 'DELETE_USER_DEVICE', - EDIT_USER: 'EDIT_USER', - ENABLE_MFA: 'ENABLE_MFA', - DISABLE_MFA: 'DISABLE_MFA', - ENABLE_TOTP_INIT: 'ENABLE_TOTP_INIT', - ENABLE_TOTP_FINISH: 'ENABLE_TOTP_FINISH', - DISABLE_TOTP: 'DISABLE_TOTP', - VERIFY_TOTP: 'VERIFY_TOTP', - ENABLE_EMAIL_MFA_INIT: 'ENABLE_EMAIL_MFA_INIT', - ENABLE_EMAIL_MFA_FINISH: 'ENABLE_EMAIL_MFA_FINISH', - DISABLE_EMAIL_MFA: 'DISABLE_EMAIL_MFA', - VERIFY_EMAIL_MFA: 'VERIFY_EMAIL_MFA', - WEBAUTHN_MFA_START: 'WEBAUTHN_MFA_START', - WEBAUTHN_MFA_FINISH: 'WEBAUTHN_MFA_FINISH', - WEBUAUTHN_DELETE_KEY: 'WEBAUTN_DELETE_KEY', - EDIT_SETTINGS: 'EDIT_SETTINGS', - RECOVERY_LOGIN: 'RECOVERY_LOGIN', - ADD_NETWORK: 'ADD_NETWORK', - CHANGE_NETWORK: 'CHANGE_NETWORK', - IMPORT_NETWORK: 'IMPORT_NETWORK', - CREATE_USER_DEVICES: 'CREATE_USER_DEVICES', - CREATE_STANDALONE_DEVICE: 'CREATE_STANDALONE_DEVICE', - EDIT_STANDALONE_DEVICE: 'EDIT_STANDALONE_DEVICE', - DELETE_STANDALONE_DEVICE: 'DELETE_STANDALONE_DEVICE', -}; diff --git a/web/src/shared/patterns.ts b/web/src/shared/patterns.ts index cfb5db0bd..b9c4d6cb7 100644 --- a/web/src/shared/patterns.ts +++ b/web/src/shared/patterns.ts @@ -63,9 +63,14 @@ export const patternValidUrl = new RegExp( '$', 'i', ); - -export const patternValidDomain = - /^(?:(?:(?:[A-Za-z-]+):\/{1,3})?(?:[A-Za-z0-9])(?:[A-Za-z0-9\-.]){1,61}(?:\.[A-Za-z]{2,})+|\[(?:(?:(?:[a-fA-F0-9]){1,4})(?::(?:[a-fA-F0-9]){1,4}){7}|::1|::)\]|(?:(?:[0-9]{1,3})(?:\.[0-9]{1,3}){3}))(?::[0-9]{1,5})?$/; +export const ipv4Pattern = /^(\d{1,3}\.){3}\d{1,3}$/; +export const ipv4WithPortPattern = /^(\d{1,3}\.){3}\d{1,3}:\d{1,5}$/; +export const ipv4WithCIDRPattern = /^(\d{1,3}\.){3}\d{1,3}\/([0-9]|[1-2][0-9]|3[0-2])$/; +export const domainPattern = + /^(?:(?:(?:[A-Za-z-]+):\/{1,3})?(?:[A-Za-z0-9])(?:[A-Za-z0-9-]*[A-Za-z0-9])?(?:\.[A-Za-z0-9](?:[A-Za-z0-9-]*[A-Za-z0-9])?)*(?:\.[A-Za-z]{2,})+|\[(?:(?:(?:[a-fA-F0-9]){1,4})(?::(?:[a-fA-F0-9]){1,4}){7}|::1|::)\]|(?:(?:[0-9]{1,3})(?:\.[0-9]{1,3}){3}))$/; + +export const domainWithPortPattern = + /^(?:(?:(?:[A-Za-z-]+):\/{1,3})?(?:[A-Za-z0-9])(?:[A-Za-z0-9\-.]){1,61}(?:\.[A-Za-z]{2,})+|\[(?:(?:(?:[a-fA-F0-9]){1,4})(?::(?:[a-fA-F0-9]){1,4}){7}|::1|::)\]|(?:(?:[0-9]{1,3})(?:\.[0-9]{1,3}){3})):[0-9]{1,5}$/; export const patternSafeUsernameCharacters = /^[a-zA-Z0-9]+[a-zA-Z0-9.\-_]*$/; diff --git a/web/src/shared/providers/AppConfigProvider.tsx b/web/src/shared/providers/AppConfigProvider.tsx new file mode 100644 index 000000000..814d14329 --- /dev/null +++ b/web/src/shared/providers/AppConfigProvider.tsx @@ -0,0 +1,28 @@ +import { useQuery } from '@tanstack/react-query'; +import { type PropsWithChildren, useEffect } from 'react'; +import api from '../api/api'; +import { isPresent } from '../defguard-ui/utils/isPresent'; +import { useApp } from '../hooks/useApp'; +import { useAuth } from '../hooks/useAuth'; + +export const AppConfigProvider = ({ children }: PropsWithChildren) => { + const isAuthenticated = useAuth((s) => isPresent(s.user)); + const { data: appInfoResponse } = useQuery({ + queryFn: api.app.info, + queryKey: ['info'], + enabled: isAuthenticated, + refetchOnWindowFocus: true, + refetchOnReconnect: true, + refetchOnMount: true, + }); + + useEffect(() => { + if (isPresent(appInfoResponse)) { + useApp.setState({ + appInfo: appInfoResponse.data, + }); + } + }, [appInfoResponse]); + + return <>{children}; +}; diff --git a/web/src/shared/providers/AppThemeProvider.tsx b/web/src/shared/providers/AppThemeProvider.tsx new file mode 100644 index 000000000..2b3365cff --- /dev/null +++ b/web/src/shared/providers/AppThemeProvider.tsx @@ -0,0 +1,35 @@ +import { type PropsWithChildren, useEffect } from 'react'; +import { themeSchema } from '../hooks/theme/types'; +import { useTheme } from '../hooks/theme/useTheme'; + +const storageKey = 'dg-theme'; + +const getPreferredColorScheme = (): 'dark' | 'light' => { + if (typeof window === 'undefined') return 'light'; + + return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; +}; + +export const AppThemeProvider = ({ children }: PropsWithChildren) => { + const { changeTheme, theme } = useTheme(); + + // biome-ignore lint/correctness/useExhaustiveDependencies: on mount effect + useEffect(() => { + const stored = localStorage.getItem(storageKey); + const pref = getPreferredColorScheme(); + if (!stored) { + if (pref !== theme) { + changeTheme(pref); + } + } else { + const result = themeSchema.safeParse(stored); + if (result.success) { + changeTheme(result.data); + } else { + changeTheme(pref); + } + } + }, []); + + return <>{children}; +}; diff --git a/web/src/shared/queries.ts b/web/src/shared/queries.ts deleted file mode 100644 index 648c0d38f..000000000 --- a/web/src/shared/queries.ts +++ /dev/null @@ -1,42 +0,0 @@ -export const QueryKeys = { - FETCH_ME: 'FETCH_ME', - FETCH_USERS_LIST: 'FETCH_USERS_LIST', - FETCH_USER_PROFILE: 'FETCH_USER_PROFILE', - FETCH_WORKERS: 'FETCH_WORKERS', - FETCH_WORKER_JOB_STATUS: 'FETCH_WORKER_JOB_STATUS', - FETCH_GROUPS: 'FETCH_GROUPS', - FETCH_GROUPS_INFO: 'FETCH_GROUPS_INFO', - FETCH_WEBHOOKS: 'FETCH_WEBHOOKS', - FETCH_NETWORKS: 'FETCH_NETWORKS', - FETCH_NETWORK: 'FETCH_NETWORK', - FETCH_USER_DEVICES: 'FETCH_USER_DEVICES', - FETCH_CLIENTS: 'FETCH_CLIENTS', - FETCH_USER_CLIENTS: 'FETCH_USER_CLIENTS', - FETCH_NETWORK_STATS: 'FETCH_NETWORK_STATS', - FETCH_NETWORK_TOKEN: 'FETCH_NETWORK_TOKEN', - FETCH_NETWORK_USERS_STATS: 'FETCH_NETWORK_USERS_STATS', - FETCH_LICENSE: 'FETCH_LICENSE', - FETCH_WORKER_TOKEN: 'FETCH_WORKER_TOKEN', - FETCH_ESSENTIAL_SETTINGS: 'FETCH_ESSENTIAL_SETTINGS', - FETCH_SETTINGS: 'FETCH_SETTINGS', - FETCH_GATEWAY_STATUS: 'FETCH_GATEWAY_STATUS', - FETCH_APP_INFO: 'FETCH_APP_INFO', - FETCH_DEVICE_CONFIG: 'FETCH_DEVICE_CONFIG', - FETCH_NETWORK_GATEWAYS_STATUS: 'FETCH_NETWORK_GATEWAYS_STATUS', - FETCH_SUPPORT_DATA: 'FETCH_SUPPORT_DATA', - FETCH_LOGS: 'FETCH_LOGS', - FETCH_AUTHENTICATION_KEYS_INFO: 'FETCH_AUTHENTICATION_KEYS_INFO', - FETCH_API_TOKENS_INFO: 'FETCH_API_TOKENS_INFO', - FETCH_OPENID_PROVIDERS: 'FETCH_OPENID_PROVIDERS', - FETCH_OPENID_INFO: 'FETCH_OPENID_INFO', - FETCH_ENTERPRISE_STATUS: 'FETCH_ENTERPRISE_STATUS', - FETCH_ENTERPRISE_SETTINGS: 'FETCH_ENTERPRISE_SETTINGS', - FETCH_ENTERPRISE_INFO: 'FETCH_ENTERPRISE_INFO', - FETCH_NEW_VERSION: 'FETCH_NEW_VERSION', - FETCH_STANDALONE_DEVICE: 'FETCH_STANDALONE_DEVICE', - FETCH_STANDALONE_DEVICE_LIST: 'FETCH_STANDALONE_DEVICE_LIST', - FETCH_ACL_CREATE_CONTEXT: 'FETCH_ACL_CREATE_CONTEXT', - FETCH_ACL_RULES: 'FETCH_ACL_RULES', - FETCH_ACL_ALIASES: 'FETCH_ACL_ALIASES', - FETCH_ACL_RULE_EDIT: 'FETCH_ACL_RULE_EDIT', -}; diff --git a/web/src/shared/query-client.ts b/web/src/shared/query-client.ts deleted file mode 100644 index fb9f3dc23..000000000 --- a/web/src/shared/query-client.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { QueryClient } from '@tanstack/query-core'; - -const queryClient = new QueryClient({ - defaultOptions: { - queries: { - staleTime: 5 * 1000, - }, - }, -}); - -export default queryClient; diff --git a/web/src/shared/query.ts b/web/src/shared/query.ts new file mode 100644 index 000000000..5821ee200 --- /dev/null +++ b/web/src/shared/query.ts @@ -0,0 +1,151 @@ +import { queryOptions } from '@tanstack/react-query'; +import api from './api/api'; +import type { UserProfile } from './api/types'; +import { updateServiceApi } from './api/update-service'; + +export const getExternalProviderQueryOptions = queryOptions({ + queryFn: api.openIdProvider.getOpenIdProvider, + queryKey: ['openid', 'provider'], + select: (resp) => resp.data, +}); + +export const getEnterpriseSettingsQueryOptions = queryOptions({ + queryFn: api.settings.getEnterpriseSettings, + queryKey: ['settings_enterprise'], + select: (resp) => resp.data, +}); + +export const getLocationQueryOptions = (id: number) => + queryOptions({ + queryFn: () => api.location.getLocation(id), + queryKey: ['network', id], + select: (resp) => resp.data, + }); + +export const getLocationsQueryOptions = queryOptions({ + queryFn: api.location.getLocations, + queryKey: ['network'], + select: (resp) => resp.data, +}); + +export const getNetworkDevicesQueryOptions = queryOptions({ + queryFn: api.network_device.getDevices, + queryKey: ['device', 'network'], + select: (resp) => resp.data, +}); + +export const userMeQueryOptions = queryOptions({ + queryFn: () => api.user.getMe, + queryKey: ['me'], + staleTime: 60_000, + throwOnError: false, + retry: false, + refetchOnWindowFocus: false, + refetchOnMount: true, + refetchOnReconnect: true, +}); + +export const userProfileQueryOptions = (username: string) => + queryOptions({ + queryFn: () => api.user.getUser(username), + select: ({ data }) => { + const res: UserProfile = { + devices: data.devices.map((device) => ({ + ...device, + biometry_enabled: data.biometric_enabled_devices.includes(device.id), + })), + security_keys: data.security_keys, + user: data.user, + }; + return res; + }, + queryKey: ['user', username], + refetchOnMount: true, + refetchOnReconnect: true, + }); + +export const clientArtifactsQueryOptions = queryOptions({ + queryFn: updateServiceApi.getClientArtifacts, + queryKey: ['update-service', 'artifacts'], + staleTime: 180 * 1000, + refetchOnWindowFocus: false, + refetchOnMount: true, + refetchOnReconnect: true, +}); + +export const getUserAuthKeysQueryOptions = (username: string) => + queryOptions({ + queryFn: () => api.user.getAuthKeys(username), + queryKey: ['user', username, 'auth_key'], + select: (response) => response.data, + refetchOnMount: true, + refetchOnReconnect: true, + }); + +export const getUserApiTokensQueryOptions = (username: string, admin: boolean) => + queryOptions({ + queryFn: () => api.user.getApiTokens(username), + queryKey: ['user', username, 'api_token'], + select: (resp) => resp.data, + refetchOnMount: true, + refetchOnReconnect: true, + throwOnError: false, + enabled: admin, + }); + +export const getUsersQueryOptions = queryOptions({ + queryFn: api.getUsersOverview, + queryKey: ['user'], + refetchOnMount: true, + refetchOnReconnect: true, +}); + +export const getGroupsInfoQueryOptions = queryOptions({ + queryFn: api.group.getGroupsInfo, + queryKey: ['group-info'], + select: (resp) => resp.data, + refetchOnMount: true, + refetchOnReconnect: true, +}); + +export const getOpenIdClientQueryOptions = queryOptions({ + queryFn: api.openIdClient.getOpenIdClients, + queryKey: ['oauth'], + select: (resp) => resp.data, +}); + +export const getWebhooksQueryOptions = queryOptions({ + queryFn: api.webhook.getWebhooks, + queryKey: ['webhook'], + select: (resp) => resp.data, +}); + +export const getSettingsQueryOptions = queryOptions({ + queryFn: api.settings.getSettings, + queryKey: ['settings'], + select: (resp) => resp.data, +}); + +export const getOpenIdProvidersQueryOptions = queryOptions({ + queryFn: api.openIdProvider.getOpenIdProvider, + queryKey: ['openid', 'provider'], + select: (resp) => resp.data, +}); + +export const getRulesQueryOptions = queryOptions({ + queryFn: api.acl.rule.getRules, + queryKey: ['acl', 'rule'], + select: (resp) => resp.data, +}); + +export const getAliasesQueryOptions = queryOptions({ + queryFn: api.acl.alias.getAliases, + queryKey: ['acl', 'alias'], + select: (resp) => resp.data, +}); + +export const getLicenseInfoQueryOptions = queryOptions({ + queryFn: api.getLicenseInfo, + queryKey: ['enterprise_info'], + select: (response) => response.data.license_info, +}); diff --git a/web/src/shared/schema/totpCode.ts b/web/src/shared/schema/totpCode.ts new file mode 100644 index 000000000..9dee228f0 --- /dev/null +++ b/web/src/shared/schema/totpCode.ts @@ -0,0 +1,16 @@ +import z from 'zod'; +import { m } from '../../paraglide/messages'; + +export const totpCodeFormSchema = z.object({ + code: z + .string() + .trim() + .min(1, m.form_error_required()) + .min( + 6, + m.form_error_min_len({ + length: 6, + }), + ) + .max(6, m.form_error_max_len({ length: 6 })), +}); diff --git a/web/src/shared/scss/_legacy-variables.scss b/web/src/shared/scss/_legacy-variables.scss deleted file mode 100644 index a4b9670d7..000000000 --- a/web/src/shared/scss/_legacy-variables.scss +++ /dev/null @@ -1,34 +0,0 @@ -:root { - --input-select: #77c9ff; - --input-warning: #dc8787; - --white: #fff; - --gray-border-dark: #e8e8e8; - --gray-border: #e8e8e8; - --bg-light: #f9f9f9; - --text-main: #222; - --gray-lighter: #cbd3d8; - --gray-light: #899ca8; - --gray-dark: #617684; - --gray-darker: #485964; - --success: #14bc6e; - --success-dark: #10a15e; - --error: #cb3f3f; - --error-dark: #b53030; - --primary: #0c8ce0; - --primary-dark: #0876be; - --light-green-bg: #eefff7; - --light-red-bg: #fff5f5; - --light-gray-bg: #f1f1f1; - --light-orange-bg: #fbf5e7; - --warning: #c8a043; - --separator-stroke: #c4c4c4; - --card-shadow: 0px 12px 24px 0px #00000014; - --transparent: rgb(0 0 0 / 0%); - --inactive-box-shadow: 0 6px 10px rgb(0 0 0 / 0%); - --buttons-box-shadow: 0 6px 10px rgb(0 0 0 / 10%); - --floating-shadow: 0 5px 7.5px rgb(0 0 0 / 7.84%); - --button-shadow-gray: 0px 6px 4px 0px rgba(49, 49, 49, 0.25); - --button-shadow-red: 0px 6px 4px 0px rgba(203, 63, 63, 0.4); - --button-shadow-green: 0px 6px 4px 0px rgba(20, 188, 110, 0.4); - --button-shadow-blue: 0px 6px 4px 0px rgba(12, 140, 224, 0.4); -} diff --git a/web/src/shared/scss/base/_base.scss b/web/src/shared/scss/base/_base.scss deleted file mode 100644 index e403a28fa..000000000 --- a/web/src/shared/scss/base/_base.scss +++ /dev/null @@ -1,188 +0,0 @@ -@use './variables' as v; -@use '../global/' as *; -@use './scrollbar'; - -html, -body, -div, -span, -applet, -object, -iframe, -h1, -h2, -h3, -h4, -h5, -h6, -p, -blockquote, -pre, -a, -abbr, -acronym, -address, -big, -cite, -code, -del, -dfn, -em, -img, -ins, -kbd, -q, -s, -samp, -small, -strike, -strong, -sub, -sup, -tt, -var, -b, -u, -i, -center, -dl, -dt, -dd, -ol, -ul, -li, -fieldset, -form, -label, -legend, -table, -caption, -tbody, -tfoot, -thead, -tr, -th, -td, -article, -aside, -canvas, -details, -embed, -figure, -figcaption, -footer, -header, -hgroup, -menu, -nav, -output, -ruby, -section, -summary, -time, -mark, -audio, -video { - margin: 0; - padding: 0; - border: 0; - vertical-align: baseline; -} - -html { - font-size: 62.5%; - height: 100%; - max-height: 100%; - overflow: hidden; -} - -body { - font-size: 1.6rem; - color: v.$gray-light; - font-family: - Poppins, - Roboto, - system-ui, - -apple-system, - BlinkMacSystemFont, - 'Segoe UI', - Oxygen, - Ubuntu, - Cantarell, - 'Open Sans', - 'Helvetica Neue', - sans-serif; - background: v.$bg-light; - overflow-x: hidden; - position: relative; - max-height: 100dvh; -} - -#app, -#root { - position: relative; - min-height: 100dvh; -} - -h1, -h2, -h3, -h4, -h5, -h6, -.title, -.nav { - font-family: Poppins; -} - -section, -div { - .title { - font-family: Poppins; - } -} - -p, -span { - font-family: Roboto; -} - -p, -span, -div, -section, -a, -h1, -h2, -h3, -h4, -h5, -h6 { - color: v.$text-main; -} - -#root { - background: v.$bg-light; -} - -input:focus, -textarea:focus, -select:focus { - outline: none; -} - -.visually-hidden, -.hidden { - @include visually-hidden; -} - -:root { - --nav-links-animation-duration: 250ms; - --nav-collapse-duration: 250ms; - --page-content-limit: 1510px; -} - -#datepicker-root { - z-index: 6; - position: relative; -} diff --git a/web/src/shared/scss/base/_index.scss b/web/src/shared/scss/base/_index.scss deleted file mode 100644 index b42a016b7..000000000 --- a/web/src/shared/scss/base/_index.scss +++ /dev/null @@ -1,6 +0,0 @@ -@forward './base'; -@forward './variables'; - -:root { - --disabled-opacity: 0.5; -} diff --git a/web/src/shared/scss/base/_scrollbar.scss b/web/src/shared/scss/base/_scrollbar.scss deleted file mode 100644 index 64f52dd3e..000000000 --- a/web/src/shared/scss/base/_scrollbar.scss +++ /dev/null @@ -1,17 +0,0 @@ -@use './variables/colors' as c; -@use '../global/mixins' as *; - -::-webkit-scrollbar { - width: 4px; - height: 4px; -} - -::-webkit-scrollbar-thumb { - @include bg-opacity(c.$gray-light, 0.4); - - border-radius: 15px; - - &:hover { - @include bg-opacity(c.$gray-light, 1); - } -} diff --git a/web/src/shared/scss/base/variables/_colors.scss b/web/src/shared/scss/base/variables/_colors.scss deleted file mode 100644 index 92cdd043d..000000000 --- a/web/src/shared/scss/base/variables/_colors.scss +++ /dev/null @@ -1,23 +0,0 @@ -$input-select: #77c9ff; -$input-warning: #dc8787; -$white: #fff; -$gray-border-dark: #e8e8e8; -$gray-border: #e8e8e8; -$bg-light: #f9f9f9; -$text-main: #222; -$gray-lighter: #cbd3d8; -$gray-light: #899ca8; -$gray-dark: #617684; -$gray-darker: #485964; -$success: #14bc6e; -$success-dark: #10a15e; -$error: #cb3f3f; -$error-dark: #b53030; -$primary: #0c8ce0; -$primary-dark: #0876be; -$light-green-bg: #eefff7; -$light-red-bg: #fff5f5; -$light-gray-bg: #f1f1f1; -$light-orange-bg: #fbf5e7; -$warning: #c8a043; -$separator-stroke: #c4c4c4; diff --git a/web/src/shared/scss/base/variables/_index.scss b/web/src/shared/scss/base/variables/_index.scss deleted file mode 100644 index 1fa10e0f8..000000000 --- a/web/src/shared/scss/base/variables/_index.scss +++ /dev/null @@ -1,3 +0,0 @@ -@forward './colors'; -@forward './text'; -@forward './nav'; diff --git a/web/src/shared/scss/base/variables/_nav.scss b/web/src/shared/scss/base/variables/_nav.scss deleted file mode 100644 index 7c444a569..000000000 --- a/web/src/shared/scss/base/variables/_nav.scss +++ /dev/null @@ -1,2 +0,0 @@ -$nav-links-animation-duration: 250ms; -$nav-collapse-duration: 250ms; diff --git a/web/src/shared/scss/base/variables/_text.scss b/web/src/shared/scss/base/variables/_text.scss deleted file mode 100644 index 1b00896ec..000000000 --- a/web/src/shared/scss/base/variables/_text.scss +++ /dev/null @@ -1,12 +0,0 @@ -$text-weights: ( - 'thin': 100, - 'light': 300, - 'regular': 400, - 'medium': 500, - 'semiBold': 600, - 'bold': 700, -); - -@mixin poppins { - font-family: Poppins; -} diff --git a/web/src/shared/scss/global/_index.scss b/web/src/shared/scss/global/_index.scss deleted file mode 100644 index c022e0c63..000000000 --- a/web/src/shared/scss/global/_index.scss +++ /dev/null @@ -1,6 +0,0 @@ -// this is prepended to all .scss files so all mixins and helpers can be used anywhere -@forward './bootstrap/'; -@forward './mixins'; -@forward '../../defguard-ui/scss/typography'; -@forward '../../defguard-ui/scss/helpers/mixins'; -@forward '../../defguard-ui/scss/helpers/animation'; diff --git a/web/src/shared/scss/global/_mixins.scss b/web/src/shared/scss/global/_mixins.scss deleted file mode 100644 index 02d6eb95e..000000000 --- a/web/src/shared/scss/global/_mixins.scss +++ /dev/null @@ -1,103 +0,0 @@ -@use '../base/variables' as v; -@use './bootstrap' as *; -@use 'sass:map'; - -@mixin text-weight($key, $map: v.$text-weights) { - font-weight: map.get($map, $key); -} - -@mixin bg-opacity($color, $opacity) { - background-color: $color; - /* The Fallback */ - background-color: rgba($color, $opacity); -} - -@mixin poppins { - font-family: Poppins; -} - -@mixin header { - @include text-weight(semiBold); - @include poppins; -} - -@mixin icon-container { - // container with centered icon within it. - display: flex; - flex-direction: column; - align-content: center; - align-items: center; - justify-content: center; -} - -@mixin typography-legacy( - $font-size: 12px, - $line-height: 14px, - $text-weight-key: medium, - $color: v.$text-main, - $font-family: 'Roboto' -) { - font-family: $font-family; - color: $color; - font-size: $font-size; - line-height: $line-height; - @include text-weight($text-weight-key); -} - -@mixin elevate() { - box-shadow: 3px 3px 6px #00000005; -} - -@mixin page-header() { - @include typography-legacy(41px, 57px, semiBold, var(--text-main), 'Poppins'); -} - -@mixin card-header() { - @include typography-legacy(20px, 28px, semiBold, var(--text-main), 'Poppins'); -} - -@mixin small-header() { - @include typography-legacy(15px, 23px, medium, var(--text-main), 'Poppins'); -} - -@mixin modal-header() { - @include typography-legacy(20px, 30px, semiBold, var(--text-main), 'Poppins'); -} - -@mixin regular-text($color: text-main) { - @include typography-legacy(15px, 18px, regular, var(--#{$color}), 'Roboto'); -} - -@mixin list-text() { - @include typography-legacy(15px, 18px, medium, var(--text-main), 'Roboto'); -} - -@mixin small-text($color: text-main) { - @include typography-legacy(12px, 18px, medium); - - color: var(--#{$color}); -} - -@mixin label-text() { - @include typography-legacy(12px, 14px, medium); - - color: var(--gray-light); -} - -@mixin outer-label() { - display: block; - margin-bottom: 0.8rem; - - @include typography-legacy(12px, 14px, medium, var(--gray-light), 'Roboto'); - - @include media-breakpoint-up(md) { - margin-bottom: 1.2rem; - } -} - -@mixin apply-card { - border-radius: 15px; - box-shadow: var(--card-shadow); - background-color: var(--white); - box-sizing: border-box; -} diff --git a/web/src/shared/scss/global/bootstrap/LICENCE b/web/src/shared/scss/global/bootstrap/LICENCE deleted file mode 100644 index c21b5a025..000000000 --- a/web/src/shared/scss/global/bootstrap/LICENCE +++ /dev/null @@ -1,23 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2011-2022 Twitter, Inc. -Copyright (c) 2011-2022 The Bootstrap Authors - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -Copied diff --git a/web/src/shared/scss/global/bootstrap/_breakpoints.scss b/web/src/shared/scss/global/bootstrap/_breakpoints.scss deleted file mode 100644 index f25130ba5..000000000 --- a/web/src/shared/scss/global/bootstrap/_breakpoints.scss +++ /dev/null @@ -1,98 +0,0 @@ -@use 'sass:map'; -@use 'sass:list'; - -$grid-breakpoints: ( - xs: 0, - sm: 576px, - md: 768px, - lg: 992px, - xl: 1200px, - xxl: 1600px, -); - -@function breakpoint-next( - $name, - $breakpoints: $grid-breakpoints, - $breakpoint-names: map-keys($breakpoints) -) { - $n: list.index($breakpoint-names, $name); - @if not $n { - @error "breakpoint `#{$name}` not found in `#{$breakpoints}`"; - } - @return if($n < length($breakpoint-names), nth($breakpoint-names, $n + 1), null); -} - -@function breakpoint-min($name, $breakpoints: $grid-breakpoints) { - $min: map.get($breakpoints, $name); - @return if($min != 0, $min, null); -} - -@function breakpoint-max($name, $breakpoints: $grid-breakpoints) { - $max: map.get($breakpoints, $name); - @return if($max and $max > 0, $max - 0.02, null); -} - -@function breakpoint-infix($name, $breakpoints: $grid-breakpoints) { - @return if(breakpoint-min($name, $breakpoints) == null, '', '-#{$name}'); -} - -@mixin media-breakpoint-up($name, $breakpoints: $grid-breakpoints) { - $min: breakpoint-min($name, $breakpoints); - @if $min { - @media (min-width: $min) { - @content; - } - } @else { - @content; - } -} - -@mixin media-breakpoint-down($name, $breakpoints: $grid-breakpoints) { - $max: breakpoint-max($name, $breakpoints); - @if $max { - @media (max-width: $max) { - @content; - } - } @else { - @content; - } -} - -@mixin media-breakpoint-between($lower, $upper, $breakpoints: $grid-breakpoints) { - $min: breakpoint-min($lower, $breakpoints); - $max: breakpoint-max($upper, $breakpoints); - - @if $min != null and $max != null { - @media (min-width: $min) and (max-width: $max) { - @content; - } - } @else if $max == null { - @include media-breakpoint-up($lower, $breakpoints) { - @content; - } - } @else if $min == null { - @include media-breakpoint-down($upper, $breakpoints) { - @content; - } - } -} - -@mixin media-breakpoint-only($name, $breakpoints: $grid-breakpoints) { - $min: breakpoint-min($name, $breakpoints); - $next: breakpoint-next($name, $breakpoints); - $max: breakpoint-max($next); - - @if $min != null and $max != null { - @media (min-width: $min) and (max-width: $max) { - @content; - } - } @else if $max == null { - @include media-breakpoint-up($name, $breakpoints) { - @content; - } - } @else if $min == null { - @include media-breakpoint-down($next, $breakpoints) { - @content; - } - } -} diff --git a/web/src/shared/scss/global/bootstrap/_functions.scss b/web/src/shared/scss/global/bootstrap/_functions.scss deleted file mode 100644 index 0089cbba9..000000000 --- a/web/src/shared/scss/global/bootstrap/_functions.scss +++ /dev/null @@ -1,13 +0,0 @@ -@function tint-color($color, $weight) { - @return mix(white, $color, $weight); -} - -// Shade a color: mix a color with black -@function shade-color($color, $weight) { - @return mix(black, $color, $weight); -} - -// Shade the color if the weight is positive, else tint it -@function shift-color($color, $weight) { - @return if($weight > 0, shade-color($color, $weight), tint-color($color, -$weight)); -} diff --git a/web/src/shared/scss/global/bootstrap/_index.scss b/web/src/shared/scss/global/bootstrap/_index.scss deleted file mode 100644 index b1e3ef13d..000000000 --- a/web/src/shared/scss/global/bootstrap/_index.scss +++ /dev/null @@ -1,2 +0,0 @@ -@forward './breakpoints'; -@forward './functions'; diff --git a/web/src/shared/scss/styles.scss b/web/src/shared/scss/styles.scss deleted file mode 100644 index e9082b04d..000000000 --- a/web/src/shared/scss/styles.scss +++ /dev/null @@ -1,3 +0,0 @@ -@use './base'; -@use './legacy-variables'; -@use '../defguard-ui/scss'; diff --git a/web/src/shared/types.ts b/web/src/shared/types.ts deleted file mode 100644 index d423ceed5..000000000 --- a/web/src/shared/types.ts +++ /dev/null @@ -1,1428 +0,0 @@ -import type { - CredentialCreationOptionsJSON, - CredentialRequestOptionsJSON, - PublicKeyCredentialWithAssertionJSON, - PublicKeyCredentialWithAttestationJSON, -} from '@github/webauthn-json'; -import type { AxiosError, AxiosPromise } from 'axios'; - -import type { AclAlias, AclStatus } from '../pages/acl/types'; -import type { - ActivityLogEventType, - ActivityLogModule, -} from '../pages/activity-log/types'; -import type { UpdateInfo } from './hooks/store/useUpdatesStore'; - -export type OutdatedProxy = { - version?: string; -}; - -export type OutdatedGateway = { - version?: string; - hostname?: string; -}; - -export type OutdatedComponents = { - proxy?: OutdatedProxy; - gateways: OutdatedGateway[]; -}; - -export type ApiError = AxiosError; - -export type ApiErrorResponse = { - msg?: string; - message?: string; -}; - -export enum UserStatus { - active = 'Active', - inactive = 'Inactive', - awaitingLogin = 'Awaiting login', -} - -export enum UserMFAMethod { - NONE = 'None', - ONE_TIME_PASSWORD = 'OneTimePassword', - EMAIL = 'Email', - WEB_AUTH_N = 'Webauthn', -} - -export enum AuthenticationKeyType { - SSH = 'ssh', - GPG = 'gpg', -} - -export type User = { - id: number; - username: string; - last_name: string; - first_name: string; - mfa_method: UserMFAMethod; - mfa_enabled: boolean; - totp_enabled: boolean; - email_mfa_enabled: boolean; - email: string; - phone?: string; - groups: string[]; - authorized_apps?: OAuth2AuthorizedApps[]; - is_active: boolean; - enrolled: boolean; - is_admin: boolean; - ldap_pass_requires_change: boolean; -}; - -export type UserProfile = { - user: User; - devices: Device[]; - security_keys: SecurityKey[]; - biometric_enabled_devices: number[]; -}; - -export interface OAuth2AuthorizedApps { - oauth2client_id: number; - oauth2client_name: string; - user_id: number; -} - -export interface SecurityKey { - id: number; - name: string; -} - -export interface Location { - name: string; - ipAddress: string; - shared: { - ipAddress: string; - }[]; -} - -export type AddDeviceResponseDevice = Omit; - -export interface Device { - id: number; - user_id: number; - name: string; - wireguard_pubkey: string; - created: string; - networks: DeviceNetworkInfo[]; -} - -export type DeviceNetworkInfo = { - device_wireguard_ips: string[]; - is_active: boolean; - network_gateway_ip: string; - network_id: number; - network_name: string; - last_connected_at?: string; - last_connected_ip?: string; -}; - -export interface AddDeviceRequest { - username: string; - name: string; - wireguard_pubkey: string; -} - -export type GatewayStatus = { - connected: boolean; - network_id: number; - network_name: string; - name?: string; - hostname: string; - uid: string; -}; - -export enum LocationMfaMode { - DISABLED = 'disabled', - INTERNAL = 'internal', - EXTERNAL = 'external', -} - -export interface Network { - id: number; - name: string; - address: string; - port: number; - endpoint: string; - connected?: boolean; - connected_at?: string; - gateways?: GatewayStatus[]; - allowed_ips?: string[]; - allowed_groups?: string[]; - dns?: string; - keepalive_interval: number; - peer_disconnect_threshold: number; - acl_enabled: boolean; - acl_default_allow: boolean; - location_mfa_mode: LocationMfaMode; -} - -export type ModifyNetworkRequest = { - id: number; - network: Omit< - Network, - 'gateways' | 'connected' | 'id' | 'connected_at' | 'allowed_ips' - > & { - allowed_ips: string; - }; -}; - -export interface ImportNetworkRequest { - name: string; - endpoint: string; - config: string; - allowed_groups: string[]; -} - -export interface MapUserDevicesRequest { - networkId: number; - devices: MappedDevice[]; -} - -export interface NetworkToken { - token: string; - grpc_url: string; -} - -export interface LoginData { - username: string; - password: string; -} - -export interface CallbackData { - code: string; - state: string; -} - -export type LoginSubjectData = { - user?: User; - // URL of an already authorized application - url?: string; - mfa?: MFALoginResponse; -}; - -export interface DeleteUserModal { - visible: boolean; - user?: User; -} - -export interface ToggleUserModal { - visible: boolean; - user?: User; -} - -export interface ProvisionKeyModal { - visible: boolean; - user?: User; -} - -export interface AddAuthenticationKeyModal { - visible: boolean; - user?: User; -} - -export interface DeleteAuthenticationKeyModal { - visible: boolean; - authenticationKey?: AuthenticationKey; -} - -export interface AddApiTokenModal { - visible: boolean; - user?: User; -} - -export interface DeleteApiTokenModal { - visible: boolean; - apiToken?: ApiToken; -} - -export interface DeleteOpenidClientModal { - visible: boolean; - client?: OpenidClient; - onSuccess?: () => void; -} - -export interface EnableOpenidClientModal { - visible: boolean; - client?: OpenidClient; - onSuccess?: () => void; -} - -export interface GenericApiResponse { - ok?: boolean; -} - -export interface ChangePasswordRequest { - new_password: string; - username: string; -} - -export interface ResetPasswordRequest { - username: string; -} - -export interface AddUserRequest { - username: string; - password?: string; - email: string; - last_name: string; - first_name: string; - phone?: string; -} - -export interface StartEnrollmentRequest { - username: string; - send_enrollment_notification: boolean; - email?: string; -} - -export interface StartEnrollmentResponse { - enrollment_url: string; - enrollment_token: string; -} -export interface GroupsResponse { - groups: string[]; -} - -export interface UserGroupRequest { - group: string; - username: string; -} - -export interface ChangeUserPasswordRequest { - new_password: string; - username: string; -} - -export interface GetNetworkStatsRequest { - from?: number; - id: Network['id']; -} - -export interface UserEditRequest { - username: string; - data: Partial; -} - -export interface MFALoginResponse { - mfa_method: UserMFAMethod; - totp_available: boolean; - webauthn_available: boolean; - email_available: boolean; -} - -export interface LoginResponse { - url?: string; - user?: User; - mfa?: MFALoginResponse; -} - -export interface OpenIdInfoResponse { - url: string; - button_display_name?: string; -} - -export interface DeleteWebAuthNKeyRequest { - username: User['username']; - keyId: SecurityKey['id']; -} - -export interface RecoveryCodes { - codes: string[]; -} - -export interface RecoveryLoginRequest { - code: string; -} - -export type MFARecoveryCodesResponse = Promise; - -export interface VersionResponse { - version: string; -} - -export interface MFAFinishResponse { - url?: string; - user?: User; -} - -export interface ImportNetworkResponse { - network: Network; - devices: ImportedDevice[]; -} - -export interface ImportedDevice { - name: string; - wireguard_ips: string[]; - wireguard_pubkey: string; - user_id?: number; -} - -export interface MappedDevice extends ImportedDevice { - user_id: number; -} - -export interface LdapInfo { - enabled: boolean; - ad: boolean; -} - -export interface AppInfo { - version: string; - network_present: boolean; - smtp_enabled: boolean; - license_info: LicenseInfo; - ldap_info: LdapInfo; - external_openid_enabled: boolean; -} - -export type GetDeviceConfigRequest = { - device_id: number; - network_id: number; -}; - -export type AddDeviceResponse = { - device: AddDeviceResponseDevice; - configs: AddDeviceConfig[]; -}; - -export type DeleteGatewayRequest = { - networkId: number; - gatewayId: string; -}; - -export type ChangePasswordSelfRequest = { - old_password: string; - new_password: string; -}; - -export type AuthCodeRequest = { - code: string; -}; - -export type AuthenticationKeyInfo = { - id: number; - name?: string; - key_type: AuthenticationKeyType; - key: string; - yubikey_serial?: string; - yubikey_id?: number; - yubikey_name?: string; -}; - -export type AuthenticationKeyRequestBase = { - username: string; -}; - -export type RenameAuthenticationKeyRequest = { - id: number; - name: string; -} & AuthenticationKeyRequestBase; - -export type AddAuthenticationKeyRequest = { - name: string; - key: string; - key_type: string; -} & AuthenticationKeyRequestBase; - -export type ApiTokenInfo = { - id: number; - name?: string; -}; - -export type ApiTokenRequestBase = { - username: string; -}; - -export type RenameApiTokenRequest = { - id: number; - name: string; -} & ApiTokenRequestBase; - -export type AddApiTokenRequest = { - name: string; -} & ApiTokenRequestBase; - -export type AddApiTokenResponse = { - token: string; -}; - -export type ModifyGroupsRequest = { - name: string; - // array of usernames - members?: string[]; - is_admin: boolean; -}; - -export type AddUsersToGroupsRequest = { - groups: string[]; - users: number[]; -}; - -export type EditGroupRequest = ModifyGroupsRequest & { - originalName: string; -}; - -export type AuthenticationKey = { - id: number; - name: string; - key_type: AuthenticationKeyType; - key: string; -}; - -export type ApiToken = { - id: number; - name: string; - created_at: string; -}; - -export type EnterpriseInfoResponse = { - license_info?: EnterpriseInfo; -}; - -export type CreateAclRuleRequest = Omit< - AclRuleInfo, - 'id' | 'expires' | 'state' | 'parent_id' -> & { - expires: string | null; -}; - -export type EditAclRuleRequest = Omit & { - expires: string | null; -}; - -export type CreateAclAliasRequest = Omit< - AclAlias, - 'id' | 'state' | 'parent_id' | 'rules' ->; - -export type EditAclAliasRequest = Omit; - -export type AclRuleInfo = { - id: number; - parent_id?: number; - state: AclStatus; - name: string; - all_networks: boolean; - allow_all_users: boolean; - deny_all_users: boolean; - allow_all_network_devices: boolean; - deny_all_network_devices: boolean; - networks: number[]; - expires?: string; - enabled: boolean; - allowed_users: number[]; - denied_users: number[]; - allowed_groups: number[]; - denied_groups: number[]; - allowed_devices: number[]; - denied_devices: number[]; - destination: string; - aliases: number[]; - ports: string; - protocols: number[]; -}; - -export type ActivityLogEvent = { - id: number; - timestamp: string; - user_id: number; - username: string; - location?: string; - ip: string; - event: ActivityLogEventType; - module: ActivityLogModule; - device: string; - description?: string; -}; - -export type PaginationParams = { - page?: number; -}; - -export type PaginationMeta = { - current_page: number; - page_size: number; - total_items: number; - total_pagers: number; - next_page?: number; -}; - -export type PaginatedResponse = { - data: T[]; - pagination: PaginationMeta; -}; - -export type AllGateWaysResponse = Record>; - -export type ActivityLogFilters = { - // Naive UTC datetime in string - from?: string; - // Naive UTC datetime in string - until?: string; - username?: string[]; - location?: string[]; - event?: ActivityLogEventType[]; - module?: ActivityLogModule[]; - search?: string; -}; - -export type ActivityLogSortKey = - | 'timestamp' - | 'username' - | 'location' - | 'ip' - | 'event' - | 'module' - | 'device'; - -export type ApiSortDirection = 'asc' | 'desc'; - -export type RequestSortParams = { - sort_by?: T; - sort_order?: ApiSortDirection; -}; - -export type ActivityLogRequestParams = ActivityLogFilters & - RequestSortParams & - PaginationParams; - -export type ActivityLogStreamType = 'vector_http' | 'logstash_http'; - -export type ActivityLogStream = { - id: number; - name: string; - stream_type: ActivityLogStreamType; - config: ActivityLogStreamConfig; -}; - -export type ActivityLogStreamVectorHttp = { - url: string; - username?: string; - password?: string; - cert?: string; -}; - -export type ActivityLogStreamLogstashHttp = { - url: string; - username?: string; - password?: string; - cert?: string; -}; - -export type ActivityLogStreamModifyRequest = { - id: number; - name: string; - stream_type: ActivityLogStreamType; - stream_config: ActivityLogStreamConfig; -}; - -export type ActivityLogStreamConfig = - | ActivityLogStreamVectorHttp - | ActivityLogStreamLogstashHttp; - -export type ActivityLogStreamCreateRequest = Omit; - -export type Api = { - getOutdatedInfo: () => Promise; - getAppInfo: () => Promise; - getNewVersion: () => Promise; - changePasswordSelf: (data: ChangePasswordSelfRequest) => Promise; - getEnterpriseInfo: () => Promise; - activityLog: { - getActivityLog: ( - params: ActivityLogRequestParams, - ) => Promise>; - }; - activityLogStream: { - getActivityLogStreams: () => Promise; - createActivityLogStream: ( - data: ActivityLogStreamCreateRequest, - ) => Promise; - modifyActivityLogStream: ( - data: ActivityLogStreamModifyRequest, - ) => Promise; - deleteActivityLogStream: (id: number) => Promise; - }; - acl: { - aliases: { - getAliases: () => Promise; - getAlias: (id: number) => Promise; - createAlias: (data: CreateAclAliasRequest) => Promise; - editAlias: (data: EditAclAliasRequest) => Promise; - deleteAlias: (id: number) => Promise; - applyAliases: (aliases: number[]) => Promise; - }; - rules: { - getRule: (id: number) => Promise; - getRules: () => Promise; - createRule: (data: CreateAclRuleRequest) => Promise; - editRule: (data: EditAclRuleRequest) => Promise; - deleteRule: (id: number) => Promise; - applyRules: (data: number[]) => Promise; - }; - }; - oAuth: { - consent: (params: unknown) => Promise; - }; - groups: { - getGroupsInfo: () => Promise; - getGroups: () => Promise; - createGroup: (data: ModifyGroupsRequest) => Promise; - editGroup: (data: EditGroupRequest) => Promise; - deleteGroup: (groupName: string) => Promise; - addUsersToGroups: (data: AddUsersToGroupsRequest) => Promise; - }; - user: { - getMe: () => Promise; - addUser: (data: AddUserRequest) => Promise; - startEnrollment: (data: StartEnrollmentRequest) => Promise; - getUser: (username: string) => Promise; - getUsers: () => Promise; - editUser: (data: UserEditRequest) => Promise; - deleteUser: (user: User) => EmptyApiResponse; - usernameAvailable: (username: string) => EmptyApiResponse; - changePassword: (data: ChangePasswordRequest) => EmptyApiResponse; - resetPassword: (data: ResetPasswordRequest) => EmptyApiResponse; - addToGroup: (data: UserGroupRequest) => EmptyApiResponse; - removeFromGroup: (data: UserGroupRequest) => EmptyApiResponse; - startDesktopActivation: ( - data: StartEnrollmentRequest, - ) => Promise; - getAuthenticationKeysInfo: ( - data: AuthenticationKeyRequestBase, - ) => Promise; - addAuthenticationKey: (data: AddAuthenticationKeyRequest) => EmptyApiResponse; - deleteAuthenticationKey: (data: { id: number; username: string }) => EmptyApiResponse; - renameAuthenticationKey: (data: { - id: number; - username: string; - name: string; - }) => EmptyApiResponse; - renameYubikey: (data: { - id: number; - username: string; - name: string; - }) => EmptyApiResponse; - deleteYubiKey: (data: { id: number; username: string }) => EmptyApiResponse; - getApiTokensInfo: (data: ApiTokenRequestBase) => Promise; - addApiToken: (data: AddApiTokenRequest) => Promise; - deleteApiToken: (data: { id: number; username: string }) => EmptyApiResponse; - renameApiToken: (data: { - id: number; - username: string; - name: string; - }) => EmptyApiResponse; - disableUserMfa: (username: string) => EmptyApiResponse; - }; - standaloneDevice: { - createManualDevice: ( - data: CreateStandaloneDeviceRequest, - ) => Promise; - createCliDevice: ( - data: CreateStandaloneDeviceRequest, - ) => Promise; - getDevice: (deviceId: number | string) => Promise; - deleteDevice: (deviceId: number | string) => Promise; - editDevice: (data: StandaloneDeviceEditRequest) => Promise; - getAvailableIp: ( - data: GetAvailableLocationIpRequest, - ) => Promise; - validateLocationIp: ( - data: ValidateLocationIpsRequest, - ) => Promise; - getDevicesList: () => Promise; - getDeviceConfig: (deviceId: number | string) => Promise; - generateAuthToken: (deviceId: number | string) => Promise; - }; - device: { - addDevice: (device: AddDeviceRequest) => Promise; - getDevice: (deviceId: string) => Promise; - getDevices: () => Promise; - getUserDevices: (username: string) => Promise; - editDevice: (device: Device) => Promise; - deleteDevice: (device: Device) => EmptyApiResponse; - downloadDeviceConfig: (data: GetDeviceConfigRequest) => Promise; - }; - network: { - addNetwork: (network: ModifyNetworkRequest['network']) => Promise; - importNetwork: (network: ImportNetworkRequest) => Promise; - mapUserDevices: (devices: MapUserDevicesRequest) => EmptyApiResponse; - getNetwork: (networkId: number) => Promise; - getNetworks: () => Promise; - editNetwork: (network: ModifyNetworkRequest) => Promise; - deleteNetwork: (networkId: number) => EmptyApiResponse; - getOverviewStats: (data: GetNetworkStatsRequest) => Promise; - getNetworkToken: (networkId: Network['id']) => Promise; - getNetworkStats: (data: GetNetworkStatsRequest) => Promise; - getGatewaysStatus: (networkId: number) => Promise; - deleteGateway: (data: DeleteGatewayRequest) => Promise; - getAllNetworksStats: (data: { from?: number }) => Promise; - getAllGatewaysStatus: () => Promise; - }; - auth: { - login: (data: LoginData) => Promise; - logout: () => EmptyApiResponse; - openid: { - getOpenIdInfo: () => Promise; - callback: (data: CallbackData) => Promise; - }; - mfa: { - disable: () => EmptyApiResponse; - enable: () => EmptyApiResponse; - recovery: (data: RecoveryLoginRequest) => Promise; - email: { - register: { - start: () => EmptyApiResponse; - finish: (data: AuthCodeRequest) => MFARecoveryCodesResponse; - }; - disable: () => EmptyApiResponse; - sendCode: () => EmptyApiResponse; - verify: (data: AuthCodeRequest) => Promise; - }; - webauthn: { - register: { - start: (data: { name: string }) => Promise; - finish: (data: WebAuthnRegistrationRequest) => MFARecoveryCodesResponse; - }; - start: () => Promise; - finish: ( - data: PublicKeyCredentialWithAssertionJSON, - ) => Promise; - deleteKey: (data: DeleteWebAuthNKeyRequest) => EmptyApiResponse; - }; - totp: { - init: () => Promise<{ secret: string }>; - enable: (data: TOTPRequest) => MFARecoveryCodesResponse; - disable: () => EmptyApiResponse; - verify: (data: TOTPRequest) => Promise; - }; - }; - }; - provisioning: { - getWorkers: () => Promise; - deleteWorker: (id: string) => EmptyApiResponse; - provisionYubiKey: (request_data: WorkerJobRequest) => Promise; - getJobStatus: (job_id?: number) => Promise; - getWorkerToken: () => Promise; - }; - webhook: { - getWebhooks: () => Promise; - deleteWebhook: (id: string) => EmptyApiResponse; - addWebhook: (data: Omit) => EmptyApiResponse; - changeWebhookState: (data: changeWebhookStateRequest) => EmptyApiResponse; - editWebhook: (data: Webhook) => EmptyApiResponse; - }; - openid: { - getOpenidClients: () => Promise; - addOpenidClient: (data: AddOpenidClientRequest) => EmptyApiResponse; - getOpenidClient: (id: string) => Promise; - editOpenidClient: (data: EditOpenidClientRequest) => EmptyApiResponse; - changeOpenidClientState: (data: ChangeOpenidClientStateRequest) => EmptyApiResponse; - deleteOpenidClient: (client_id: string) => EmptyApiResponse; - verifyOpenidClient: (data: VerifyOpenidClientRequest) => EmptyApiResponse; - getUserClients: (username: string) => Promise; - removeUserClient: (data: RemoveUserClientRequest) => EmptyApiResponse; - }; - settings: { - getSettings: () => Promise; - editSettings: (data: Settings) => EmptyApiResponse; - setDefaultBranding: (id: string) => Promise; - patchSettings: (data: Partial) => EmptyApiResponse; - getEssentialSettings: () => Promise; - getEnterpriseSettings: () => Promise; - patchEnterpriseSettings: (data: Partial) => EmptyApiResponse; - testLdapSettings: () => Promise; - fetchOpenIdProviders: () => Promise; - addOpenIdProvider: (data: OpenIdProvider) => Promise; - deleteOpenIdProvider: (name: string) => Promise; - editOpenIdProvider: (data: OpenIdProvider) => Promise; - testDirsync: () => Promise; - }; - support: { - downloadSupportData: () => Promise; - downloadLogs: () => Promise; - }; - mail: { - sendTestMail: (data: TestMail) => EmptyApiResponse; - sendSupportMail: () => EmptyApiResponse; - }; -}; - -export interface NavigationStore { - isNavigationOpen: boolean; - user?: User; - webhook?: Webhook; - openidclient?: OpenidClient; - setNavigationOpen: (v: boolean) => void; - setNavigationUser: (user: User) => void; - setNavigationWebhook: (webhook: Webhook) => void; - setNavigationOpenidClient: (openidclient: OpenidClient) => void; - setState: (newState: Partial) => void; -} - -export type EmptyApiResponse = AxiosPromise; - -export interface WorkerCreateJobResponse { - id: number; -} -export interface Workers { - [worker_name: string]: boolean; -} - -export interface WorkerJobStatus { - success?: boolean; - errorMessage?: string; -} - -export interface WorkerJobStatusError { - message?: string; -} -export interface WorkerJobStatusRequest { - jobId: number; -} - -export interface WorkerToken { - token: string; -} - -export interface WorkerJobRequest { - worker: string; - username: string; -} - -export interface WorkerJobResponse { - id: number; -} - -export interface KeyDetailModal { - visible: boolean; - user?: User; -} - -export interface KeyDeleteModal { - visible: boolean; -} - -export interface ChangePasswordModal { - visible: boolean; - user?: User; -} - -export interface ChangeUserPasswordModal { - visible: boolean; - user?: User; -} - -export interface UserProfileStore { - editMode: boolean; - setEditMode: (value: boolean) => void; -} - -export interface OpenidClientStore { - editMode: boolean; - setEditMode: (value: boolean) => void; -} - -export interface AddWebhookModal { - visible: boolean; -} - -export interface EditWebhookModal { - visible: boolean; - webhook?: Webhook; -} - -export type AddDeviceConfig = { - network_id: number; - network_name: string; - config: string; -}; - -export interface Provisioner { - id: string; - connected: boolean; - ip: string; -} - -export type ModalSetter = (newValues: Partial) => void; - -export interface StandardModalState { - visible: boolean; -} - -export interface RecoveryCodesModal extends StandardModalState { - codes?: string[]; -} - -export interface WebhookModal extends StandardModalState { - webhook?: Webhook; -} - -export interface OpenIdClientModal extends StandardModalState { - client?: OpenidClient; - viewMode: boolean; -} - -// DO NOT EXTEND THIS STORE -/** - * this approach is outdated use individual stores instead - */ -export interface UseModalStore { - openIdClientModal: OpenIdClientModal; - setOpenIdClientModal: ModalSetter; - // DO NOT EXTEND THIS STORE - keyDetailModal: KeyDetailModal; - // DO NOT EXTEND THIS STORE - keyDeleteModal: KeyDeleteModal; - // DO NOT EXTEND THIS STORE - deleteUserModal: DeleteUserModal; - // DO NOT EXTEND THIS STORE - toggleUserModal: ToggleUserModal; - // DO NOT EXTEND THIS STORE - changePasswordModal: ChangePasswordModal; - // DO NOT EXTEND THIS STORE - provisionKeyModal: ProvisionKeyModal; - // DO NOT EXTEND THIS STORE - webhookModal: WebhookModal; - // DO NOT EXTEND THIS STORE - addOpenidClientModal: StandardModalState; - // DO NOT EXTEND THIS STORE - deleteOpenidClientModal: DeleteOpenidClientModal; - // DO NOT EXTEND THIS STORE - enableOpenidClientModal: EnableOpenidClientModal; - // DO NOT EXTEND THIS STORE - manageWebAuthNKeysModal: StandardModalState; - // DO NOT EXTEND THIS STORE - addSecurityKeyModal: StandardModalState; - // DO NOT EXTEND THIS STORE - registerTOTP: StandardModalState; - // DO NOT EXTEND THIS STORE - recoveryCodesModal: RecoveryCodesModal; - // DO NOT EXTEND THIS STORE - setState: (data: Partial) => void; - // DO NOT EXTEND THIS STORE - setWebhookModal: ModalSetter; - // DO NOT EXTEND THIS STORE - setRecoveryCodesModal: ModalSetter; - // DO NOT EXTEND THIS STORE - setKeyDetailModal: ModalSetter; - // DO NOT EXTEND THIS STORE - setKeyDeleteModal: ModalSetter; - // DO NOT EXTEND THIS STORE - setDeleteUserModal: ModalSetter; - // DO NOT EXTEND THIS STORE - setToggleUserModal: ModalSetter; - // DO NOT EXTEND THIS STORE - setProvisionKeyModal: ModalSetter; - // DO NOT EXTEND THIS STORE - setChangePasswordModal: ModalSetter; - // DO NOT EXTEND THIS STORE - setAddOpenidClientModal: ModalSetter; - // DO NOT EXTEND THIS STORE - setDeleteOpenidClientModal: ModalSetter; - // DO NOT EXTEND THIS STORE - setEnableOpenidClientModal: ModalSetter; -} - -export interface UseOpenIDStore { - openIDRedirect?: boolean; - setOpenIDStore: (newValues: Partial>) => void; -} - -/** - * full defguard instance Settings - */ -export type Settings = SettingsModules & - SettingsSMTP & - SettingsEnrollment & - SettingsBranding & - SettingsLDAP & - SettingsOpenID & - SettingsLicense & - SettingsGatewayNotifications; - -// essentials for core frontend, includes only those that are required for frontend operations -export type SettingsEssentials = SettingsModules & SettingsBranding; - -export type SettingsEnrollment = { - enrollment_vpn_step_optional: boolean; - enrollment_welcome_message: string; - enrollment_welcome_email: string; - enrollment_welcome_email_subject: string; - enrollment_use_welcome_message_as_email: boolean; -}; - -export type SettingsSMTP = { - smtp_server?: string; - smtp_port?: number; - smtp_encryption: string; - smtp_user?: string; - smtp_password?: string; - smtp_sender?: string; -}; - -export type SettingsModules = { - openid_enabled: boolean; - wireguard_enabled: boolean; - webhooks_enabled: boolean; - worker_enabled: boolean; -}; - -export type SettingsBranding = { - instance_name: string; - main_logo_url: string; - nav_logo_url: string; -}; - -export type SettingsLDAP = { - ldap_bind_password?: string; - ldap_bind_username?: string; - ldap_url?: string; - ldap_group_member_attr: string; - ldap_group_obj_class: string; - ldap_group_search_base: string; - ldap_groupname_attr: string; - ldap_member_attr: string; - ldap_user_obj_class: string; - ldap_user_auxiliary_obj_classes: string[]; - ldap_user_search_base: string; - ldap_username_attr: string; - ldap_enabled: boolean; - ldap_sync_enabled: boolean; - ldap_is_authoritative: boolean; - ldap_use_starttls: boolean; - ldap_tls_verify_cert: boolean; - ldap_sync_interval: number; - ldap_uses_ad: boolean; - ldap_user_rdn_attr?: string; - ldap_sync_groups: string[]; -}; - -export type SettingsOpenID = { - openid_create_account: boolean; -}; - -export type SettingsLicense = { - license: string; -}; - -export type SettingsGatewayNotifications = { - gateway_disconnect_notifications_enabled: boolean; - gateway_disconnect_notifications_inactivity_threshold: number; - gateway_disconnect_notifications_reconnect_notification_enabled: boolean; -}; - -export type SettingsEnterprise = { - admin_device_management: boolean; - disable_all_traffic: boolean; - only_client_activation: boolean; -}; - -export type EnterpriseLicenseInfo = { - valid_until?: string; - subscription: boolean; -}; - -export type EnterpriseStatus = { - enabled: boolean; -}; - -export type EnterpriseInfo = { - expired: boolean; - limits_exceeded: boolean; - subscription: boolean; - // iso utc date - valid_until: string; -}; - -export interface Webhook { - id: string; - url: string; - description: string; - token: string; - enabled: boolean; - on_user_created: boolean; - on_user_deleted: boolean; - on_user_modified: boolean; - on_hwkey_provision: boolean; -} - -export interface OpenidClient { - id: string; - name: string; - client_id: string; - client_secret: string; - redirect_uri: string[]; - scope: string[]; - enabled: boolean; -} - -export interface OpenIdInfo { - settings: { - create_account: boolean; - }; - provider?: OpenIdProvider; -} - -export interface OpenIdProvider { - id: number; - name: string; - base_url: string; - client_id: string; - client_secret: string; - display_name: string; - google_service_account_key?: string; - google_service_account_email?: string; - admin_email?: string; - directory_sync_enabled: boolean; - directory_sync_interval: number; - directory_sync_user_behavior: 'keep' | 'disable' | 'delete'; - directory_sync_admin_behavior: 'keep' | 'disable' | 'delete'; - directory_sync_target: 'all' | 'users' | 'groups'; - okta_private_jwk?: string; - okta_dirsync_client_id?: string; - directory_sync_group_match?: string; -} - -export enum OpenIdSyncBehavior { - KEEP = 'keep', - DISABLE = 'disable', - DELETE = 'delete', -} - -export enum OpenIdSyncTarget { - ALL = 'all', - USERS = 'users', - GROUPS = 'groups', -} - -export interface EditOpenidClientRequest { - id: string; - name: string; - client_id: string; - client_secret: string; - redirect_uri: string[]; - enabled: boolean; -} - -export interface AddOpenidClientRequest { - name: string; - redirect_uri: string[]; - enabled: boolean; - scope: string[]; -} - -export interface changeWebhookStateRequest { - id: string; - enabled: boolean; -} - -export interface ChangeOpenidClientStateRequest { - clientId: string; - enabled: boolean; -} - -export interface VerifyOpenidClientRequest { - client_id: string; - scope: string; - redirect_uri: string; - response_type: string; - state: string; - nonce: string; - allow: boolean; -} - -export interface AuthorizedClient { - id: string; - username: string; - client_id: string; - home_url: string; - date: string; -} - -export enum OverviewLayoutType { - GRID = 'GRID', - LIST = 'LIST', - MAP = 'MAP', -} - -export interface OverviewStore { - viewMode: OverviewLayoutType; - defaultViewMode: OverviewLayoutType; - networks?: Network[]; - selectedNetworkId?: number; - setState: (override: Partial) => void; -} - -export interface NetworkSpeedStats { - collected_at: string; - download: number; - upload: number; -} - -export interface NetworkDeviceStats { - connected_at: string; - id: number; - name: string; - public_ip: string; - wireguard_ips: string[]; - stats: NetworkSpeedStats[]; -} - -export type OverviewStatsResponse = { - user_devices: NetworkUserStats[]; - network_devices: StandaloneDeviceStats[]; -}; - -export type StandaloneDeviceStats = { - id: number; - stats: NetworkSpeedStats[]; - user_id: number; - name: string; - wireguard_ips: string[]; - public_ip?: string; - connected_at?: string; -}; - -export interface NetworkUserStats { - user: User; - devices: NetworkDeviceStats[]; -} - -export interface WireguardNetworkStats { - active_users: number; - active_user_devices: number; - active_network_devices: number; - current_active_users: number; - current_active_user_devices: number; - current_active_network_devices: number; - upload: number; - download: number; - transfer_series: NetworkSpeedStats[]; -} - -export interface TOTPRequest { - code: string; -} - -export interface WebAuthnRegistrationRequest { - name: string; - rpkc: PublicKeyCredentialWithAttestationJSON; -} - -export interface RemoveUserClientRequest { - username: string; - client_id: number; -} - -export interface TestMail { - to: string; -} - -export type SMTPError = AxiosError<{ error: string }>; - -export type Group = string; - -export type GroupInfo = { - id: number; - name: string; - members: string[]; - vpn_locations: string[]; - is_admin: boolean; -}; - -export type DirsyncTestResponse = { - message: string; - success: boolean; -}; - -export type CreateStandaloneDeviceRequest = { - name: string; - location_id: number; - assigned_ips: string[]; - wireguard_pubkey?: string; - description?: string; -}; - -export type ValidateLocationIpsRequest = { - ips: string[]; - location: number | string; -}; - -export type ValidateLocationIpsResult = { - available: boolean; - valid: boolean; -}; - -export type GetAvailableLocationIpRequest = { - locationId: number | string; -}; - -export type GetAvailableLocationIpResponse = { - ip: string; - network_part: string; - modifiable_part: string; - network_prefix: string; -}[]; - -export type StandaloneDevice = { - id: number; - name: string; - assigned_ips: string[]; - description?: string; - added_by: string; - added_date: string; - configured: boolean; - // when configured is false this will be empty - wireguard_pubkey?: string; - location: { - id: number; - name: string; - }; - split_ips: [ - { - network_part: string; - modifiable_part: string; - network_prefix: string; - }, - ]; -}; - -export type DeviceConfigurationResponse = { - address: string; - allowed_ips: string[]; - config: string; - endpoint: string; - keepalive_interval: number; - network_id: number; - network_name: string; - pubkey: string; - location_mfa_mode: LocationMfaMode; -}; - -export type CreateStandaloneDeviceResponse = { - config: DeviceConfigurationResponse; - device: StandaloneDevice; -}; - -export type StandaloneDeviceEditRequest = { - id: number; - assigned_ips: string[]; - description?: string; - name: string; -}; - -export type LicenseLimits = { - user: boolean; - device: boolean; - wireguard_network: boolean; -}; - -export type LicenseInfo = { - enterprise: boolean; - limits_exceeded: LicenseLimits; - any_limit_exceeded: boolean; - is_enterprise_free: boolean; -}; diff --git a/web/src/shared/utils/chainName.ts b/web/src/shared/utils/chainName.ts deleted file mode 100644 index 2f8d0a120..000000000 --- a/web/src/shared/utils/chainName.ts +++ /dev/null @@ -1,28 +0,0 @@ -export interface Chain { - name: string; - network: string; -} - -export const chains: Record = { - 1: { name: 'Ethereum', network: 'Mainnet' }, - 3: { name: 'Ethereum', network: 'Ropsten' }, - 4: { name: 'Ethereum', network: 'Rinkeby' }, - 5: { name: 'Ethereum', network: 'Goerli' }, - 42: { name: 'Ethereum', network: 'Kovan' }, - 11155111: { name: 'Ethereum', network: 'Sepolia' }, - 10: { name: 'Optimism', network: 'Mainnet' }, - 69: { name: 'Optimism', network: 'Kovan' }, - 420: { name: 'Optimism', network: 'Goerli' }, - 137: { name: 'Polygon', network: 'Mainnet' }, - 80001: { name: 'Polygon', network: 'Mumbai' }, - 42161: { name: 'Arbitrum', network: 'One' }, - 421613: { name: 'Arbitrum', network: 'Goerli' }, - 421611: { name: 'Arbitrum', network: 'Rinkeby' }, - 1337: { name: 'Localhost', network: 'Local' }, - 31337: { name: 'Hardhat', network: 'Local' }, -}; - -export const chainName = (id: number): string | undefined => { - const chain = chains[id]; - return chain ? `${chain.name} ${chain.network}` : undefined; -}; diff --git a/web/src/shared/utils/checkPlatform.ts b/web/src/shared/utils/checkPlatform.ts deleted file mode 100644 index 108e2b7a9..000000000 --- a/web/src/shared/utils/checkPlatform.ts +++ /dev/null @@ -1,16 +0,0 @@ -export enum SupportedPlatform { - LINUX = 'LINUX', - WINDOWS = 'WINDOWS', - MAC = 'MAC', -} - -export const checkPlatform = (): SupportedPlatform => { - const platform = navigator.platform; - if (platform.includes('Mac') || platform.includes('iPhone')) { - return SupportedPlatform.MAC; - } - if (platform.includes('Win')) { - return SupportedPlatform.WINDOWS; - } - return SupportedPlatform.LINUX; -}; diff --git a/web/src/shared/utils/dateSortingFn.ts b/web/src/shared/utils/dateSortingFn.ts new file mode 100644 index 000000000..19e9c463a --- /dev/null +++ b/web/src/shared/utils/dateSortingFn.ts @@ -0,0 +1,12 @@ +import type { SortingFn } from '@tanstack/react-table'; +import dayjs from 'dayjs'; + +export const dateSortingFn: SortingFn = (row, row2, colId) => { + const first = dayjs(row.getValue(colId)); + const second = dayjs(row2.getValue(colId)); + return first.valueOf() - second.valueOf(); +}; + +export const tableSortingFns: Record> = { + dateIso: dateSortingFn, +} as const; diff --git a/web/src/shared/utils/detectClickOutside.ts b/web/src/shared/utils/detectClickOutside.ts deleted file mode 100644 index 933609a88..000000000 --- a/web/src/shared/utils/detectClickOutside.ts +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Checks if mouse event clicked within any of provided rects - */ -export const detectClickInside = (event: MouseEvent, rects: DOMRect[]) => { - for (const domRect of rects) { - if (domRect) { - const start_x = domRect?.x; - const start_y = domRect?.y; - const end_x = start_x + domRect?.width; - const end_y = start_y + domRect.height; - if ( - event.clientX >= start_x && - event.clientX <= end_x && - event.clientY >= start_y && - event.clientY <= end_y - ) { - return true; - } - } - } - return false; -}; diff --git a/web/src/shared/utils/displayDate.ts b/web/src/shared/utils/displayDate.ts index 2d5a93ac7..77fc43e75 100644 --- a/web/src/shared/utils/displayDate.ts +++ b/web/src/shared/utils/displayDate.ts @@ -1,3 +1,10 @@ -import dayjs, { type Dayjs } from 'dayjs'; +import dayjs from 'dayjs'; -export const dateToLocal = (value: string): Dayjs => dayjs.utc(value).local(); +const defaultFormat = 'DD/MM/YYYY | HH:mm'; + +export const displayDate = (date: string | number) => { + if (typeof date === 'number') { + return dayjs.unix(date).local().format(defaultFormat); + } + return dayjs.utc(date).local().format(defaultFormat); +}; diff --git a/web/src/shared/utils/download.ts b/web/src/shared/utils/download.ts new file mode 100644 index 000000000..50ca63c19 --- /dev/null +++ b/web/src/shared/utils/download.ts @@ -0,0 +1,25 @@ +export const downloadText = ( + content: string, + filename: string, + extension?: 'txt' | 'pub' | 'conf', +) => { + extension = extension ?? 'txt'; + const blob = new Blob([content], { type: 'text/plain;charset=utf-8' }); + downloadFile(blob, filename, extension); +}; + +export const downloadFile = (blob: Blob, filename: string, extension: string) => { + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.style = 'visibility: hidden;'; + a.download = `${filename}.${extension}`; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + + // workaround for firefox + setTimeout(() => { + URL.revokeObjectURL(url); + }, 5_000); +}; diff --git a/web/src/shared/utils/downloadWGConfig.ts b/web/src/shared/utils/downloadWGConfig.ts deleted file mode 100644 index b395a0768..000000000 --- a/web/src/shared/utils/downloadWGConfig.ts +++ /dev/null @@ -1,10 +0,0 @@ -import saveAs from 'file-saver'; - -export const downloadWGConfig = (config: string, fileName: string) => { - const blob = new Blob([config.replace(/^[^\S\r\n]+|[^\S\r\n]+$/gm, '')], { - // octet-stream is used here as a workaround: some browsers will append - // an additional .txt extension to the file name if the MIME type is text/plain. - type: 'application/octet-stream', - }); - saveAs(blob, `${fileName.toLowerCase()}.conf`); -}; diff --git a/web/src/shared/utils/enumFromObjValues.ts b/web/src/shared/utils/enumFromObjValues.ts new file mode 100644 index 000000000..1209e1f19 --- /dev/null +++ b/web/src/shared/utils/enumFromObjValues.ts @@ -0,0 +1,5 @@ +import z from 'zod'; + +export const enumFromObjValues = , V extends string>( + obj: T, +) => z.enum(Object.values(obj) as [V, ...V[]]); diff --git a/web/src/shared/utils/extractInitials.ts b/web/src/shared/utils/extractInitials.ts deleted file mode 100644 index 1d5d2f7a5..000000000 --- a/web/src/shared/utils/extractInitials.ts +++ /dev/null @@ -1,10 +0,0 @@ -export const extractInitials = (val: string): string => { - const sp = val.split(' '); - try { - const res = `${sp[0][0].toUpperCase()}${sp[1] ? sp[1][0].toUpperCase() : ''}`; - return res; - } catch (error) { - console.error(error); - return ''; - } -}; diff --git a/web/src/shared/utils/form/selectifyNetwork.ts b/web/src/shared/utils/form/selectifyNetwork.ts deleted file mode 100644 index e21a728ed..000000000 --- a/web/src/shared/utils/form/selectifyNetwork.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type { SelectOption } from '../../defguard-ui/components/Layout/Select/types'; -import type { Network } from '../../types'; - -export const selectifyNetworks = (data: Network[]): SelectOption[] => - data.map((network) => ({ - key: network.id, - label: network.name, - value: network.id, - })); diff --git a/web/src/pages/overview/OverviewConnectedUsers/UserConnectionCard/formatConnectionTime.ts b/web/src/shared/utils/formatConnectionTime.ts similarity index 87% rename from web/src/pages/overview/OverviewConnectedUsers/UserConnectionCard/formatConnectionTime.ts rename to web/src/shared/utils/formatConnectionTime.ts index ad098f9fe..37e339921 100644 --- a/web/src/pages/overview/OverviewConnectedUsers/UserConnectionCard/formatConnectionTime.ts +++ b/web/src/shared/utils/formatConnectionTime.ts @@ -18,8 +18,8 @@ const short = humanizeDuration.humanizer({ }); export const formatConnectionTime = (connectedAt: string): string => { - const day = dayjs.utc(connectedAt); - const diff = dayjs().utc().diff(day, 'ms'); + const day = dayjs.utc(connectedAt).local(); + const diff = dayjs().diff(day, 'ms'); const res = short(diff, { largest: 2, diff --git a/web/src/shared/utils/formatFileName.ts b/web/src/shared/utils/formatFileName.ts new file mode 100644 index 000000000..58bf44e82 --- /dev/null +++ b/web/src/shared/utils/formatFileName.ts @@ -0,0 +1,2 @@ +export const formatFileName = (value: string) => + value.trim().replaceAll(' ', '_').toLowerCase(); diff --git a/web/src/shared/utils/invalidateMultipleQueries.ts b/web/src/shared/utils/invalidateMultipleQueries.ts deleted file mode 100644 index cb49123bb..000000000 --- a/web/src/shared/utils/invalidateMultipleQueries.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { QueryClient, QueryKey } from '@tanstack/query-core'; - -export const invalidateMultipleQueries = ( - client: QueryClient, - keys: QueryKey[] | string[], -): void => { - keys.forEach((k) => { - if (Array.isArray(k)) { - void client.invalidateQueries({ - queryKey: k, - }); - } else { - void client.invalidateQueries({ - queryKey: [k], - }); - } - }); -}; diff --git a/web/src/shared/utils/localeToDatepicker.ts b/web/src/shared/utils/localeToDatepicker.ts deleted file mode 100644 index 4cf878c77..000000000 --- a/web/src/shared/utils/localeToDatepicker.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { Locales } from '../../i18n/i18n-types'; - -export const localeToDatePicker = (val: Locales): string => { - switch (val) { - case 'en': - return 'en-US'; - default: - return val; - } -}; diff --git a/web/src/shared/utils/omitNull.ts b/web/src/shared/utils/omitNull.ts deleted file mode 100644 index d20fb2d0f..000000000 --- a/web/src/shared/utils/omitNull.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { omitBy } from 'lodash-es'; - -export const omitNull = (val?: T): Partial => - omitBy(val, (v) => v === null); diff --git a/web/src/shared/utils/openVirtualLink.ts b/web/src/shared/utils/openVirtualLink.ts new file mode 100644 index 000000000..145ddb848 --- /dev/null +++ b/web/src/shared/utils/openVirtualLink.ts @@ -0,0 +1,17 @@ +import { externalLink } from '../constants'; + +export const openClientLink = (value?: string): void => { + const href = value ?? externalLink.defguard.download; + openVirtualLink(href); +}; + +export const openVirtualLink = (value: string): void => { + const anchorElement = document.createElement('a'); + anchorElement.style.display = 'none'; + anchorElement.href = value; + anchorElement.target = '_blank'; + anchorElement.rel = 'noopener noreferrer'; + document.body.appendChild(anchorElement); + anchorElement.click(); + anchorElement.remove(); +}; diff --git a/web/src/shared/utils/searchByKeys.ts b/web/src/shared/utils/searchByKeys.ts deleted file mode 100644 index 5d6acd073..000000000 --- a/web/src/shared/utils/searchByKeys.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { diceCoefficient } from 'dice-coefficient'; -// Search in object by multiple keys, this assumes that value under given keys is a string -export const searchByKeys = ( - obj: T, - searchedKeys: Array, - searchValue: string, -): boolean => { - if (searchValue === '') return true; - for (const key of searchedKeys) { - if (typeof obj[key] !== 'string') { - throw Error( - `Usage of searchByKeys is allowed only on values of type string! Value under key ${key.toString()} is not a string.`, - ); - } - const val = obj[key] as string; - const loweredValue = val.toLowerCase(); - const loweredSearch = searchValue.toLowerCase(); - const score = diceCoefficient(loweredValue, loweredSearch); - const includes = loweredValue.includes(loweredSearch); - if (score >= 0.6 || includes) { - return true; - } - } - return false; -}; diff --git a/web/src/shared/utils/sortByDate.ts b/web/src/shared/utils/sortByDate.ts deleted file mode 100644 index 942be8987..000000000 --- a/web/src/shared/utils/sortByDate.ts +++ /dev/null @@ -1,19 +0,0 @@ -import dayjs from 'dayjs'; - -/** - * Sorts array of objects by date field that came from core - **/ -export const sortByDate = ( - items: T[], - extraction: (item: T) => string, - descending = false, -): T[] => { - return items.sort((itemA, itemB) => { - const dateA = dayjs.utc(extraction(itemA)).toDate().getTime(); - const dateB = dayjs.utc(extraction(itemB)).toDate().getTime(); - if (descending) { - return dateB - dateA; - } - return dateA - dateB; - }); -}; diff --git a/web/src/shared/utils/stats.ts b/web/src/shared/utils/stats.ts new file mode 100644 index 000000000..6ff3db5f1 --- /dev/null +++ b/web/src/shared/utils/stats.ts @@ -0,0 +1,23 @@ +import dayjs from 'dayjs'; +import { orderBy } from 'lodash-es'; +import type { TransferStats } from '../api/types'; + +export interface TransferChartData { + download: number; + upload: number; + timestamp: number; +} + +export const mapTransferToChart = (stats: TransferStats[]): TransferChartData[] => { + // filter out blanks so visually chart is more dense as empty ticks take space + const distilled = stats.filter((tick) => tick.download || tick.upload); + const res = distilled.map( + ({ download, upload, collected_at }): TransferChartData => ({ + download, + upload, + // always display date in local + timestamp: dayjs.utc(collected_at).local().unix(), + }), + ); + return orderBy(res, ['timestamp'], ['asc']); +}; diff --git a/web/src/shared/utils/stringToBlob.ts b/web/src/shared/utils/stringToBlob.ts deleted file mode 100644 index e55b87c88..000000000 --- a/web/src/shared/utils/stringToBlob.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const stringToBlob = (value: string): Blob => { - const blob = new Blob([value.replace(/^[^\S\r\n]+|[^\S\r\n]+$/gm, '')], { - type: 'text/plain;charset=utf-8', - }); - return blob; -}; diff --git a/web/src/shared/utils/titleCase.ts b/web/src/shared/utils/titleCase.ts deleted file mode 100644 index 8402b63fe..000000000 --- a/web/src/shared/utils/titleCase.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const titleCase = (str: string): string => { - const res = str.toLowerCase().split(' '); - return res.map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(' '); -}; diff --git a/web/src/shared/utils/trimObjectStrings.ts b/web/src/shared/utils/trimObjectStrings.ts deleted file mode 100644 index 0d1fef213..000000000 --- a/web/src/shared/utils/trimObjectStrings.ts +++ /dev/null @@ -1,25 +0,0 @@ -/**Search for strings in object and trims them, designed for preparing form values to be sent to backend */ -export const trimObjectStrings = (obj: T): T => { - if (typeof obj !== 'object' || obj === null) { - return obj; - } - - if (Array.isArray(obj)) { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return obj.map((item) => trimObjectStrings(item)) as unknown as T; - } - - const trimmedObj: Record = {}; - - Object.entries(obj).forEach(([key, value]) => { - if (typeof value === 'string') { - trimmedObj[key] = value.trim(); - } else if (typeof value === 'object' && value !== null) { - trimmedObj[key] = trimObjectStrings(value); - } else { - trimmedObj[key] = value; - } - }); - - return trimmedObj as T; -}; diff --git a/web/src/shared/validate.ts b/web/src/shared/validate.ts new file mode 100644 index 000000000..19208eb05 --- /dev/null +++ b/web/src/shared/validate.ts @@ -0,0 +1,157 @@ +import ipaddr from 'ipaddr.js'; +import { + domainPattern, + ipv4Pattern, + ipv4WithCIDRPattern, + ipv4WithPortPattern, +} from './patterns'; + +export const Validate = { + IPv4: (ip: string): boolean => { + if (!ipv4Pattern.test(ip)) { + return false; + } + if (!ipaddr.IPv4.isValid(ip)) { + return false; + } + return true; + }, + IPv4withPort: (ip: string): boolean => { + if (!ipv4WithPortPattern.test(ip)) { + return false; + } + const addr = ip.split(':'); + if (!ipaddr.IPv4.isValid(addr[0]) || !Validate.Port(addr[1])) { + return false; + } + return true; + }, + IPv6: (ip: string): boolean => { + if (!ipaddr.IPv6.isValid(ip)) { + return false; + } + return true; + }, + IPv6withPort: (ip: string): boolean => { + if (ip.includes(']')) { + const address = ip.split(']'); + const ipv6 = address[0].replaceAll('[', '').replaceAll(']', ''); + const port = address[1].replaceAll(']', '').replaceAll(':', ''); + if (!ipaddr.IPv6.isValid(ipv6)) { + return false; + } + if (!Validate.Port(port)) { + return false; + } + } else { + return false; + } + return true; + }, + CIDRv4: (ip: string): boolean => { + if (!ipv4WithCIDRPattern.test(ip)) { + return false; + } + if (ip.endsWith('/0')) { + return false; + } + if (!ipaddr.IPv4.isValidCIDR(ip)) { + return false; + } + return true; + }, + CIDRv6: (ip: string): boolean => { + if (ip.endsWith('/0')) { + return false; + } + if (!ipaddr.IPv6.isValidCIDR(ip)) { + return false; + } + return true; + }, + Domain: (ip: string): boolean => { + if (!domainPattern.test(ip)) { + return false; + } + return true; + }, + DomainWithPort: (ip: string): boolean => { + const splitted = ip.split(':'); + const domain = splitted[0]; + const port = splitted[1]; + if (!Validate.Port(port)) { + return false; + } + if (!domainPattern.test(domain)) { + return false; + } + return true; + }, + Port: (val: string): boolean => { + const parsed = Number(val); + if (Number.isNaN(parsed) || !Number.isInteger(parsed)) { + return false; + } + return 0 < parsed && parsed <= 65535; + }, + Empty: (val: string): boolean => { + if (val === '' || !val) { + return true; + } + return false; + }, + any: ( + value: string | undefined, + validators: Array<(val: string) => boolean>, + allowList: boolean = false, + splitWith = ',', + ): boolean => { + if (!value) { + return true; + } + const items = value.replaceAll(' ', '').split(splitWith); + + if (items.length > 1 && !allowList) { + return false; + } + + for (const item of items) { + let valid = false; + for (const validator of validators) { + if (validator(item)) { + valid = true; + break; + } + } + if (!valid) { + return false; + } + } + + return true; + }, + all: ( + value: string | undefined, + validators: Array<(val: string) => boolean>, + allowList: boolean = false, + splitWith = ',', + ): boolean => { + if (!value) { + return true; + } + const items = value.replaceAll(' ', '').split(splitWith); + + if (items.length > 1 && !allowList) { + return false; + } + for (const item of items) { + for (const validator of validators) { + if (!validator(item)) { + return false; + } + } + } + + return true; + }, +} as const; diff --git a/web/src/shared/validators.ts b/web/src/shared/validators.ts index 4bc73aa9d..0d18f4af4 100644 --- a/web/src/shared/validators.ts +++ b/web/src/shared/validators.ts @@ -1,22 +1,19 @@ import ipaddr from 'ipaddr.js'; import { z } from 'zod'; +import { m } from '../paraglide/messages'; +import { patternStrictIpV4, patternValidWireguardKey } from './patterns'; +import { Validate } from './validate'; -import { patternValidDomain, patternValidWireguardKey } from './patterns'; - -export const validateWireguardPublicKey = (props: { - requiredError: string; - minError: string; - maxError: string; - validKeyError: string; -}) => +export const validateWireguardPublicKey = () => z - .string({ - invalid_type_error: props.requiredError, - required_error: props.requiredError, - }) - .min(44, props.minError) - .max(44, props.maxError) - .regex(patternValidWireguardKey, props.validKeyError); + .string(m.form_error_required()) + .length( + 44, + m.form_error_len({ + length: 44, + }), + ) + .regex(patternValidWireguardKey, m.form_error_invalid()); // Returns false when invalid export const validateIpOrDomain = ( @@ -24,11 +21,13 @@ export const validateIpOrDomain = ( allowMask = false, allowIPv6 = false, ): boolean => { - return ( - (allowIPv6 && validateIPv6(val, allowMask)) || - validateIPv4(val, allowMask) || - patternValidDomain.test(val) - ); + const hasLetter = /\p{L}/u.test(val); + const hasColon = /:/.test(val); + if (!hasLetter || hasColon) { + return (allowIPv6 && validateIPv6(val, allowMask)) || validateIPv4(val, allowMask); + } else { + return Validate.Domain(val); + } }; // Returns false when invalid @@ -41,6 +40,7 @@ export const validateIpList = ( .replace(' ', '') .split(splitWith) .every((el) => { + if (!el.includes('/') && allowMasks) return false; return validateIPv4(el, allowMasks) || validateIPv6(el, allowMasks); }); }; @@ -57,7 +57,7 @@ export const validateIpOrDomainList = ( for (const value of split) { if ( !validateIPv4(value, allowMasks) && - !patternValidDomain.test(value) && + !Validate.Domain(value) && (!allowIPv6 || !validateIPv6(value, allowMasks)) ) { return false; @@ -69,15 +69,35 @@ export const validateIpOrDomainList = ( // Returns false when invalid export const validateIPv4 = (ip: string, allowMask = false): boolean => { if (allowMask) { + if (ip.endsWith('/0')) { + return false; + } if (ip.includes('/')) { return ipaddr.IPv4.isValidCIDR(ip); } } + const ipv4Pattern = /^(\d{1,3}\.){3}\d{1,3}$/; + const ipv4WithPortPattern = /^(\d{1,3}\.){3}\d{1,3}:\d{1,5}$/; + if (!ipv4Pattern.test(ip) && !ipv4WithPortPattern.test(ip)) { + return false; + } + + if (ipv4WithPortPattern.test(ip)) { + const [address, port] = ip.split(':'); + ip = address; + if (!validatePort(port)) { + return false; + } + } + return ipaddr.IPv4.isValid(ip); }; export const validateIPv6 = (ip: string, allowMask = false): boolean => { if (allowMask) { + if (ip.endsWith('/0')) { + return false; + } if (ip.includes('/')) { return ipaddr.IPv6.isValidCIDR(ip); } @@ -95,3 +115,146 @@ export const validatePort = (val: string) => { export const numericString = (val: string) => /^\d+$/.test(val); export const numericStringFloat = (val: string) => /^\d*\.?\d+$/.test(val); + +export const aclPortsValidator = z + .string() + .refine((value: string) => { + if (value === '') return true; + const regexp = new RegExp(/^(?:\d+(?:-\d+)*)(?:(?:\s*,\s*|\s+)\d+(?:-\d+)*)*$/); + return regexp.test(value); + }, m.form_error_invalid()) + .refine((value: string) => { + if (value === '') return true; + // check if there is no duplicates in given port field + const trimmed = value + .replaceAll(' ', '') + .replaceAll('-', ' ') + .replaceAll(',', ' ') + .split(' ') + .filter((v) => v !== ''); + const found: number[] = []; + for (const entry of trimmed) { + const num = parseInt(entry, 10); + if (Number.isNaN(num)) { + return false; + } + if (found.includes(num)) { + return false; + } + found.push(num); + } + return true; + }, m.form_error_invalid()) + .refine((value: string) => { + if (value === '') return true; + // check if ranges in input are valid means follow pattern - + const matches = value.match(/\b\d+-\d+\b/g); + if (Array.isArray(matches)) { + for (const match of matches) { + const split = match.split('-'); + if (split.length !== 2) { + return false; + } + const start = split[0]; + const end = split[1]; + if (start >= end) { + return false; + } + } + } + return true; + }, m.form_error_invalid()); + +const validateIpPart = (input: string): ipaddr.IPv4 | ipaddr.IPv6 | null => { + if (!ipaddr.isValid(input)) return null; + const ip = ipaddr.parse(input); + if (ip.kind() === 'ipv6') { + return ip; + } + if (!patternStrictIpV4.test(input)) return null; + return ip; +}; + +function dottedMaskToPrefix(mask: string): number | null { + if (!mask.includes('.')) return Number(mask); + const maskTest = + /^(?:255\.255\.255\.(?:0|128|192|224|240|248|252|254|255)|255\.255\.(?:0|128|192|224|240|248|252|254|255)\.0|255\.(?:0|128|192|224|240|248|252|254|255)\.0\.0|(?:0|128|192|224|240|248|252|254|255)\.0\.0\.0)$/; + if (!maskTest.test(mask)) return null; + if (mask.split('.').length !== 4) return null; + const parts = mask.split('.').map(Number); + if (parts.length !== 4 || parts.some((part) => part < 0 || part > 255)) return null; + + const binary = parts.map((part) => part.toString(2).padStart(8, '0')).join(''); + if (!/^1*0*$/.test(binary)) return null; + + return binary.indexOf('0') === -1 ? 32 : binary.indexOf('0'); +} + +function parseSubnet(input: string): [ipaddr.IPv4 | ipaddr.IPv6, number] | null { + const [ipPart, maskPart] = input.split('/'); + if (!ipaddr.isValid(ipPart) || !maskPart) return null; + const ip = ipaddr.parse(ipPart); + const kind = ip.kind(); + + if (kind === 'ipv6') { + const prefix = parseInt(maskPart, 10); + if (typeof prefix !== 'number' || Number.isNaN(prefix)) { + return null; + } + return [ip, prefix]; + } + if (!patternStrictIpV4.test(ipPart)) return null; + + const prefix = dottedMaskToPrefix(maskPart); + if (prefix === null) return null; + + return [ip, prefix]; +} + +function isValidIpOrCidr(input: string): boolean { + try { + if (input.includes('/')) { + const parsed = parseSubnet(input); + if (!parsed) return false; + const [ip, mask] = parsed; + const cidr = ipaddr.parseCIDR(`${ip.toString()}/${mask}`); + return cidr[0] !== undefined && typeof cidr[1] === 'number'; + } else { + return validateIpPart(input) !== null; + } + } catch { + return false; + } +} + +export const aclDestinationValidator = z.string().refine((value: string) => { + if (value === '') return true; + + const entries = value.split(',').map((s) => s.trim()); + + for (const entry of entries) { + if (entry.includes('-')) { + const [start, end] = entry.split('-').map((s) => s.trim()); + + // reject CIDR notation used in ranges + if (start.includes('/') || end.includes('/')) return false; + + if (!ipaddr.isValid(start) || !ipaddr.isValid(end)) return false; + + const startAddr = ipaddr.parse(start); + const endAddr = ipaddr.parse(end); + + // reject different ip versions in ranges + if (startAddr.kind() !== endAddr.kind()) return false; + + // reject invalid order in ranges + if (startAddr.toByteArray().join('.') > endAddr.toByteArray().join('.')) { + return false; + } + } else { + if (!isValidIpOrCidr(entry)) return false; + } + } + + return true; +}, m.form_error_invalid()); diff --git a/web/src/shared/validators/password.ts b/web/src/shared/validators/password.ts deleted file mode 100644 index 1be1c3d2b..000000000 --- a/web/src/shared/validators/password.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { z } from 'zod'; - -import type { TranslationFunctions } from '../../i18n/i18n-types'; -import { - patternAtLeastOneDigit, - patternAtLeastOneLowerCaseChar, - patternAtLeastOneSpecialChar, - patternAtLeastOneUpperCaseChar, - patternSafePasswordCharacters, -} from '../patterns'; - -export const passwordValidator = (LL: TranslationFunctions) => - z - .string() - .min(1, LL.form.error.required()) - .min(8, LL.form.error.minimumLength()) - .max(128, LL.form.error.maximumLength()) - .regex(patternAtLeastOneDigit, LL.form.error.oneDigit()) - .regex(patternAtLeastOneSpecialChar, LL.form.error.oneSpecial()) - .regex(patternAtLeastOneUpperCaseChar, LL.form.error.oneUppercase()) - .regex(patternAtLeastOneLowerCaseChar, LL.form.error.oneLowercase()) - .regex(patternSafePasswordCharacters, LL.form.error.forbiddenCharacter()); diff --git a/web/src/shared/variants.ts b/web/src/shared/variants.ts deleted file mode 100644 index 2c110ee1a..000000000 --- a/web/src/shared/variants.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { Variants } from 'motion/react'; - -export const tableBodyVariants: Variants = { - hidden: { - opacity: 0, - }, - idle: { - opacity: 1, - }, -}; - -export const tableRowVariants: Variants = { - idle: () => ({ - boxShadow: '5px 10px 20px rgba(0, 0, 0, 0)', - }), - hover: { - boxShadow: '5px 10px 20px rgba(0, 0, 0, 0.1)', - }, -}; - -export const rowIconVariants: Variants = { - idle: { - opacity: 0, - }, - hover: { - opacity: 1, - }, -}; - -export const standardVariants: Variants = { - hidden: { - opacity: 0, - }, - show: { - opacity: 1, - }, -}; diff --git a/web/src/vite.d.ts b/web/src/vite.d.ts deleted file mode 100644 index f48e2e3ea..000000000 --- a/web/src/vite.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -interface ImportMetaEnv { - VITE_API_BASE_URL?: string; -} diff --git a/web/tsconfig.app.json b/web/tsconfig.app.json index cbcdbd829..9d1bae353 100644 --- a/web/tsconfig.app.json +++ b/web/tsconfig.app.json @@ -1,42 +1,39 @@ { "compilerOptions": { - "target": "ES2023", + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2022", "useDefineForClassFields": true, "lib": [ - "ESNext", + "ES2022", "DOM", "DOM.Iterable" ], "module": "ESNext", + "types": [ + "vite/client" + ], "skipLibCheck": true, - "allowSyntheticDefaultImports": true, /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, - "resolveJsonModule": true, - "isolatedModules": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", "noEmit": true, "jsx": "react-jsx", /* Linting */ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, + "erasableSyntaxOnly": true, "noFallthroughCasesInSwitch": true, - "plugins": [ - { - "name": "typescript-eslint-language-service" - } - ], - "types": [ - "vite/client" - ] + "noUncheckedSideEffectImports": true, + "allowJs": true, + "checkJs": false }, "include": [ "src" ], "exclude": [ "node_modules", - "dist", - "build" ] } diff --git a/web/tsconfig.app.tsbuildinfo b/web/tsconfig.app.tsbuildinfo deleted file mode 100644 index 23ff5a3be..000000000 --- a/web/tsconfig.app.tsbuildinfo +++ /dev/null @@ -1 +0,0 @@ -{"root":["./src/gif.d.ts","./src/main.tsx","./src/markdown.d.ts","./src/vite.d.ts","./src/components/apploader.tsx","./src/components/i18nprovider.tsx","./src/components/app/app.tsx","./src/components/navigation/navigation.tsx","./src/components/navigation/config.ts","./src/components/navigation/types.ts","./src/components/navigation/components/devicespagenavigationicon.tsx","./src/components/navigation/components/applicationversion/applicationversion.tsx","./src/components/navigation/components/navigationbar/navigationbar.tsx","./src/components/navigation/components/navigationdesktop/navigationdesktop.tsx","./src/components/navigation/components/navigationdesktop/navigationcollapse/navigationcollapse.tsx","./src/components/navigation/components/navigationlink/navigationlink.tsx","./src/components/navigation/components/navigationmobile/navigationmobile.tsx","./src/components/navigation/components/navigationmobile/mobilenavmodal/mobilenavmodal.tsx","./src/components/navigation/components/icons/navigationactivitylogpageicon.tsx","./src/components/navigation/hooks/usenavigationstore.ts","./src/i18n/formatters.ts","./src/i18n/i18n-react.tsx","./src/i18n/i18n-types.ts","./src/i18n/i18n-util.async.ts","./src/i18n/i18n-util.sync.ts","./src/i18n/i18n-util.ts","./src/i18n/en/index.ts","./src/i18n/ko/index.ts","./src/i18n/pl/index.ts","./src/pages/acl/aclcreatedataprovider.tsx","./src/pages/acl/aclroutes.tsx","./src/pages/acl/acl-context.tsx","./src/pages/acl/types.ts","./src/pages/acl/utils.ts","./src/pages/acl/validators.ts","./src/pages/acl/aclcreatepage/aclcreatepage.tsx","./src/pages/acl/aclcreatepage/components/dialogselect/dialogselect.tsx","./src/pages/acl/aclcreatepage/components/dialogselect/dialogselectbuttonicon.tsx","./src/pages/acl/aclcreatepage/components/dialogselect/formdialogselect.tsx","./src/pages/acl/aclcreatepage/components/dialogselect/types.ts","./src/pages/acl/aclcreatepage/components/dialogselect/dialogselectmodal/dialogselectmodal.tsx","./src/pages/acl/aclindexpage/aclindexpage.tsx","./src/pages/acl/aclindexpage/components/deploychangesicon.tsx","./src/pages/acl/aclindexpage/components/aclindexaliases/aclindexaliases.tsx","./src/pages/acl/aclindexpage/components/aclindexaliases/types.ts","./src/pages/acl/aclindexpage/components/aclindexaliases/components/aliaseditbutton.tsx","./src/pages/acl/aclindexpage/components/aclindexaliases/components/aliaseslist.tsx","./src/pages/acl/aclindexpage/components/aclindexaliases/components/aclaliasstatus/aclaliasstatus.tsx","./src/pages/acl/aclindexpage/components/aclindexaliases/modals/aclaliasapplyconfirmmodal/aclaliasapplyconfirmmodal.tsx","./src/pages/acl/aclindexpage/components/aclindexaliases/modals/aclaliasdeleteblockmodal/aclaliasdeleteblockmodal.tsx","./src/pages/acl/aclindexpage/components/aclindexaliases/modals/aclaliasdeleteblockmodal/store.tsx","./src/pages/acl/aclindexpage/components/aclindexaliases/modals/alcaliascemodal/alcaliascemodal.tsx","./src/pages/acl/aclindexpage/components/aclindexaliases/modals/alcaliascemodal/store.tsx","./src/pages/acl/aclindexpage/components/aclindexrules/aclindexrules.tsx","./src/pages/acl/aclindexpage/components/aclindexrules/components/aclrulestatus/aclrulestatus.tsx","./src/pages/acl/aclindexpage/components/aclindexrules/components/aclrulesapplyconfirmmodal/aclrulesapplyconfirmmodal.tsx","./src/pages/acl/aclindexpage/components/acllistskeleton/acllistskeleton.tsx","./src/pages/acl/aclindexpage/components/shared/aclaliaskindicon.tsx","./src/pages/acl/aclindexpage/components/shared/dividerheader.tsx","./src/pages/acl/aclindexpage/components/shared/networkaccesstypeicon.tsx","./src/pages/acl/aclindexpage/components/shared/types.ts","./src/pages/acl/aclindexpage/components/shared/aclmessageboxes/aclmessageboxes.tsx","./src/pages/activity-log/activitylogpage.tsx","./src/pages/activity-log/types.ts","./src/pages/activity-log/components/activitylist.tsx","./src/pages/activity-log/components/activitytimerangemodal.tsx","./src/pages/adddevice/adddevicepage.tsx","./src/pages/adddevice/types.ts","./src/pages/adddevice/hooks/useadddevicepagestore.tsx","./src/pages/adddevice/steps/adddeviceclientconfigurationstep/adddeviceclientconfigurationstep.tsx","./src/pages/adddevice/steps/adddeviceconfigstep/adddeviceconfigstep.tsx","./src/pages/adddevice/steps/adddevicesetupmethodstep/adddevicesetupmethodstep.tsx","./src/pages/adddevice/steps/adddevicesetupmethodstep/types.ts","./src/pages/adddevice/steps/adddevicesetupmethodstep/components/devicesetupmethodcard/devicesetupmethodcard.tsx","./src/pages/adddevice/steps/adddevicesetupstep/adddevicesetupstep.tsx","./src/pages/adddevice/utils/enrollmenttotoken.ts","./src/pages/allow/openidallowpage.tsx","./src/pages/auth/authpage.tsx","./src/pages/auth/callback/callback.tsx","./src/pages/auth/login/login.tsx","./src/pages/auth/login/components/oidcbuttons.tsx","./src/pages/auth/mfaroute/mfaroute.tsx","./src/pages/auth/mfaroute/mfaemail/mfaemail.tsx","./src/pages/auth/mfaroute/mfanav/mfanav.tsx","./src/pages/auth/mfaroute/mfarecovery/mfarecovery.tsx","./src/pages/auth/mfaroute/mfatotpauth/mfatotpauth.tsx","./src/pages/auth/mfaroute/mfawebauthn/mfawebauthn.tsx","./src/pages/auth/shared/hooks/usemfastore.tsx","./src/pages/devices/devicespage.tsx","./src/pages/devices/types.ts","./src/pages/devices/components/adddeviceicon.tsx","./src/pages/devices/components/deviceslist/deviceslist.tsx","./src/pages/devices/components/deviceslist/modals/confirmdevicedeletemodal.tsx","./src/pages/devices/hooks/usedeletestandalonedevicemodal.tsx","./src/pages/devices/hooks/usedevicespage.tsx","./src/pages/devices/hooks/useeditstandalonedevicemodal.tsx","./src/pages/devices/modals/addstandalonedevicemodal/addstandalonedevicemodal.tsx","./src/pages/devices/modals/addstandalonedevicemodal/store.tsx","./src/pages/devices/modals/addstandalonedevicemodal/types.ts","./src/pages/devices/modals/addstandalonedevicemodal/steps/finishclistep/finishclistep.tsx","./src/pages/devices/modals/addstandalonedevicemodal/steps/finishmanualstep/finishmanualstep.tsx","./src/pages/devices/modals/addstandalonedevicemodal/steps/methodstep/methodstep.tsx","./src/pages/devices/modals/addstandalonedevicemodal/steps/setupclistep/setupclistep.tsx","./src/pages/devices/modals/addstandalonedevicemodal/steps/setupmanualstep/setupmanualstep.tsx","./src/pages/devices/modals/editstandalonedevicemodal/editstandalonemodal.tsx","./src/pages/devices/modals/standalonedeviceconfigmodal/standalonedeviceconfigmodal.tsx","./src/pages/devices/modals/standalonedeviceconfigmodal/store.tsx","./src/pages/devices/modals/standalonedeviceenrollmentmodal/standalonedeviceenrollmentmodal.tsx","./src/pages/devices/modals/standalonedeviceenrollmentmodal/store.tsx","./src/pages/devices/modals/components/types.ts","./src/pages/devices/modals/components/standalonedevicemodalenrollmentcontent/standalonedevicemodalenrollmentcontent.tsx","./src/pages/devices/modals/components/standalonedevicemodalform/standalonedevicemodalform.tsx","./src/pages/enrollment/enrollmentpage.tsx","./src/pages/enrollment/components/enrollmentemail/enrollmentemail.tsx","./src/pages/enrollment/components/enrollmentvpn/enrollmentvpn.tsx","./src/pages/enrollment/components/enrollmentwelcomemessage/enrollmentwelcomemessage.tsx","./src/pages/enrollment/hooks/useenrollmentstore.tsx","./src/pages/groups/groupspage.tsx","./src/pages/groups/components/groupslist/groupslist.tsx","./src/pages/groups/components/groupsmanagement/groupsmanagement.tsx","./src/pages/groups/components/modals/addgroupmodal/addgroupmodal.tsx","./src/pages/groups/components/modals/addgroupmodal/useaddgroupmodal.tsx","./src/pages/groups/components/modals/addgroupmodal/components/groupformselectall/groupformselectall.tsx","./src/pages/groups/components/modals/addgroupmodal/components/userselect/userselect.tsx","./src/pages/loader/loaderpage.tsx","./src/pages/network/networkpage.tsx","./src/pages/network/networkcontrols/networkcontrols.tsx","./src/pages/network/networkeditform/networkeditform.tsx","./src/pages/network/networkgateway/networkgateway.tsx","./src/pages/network/networktabs/networktabs.tsx","./src/pages/network/hooks/usenetworkpagestore.ts","./src/pages/openid/openidclientslistpage/openidclientslistpage.tsx","./src/pages/openid/modals/openidclientmodal/openidclientmodal.tsx","./src/pages/openid/modals/openidclientmodal/openidclientmodalform.tsx","./src/pages/openid/modals/openidclientmodal/types.ts","./src/pages/openid/modals/openidclientmodal/components/openidclientmodalformscopes.tsx","./src/pages/overview/overviewpage.tsx","./src/pages/overview/overviewconnectedusers/overviewconnectedusers.tsx","./src/pages/overview/overviewconnectedusers/userconnectioncard/userconnectioncard.tsx","./src/pages/overview/overviewconnectedusers/userconnectioncard/formatconnectiontime.ts","./src/pages/overview/overviewconnectedusers/userconnectionlistitem/userconnectionlistitem.tsx","./src/pages/overview/overviewconnectedusers/shared/components/networkusagechart/networkusagechart.tsx","./src/pages/overview/overviewexpandable/overviewexpandable.tsx","./src/pages/overview/overviewheader/overviewheader.tsx","./src/pages/overview/overviewheader/overviewnetworkselect/overviewnetworkselect.tsx","./src/pages/overview/overviewstats/overviewstats.tsx","./src/pages/overview/overviewstats/utils.ts","./src/pages/overview/overviewstatsfilterselect/overviewstatsfilterselect.tsx","./src/pages/overview/overviewviewselect/overviewviewselect.tsx","./src/pages/overview/helpers/stats.ts","./src/pages/overview/hooks/store/useoverviewstore.ts","./src/pages/overview-index/overviewindexpage.tsx","./src/pages/overview-index/components/editlocationssettingsbutton/editlocationssettingsbutton.tsx","./src/pages/overview-index/components/overviewnetworkselection/overviewnetworkselection.tsx","./src/pages/overview-index/components/overviewtimeselection/overviewtimeselection.tsx","./src/pages/overview-index/components/hooks/useoverviewtimeselection.ts","./src/pages/provisioners/provisionerspage.tsx","./src/pages/provisioners/components/provisionerslist/provisionerslist.tsx","./src/pages/provisioners/components/provisioningstationsetupcard/provisioningstationsetupcard.tsx","./src/pages/provisioners/components/modals/deleteprovisionermodal.tsx","./src/pages/provisioners/components/modals/usedeleteprovisionermodal.tsx","./src/pages/redirect/redirectpage.tsx","./src/pages/settings/settingspage.tsx","./src/pages/settings/components/activitylogstreamsettings/activitylogstreamsettings.tsx","./src/pages/settings/components/activitylogstreamsettings/modals/createactivitylogstreammodal/createactivitylogstreammodal.tsx","./src/pages/settings/components/activitylogstreamsettings/modals/createactivitylogstreammodal/store.tsx","./src/pages/settings/components/activitylogstreamsettings/modals/logstashhttpstreamcemodal/logstashhttpstreamcemodal.tsx","./src/pages/settings/components/activitylogstreamsettings/modals/logstashhttpstreamcemodal/store.tsx","./src/pages/settings/components/activitylogstreamsettings/modals/vectorhttpstreamcemodal/vectorhttpstreamcemodal.tsx","./src/pages/settings/components/activitylogstreamsettings/modals/vectorhttpstreamcemodal/store.tsx","./src/pages/settings/components/activitylogstreamsettings/utils/activitylogstreamtolabel.ts","./src/pages/settings/components/enterprisesettings/enterprisesettings.tsx","./src/pages/settings/components/enterprisesettings/components/enterpriseform.tsx","./src/pages/settings/components/globalsettings/globalsettings.tsx","./src/pages/settings/components/globalsettings/components/globalsettingsform/globalsettingsform.tsx","./src/pages/settings/components/globalsettings/components/licensesettings/licensesettings.tsx","./src/pages/settings/components/ldapsettings/ldapsettings.tsx","./src/pages/settings/components/ldapsettings/components/ldapconnectiontest.tsx","./src/pages/settings/components/ldapsettings/components/ldapsettingsform.tsx","./src/pages/settings/components/ldapsettings/components/ldapsettingsleft.tsx","./src/pages/settings/components/ldapsettings/components/ldapsettingsright.tsx","./src/pages/settings/components/notificationsettings/notificationsettings.tsx","./src/pages/settings/components/notificationsettings/components/gatewaynotificationsform.tsx","./src/pages/settings/components/notificationsettings/components/notificationsettingsform.tsx","./src/pages/settings/components/openidsettings/openidsettings.tsx","./src/pages/settings/components/openidsettings/components/directorysyncsettings.tsx","./src/pages/settings/components/openidsettings/components/openidgeneralsettings.tsx","./src/pages/settings/components/openidsettings/components/openidprovidersettings.tsx","./src/pages/settings/components/openidsettings/components/openidsettingsform.tsx","./src/pages/settings/components/openidsettings/components/supportedproviders.ts","./src/pages/settings/components/smtpsettings/smtpsettings.tsx","./src/pages/settings/components/smtpsettings/components/smtpsettingsform/smtpsettingsform.tsx","./src/pages/settings/components/smtpsettings/components/smtptest/smtptest.tsx","./src/pages/settings/components/smtpsettings/components/smtptest/smtptestmodal.tsx","./src/pages/settings/components/smtpsettings/components/smtptest/usesmtptestmodal.ts","./src/pages/settings/hooks/usesettingspage.tsx","./src/pages/support/supportpage.tsx","./src/pages/support/components/builtbycard/builtbycard.tsx","./src/pages/support/components/debugdatacard/debugdatacard.tsx","./src/pages/support/components/debugdatacard/components/sendsupportdatamodal.tsx","./src/pages/support/components/supportcard/supportcard.tsx","./src/pages/users/userspage.tsx","./src/pages/users/userssharedmodals.tsx","./src/pages/users/userprofile/userprofile.tsx","./src/pages/users/userprofile/profiledetails/profiledetails.tsx","./src/pages/users/userprofile/profiledetails/profiledetailsform/profiledetailsform.tsx","./src/pages/users/userprofile/profiledetails/profiledetailsform/profiledetailsformappsfield.tsx","./src/pages/users/userprofile/userapitokens/userapitokens.tsx","./src/pages/users/userprofile/userapitokens/apitokenlist/apitokenlist.tsx","./src/pages/users/userprofile/userapitokens/apitokenlist/apitokenitem/apitokenitem.tsx","./src/pages/users/userprofile/userapitokens/deleteapitokenmodal/deleteapitokenmodal.tsx","./src/pages/users/userprofile/userapitokens/deleteapitokenmodal/usedeleteapitokenmodal.ts","./src/pages/users/userprofile/userauthinfo/userauthinfo.tsx","./src/pages/users/userprofile/userauthinfo/userauthinfomfa.tsx","./src/pages/users/userprofile/userauthinfo/userauthinfopassword.tsx","./src/pages/users/userprofile/userauthinfo/userauthinforecovery.tsx","./src/pages/users/userprofile/userauthinfo/modals/changeselfpasswordmodal/changeselfpasswordmodal.tsx","./src/pages/users/userprofile/userauthinfo/modals/changeselfpasswordmodal/components/changeselfpasswordform.tsx","./src/pages/users/userprofile/userauthinfo/modals/changeselfpasswordmodal/hooks/usechangeselfpasswordmodal.ts","./src/pages/users/userprofile/userauthinfo/modals/managewebauthnmodal/managewebauthnmodal.tsx","./src/pages/users/userprofile/userauthinfo/modals/managewebauthnmodal/components/registerwebauthnform.tsx","./src/pages/users/userprofile/userauthinfo/modals/managewebauthnmodal/components/webauthnkeyrow.tsx","./src/pages/users/userprofile/userauthinfo/modals/recoverycodesmodal/recoverycodesmodal.tsx","./src/pages/users/userprofile/userauthinfo/modals/registeremailmfamodal/registeremailmfamodal.tsx","./src/pages/users/userprofile/userauthinfo/modals/registeremailmfamodal/components/registermfaemailform/registermfaemailform.tsx","./src/pages/users/userprofile/userauthinfo/modals/registeremailmfamodal/hooks/useemailmfamodal.tsx","./src/pages/users/userprofile/userauthinfo/modals/registertotpmodal/registertotpmodal.tsx","./src/pages/users/userprofile/userauthenticationkeys/userauthenticationkeys.tsx","./src/pages/users/userprofile/userauthenticationkeys/authenticationkeylist/authenticationkeylist.tsx","./src/pages/users/userprofile/userauthenticationkeys/authenticationkeylist/authenticationkeyitem/authenticationkeyitem.tsx","./src/pages/users/userprofile/userauthenticationkeys/authenticationkeylist/authenticationkeyitemyubikey/authenticationkeyitemyubikey.tsx","./src/pages/users/userprofile/userauthenticationkeys/deleteauthenticationkeymodal/deleteauthenticationkeymodal.tsx","./src/pages/users/userprofile/userauthenticationkeys/deleteauthenticationkeymodal/usedeleteauthenticationkeymodal.ts","./src/pages/users/userprofile/userdevices/userdevices.tsx","./src/pages/users/userprofile/userdevices/devicecard/devicecard.tsx","./src/pages/users/userprofile/userdevices/hooks/usedeletedevicemodal.ts","./src/pages/users/userprofile/userdevices/hooks/usedeviceconfigmodal.tsx","./src/pages/users/userprofile/userdevices/hooks/useeditdevicemodal.ts","./src/pages/users/userprofile/userdevices/modals/deleteuserdevicemodal/deleteuserdevicemodal.tsx","./src/pages/users/userprofile/userdevices/modals/deviceconfigmodal/deviceconfigmodal.tsx","./src/pages/users/userprofile/userdevices/modals/edituserdevicemodal/edituserdevicemodal.tsx","./src/pages/users/userprofile/userdevices/modals/edituserdevicemodal/userdeviceeditform.tsx","./src/pages/users/usersoverview/usersoverview.tsx","./src/pages/users/usersoverview/components/usereditbutton/resetpasswordbutton.tsx","./src/pages/users/usersoverview/components/usereditbutton/usereditbutton.tsx","./src/pages/users/usersoverview/components/userslist/userslist.tsx","./src/pages/users/usersoverview/components/userslist/types.ts","./src/pages/users/usersoverview/components/userslist/components/userlistrow.tsx","./src/pages/users/usersoverview/components/userslist/components/userslistgroups.tsx","./src/pages/users/usersoverview/components/userslist/modals/usergroupslistmodal/usergroupslistmodal.tsx","./src/pages/users/usersoverview/components/userslist/modals/usergroupslistmodal/useusergroupslistmodal.ts","./src/pages/users/usersoverview/modals/addusermodal/addusermodal.tsx","./src/pages/users/usersoverview/modals/addusermodal/components/adduserform/adduserform.tsx","./src/pages/users/usersoverview/modals/addusermodal/components/enrollmenttokencard/enrollmenttokencard.tsx","./src/pages/users/usersoverview/modals/addusermodal/components/startenrollmentform/startenrollmentform.tsx","./src/pages/users/usersoverview/modals/addusermodal/hooks/useaddusermodal.tsx","./src/pages/users/usersoverview/modals/assigngroupsmodal/assigngroupsmodal.tsx","./src/pages/users/usersoverview/modals/assigngroupsmodal/store.ts","./src/pages/users/shared/components/addcomponentbox/addbuttonicon.tsx","./src/pages/users/shared/components/addcomponentbox/addcomponentbox.tsx","./src/pages/users/shared/components/keybox/keybox.tsx","./src/pages/users/shared/modals/addapitokenmodal/addapitokenmodal.tsx","./src/pages/users/shared/modals/addapitokenmodal/useaddapitokenmodal.ts","./src/pages/users/shared/modals/addapitokenmodal/components/addapitokenform/addapitokenform.tsx","./src/pages/users/shared/modals/addauthenticationkeymodal/addauthenticationkeymodal.tsx","./src/pages/users/shared/modals/addauthenticationkeymodal/types.ts","./src/pages/users/shared/modals/addauthenticationkeymodal/useaddauthorizationkeymodal.ts","./src/pages/users/shared/modals/addauthenticationkeymodal/components/addauthenticationkeyform/addauthenticationkeyform.tsx","./src/pages/users/shared/modals/addauthenticationkeymodal/components/addauthenticationkeyyubikey/addauthenticationkeyyubikey.tsx","./src/pages/users/shared/modals/addauthenticationkeymodal/components/addauthenticationkeyyubikey/components/provisionerrow.tsx","./src/pages/users/shared/modals/changeuserpasswordmodal/changepasswordform.tsx","./src/pages/users/shared/modals/changeuserpasswordmodal/changeuserpasswordmodal.tsx","./src/pages/users/shared/modals/deleteusermodal/deleteusermodal.tsx","./src/pages/users/shared/modals/disablemfamodal/disablemfamodal.tsx","./src/pages/users/shared/modals/disablemfamodal/store.ts","./src/pages/users/shared/modals/renameapitokenmodal/renameapitokenmodal.tsx","./src/pages/users/shared/modals/renameapitokenmodal/userenameapitokenmodal.tsx","./src/pages/users/shared/modals/renameauthenticationkeymodal/renameauthenticationkeymodal.tsx","./src/pages/users/shared/modals/renameauthenticationkeymodal/userenameauthenticationkeymodal.tsx","./src/pages/users/shared/modals/toggleusermodal/toggleusermodal.tsx","./src/pages/webhooks/webhookslistpage.tsx","./src/pages/webhooks/modals/webhookmodal/webhookform.tsx","./src/pages/webhooks/modals/webhookmodal/webhookmodal.tsx","./src/pages/wizard/wizardpage.tsx","./src/pages/wizard/components/wizardmapdevices/wizardmapdevices.tsx","./src/pages/wizard/components/wizardmapdevices/components/mapdevicerow.tsx","./src/pages/wizard/components/wizardnav/wizardnav.tsx","./src/pages/wizard/components/wizardnetworkconfiguration/wizardnetworkconfiguration.tsx","./src/pages/wizard/components/wizardnetworkimport/wizardnetworkimport.tsx","./src/pages/wizard/components/wizardtype/wizardtype.tsx","./src/pages/wizard/components/wizardtype/components/wizardtypeoptioncard/wizardtypeoptioncard.tsx","./src/pages/wizard/components/wizardwelcome/wizardwelcome.tsx","./src/pages/wizard/hooks/usewizardstore.ts","./src/pages/wizard/types/interfaces.ts","./src/pages/wizard/types/types.ts","./src/shared/constants.ts","./src/shared/links.ts","./src/shared/messageids.ts","./src/shared/mutations.ts","./src/shared/patterns.ts","./src/shared/queries.ts","./src/shared/query-client.ts","./src/shared/types.ts","./src/shared/validators.ts","./src/shared/variants.ts","./src/shared/components/form/formacldefaultpolicyselect/formacldefaultpolicy.tsx","./src/shared/components/layout/copyfield/copyfield.tsx","./src/shared/components/layout/dateinput/dateinput.tsx","./src/shared/components/layout/dateinput/formdateinput.tsx","./src/shared/components/layout/dateinput/types.ts","./src/shared/components/layout/enterpriseupgradetoast/enterpriseupgradetoast.tsx","./src/shared/components/layout/enterpriseupgradetoast/types.ts","./src/shared/components/layout/expandablesection/expandablesection.tsx","./src/shared/components/layout/listcelltags/listcelltags.tsx","./src/shared/components/layout/listcelltext/listcelltext.tsx","./src/shared/components/layout/listheader/listheader.tsx","./src/shared/components/layout/listheader/types.ts","./src/shared/components/layout/managementpagelayout/managementpagelayout.tsx","./src/shared/components/layout/managementpagelayout/types.ts","./src/shared/components/layout/pagecontainer/pagecontainer.tsx","./src/shared/components/layout/pagelayout/pagelayout.tsx","./src/shared/components/layout/pagelimiter/pagelimiter.tsx","./src/shared/components/layout/rendermarkdown/rendermarkdown.tsx","./src/shared/components/layout/sectionwithcard/sectionwithcard.tsx","./src/shared/components/layout/upgradelicensemodal/upgradelicensemodal.tsx","./src/shared/components/layout/upgradelicensemodal/store.tsx","./src/shared/components/layout/upgradelicensemodal/types.ts","./src/shared/components/layout/versionupdatetoast/versionupdatetoast.tsx","./src/shared/components/layout/versionupdatetoast/types.ts","./src/shared/components/layout/wireguardconfigexpandable/wireguardconfigexpandable.tsx","./src/shared/components/layout/buttons/addbutton/addbutton.tsx","./src/shared/components/layout/buttons/filterbutton/filterbutton.tsx","./src/shared/components/router/guards/protectedroute/protectedroute.tsx","./src/shared/components/i18n/rendertranslation/rendertranslation.tsx","./src/shared/components/modals/filtergroupsmodal/filtergroupsmodal.tsx","./src/shared/components/modals/filtergroupsmodal/types.ts","./src/shared/components/modals/updatenotificationmodal/updatenotificationmodal.tsx","./src/shared/components/modals/updatenotificationmodal/components/updatenotificationmodalicons.tsx","./src/shared/components/network/deviceconfigscard/deviceconfigscard.tsx","./src/shared/components/network/deviceconfigscard/types.ts","./src/shared/components/network/gatewaysstatus/gatewaystatusicon.tsx","./src/shared/components/network/gatewaysstatus/types.ts","./src/shared/components/network/gatewaysstatus/allnetworksgatewaysstatus/allnetworksgatewaysstatus.tsx","./src/shared/components/network/gatewaysstatus/gatewaysfloatingstatus/gatewaysfloatingstatus.tsx","./src/shared/components/network/gatewaysstatus/gatewaysstatusinfo/gatewaysstatusinfo.tsx","./src/shared/components/network/gatewaysstatus/networkgatewaysstatus/networkgatewaysstatus.tsx","./src/shared/components/svg/avatar01blue.tsx","./src/shared/components/svg/avatar01gray.tsx","./src/shared/components/svg/avatar02blue.tsx","./src/shared/components/svg/avatar02gray.tsx","./src/shared/components/svg/avatar03blue.tsx","./src/shared/components/svg/avatar03gray.tsx","./src/shared/components/svg/avatar04blue.tsx","./src/shared/components/svg/avatar04gray.tsx","./src/shared/components/svg/avatar05blue.tsx","./src/shared/components/svg/avatar05gray.tsx","./src/shared/components/svg/avatar06blue.tsx","./src/shared/components/svg/avatar06gray.tsx","./src/shared/components/svg/avatar07blue.tsx","./src/shared/components/svg/avatar07gray.tsx","./src/shared/components/svg/avatar08blue.tsx","./src/shared/components/svg/avatar08gray.tsx","./src/shared/components/svg/avatar09blue.tsx","./src/shared/components/svg/avatar09gray.tsx","./src/shared/components/svg/avatar10blue.tsx","./src/shared/components/svg/avatar10gray.tsx","./src/shared/components/svg/avatar11blue.tsx","./src/shared/components/svg/avatar11gray.tsx","./src/shared/components/svg/avatar12blue.tsx","./src/shared/components/svg/avatar12gray.tsx","./src/shared/components/svg/defguardlogo.tsx","./src/shared/components/svg/defguardlogologin.tsx","./src/shared/components/svg/defguardnavlogo.tsx","./src/shared/components/svg/defguardnavlogocollapsed.tsx","./src/shared/components/svg/defguardnoicon.tsx","./src/shared/components/svg/glowicon.tsx","./src/shared/components/svg/icon24hconnections.tsx","./src/shared/components/svg/iconactiveconnections.tsx","./src/shared/components/svg/iconactivityadd.tsx","./src/shared/components/svg/iconactivityremoved.tsx","./src/shared/components/svg/iconactivitywarning.tsx","./src/shared/components/svg/iconarrowdouble.tsx","./src/shared/components/svg/iconarrowdoublegrayleft.tsx","./src/shared/components/svg/iconarrowgraydown.tsx","./src/shared/components/svg/iconarrowgraydown1.tsx","./src/shared/components/svg/iconarrowgrayleft.tsx","./src/shared/components/svg/iconarrowgrayright.tsx","./src/shared/components/svg/iconarrowgraysmall.tsx","./src/shared/components/svg/iconarrowgrayup.tsx","./src/shared/components/svg/iconarrowgrayup1.tsx","./src/shared/components/svg/iconarrowsingle.tsx","./src/shared/components/svg/iconarrowsingle2.tsx","./src/shared/components/svg/iconarrowwhiteleft.tsx","./src/shared/components/svg/iconasterix.tsx","./src/shared/components/svg/iconauthenticationkey.tsx","./src/shared/components/svg/iconcancel.tsx","./src/shared/components/svg/iconcancelalt.tsx","./src/shared/components/svg/iconcheckmark.tsx","./src/shared/components/svg/iconcheckmarkgreen.tsx","./src/shared/components/svg/iconcheckmarkwhite.tsx","./src/shared/components/svg/iconcheckmarkwhite1.tsx","./src/shared/components/svg/iconcheckmarkwhitebig.tsx","./src/shared/components/svg/iconclip.tsx","./src/shared/components/svg/iconclosegray.tsx","./src/shared/components/svg/iconcollapse.tsx","./src/shared/components/svg/iconconnected.tsx","./src/shared/components/svg/iconcopy.tsx","./src/shared/components/svg/icondeactivated.tsx","./src/shared/components/svg/icondelete.tsx","./src/shared/components/svg/icondfgopenidredirect.tsx","./src/shared/components/svg/icondisconnected.tsx","./src/shared/components/svg/icondownload.tsx","./src/shared/components/svg/iconedit.tsx","./src/shared/components/svg/iconeditalt.tsx","./src/shared/components/svg/iconeditalthover.tsx","./src/shared/components/svg/iconedithover.tsx","./src/shared/components/svg/iconeditnetwork.tsx","./src/shared/components/svg/iconeth.tsx","./src/shared/components/svg/iconexpand.tsx","./src/shared/components/svg/iconfilter.tsx","./src/shared/components/svg/iconhamburgerclose.tsx","./src/shared/components/svg/iconhamburgermenu.tsx","./src/shared/components/svg/iconhamburgermenu1.tsx","./src/shared/components/svg/iconhourglass.tsx","./src/shared/components/svg/iconhourglasshover.tsx","./src/shared/components/svg/iconinfo.tsx","./src/shared/components/svg/iconinfoerror.tsx","./src/shared/components/svg/iconinfonormal.tsx","./src/shared/components/svg/iconinfosuccess.tsx","./src/shared/components/svg/iconinfosuccess1.tsx","./src/shared/components/svg/iconinfowarning.tsx","./src/shared/components/svg/iconkey.tsx","./src/shared/components/svg/iconlistorderdown.tsx","./src/shared/components/svg/iconlistorderdownhover.tsx","./src/shared/components/svg/iconlistorderup.tsx","./src/shared/components/svg/iconlistorderuphover.tsx","./src/shared/components/svg/iconnavgroups.tsx","./src/shared/components/svg/iconnavhamburger.tsx","./src/shared/components/svg/iconnavkey.tsx","./src/shared/components/svg/iconnavlocations.tsx","./src/shared/components/svg/iconnavlogout.tsx","./src/shared/components/svg/iconnavopenid.tsx","./src/shared/components/svg/iconnavoverview.tsx","./src/shared/components/svg/iconnavprofile.tsx","./src/shared/components/svg/iconnavprofile1.tsx","./src/shared/components/svg/iconnavprovisioners.tsx","./src/shared/components/svg/iconnavsettings.tsx","./src/shared/components/svg/iconnavsupport.tsx","./src/shared/components/svg/iconnavusers.tsx","./src/shared/components/svg/iconnavvpn.tsx","./src/shared/components/svg/iconnavwebhook.tsx","./src/shared/components/svg/iconnavwebhooks.tsx","./src/shared/components/svg/iconnavyubikey.tsx","./src/shared/components/svg/iconnetworkload.tsx","./src/shared/components/svg/iconopenmodal.tsx","./src/shared/components/svg/iconpacketsin.tsx","./src/shared/components/svg/iconpacketsout.tsx","./src/shared/components/svg/iconplusgray.tsx","./src/shared/components/svg/iconpluswhite.tsx","./src/shared/components/svg/iconpopupclose.tsx","./src/shared/components/svg/iconreadmore.tsx","./src/shared/components/svg/iconredirect.tsx","./src/shared/components/svg/iconsearch.tsx","./src/shared/components/svg/iconsearchhover.tsx","./src/shared/components/svg/iconsettings.tsx","./src/shared/components/svg/iconsuccesslarge.tsx","./src/shared/components/svg/icontagdismiss.tsx","./src/shared/components/svg/icontrash.tsx","./src/shared/components/svg/iconupgrade.tsx","./src/shared/components/svg/iconuseraddnew.tsx","./src/shared/components/svg/iconuserlist.tsx","./src/shared/components/svg/iconuserlistelement.tsx","./src/shared/components/svg/iconuserlistexpanded.tsx","./src/shared/components/svg/iconuserlisthover.tsx","./src/shared/components/svg/iconwaiting.tsx","./src/shared/components/svg/iconwaitinghover.tsx","./src/shared/components/svg/iconwallet.tsx","./src/shared/components/svg/iconwarning.tsx","./src/shared/components/svg/iconx.tsx","./src/shared/components/svg/imagemeshnetwork.tsx","./src/shared/components/svg/imageregularnetwork.tsx","./src/shared/components/svg/importconfig.tsx","./src/shared/components/svg/logodefguardwhite.tsx","./src/shared/components/svg/manualconfig.tsx","./src/shared/components/svg/metamaskicon.tsx","./src/shared/components/svg/phantomicon.tsx","./src/shared/components/svg/qriconwhite.tsx","./src/shared/components/svg/subtract.tsx","./src/shared/components/svg/wireguardlogo.tsx","./src/shared/components/svg/yubikeyprovisioninggraphic.tsx","./src/shared/components/utils/delayrender/delayrender.tsx","./src/shared/defguard-ui/components/form/formcheckbox/formcheckbox.tsx","./src/shared/defguard-ui/components/form/formdevtools/formdevtools.tsx","./src/shared/defguard-ui/components/form/forminput/forminput.tsx","./src/shared/defguard-ui/components/form/formlocationip/formlocationip.tsx","./src/shared/defguard-ui/components/form/formlocationip/type.ts","./src/shared/defguard-ui/components/form/formselect/formselect.tsx","./src/shared/defguard-ui/components/form/formtextarea/formtextarea.tsx","./src/shared/defguard-ui/components/form/formtoggle/formtoggle.tsx","./src/shared/defguard-ui/components/layout/actionbutton/actionbutton.tsx","./src/shared/defguard-ui/components/layout/actionbutton/types.ts","./src/shared/defguard-ui/components/layout/actionbutton/icons/actioniconconfig.tsx","./src/shared/defguard-ui/components/layout/activitystatus/activitystatus.tsx","./src/shared/defguard-ui/components/layout/activitystatus/types.ts","./src/shared/defguard-ui/components/layout/avatarbox/avatarbox.tsx","./src/shared/defguard-ui/components/layout/badge/badge.tsx","./src/shared/defguard-ui/components/layout/badge/types.ts","./src/shared/defguard-ui/components/layout/biginfobox/biginfobox.tsx","./src/shared/defguard-ui/components/layout/button/button.tsx","./src/shared/defguard-ui/components/layout/button/types.ts","./src/shared/defguard-ui/components/layout/card/card.tsx","./src/shared/defguard-ui/components/layout/cardtabs/cardtabs.tsx","./src/shared/defguard-ui/components/layout/cardtabs/types.ts","./src/shared/defguard-ui/components/layout/cardtabs/components/cardtab.tsx","./src/shared/defguard-ui/components/layout/checkbox/checkbox.tsx","./src/shared/defguard-ui/components/layout/checkbox/types.ts","./src/shared/defguard-ui/components/layout/deviceavatar/deviceavatar.tsx","./src/shared/defguard-ui/components/layout/deviceavatar/utils/getdeviceavatar.ts","./src/shared/defguard-ui/components/layout/divider/divider.tsx","./src/shared/defguard-ui/components/layout/divider/types.ts","./src/shared/defguard-ui/components/layout/editbutton/editbutton.tsx","./src/shared/defguard-ui/components/layout/editbutton/editbuttonoption.tsx","./src/shared/defguard-ui/components/layout/editbutton/types.ts","./src/shared/defguard-ui/components/layout/expandablecard/expandablecard.tsx","./src/shared/defguard-ui/components/layout/fielderror/fielderror.tsx","./src/shared/defguard-ui/components/layout/floatingarrow/floatingarrow.tsx","./src/shared/defguard-ui/components/layout/floatingbox/floatingbox.tsx","./src/shared/defguard-ui/components/layout/floatingmenu/floatingmenu.tsx","./src/shared/defguard-ui/components/layout/floatingmenu/floatingmenuarrow.tsx","./src/shared/defguard-ui/components/layout/floatingmenu/floatingmenuprovider.tsx","./src/shared/defguard-ui/components/layout/floatingmenu/floatingmenutrigger.tsx","./src/shared/defguard-ui/components/layout/floatingmenu/types.ts","./src/shared/defguard-ui/components/layout/floatingmenu/usefloatingmenucontext.tsx","./src/shared/defguard-ui/components/layout/helper/helper.tsx","./src/shared/defguard-ui/components/layout/iconcontainer/iconcontainer.tsx","./src/shared/defguard-ui/components/layout/input/input.tsx","./src/shared/defguard-ui/components/layout/input/types.ts","./src/shared/defguard-ui/components/layout/interactionbox/interactionbox.tsx","./src/shared/defguard-ui/components/layout/label/label.tsx","./src/shared/defguard-ui/components/layout/labeledcheckbox/labeledcheckbox.tsx","./src/shared/defguard-ui/components/layout/limitedtext/limitedtext.tsx","./src/shared/defguard-ui/components/layout/listitemcount/listitemcount.tsx","./src/shared/defguard-ui/components/layout/loaderspinner/loaderspinner.tsx","./src/shared/defguard-ui/components/layout/messagebox/messagebox.tsx","./src/shared/defguard-ui/components/layout/messagebox/types.ts","./src/shared/defguard-ui/components/layout/messagebox/utils.ts","./src/shared/defguard-ui/components/layout/networkspeed/networkspeed.tsx","./src/shared/defguard-ui/components/layout/networkspeed/types.ts","./src/shared/defguard-ui/components/layout/nodata/nodata.tsx","./src/shared/defguard-ui/components/layout/radiobutton/radiobutton.tsx","./src/shared/defguard-ui/components/layout/rowbox/rowbox.tsx","./src/shared/defguard-ui/components/layout/search/search.tsx","./src/shared/defguard-ui/components/layout/select/select.tsx","./src/shared/defguard-ui/components/layout/select/types.ts","./src/shared/defguard-ui/components/layout/select/components/resizableinput/resizableinput.tsx","./src/shared/defguard-ui/components/layout/select/components/selectoptionrow/selectoptionrow.tsx","./src/shared/defguard-ui/components/layout/selectrow/selectrow.tsx","./src/shared/defguard-ui/components/layout/tag/tag.tsx","./src/shared/defguard-ui/components/layout/textcontainer/textcontainer.tsx","./src/shared/defguard-ui/components/layout/textarea/textarea.tsx","./src/shared/defguard-ui/components/layout/textarea/types.ts","./src/shared/defguard-ui/components/layout/textareaautoresizable/textareaautoresizable.tsx","./src/shared/defguard-ui/components/layout/textareaautoresizable/types.ts","./src/shared/defguard-ui/components/layout/toastmanager/toastmanager.tsx","./src/shared/defguard-ui/components/layout/toastmanager/toast/toast.tsx","./src/shared/defguard-ui/components/layout/toastmanager/toast/types.ts","./src/shared/defguard-ui/components/layout/toggle/toggle.tsx","./src/shared/defguard-ui/components/layout/toggle/types.ts","./src/shared/defguard-ui/components/layout/toggle/components/toggleoption/toggleoption.tsx","./src/shared/defguard-ui/components/layout/userinitials/userinitials.tsx","./src/shared/defguard-ui/components/layout/virtualizedlist/virtualizedlist.tsx","./src/shared/defguard-ui/components/layout/virtualizedlist/virtualizedlistsorticon.tsx","./src/shared/defguard-ui/components/layout/virtualizedlist/types.ts","./src/shared/defguard-ui/components/layout/modals/confirmmodal/confirmmodal.tsx","./src/shared/defguard-ui/components/layout/modals/confirmmodal/types.ts","./src/shared/defguard-ui/components/layout/modals/modal/modal.tsx","./src/shared/defguard-ui/components/layout/modals/modal/types.ts","./src/shared/defguard-ui/components/layout/modals/modalwithtitle/modalwithtitle.tsx","./src/shared/defguard-ui/components/icons/activityicon/activityicon.tsx","./src/shared/defguard-ui/components/icons/activityicon/types.ts","./src/shared/defguard-ui/components/icons/arrowsingle/arrowsingle.tsx","./src/shared/defguard-ui/components/icons/arrowsingle/types.ts","./src/shared/defguard-ui/components/svg/avatar01.tsx","./src/shared/defguard-ui/components/svg/avatar02.tsx","./src/shared/defguard-ui/components/svg/avatar03.tsx","./src/shared/defguard-ui/components/svg/avatar04.tsx","./src/shared/defguard-ui/components/svg/avatar05.tsx","./src/shared/defguard-ui/components/svg/avatar06.tsx","./src/shared/defguard-ui/components/svg/avatar07.tsx","./src/shared/defguard-ui/components/svg/avatar08.tsx","./src/shared/defguard-ui/components/svg/avatar09.tsx","./src/shared/defguard-ui/components/svg/avatar10.tsx","./src/shared/defguard-ui/components/svg/avatar11.tsx","./src/shared/defguard-ui/components/svg/avatar12.tsx","./src/shared/defguard-ui/components/svg/checkboxchecked.tsx","./src/shared/defguard-ui/components/svg/checkboxunchecked.tsx","./src/shared/defguard-ui/components/svg/iconarrowsinglelarge.tsx","./src/shared/defguard-ui/components/svg/iconarrowsinglesmall.tsx","./src/shared/defguard-ui/components/svg/iconasterix.tsx","./src/shared/defguard-ui/components/svg/iconcancel.tsx","./src/shared/defguard-ui/components/svg/iconconnection.tsx","./src/shared/defguard-ui/components/svg/iconcopy.tsx","./src/shared/defguard-ui/components/svg/icondownload.tsx","./src/shared/defguard-ui/components/svg/iconhamburgerdotted.tsx","./src/shared/defguard-ui/components/svg/iconhamburgernav.tsx","./src/shared/defguard-ui/components/svg/iconinfo.tsx","./src/shared/defguard-ui/components/svg/iconinfosuccess.tsx","./src/shared/defguard-ui/components/svg/iconloupe.tsx","./src/shared/defguard-ui/components/svg/iconoutsidelink.tsx","./src/shared/defguard-ui/components/svg/iconplus.tsx","./src/shared/defguard-ui/components/svg/iconqr.tsx","./src/shared/defguard-ui/components/svg/iconsettings.tsx","./src/shared/defguard-ui/components/svg/iconstatus.tsx","./src/shared/defguard-ui/components/svg/iconstatusblank.tsx","./src/shared/defguard-ui/components/svg/iconwarning.tsx","./src/shared/defguard-ui/components/svg/iconx.tsx","./src/shared/defguard-ui/hooks/usesize.tsx","./src/shared/defguard-ui/hooks/theme/types.ts","./src/shared/defguard-ui/hooks/theme/usetheme.tsx","./src/shared/defguard-ui/hooks/theme/utils.ts","./src/shared/defguard-ui/hooks/toasts/usetoaststore.ts","./src/shared/defguard-ui/hooks/toasts/usetoaster.tsx","./src/shared/defguard-ui/utils/detectclickoutside.ts","./src/shared/defguard-ui/utils/iscomparable.ts","./src/shared/defguard-ui/utils/ispresent.ts","./src/shared/helpers/displaydate.ts","./src/shared/helpers/getuserfullname.ts","./src/shared/helpers/useeffectonce.ts","./src/shared/hooks/useapi.tsx","./src/shared/hooks/useclipboard.tsx","./src/shared/hooks/usetoaster.tsx","./src/shared/hooks/api/api-client.ts","./src/shared/hooks/api/api.ts","./src/shared/hooks/api/axios-client.ts","./src/shared/hooks/api/provider.tsx","./src/shared/hooks/api/store.ts","./src/shared/hooks/store/useappstore.ts","./src/shared/hooks/store/useauthstore.ts","./src/shared/hooks/store/useenterpriseupgradestore.tsx","./src/shared/hooks/store/usemodalstore.ts","./src/shared/hooks/store/useopenidclientstore.tsx","./src/shared/hooks/store/useupdatesstore.tsx","./src/shared/hooks/store/useuserprofilestore.ts","./src/shared/utils/chainname.ts","./src/shared/utils/checkplatform.ts","./src/shared/utils/convertfromstringtobuffer.ts","./src/shared/utils/detectclickoutside.ts","./src/shared/utils/downloadwgconfig.ts","./src/shared/utils/extractinitials.ts","./src/shared/utils/generatewgkeys.ts","./src/shared/utils/invalidatemultiplequeries.ts","./src/shared/utils/localetodatepicker.ts","./src/shared/utils/omitnull.ts","./src/shared/utils/removeemptystrings.ts","./src/shared/utils/removenulls.ts","./src/shared/utils/searchbykeys.ts","./src/shared/utils/sortbydate.ts","./src/shared/utils/stringtoblob.ts","./src/shared/utils/titlecase.ts","./src/shared/utils/trimobjectstrings.ts","./src/shared/utils/form/selectifynetwork.ts","./src/shared/validators/password.ts"],"errors":true,"version":"5.8.3"} \ No newline at end of file diff --git a/web/tsconfig.json b/web/tsconfig.json index 7a38057a8..1ffef600d 100644 --- a/web/tsconfig.json +++ b/web/tsconfig.json @@ -1,14 +1,7 @@ { "files": [], - "exclude": [ - "./src/i18n/**" - ], "references": [ - { - "path": "./tsconfig.app.json" - }, - { - "path": "./tsconfig.node.json" - } + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } ] } diff --git a/web/tsconfig.node.json b/web/tsconfig.node.json index 065635473..8a67f62f4 100644 --- a/web/tsconfig.node.json +++ b/web/tsconfig.node.json @@ -1,26 +1,26 @@ { "compilerOptions": { "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", - "target": "ES2022", - "lib": [ - "ES2023" - ], + "target": "ES2023", + "lib": ["ES2023"], "module": "ESNext", + "types": ["node"], "skipLibCheck": true, + /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, - "isolatedModules": true, + "verbatimModuleSyntax": true, "moduleDetection": "force", "noEmit": true, + /* Linting */ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true - // "noUncheckedSideEffectImports": true + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true }, - "include": [ - "vite.config.mts" - ] + "include": ["vite.config.ts"] } diff --git a/web/vite.config.mts b/web/vite.config.mts deleted file mode 100644 index 6d699f994..000000000 --- a/web/vite.config.mts +++ /dev/null @@ -1,74 +0,0 @@ -import 'dotenv/config'; - -import react from '@vitejs/plugin-react-swc'; -import autoprefixer from 'autoprefixer'; -import * as path from 'path'; -import { defineConfig } from 'vite'; - -// eslint-disable-next-line no-empty-pattern -export default ({}) => { - let proxyTarget = 'http://127.0.0.1:8000'; - const envProxyTarget = process.env.PROXY_TARGET; - - if (envProxyTarget && envProxyTarget.length > 0) { - proxyTarget = envProxyTarget; - } - - return defineConfig({ - clearScreen: false, - plugins: [react()], - server: { - strictPort: false, - port: 3000, - cors: true, - proxy: { - '/api': { - target: proxyTarget, - changeOrigin: true, - secure: false, - }, - '/.well-known': { - target: proxyTarget, - changeOrigin: true, - secure: false, - }, - '/svg': { - target: proxyTarget, - changeOrigin: true, - secure: false, - }, - }, - fs: { - allow: ['.'], - }, - }, - envPrefix: ['VITE_'], - assetsInclude: ['./src/shared/assets/**/*'], - resolve: { - alias: { - '@scss': path.resolve(__dirname, './src/shared/scss'), - '@scssutils': path.resolve(__dirname, './src/shared/scss/global'), - }, - }, - build: { - chunkSizeWarningLimit: 10000, - rollupOptions: { - logLevel: 'silent', - // eslint-disable-next-line @typescript-eslint/no-unused-vars - onwarn: (_warning, _warn) => { - return; - }, - }, - }, - css: { - preprocessorOptions: { - scss: { - additionalData: `@use "@scssutils" as *;\n`, - }, - }, - postcss: { - plugins: [autoprefixer], - }, - }, - }); -}; diff --git a/web/vite.config.ts b/web/vite.config.ts new file mode 100644 index 000000000..664ff0f40 --- /dev/null +++ b/web/vite.config.ts @@ -0,0 +1,101 @@ +import { devtools } from '@tanstack/devtools-vite'; +import { paraglideVitePlugin } from '@inlang/paraglide-js'; +import { defineConfig, loadEnv, type ProxyOptions } from 'vite'; +import { ViteImageOptimizer } from 'vite-plugin-image-optimizer'; +import { tanstackRouter } from '@tanstack/router-plugin/vite'; +import autoprefixer from 'autoprefixer'; +import react from '@vitejs/plugin-react-swc'; +import * as path from 'path'; + +const isEnvTrue = (val: string | null | undefined) => { + if (typeof val === 'string' && val.length > 0) { + return ['true', '1', 'yes'].includes(val.toLowerCase().trim()); + } + return false; +}; + +// https://vite.dev/config/ +export default defineConfig(({ mode }) => { + const env = loadEnv(mode, process.cwd(), ''); + + const proxyOptions: Record = {}; + const proxyUpdateServiceTarget = env.UPDATE_TARGET_URL; + const proxyApiTargetEnv = env.PROXY_API_TARGET; + const proxyApiSecureEnv = env.PROXY_API_SECURE; + + if (mode === 'development') { + // update service + if (proxyUpdateServiceTarget && proxyUpdateServiceTarget.length) { + proxyOptions['/update'] = { + target: proxyUpdateServiceTarget, + changeOrigin: true, + secure: true, + rewrite: (path) => path.replace(/^\/update/, ''), + }; + } + // api + + let apiTarget: string; + const apiSecure = isEnvTrue(proxyApiSecureEnv); + if (proxyApiTargetEnv && proxyApiTargetEnv.length) { + apiTarget = proxyApiTargetEnv; + } else { + apiTarget = 'http://127.0.0.1:8000'; + } + proxyOptions['/api'] = { + target: apiTarget, + changeOrigin: true, + secure: apiSecure, + }; + proxyOptions['/.well-known'] = { + target: apiTarget, + changeOrigin: true, + secure: apiSecure, + }; + } + + return { + server: { + strictPort: true, + port: 3000, + cors: true, + proxy: { + ...proxyOptions, + }, + }, + plugins: [ + devtools(), + paraglideVitePlugin({ + project: './project.inlang', + outdir: './src/paraglide', + strategy: ['localStorage', 'preferredLanguage', 'baseLocale'], + }), + tanstackRouter({ + target: 'react', + autoCodeSplitting: true, + }), + ViteImageOptimizer({ + test: /\.(jpe?g|png|gif|tiff|webp|avif)$/i, + }), + react(), + ], + resolve: { + alias: { + '@scssutils': path.resolve(__dirname, './src/shared/defguard-ui/scss/global'), + }, + }, + css: { + preprocessorOptions: { + scss: { + additionalData: `@use "@scssutils" as *;\n`, + }, + }, + postcss: { + plugins: [autoprefixer({})], + }, + }, + build: { + chunkSizeWarningLimit: 2500, + }, + }; +});