Merge branch 'ADG-9415' of https://bit.int.agrd.dev/scm/dns/adguard-home into ADG-9415

This commit is contained in:
Igor Lobanov
2025-02-10 09:43:32 +01:00
22 changed files with 366 additions and 295 deletions

46
client/package-lock.json generated vendored
View File

@@ -52,7 +52,7 @@
"@babel/plugin-transform-runtime": "^7.24.3",
"@babel/preset-env": "^7.24.5",
"@babel/preset-react": "^7.24.1",
"@playwright/test": "^1.49.1",
"@playwright/test": "1.50.1",
"@types/lodash": "^4.17.4",
"@types/node": "^22.10.2",
"@types/react": "^17.0.80",
@@ -3195,13 +3195,13 @@
}
},
"node_modules/@playwright/test": {
"version": "1.49.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.49.1.tgz",
"integrity": "sha512-Ky+BVzPz8pL6PQxHqNRW1k3mIyv933LML7HktS8uik0bUXNCdPhoS/kLihiO1tMf/egaJb4IutXd7UywvXEW+g==",
"version": "1.50.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.50.1.tgz",
"integrity": "sha512-Jii3aBg+CEDpgnuDxEp/h7BimHcUTDlpEtce89xEumlJ5ef2hqepZ+PWp1DDpYC/VO9fmWVI1IlEaoI5fK9FXQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright": "1.49.1"
"playwright": "1.50.1"
},
"bin": {
"playwright": "cli.js"
@@ -12575,13 +12575,13 @@
}
},
"node_modules/playwright": {
"version": "1.49.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.1.tgz",
"integrity": "sha512-VYL8zLoNTBxVOrJBbDuRgDWa3i+mfQgDTrL8Ah9QXZ7ax4Dsj0MSq5bYgytRnDVVe+njoKnfsYkH3HzqVj5UZA==",
"version": "1.50.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.50.1.tgz",
"integrity": "sha512-G8rwsOQJ63XG6BbKj2w5rHeavFjy5zynBA9zsJMMtBoe/Uf757oG12NXz6e6OirF7RCrTVAKFXbLmn1RbL7Qaw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.49.1"
"playwright-core": "1.50.1"
},
"bin": {
"playwright": "cli.js"
@@ -12594,9 +12594,9 @@
}
},
"node_modules/playwright-core": {
"version": "1.49.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.1.tgz",
"integrity": "sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg==",
"version": "1.50.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.50.1.tgz",
"integrity": "sha512-ra9fsNWayuYumt+NiM069M6OkcRb1FZSK8bgi66AtpFoWkg2+y0bJSNmkFrWhMbEBbVKC/EruAHH3g0zmtwGmQ==",
"dev": true,
"license": "Apache-2.0",
"bin": {
@@ -20103,12 +20103,12 @@
"dev": true
},
"@playwright/test": {
"version": "1.49.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.49.1.tgz",
"integrity": "sha512-Ky+BVzPz8pL6PQxHqNRW1k3mIyv933LML7HktS8uik0bUXNCdPhoS/kLihiO1tMf/egaJb4IutXd7UywvXEW+g==",
"version": "1.50.1",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.50.1.tgz",
"integrity": "sha512-Jii3aBg+CEDpgnuDxEp/h7BimHcUTDlpEtce89xEumlJ5ef2hqepZ+PWp1DDpYC/VO9fmWVI1IlEaoI5fK9FXQ==",
"dev": true,
"requires": {
"playwright": "1.49.1"
"playwright": "1.50.1"
}
},
"@rollup/rollup-android-arm-eabi": {
@@ -27071,13 +27071,13 @@
}
},
"playwright": {
"version": "1.49.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.49.1.tgz",
"integrity": "sha512-VYL8zLoNTBxVOrJBbDuRgDWa3i+mfQgDTrL8Ah9QXZ7ax4Dsj0MSq5bYgytRnDVVe+njoKnfsYkH3HzqVj5UZA==",
"version": "1.50.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.50.1.tgz",
"integrity": "sha512-G8rwsOQJ63XG6BbKj2w5rHeavFjy5zynBA9zsJMMtBoe/Uf757oG12NXz6e6OirF7RCrTVAKFXbLmn1RbL7Qaw==",
"dev": true,
"requires": {
"fsevents": "2.3.2",
"playwright-core": "1.49.1"
"playwright-core": "1.50.1"
},
"dependencies": {
"fsevents": {
@@ -27090,9 +27090,9 @@
}
},
"playwright-core": {
"version": "1.49.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.49.1.tgz",
"integrity": "sha512-BzmpVcs4kE2CH15rWfzpjzVGhWERJfmnXmniSyKeRZUs9Ws65m+RGIi7mjJK/euCegfn3i7jvqWeWyHe9y3Vgg==",
"version": "1.50.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.50.1.tgz",
"integrity": "sha512-ra9fsNWayuYumt+NiM069M6OkcRb1FZSK8bgi66AtpFoWkg2+y0bJSNmkFrWhMbEBbVKC/EruAHH3g0zmtwGmQ==",
"dev": true
},
"popper.js": {

2
client/package.json vendored
View File

@@ -65,7 +65,7 @@
"@babel/plugin-transform-runtime": "^7.24.3",
"@babel/preset-env": "^7.24.5",
"@babel/preset-react": "^7.24.1",
"@playwright/test": "^1.49.1",
"@playwright/test": "1.50.1",
"@types/lodash": "^4.17.4",
"@types/node": "^22.10.2",
"@types/react": "^17.0.80",

View File

@@ -10,6 +10,7 @@ import Card from '../ui/Card';
import DomainCell from './DomainCell';
import { DASHBOARD_TABLES_DEFAULT_PAGE_SIZE, TABLES_MIN_ROWS } from '../../helpers/constants';
import { formatNumber } from '../../helpers/helpers';
interface TimeCellProps {
value?: string | number;
@@ -20,7 +21,7 @@ const TimeCell = ({ value }: TimeCellProps) => {
return '';
}
const valueInMilliseconds = round(Number(value) * 1000);
const valueInMilliseconds = formatNumber(round(Number(value) * 1000));
return (
<div className="logs__row o-hidden">

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useRef } from 'react';
import React from 'react';
import { Trans, useTranslation } from 'react-i18next';
@@ -92,19 +92,7 @@ export type EncryptionFormValues = {
type Props = {
initialValues: EncryptionFormValues;
processingConfig: boolean;
processingValidate: boolean;
status_key?: string;
not_after?: string;
warning_validation?: string;
valid_chain?: boolean;
valid_key?: boolean;
valid_cert?: boolean;
valid_pair?: boolean;
dns_names?: string[];
key_type?: string;
issuer?: string;
subject?: string;
encryption: EncryptionData;
onSubmit: (values: EncryptionFormValues) => void;
debouncedConfigValidation: (values: EncryptionFormValues) => void;
setTlsConfig: (values: Partial<EncryptionData>) => void;
@@ -130,24 +118,28 @@ const defaultValues = {
export const Form = ({
initialValues,
processingConfig,
processingValidate,
not_after,
valid_chain,
valid_key,
valid_cert,
valid_pair,
dns_names,
key_type,
issuer,
subject,
warning_validation,
encryption,
onSubmit,
setTlsConfig,
debouncedConfigValidation,
validateTlsConfig,
}: Props) => {
const { t } = useTranslation();
const previousValuesRef = useRef<EncryptionFormValues>(initialValues);
const {
not_after,
valid_chain,
valid_key,
valid_cert,
valid_pair,
dns_names,
key_type,
issuer,
subject,
warning_validation,
processingConfig,
processingValidate,
} = encryption;
const {
control,
@@ -166,8 +158,6 @@ export const Form = ({
mode: 'onBlur',
});
const watchedValues = watch();
const {
enabled: isEnabled,
serve_plain_dns: servePlainDns,
@@ -178,16 +168,11 @@ export const Form = ({
private_key_saved: privateKeySaved,
certificate_path: certificatePath,
certificate_source: certificateSource,
} = watchedValues;
} = watch();
useEffect(() => {
const previousValues = previousValuesRef.current;
if (JSON.stringify(previousValues) !== JSON.stringify(watchedValues)) {
// TODO(ik) onChange TLS config validation
previousValuesRef.current = watchedValues;
}
}, [watchedValues]);
const handleBlur = () => {
debouncedConfigValidation(getValues());
};
const isSavingDisabled = () => {
const processing = isSubmitting || processingConfig || processingValidate;
@@ -243,7 +228,9 @@ export const Form = ({
<Controller
name="enabled"
control={control}
render={({ field }) => <Checkbox {...field} title={t('encryption_enable')} />}
render={({ field }) => (
<Checkbox {...field} title={t('encryption_enable')} onBlur={handleBlur} />
)}
/>
</div>
@@ -288,6 +275,7 @@ export const Form = ({
placeholder={t('encryption_server_enter')}
error={fieldState.error?.message}
disabled={!isEnabled}
onBlur={handleBlur}
/>
)}
/>
@@ -337,6 +325,7 @@ export const Form = ({
const { value } = e.target;
field.onChange(toNumber(value));
}}
onBlur={handleBlur}
/>
)}
/>
@@ -368,6 +357,7 @@ export const Form = ({
const { value } = e.target;
field.onChange(toNumber(value));
}}
onBlur={handleBlur}
/>
)}
/>
@@ -399,6 +389,7 @@ export const Form = ({
const { value } = e.target;
field.onChange(toNumber(value));
}}
onBlur={handleBlur}
/>
)}
/>
@@ -457,6 +448,7 @@ export const Form = ({
placeholder={t('encryption_certificates_input')}
disabled={!isEnabled}
error={fieldState.error?.message}
onBlur={handleBlur}
/>
)}
/>
@@ -471,6 +463,7 @@ export const Form = ({
placeholder={t('encryption_certificate_path')}
error={fieldState.error?.message}
disabled={!isEnabled}
onBlur={handleBlur}
/>
)}
/>
@@ -527,6 +520,7 @@ export const Form = ({
}
field.onChange(checked);
}}
onBlur={handleBlur}
/>
)}
/>
@@ -540,6 +534,7 @@ export const Form = ({
placeholder={t('encryption_key_input')}
disabled={!isEnabled || privateKeySaved}
error={fieldState.error?.message}
onBlur={handleBlur}
/>
)}
/>
@@ -555,6 +550,7 @@ export const Form = ({
placeholder={t('encryption_private_key_path')}
error={fieldState.error?.message}
disabled={!isEnabled}
onBlur={handleBlur}
/>
)}
/>

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useCallback, useMemo } from 'react';
import React, { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { debounce } from 'lodash';
import { DEBOUNCE_TIMEOUT, ENCRYPTION_SOURCE } from '../../../helpers/constants';
@@ -18,19 +18,37 @@ type Props = {
export const Encryption = ({ encryption, setTlsConfig, validateTlsConfig }: Props) => {
const { t } = useTranslation();
useEffect(() => {
if (encryption.enabled) {
validateTlsConfig(encryption);
}
}, [encryption, validateTlsConfig]);
const initialValues = useMemo((): EncryptionFormValues => {
const { certificate_chain, private_key, private_key_saved } = encryption;
const {
enabled,
serve_plain_dns,
server_name,
force_https,
port_https,
port_dns_over_tls,
port_dns_over_quic,
certificate_chain,
private_key,
certificate_path,
private_key_path,
private_key_saved,
} = encryption;
const certificate_source = certificate_chain ? ENCRYPTION_SOURCE.CONTENT : ENCRYPTION_SOURCE.PATH;
const key_source = private_key || private_key_saved ? ENCRYPTION_SOURCE.CONTENT : ENCRYPTION_SOURCE.PATH;
return {
...encryption,
enabled,
serve_plain_dns,
server_name,
force_https,
port_https,
port_dns_over_tls,
port_dns_over_quic,
certificate_chain,
private_key,
certificate_path,
private_key_path,
private_key_saved,
certificate_source,
key_source,
};
@@ -75,7 +93,7 @@ export const Encryption = ({ encryption, setTlsConfig, validateTlsConfig }: Prop
}
}, []);
const debouncedConfigValidation = useCallback(debounce(validateConfig, DEBOUNCE_TIMEOUT), [validateConfig]);
const debouncedConfigValidation = useMemo(() => debounce(validateConfig, DEBOUNCE_TIMEOUT), [validateConfig]);
return (
<div className="encryption">
@@ -94,7 +112,7 @@ export const Encryption = ({ encryption, setTlsConfig, validateTlsConfig }: Prop
debouncedConfigValidation={debouncedConfigValidation}
setTlsConfig={setTlsConfig}
validateTlsConfig={validateTlsConfig}
{...encryption}
encryption={encryption}
/>
</Card>
)}

View File

@@ -12,10 +12,14 @@ type Props = {
className?: string;
error?: string;
onChange: (value: boolean) => void;
onBlur?: () => void;
};
export const Checkbox = forwardRef<HTMLInputElement, Props>(
({ title, subtitle, value, name, disabled, error, className = 'checkbox--form', onChange, ...rest }, ref) => (
(
{ title, subtitle, value, name, disabled, error, className = 'checkbox--form', onChange, onBlur, ...rest },
ref,
) => (
<>
<label className={clsx('checkbox', className)}>
<span className="checkbox__marker" />
@@ -26,6 +30,7 @@ export const Checkbox = forwardRef<HTMLInputElement, Props>(
disabled={disabled}
checked={value}
onChange={(e) => onChange(e.target.checked)}
onBlur={onBlur}
ref={ref}
{...rest}
/>

View File

@@ -453,7 +453,7 @@ export const getParamsForClientsSearch = (data: any, param: any, additionalParam
});
return {
clients: Array.from(clients).map(id => ({ id })),
clients: Array.from(clients).map((id) => ({ id })),
};
};
@@ -670,9 +670,16 @@ export const countClientsStatistics = (ids: any, autoClients: any) => {
* @param {function} t translate
* @returns {string}
*/
export const formatElapsedMs = (elapsedMs: any, t: any) => {
const formattedElapsedMs = parseInt(elapsedMs, 10) || parseFloat(elapsedMs).toFixed(2);
return `${formattedElapsedMs} ${t('milliseconds_abbreviation')}`;
export const formatElapsedMs = (elapsedMs: string, t: (key: string) => string) => {
const parsedElapsedMs = parseInt(elapsedMs, 10);
if (Number.isNaN(parsedElapsedMs)) {
return elapsedMs;
}
const formattedMs = formatNumber(parsedElapsedMs);
return `${formattedMs} ${t('milliseconds_abbreviation')}`;
};
/**