From 2c123ddc8ef54b8fe4c6ab1e6930abff0adfb5c3 Mon Sep 17 00:00:00 2001 From: Kuba <78603704+jakub-tldr@users.noreply.github.com> Date: Tue, 24 Mar 2026 21:56:29 +0100 Subject: [PATCH 1/2] map instead of replaceall --- web/src/shared/validate.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/src/shared/validate.ts b/web/src/shared/validate.ts index fa7c07c4b..0b5167852 100644 --- a/web/src/shared/validate.ts +++ b/web/src/shared/validate.ts @@ -118,7 +118,7 @@ export const Validate = { if (!value) { return true; } - const items = value.replaceAll(' ', '').split(splitWith); + const items = value.split(splitWith).map((item) => item.trim()); if (items.length > 1 && !allowList) { return false; @@ -148,7 +148,7 @@ export const Validate = { if (!value) { return true; } - const items = value.replaceAll(' ', '').split(splitWith); + const items = value.split(splitWith).map((item) => item.trim()); if (items.length > 1 && !allowList) { return false; From bff47e92c6e251042924bc9d884f3ea0738e8815 Mon Sep 17 00:00:00 2001 From: Kuba <78603704+jakub-tldr@users.noreply.github.com> Date: Tue, 24 Mar 2026 22:26:32 +0100 Subject: [PATCH 2/2] display toaster when creating network which can't contain existing devices --- .../steps/AddLocationInternalVpnStep.tsx | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/web/src/pages/AddLocationPage/steps/AddLocationInternalVpnStep.tsx b/web/src/pages/AddLocationPage/steps/AddLocationInternalVpnStep.tsx index 4d4bc5295..3225e56e8 100644 --- a/web/src/pages/AddLocationPage/steps/AddLocationInternalVpnStep.tsx +++ b/web/src/pages/AddLocationPage/steps/AddLocationInternalVpnStep.tsx @@ -1,11 +1,15 @@ +import { useQuery } from '@tanstack/react-query'; +import { toNumber } from 'lodash-es'; import z from 'zod'; import { useShallow } from 'zustand/react/shallow'; import { m } from '../../../paraglide/messages'; +import api from '../../../shared/api/api'; import { Controls } from '../../../shared/components/Controls/Controls'; import { DescriptionBlock } from '../../../shared/components/DescriptionBlock/DescriptionBlock'; import { WizardCard } from '../../../shared/components/wizard/WizardCard/WizardCard'; import { Button } from '../../../shared/defguard-ui/components/Button/Button'; import { SizedBox } from '../../../shared/defguard-ui/components/SizedBox/SizedBox'; +import { Snackbar } from '../../../shared/defguard-ui/providers/snackbar/snackbar'; import { ThemeSpacing } from '../../../shared/defguard-ui/types'; import { useAppForm } from '../../../shared/form'; import { formChangeLogic } from '../../../shared/formLogic'; @@ -13,6 +17,17 @@ import { Validate } from '../../../shared/validate'; import { AddLocationPageStep } from '../types'; import { useAddLocationStore } from '../useAddLocationStore'; +const networkSize = (network_address: string): number => { + let minimal_cidr = 32; + for (const address of network_address.split(',')) { + const cidr = toNumber(address.trim().split('/')[1]); + if (cidr < minimal_cidr) { + minimal_cidr = cidr; + } + } + return 2 ** (32 - minimal_cidr) - 3; +}; + const formSchema = z.object({ address: z .string(m.form_error_required()) @@ -56,6 +71,12 @@ const formSchema = z.object({ type FormFields = z.infer; export const AddLocationInternalVpnStep = () => { + const { data: devices } = useQuery({ + queryKey: ['device', 'all'], + queryFn: api.device.getDevices, + select: (resp) => resp.data, + }); + const defaultValues = useAddLocationStore( useShallow( (s): FormFields => ({ @@ -73,6 +94,14 @@ export const AddLocationInternalVpnStep = () => { onChange: formSchema, }, onSubmit: ({ value }) => { + const deviceCount = Array.isArray(devices) ? devices.length : 0; + const network_size = networkSize(value.address); + if (deviceCount > network_size) { + Snackbar.error( + `The network is too small to accommodate all existing devices (network capacity: ${network_size}, total devices: ${deviceCount}).`, + ); + return; + } useAddLocationStore.setState({ ...value, allowed_ips: value.allowed_ips ?? '',