diff --git a/client_v3/src/__locales/en.json b/client_v3/src/__locales/en.json index d097cdcbf..c76abb121 100644 --- a/client_v3/src/__locales/en.json +++ b/client_v3/src/__locales/en.json @@ -75,6 +75,7 @@ "client_id_placeholder": "Enter a ClientID", "client_added": "Client added", "client_removed": "Client removed", + "client_name_already_exists": "Client with this name already exists", "download_mobileconfig": "Download configuration file", "plain_dns": "Plain DNS", "rate_limit_subnet_len_ipv4_error": "The IPv4 subnet prefix length should be between 0 and 32", @@ -634,6 +635,7 @@ "dhcp_table_mac_address": "MAC address", "dhcp_table_ip_address": "IP address", "dhcp_table_hostname": "Hostname", + "dhcp_mac_address_already_added": "This MAC address is already added", "reset_settings": "Reset settings", "reset_settings_confirm": "Yes, reset", "actions_table_header": "Actions", @@ -790,6 +792,7 @@ "notify_undo": "Undo", "user_rules_domain_is_allowed": "Domain is allowed", "user_rules_rule_added_to_custom_filtering_rules": "Rule added to custom filtering rules", + "user_rules_rule_added": "User rule added: %rule%", "user_rules_disable_parental_control": "Disable Parental control", "user_rules_parental_control_disabled": "Parental control disabled", "user_rules_disable_browsing_security": "Disable Browsing security", diff --git a/client_v3/src/components/Clients/AddClient/AddClient.tsx b/client_v3/src/components/Clients/AddClient/AddClient.tsx index 970c6ec33..d2bf18324 100644 --- a/client_v3/src/components/Clients/AddClient/AddClient.tsx +++ b/client_v3/src/components/Clients/AddClient/AddClient.tsx @@ -1,4 +1,4 @@ -import { createMemo, createEffect, Show, onMount } from 'solid-js'; +import { createMemo, createEffect, createSignal, Show, onMount } from 'solid-js'; import { useNavigate, useParams, useLocation } from '@solidjs/router'; import cn from 'clsx'; @@ -37,11 +37,24 @@ export const AddClient = () => { const params = useParams<{ clientName?: string }>(); const location = useLocation(); + const [nameError, setNameError] = createSignal(); + + createEffect(() => { + const formErrors = clientFormState.formErrors; + setNameError( + typeof formErrors.name === 'string' ? formErrors.name : undefined, + ); + }); + onMount(() => { getClients(); }); - // Set initial ID from query params + const handleNameChange = (e: Event) => { + const value = (e.target as HTMLInputElement).value; + updateClientFormField({ field: 'name', value }); + setNameError(undefined); + }; createEffect(() => { const searchParams = new URLSearchParams(location.search); const id = searchParams.get('id'); @@ -180,27 +193,13 @@ export const AddClient = () => { data-testid="client-form-name" type="text" value={clientFormState.name} - onChange={(e: Event) => - updateClientFormField({ - field: 'name', - value: (e.target as HTMLInputElement).value, - }) - } - onInput={(e: Event) => - updateClientFormField({ - field: 'name', - value: (e.target as HTMLInputElement).value, - }) - } + onChange={handleNameChange} + onInput={handleNameChange} placeholder={intl.getMessage('clients_add_default_name')} label={intl.getMessage('clients_add_name')} size="large" - error={!!clientFormState.formErrors.name} - errorMessage={ - typeof clientFormState.formErrors.name === 'string' - ? clientFormState.formErrors.name - : undefined - } + error={!!nameError()} + errorMessage={nameError()} /> diff --git a/client_v3/src/components/Dhcp/Dhcp.tsx b/client_v3/src/components/Dhcp/Dhcp.tsx index 0700233ad..6d342b360 100644 --- a/client_v3/src/components/Dhcp/Dhcp.tsx +++ b/client_v3/src/components/Dhcp/Dhcp.tsx @@ -176,7 +176,7 @@ export const Dhcp = () => { const handleLeaseModalSubmit = (data: LeaseData) => { if (dhcpState.modalType === MODAL_TYPE.EDIT_LEASE) { - updateStaticLease({ target: dhcpState.leaseModalConfig!, update: data }); + updateStaticLease(data); } else { addStaticLease(data); } diff --git a/client_v3/src/components/DnsSettings/ServerConfig/blocks/Form/Form.tsx b/client_v3/src/components/DnsSettings/ServerConfig/blocks/Form/Form.tsx index 59ab2ff45..24c605f10 100644 --- a/client_v3/src/components/DnsSettings/ServerConfig/blocks/Form/Form.tsx +++ b/client_v3/src/components/DnsSettings/ServerConfig/blocks/Form/Form.tsx @@ -176,7 +176,7 @@ export const Form = (props: Props) => { setWhitelistError( ratelimitWhitelist() ? validateIpPerLine(ratelimitWhitelist()) || '' : '', ); - setTtlError(validateRequiredValue(String(blockedResponseTtl())) || ''); + setTtlError(validateRequiredValue(String(blockedResponseTtl())) || validateBetween(blockedResponseTtl(), UINT32_RANGE.MIN, UINT32_RANGE.MAX) || ''); if (ednsCsUseCustom()) { const err = diff --git a/client_v3/src/components/DnsSettings/Upstream/blocks/Form/Form.tsx b/client_v3/src/components/DnsSettings/Upstream/blocks/Form/Form.tsx index f9a5f89e6..6c6c9444e 100644 --- a/client_v3/src/components/DnsSettings/Upstream/blocks/Form/Form.tsx +++ b/client_v3/src/components/DnsSettings/Upstream/blocks/Form/Form.tsx @@ -11,7 +11,7 @@ import { Button } from 'panel/common/ui/Button'; import { FaqTooltip } from 'panel/common/ui/FaqTooltip'; import intl from 'panel/common/intl'; import { DNS_REQUEST_OPTIONS, UINT32_RANGE, UPSTREAM_TIMEOUT } from 'panel/helpers/constants'; -import { validateMinValue, validateUpstreams } from 'panel/helpers/validators'; +import { validateMinValue, validateMaxValue, validateUpstreams } from 'panel/helpers/validators'; import theme from 'panel/lib/theme'; import { Examples } from '../Examples'; @@ -124,7 +124,9 @@ export const Form = (props: FormProps) => { }; const validateUpstreamTimeout = () => { - const err = validateMinValue(upstreamTimeout(), UPSTREAM_TIMEOUT.MIN); + const err = + validateMinValue(upstreamTimeout(), UPSTREAM_TIMEOUT.MIN) || + validateMaxValue(upstreamTimeout(), UPSTREAM_TIMEOUT.MAX); setUpstreamTimeoutError(err || ''); }; diff --git a/client_v3/src/components/FilterLists/blocks/ListsTable/ListsTable.module.pcss b/client_v3/src/components/FilterLists/blocks/ListsTable/ListsTable.module.pcss index f7930af07..11153be0e 100644 --- a/client_v3/src/components/FilterLists/blocks/ListsTable/ListsTable.module.pcss +++ b/client_v3/src/components/FilterLists/blocks/ListsTable/ListsTable.module.pcss @@ -153,7 +153,6 @@ .cellNameLabel { flex: 1; min-width: 0; - padding-top: 8px; text-align: left; word-break: break-word; overflow: hidden; diff --git a/client_v3/src/components/FilterLists/blocks/RewritesTable/RewritesTable.tsx b/client_v3/src/components/FilterLists/blocks/RewritesTable/RewritesTable.tsx index ef55da765..72ed28f0a 100644 --- a/client_v3/src/components/FilterLists/blocks/RewritesTable/RewritesTable.tsx +++ b/client_v3/src/components/FilterLists/blocks/RewritesTable/RewritesTable.tsx @@ -74,7 +74,7 @@ export const RewritesTable = (props: Props) => { }, accessor: 'enabled', sortable: false, - fitContent: true, + width: 64, className: s.cellNameToggleOuter, render: (value: boolean, row: Rewrite) => { const { domain, enabled } = row; diff --git a/client_v3/src/components/QueryLog/blocks/Header/Header.tsx b/client_v3/src/components/QueryLog/blocks/Header/Header.tsx index 72e024193..e7e75655c 100644 --- a/client_v3/src/components/QueryLog/blocks/Header/Header.tsx +++ b/client_v3/src/components/QueryLog/blocks/Header/Header.tsx @@ -1,4 +1,4 @@ -import { createSignal, createMemo, createEffect, on, onCleanup, Show } from 'solid-js'; +import { createSignal, createMemo, createEffect, on, onCleanup, Show, untrack } from 'solid-js'; import cn from 'clsx'; import intl from 'panel/common/intl'; @@ -105,7 +105,7 @@ const REASON_OPTIONS = [ ]; export const Header = (props: Props) => { - const [searchValue, setSearchValue] = createSignal(props.currentSearch); + const [searchValue, setSearchValue] = createSignal(untrack(() => props.currentSearch)); let debounceTimer: ReturnType | null = null; const isMobile = useIsMobile(); diff --git a/client_v3/src/helpers/validators.ts b/client_v3/src/helpers/validators.ts index b21995cff..9f33f6343 100644 --- a/client_v3/src/helpers/validators.ts +++ b/client_v3/src/helpers/validators.ts @@ -594,7 +594,7 @@ export const validateMacNotDuplicate = (existingLeases: LeaseEntry[], editMac?: string): ((value?: string) => ValidationResult) => (value) => { if (value && value !== editMac && existingLeases.some((lease) => lease.mac === value)) { - return intl.getMessage('form_error_mac_already_added'); + return intl.getMessage('dhcp_mac_address_already_added'); } return undefined; }; diff --git a/client_v3/src/stores/clientForm.ts b/client_v3/src/stores/clientForm.ts index ae85cad70..baf786f30 100644 --- a/client_v3/src/stores/clientForm.ts +++ b/client_v3/src/stores/clientForm.ts @@ -147,6 +147,11 @@ export const computeExistingClientIds = (): string[] => .filter((c: Client) => state.mode !== 'edit' || c.name !== state.originalName) .flatMap((c: Client) => c.ids); +export const computeExistingClientNames = (): string[] => + (dashboardState.clients || []) + .filter((c: Client) => state.mode !== 'edit' || c.name !== state.originalName) + .map((c: Client) => c.name); + export const saveClient = async (): Promise => { const errors: Record = {}; @@ -154,6 +159,13 @@ export const saveClient = async (): Promise => { errors.name = intl.getMessage('form_error_required'); } + if (!errors.name) { + const existingClientNames = computeExistingClientNames(); + if (existingClientNames.includes(state.name.trim())) { + errors.name = intl.getMessage('client_name_already_exists'); + } + } + const existingClientIds = computeExistingClientIds(); const idErrors = state.ids.map((id: string, index: number) => { diff --git a/client_v3/src/stores/dhcp.ts b/client_v3/src/stores/dhcp.ts index 7b21466f4..eb52b3a9f 100644 --- a/client_v3/src/stores/dhcp.ts +++ b/client_v3/src/stores/dhcp.ts @@ -290,15 +290,15 @@ export const removeStaticLease = async (lease: Lease) => { } }; -export const updateStaticLease = async (config: { target: Lease; update: Lease }) => { +export const updateStaticLease = async (lease: Lease) => { setState('processingUpdating', true); try { - await apiClient.updateStaticLease(config); + await apiClient.updateStaticLease(lease); setState('processingUpdating', false); toggleLeaseModal(); addSuccessToast( intl.getMessage('dhcp_lease_updated', { - key: config.update.hostname || config.update.ip, + key: lease.hostname || lease.ip, }), ); await getDhcpStatus(); diff --git a/client_v3/src/stores/filtering.ts b/client_v3/src/stores/filtering.ts index c794b7fa4..48fdd7571 100644 --- a/client_v3/src/stores/filtering.ts +++ b/client_v3/src/stores/filtering.ts @@ -104,7 +104,7 @@ export const blockDomain = async (domain: string): Promise => { addSuccessToast( createUndoToast( - intl.getMessage('user_rules_rule_added_to_custom_filtering_rules'), + intl.getMessage('user_rules_rule_added', { rule }), intl.getMessage('notify_undo'), async () => { const didUndo = await setRules(previousRules); @@ -137,7 +137,7 @@ export const unblockDomain = async (domain: string): Promise => { addSuccessToast( createUndoToast( - intl.getMessage('user_rules_rule_added_to_custom_filtering_rules'), + intl.getMessage('user_rules_rule_added', { rule: desiredRule }), intl.getMessage('notify_undo'), async () => { const didUndo = await setRules(previousRules); @@ -225,7 +225,7 @@ export const toggleBlocking = async ( addSuccessToast( createUndoToast( - intl.getMessage('user_rules_rule_added_to_custom_filtering_rules'), + intl.getMessage('user_rules_rule_added', { rule: desiredRule }), intl.getMessage('notify_undo'), async () => { const didUndo = await setRules(previousRules);