fix validation issues

This commit is contained in:
Ildar Kamalov 2026-06-25 16:48:10 +03:00
parent 0e677a7349
commit 20158785f0
12 changed files with 50 additions and 35 deletions

View file

@ -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: <strong>%rule%</strong>",
"user_rules_disable_parental_control": "Disable Parental control",
"user_rules_parental_control_disabled": "Parental control disabled",
"user_rules_disable_browsing_security": "Disable Browsing security",

View file

@ -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<string | undefined>();
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()}
/>
</div>

View file

@ -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);
}

View file

@ -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 =

View file

@ -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 || '');
};

View file

@ -153,7 +153,6 @@
.cellNameLabel {
flex: 1;
min-width: 0;
padding-top: 8px;
text-align: left;
word-break: break-word;
overflow: hidden;

View file

@ -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;

View file

@ -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<typeof setTimeout> | null = null;
const isMobile = useIsMobile();

View file

@ -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;
};

View file

@ -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<boolean> => {
const errors: Record<string, string | string[]> = {};
@ -154,6 +159,13 @@ export const saveClient = async (): Promise<boolean> => {
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) => {

View file

@ -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();

View file

@ -104,7 +104,7 @@ export const blockDomain = async (domain: string): Promise<boolean> => {
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<boolean> => {
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);