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