fix forms
This commit is contained in:
@@ -1,47 +1,67 @@
|
||||
import React from 'react';
|
||||
import React, { ReactNode } from 'react';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
|
||||
import i18next from 'i18next';
|
||||
import { CLIENT_ID_LINK } from '../../../../helpers/constants';
|
||||
import { removeEmptyLines, trimMultilineString } from '../../../../helpers/helpers';
|
||||
import { Textarea } from '../../../ui/Controls/Textarea';
|
||||
|
||||
const fields = [
|
||||
type FormData = {
|
||||
allowed_clients: string;
|
||||
disallowed_clients: string;
|
||||
blocked_hosts: string;
|
||||
};
|
||||
|
||||
const fields: {
|
||||
id: keyof FormData;
|
||||
title: string;
|
||||
subtitle: ReactNode;
|
||||
normalizeOnBlur: (value: string) => string;
|
||||
}[] = [
|
||||
{
|
||||
id: 'allowed_clients',
|
||||
title: 'access_allowed_title',
|
||||
subtitle: 'access_allowed_desc',
|
||||
title: i18next.t('access_allowed_title'),
|
||||
subtitle: (
|
||||
<Trans
|
||||
components={{
|
||||
a: <a href={CLIENT_ID_LINK} target="_blank" rel="noopener noreferrer" />,
|
||||
}}>
|
||||
access_allowed_desc
|
||||
</Trans>
|
||||
),
|
||||
normalizeOnBlur: removeEmptyLines,
|
||||
},
|
||||
{
|
||||
id: 'disallowed_clients',
|
||||
title: 'access_disallowed_title',
|
||||
subtitle: 'access_disallowed_desc',
|
||||
title: i18next.t('access_disallowed_title'),
|
||||
subtitle: (
|
||||
<Trans
|
||||
components={{
|
||||
a: <a href={CLIENT_ID_LINK} target="_blank" rel="noopener noreferrer" />,
|
||||
}}>
|
||||
access_disallowed_desc
|
||||
</Trans>
|
||||
),
|
||||
normalizeOnBlur: trimMultilineString,
|
||||
},
|
||||
{
|
||||
id: 'blocked_hosts',
|
||||
title: 'access_blocked_title',
|
||||
subtitle: 'access_blocked_desc',
|
||||
title: i18next.t('access_blocked_title'),
|
||||
subtitle: i18next.t('access_blocked_desc'),
|
||||
normalizeOnBlur: removeEmptyLines,
|
||||
},
|
||||
];
|
||||
|
||||
interface FormProps {
|
||||
type FormProps = {
|
||||
initialValues?: {
|
||||
allowed_clients?: string;
|
||||
disallowed_clients?: string;
|
||||
blocked_hosts?: string;
|
||||
};
|
||||
onSubmit: (data: any) => void;
|
||||
onSubmit: (data: FormData) => void;
|
||||
processingSet: boolean;
|
||||
}
|
||||
|
||||
interface FormData {
|
||||
allowed_clients: string;
|
||||
disallowed_clients: string;
|
||||
blocked_hosts: string;
|
||||
}
|
||||
};
|
||||
|
||||
const Form = ({ initialValues, onSubmit, processingSet }: FormProps) => {
|
||||
const { t } = useTranslation();
|
||||
@@ -70,7 +90,7 @@ const Form = ({ initialValues, onSubmit, processingSet }: FormProps) => {
|
||||
}: {
|
||||
id: keyof FormData;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
subtitle: ReactNode;
|
||||
normalizeOnBlur: (value: string) => string;
|
||||
}) => {
|
||||
const disabled = allowedClients && id === 'disallowed_clients';
|
||||
@@ -78,22 +98,11 @@ const Form = ({ initialValues, onSubmit, processingSet }: FormProps) => {
|
||||
return (
|
||||
<div key={id} className="form__group mb-5">
|
||||
<label className="form__label form__label--with-desc" htmlFor={id}>
|
||||
{t(title)}
|
||||
{disabled && (
|
||||
<>
|
||||
<span> </span>({t('disabled')})
|
||||
</>
|
||||
)}
|
||||
{title}
|
||||
{disabled && <> ({t('disabled')})</>}
|
||||
</label>
|
||||
|
||||
<div className="form__desc form__desc--top">
|
||||
<Trans
|
||||
components={{
|
||||
a: <a href={CLIENT_ID_LINK} target="_blank" rel="noopener noreferrer" />,
|
||||
}}>
|
||||
{subtitle}
|
||||
</Trans>
|
||||
</div>
|
||||
<div className="form__desc form__desc--top">{subtitle}</div>
|
||||
|
||||
<Controller
|
||||
name={id}
|
||||
@@ -102,6 +111,7 @@ const Form = ({ initialValues, onSubmit, processingSet }: FormProps) => {
|
||||
<Textarea
|
||||
{...field}
|
||||
id={id}
|
||||
data-testid={id}
|
||||
disabled={disabled || processingSet}
|
||||
onBlur={(e) => {
|
||||
field.onChange(normalizeOnBlur(e.target.value));
|
||||
@@ -115,21 +125,13 @@ const Form = ({ initialValues, onSubmit, processingSet }: FormProps) => {
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
{fields.map((f) =>
|
||||
renderField(
|
||||
f as {
|
||||
id: keyof FormData;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
normalizeOnBlur: (value: string) => string;
|
||||
},
|
||||
),
|
||||
)}
|
||||
{fields.map((f) => renderField(f))}
|
||||
|
||||
<div className="card-actions">
|
||||
<div className="btn-list">
|
||||
<button
|
||||
type="submit"
|
||||
data-testid="access_save"
|
||||
className="btn btn-success btn-standard"
|
||||
disabled={isSubmitting || !isDirty || processingSet}>
|
||||
{t('save_config')}
|
||||
|
||||
@@ -1,31 +1,33 @@
|
||||
import React from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import i18next from 'i18next';
|
||||
import { clearDnsCache } from '../../../../actions/dnsConfig';
|
||||
import { CACHE_CONFIG_FIELDS, UINT32_RANGE } from '../../../../helpers/constants';
|
||||
import { replaceZeroWithEmptyString } from '../../../../helpers/helpers';
|
||||
import { RootState } from '../../../../initialState';
|
||||
import { Checkbox } from '../../../ui/Controls/Checkbox';
|
||||
|
||||
const INPUTS_FIELDS = [
|
||||
{
|
||||
name: CACHE_CONFIG_FIELDS.cache_size,
|
||||
title: 'cache_size',
|
||||
description: 'cache_size_desc',
|
||||
placeholder: 'enter_cache_size',
|
||||
title: i18next.t('cache_size'),
|
||||
description: i18next.t('cache_size_desc'),
|
||||
placeholder: i18next.t('enter_cache_size'),
|
||||
},
|
||||
{
|
||||
name: CACHE_CONFIG_FIELDS.cache_ttl_min,
|
||||
title: 'cache_ttl_min_override',
|
||||
description: 'cache_ttl_min_override_desc',
|
||||
placeholder: 'enter_cache_ttl_min_override',
|
||||
title: i18next.t('cache_ttl_min_override'),
|
||||
description: i18next.t('cache_ttl_min_override_desc'),
|
||||
placeholder: i18next.t('enter_cache_ttl_min_override'),
|
||||
},
|
||||
{
|
||||
name: CACHE_CONFIG_FIELDS.cache_ttl_max,
|
||||
title: 'cache_ttl_max_override',
|
||||
description: 'cache_ttl_max_override_desc',
|
||||
placeholder: 'enter_cache_ttl_max_override',
|
||||
title: i18next.t('cache_ttl_max_override'),
|
||||
description: i18next.t('cache_ttl_max_override_desc'),
|
||||
placeholder: i18next.t('enter_cache_ttl_max_override'),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -51,6 +53,7 @@ const Form = ({ initialValues, onSubmit }: CacheFormProps) => {
|
||||
register,
|
||||
handleSubmit,
|
||||
watch,
|
||||
control,
|
||||
formState: { isSubmitting, isDirty },
|
||||
} = useForm<FormData>({
|
||||
mode: 'onBlur',
|
||||
@@ -81,15 +84,16 @@ const Form = ({ initialValues, onSubmit }: CacheFormProps) => {
|
||||
<div className="col-12 col-md-7 p-0">
|
||||
<div className="form__group form__group--settings">
|
||||
<label htmlFor={name} className="form__label form__label--with-desc">
|
||||
{t(title)}
|
||||
{title}
|
||||
</label>
|
||||
|
||||
<div className="form__desc form__desc--top">{t(description)}</div>
|
||||
<div className="form__desc form__desc--top">{description}</div>
|
||||
|
||||
<input
|
||||
type="number"
|
||||
data-testid={`dns_${name}`}
|
||||
className="form-control"
|
||||
placeholder={t(placeholder)}
|
||||
placeholder={placeholder}
|
||||
disabled={processingSetConfig}
|
||||
min={0}
|
||||
max={UINT32_RANGE.MAX}
|
||||
@@ -108,26 +112,26 @@ const Form = ({ initialValues, onSubmit }: CacheFormProps) => {
|
||||
<div className="row">
|
||||
<div className="col-12 col-md-7">
|
||||
<div className="form__group form__group--settings">
|
||||
<label className="checkbox">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="checkbox__input"
|
||||
disabled={processingSetConfig}
|
||||
{...register('cache_optimistic')}
|
||||
/>
|
||||
<span className="checkbox__label">
|
||||
<span className="checkbox__label-text checkbox__label-text--long">
|
||||
<span className="checkbox__label-title">{t('cache_optimistic')}</span>
|
||||
<span className="checkbox__label-subtitle">{t('cache_optimistic_desc')}</span>
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
<Controller
|
||||
name="cache_optimistic"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Checkbox
|
||||
{...field}
|
||||
data-testid="dns_cache_optimistic"
|
||||
title={t('cache_optimistic')}
|
||||
subtitle={t('cache_optimistic_desc')}
|
||||
disabled={processingSetConfig}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
data-testid="dns_save"
|
||||
className="btn btn-success btn-standard btn-large"
|
||||
disabled={isSubmitting || !isDirty || processingSetConfig || minExceedsMax}>
|
||||
{t('save_btn')}
|
||||
@@ -135,6 +139,7 @@ const Form = ({ initialValues, onSubmit }: CacheFormProps) => {
|
||||
|
||||
<button
|
||||
type="button"
|
||||
data-testid="dns_clear"
|
||||
className="btn btn-outline-secondary btn-standard form__button"
|
||||
onClick={handleClearCache}>
|
||||
{t('clear_cache')}
|
||||
|
||||
@@ -6,8 +6,11 @@ import i18next from 'i18next';
|
||||
import { validateIp, validateIpv4, validateIpv6, validateRequiredValue } from '../../../../helpers/validators';
|
||||
|
||||
import { BLOCKING_MODES, UINT32_RANGE } from '../../../../helpers/constants';
|
||||
import { removeEmptyLines } from '../../../../helpers/helpers';
|
||||
import { Checkbox } from '../../../ui/Controls/Checkbox';
|
||||
import { Input } from '../../../ui/Controls/Input';
|
||||
import { toNumber } from '../../../../helpers/form';
|
||||
import { Textarea } from '../../../ui/Controls/Textarea';
|
||||
import { Radio } from '../../../ui/Controls/Radio';
|
||||
|
||||
const checkboxes: {
|
||||
name: 'dnssec_enabled' | 'disable_ipv6';
|
||||
@@ -26,19 +29,57 @@ const checkboxes: {
|
||||
},
|
||||
];
|
||||
|
||||
const customIps = [
|
||||
const customIps: {
|
||||
name: 'blocking_ipv4' | 'blocking_ipv6';
|
||||
label: string;
|
||||
description: string;
|
||||
validateIp: (value: string) => string;
|
||||
}[] = [
|
||||
{
|
||||
description: 'blocking_ipv4_desc',
|
||||
name: 'blocking_ipv4',
|
||||
label: i18next.t('blocking_ipv4'),
|
||||
description: i18next.t('blocking_ipv4_desc'),
|
||||
validateIp: validateIpv4,
|
||||
},
|
||||
{
|
||||
description: 'blocking_ipv6_desc',
|
||||
name: 'blocking_ipv6',
|
||||
label: i18next.t('blocking_ipv6'),
|
||||
description: i18next.t('blocking_ipv6_desc'),
|
||||
validateIp: validateIpv6,
|
||||
},
|
||||
];
|
||||
|
||||
const blockingModeOptions = [
|
||||
{
|
||||
value: BLOCKING_MODES.default,
|
||||
label: i18next.t('default'),
|
||||
},
|
||||
{
|
||||
value: BLOCKING_MODES.refused,
|
||||
label: i18next.t('refused'),
|
||||
},
|
||||
{
|
||||
value: BLOCKING_MODES.nxdomain,
|
||||
label: i18next.t('nxdomain'),
|
||||
},
|
||||
{
|
||||
value: BLOCKING_MODES.null_ip,
|
||||
label: i18next.t('null_ip'),
|
||||
},
|
||||
{
|
||||
value: BLOCKING_MODES.custom_ip,
|
||||
label: i18next.t('custom_ip'),
|
||||
},
|
||||
];
|
||||
|
||||
const blockingModeDescriptions = [
|
||||
i18next.t(`blocking_mode_default`),
|
||||
i18next.t(`blocking_mode_refused`),
|
||||
i18next.t(`blocking_mode_nxdomain`),
|
||||
i18next.t(`blocking_mode_null_ip`),
|
||||
i18next.t(`blocking_mode_custom_ip`),
|
||||
];
|
||||
|
||||
type FormData = {
|
||||
ratelimit: number;
|
||||
ratelimit_subnet_len_ipv4: number;
|
||||
@@ -46,7 +87,7 @@ type FormData = {
|
||||
ratelimit_whitelist: string;
|
||||
edns_cs_enabled: boolean;
|
||||
edns_cs_use_custom: boolean;
|
||||
edns_cs_custom_ip?: boolean;
|
||||
edns_cs_custom_ip?: string;
|
||||
dnssec_enabled: boolean;
|
||||
disable_ipv6: boolean;
|
||||
blocking_mode: string;
|
||||
@@ -65,11 +106,10 @@ const Form = ({ processing, initialValues, onSubmit }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
watch,
|
||||
control,
|
||||
formState: { errors, isSubmitting, isDirty },
|
||||
formState: { isSubmitting, isDirty },
|
||||
} = useForm<FormData>({
|
||||
mode: 'onBlur',
|
||||
defaultValues: initialValues,
|
||||
@@ -84,107 +124,102 @@ const Form = ({ processing, initialValues, onSubmit }: Props) => {
|
||||
<div className="row">
|
||||
<div className="col-12 col-md-7">
|
||||
<div className="form__group form__group--settings">
|
||||
<label htmlFor="ratelimit" className="form__label form__label--with-desc">
|
||||
{t('rate_limit')}
|
||||
</label>
|
||||
|
||||
<div className="form__desc form__desc--top">{t('rate_limit_desc')}</div>
|
||||
|
||||
<input
|
||||
id="ratelimit"
|
||||
type="number"
|
||||
className="form-control"
|
||||
disabled={processing}
|
||||
{...register('ratelimit', {
|
||||
required: t('form_error_required'),
|
||||
valueAsNumber: true,
|
||||
min: UINT32_RANGE.MIN,
|
||||
max: UINT32_RANGE.MAX,
|
||||
})}
|
||||
<Controller
|
||||
name="ratelimit"
|
||||
control={control}
|
||||
rules={{ validate: validateRequiredValue }}
|
||||
render={({ field, fieldState }) => (
|
||||
<Input
|
||||
{...field}
|
||||
data-testid="dns_config_ratelimit"
|
||||
type="number"
|
||||
label={t('rate_limit')}
|
||||
desc={t('rate_limit_desc')}
|
||||
error={fieldState.error?.message}
|
||||
min={UINT32_RANGE.MIN}
|
||||
max={UINT32_RANGE.MAX}
|
||||
disabled={processing}
|
||||
onChange={(e) => {
|
||||
const { value } = e.target;
|
||||
field.onChange(toNumber(value));
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{errors.ratelimit && (
|
||||
<div className="form__message form__message--error">{errors.ratelimit.message}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col-12 col-md-7">
|
||||
<div className="form__group form__group--settings">
|
||||
<label htmlFor="ratelimit_subnet_len_ipv4" className="form__label form__label--with-desc">
|
||||
{t('rate_limit_subnet_len_ipv4')}
|
||||
</label>
|
||||
|
||||
<div className="form__desc form__desc--top">{t('rate_limit_subnet_len_ipv4_desc')}</div>
|
||||
|
||||
<input
|
||||
id="ratelimit_subnet_len_ipv4"
|
||||
type="number"
|
||||
className="form-control"
|
||||
disabled={processing}
|
||||
{...register('ratelimit_subnet_len_ipv4', {
|
||||
required: t('form_error_required'),
|
||||
valueAsNumber: true,
|
||||
min: 0,
|
||||
max: 32,
|
||||
})}
|
||||
<Controller
|
||||
name="ratelimit_subnet_len_ipv4"
|
||||
control={control}
|
||||
rules={{ validate: validateRequiredValue }}
|
||||
render={({ field, fieldState }) => (
|
||||
<Input
|
||||
{...field}
|
||||
data-testid="dns_config_subnet_ipv4"
|
||||
type="number"
|
||||
label={t('rate_limit_subnet_len_ipv4')}
|
||||
desc={t('rate_limit_subnet_len_ipv4_desc')}
|
||||
error={fieldState.error?.message}
|
||||
min={0}
|
||||
max={32}
|
||||
disabled={processing}
|
||||
onChange={(e) => {
|
||||
const { value } = e.target;
|
||||
field.onChange(toNumber(value));
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{errors.ratelimit_subnet_len_ipv4 && (
|
||||
<div className="form__message form__message--error">
|
||||
{errors.ratelimit_subnet_len_ipv4.message}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col-12 col-md-7">
|
||||
<div className="form__group form__group--settings">
|
||||
<label htmlFor="ratelimit_subnet_len_ipv6" className="form__label form__label--with-desc">
|
||||
{t('rate_limit_subnet_len_ipv6')}
|
||||
</label>
|
||||
|
||||
<div className="form__desc form__desc--top">{t('rate_limit_subnet_len_ipv6_desc')}</div>
|
||||
|
||||
<input
|
||||
id="ratelimit_subnet_len_ipv6"
|
||||
type="number"
|
||||
className="form-control"
|
||||
disabled={processing}
|
||||
{...register('ratelimit_subnet_len_ipv6', {
|
||||
required: t('form_error_required'),
|
||||
valueAsNumber: true,
|
||||
min: 0,
|
||||
max: 128,
|
||||
})}
|
||||
<Controller
|
||||
name="ratelimit_subnet_len_ipv6"
|
||||
control={control}
|
||||
rules={{ validate: validateRequiredValue }}
|
||||
render={({ field, fieldState }) => (
|
||||
<Input
|
||||
{...field}
|
||||
data-testid="dns_config_subnet_ipv6"
|
||||
type="number"
|
||||
label={t('rate_limit_subnet_len_ipv6')}
|
||||
desc={t('rate_limit_subnet_len_ipv6_desc')}
|
||||
error={fieldState.error?.message}
|
||||
min={0}
|
||||
max={128}
|
||||
disabled={processing}
|
||||
onChange={(e) => {
|
||||
const { value } = e.target;
|
||||
field.onChange(toNumber(value));
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{errors.ratelimit_subnet_len_ipv6 && (
|
||||
<div className="form__message form__message--error">
|
||||
{errors.ratelimit_subnet_len_ipv6.message}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col-12 col-md-7">
|
||||
<div className="form__group form__group--settings">
|
||||
<label htmlFor="ratelimit_whitelist" className="form__label form__label--with-desc">
|
||||
{t('rate_limit_whitelist')}
|
||||
</label>
|
||||
|
||||
<div className="form__desc form__desc--top">{t('rate_limit_whitelist_desc')}</div>
|
||||
|
||||
<textarea
|
||||
id="ratelimit_whitelist"
|
||||
className="form-control"
|
||||
disabled={processing}
|
||||
{...register('ratelimit_whitelist', {
|
||||
onChange: removeEmptyLines,
|
||||
})}
|
||||
<Controller
|
||||
name="ratelimit_whitelist"
|
||||
control={control}
|
||||
render={({ field, fieldState }) => (
|
||||
<Textarea
|
||||
{...field}
|
||||
data-testid="dns_config_subnet_ipv6"
|
||||
label={t('rate_limit_whitelist')}
|
||||
desc={t('rate_limit_whitelist_desc')}
|
||||
error={fieldState.error?.message}
|
||||
disabled={processing}
|
||||
trimOnBlur
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{errors.ratelimit_whitelist && (
|
||||
<div className="form__message form__message--error">
|
||||
{errors.ratelimit_whitelist.message}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -194,7 +229,12 @@ const Form = ({ processing, initialValues, onSubmit }: Props) => {
|
||||
name="edns_cs_enabled"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Checkbox {...field} title={t('edns_enable')} disabled={processing} />
|
||||
<Checkbox
|
||||
{...field}
|
||||
data-testid="dns_config_edns_cs_enabled"
|
||||
title={t('edns_enable')}
|
||||
disabled={processing}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
@@ -208,6 +248,7 @@ const Form = ({ processing, initialValues, onSubmit }: Props) => {
|
||||
render={({ field }) => (
|
||||
<Checkbox
|
||||
{...field}
|
||||
data-testid="dns_config_edns_use_custom_ip"
|
||||
title={t('edns_use_custom_ip')}
|
||||
disabled={processing || !edns_cs_enabled}
|
||||
/>
|
||||
@@ -216,15 +257,23 @@ const Form = ({ processing, initialValues, onSubmit }: Props) => {
|
||||
</div>
|
||||
|
||||
{edns_cs_use_custom && (
|
||||
<input
|
||||
id="edns_cs_custom_ip"
|
||||
type="text"
|
||||
className="form-control"
|
||||
disabled={processing || !edns_cs_enabled}
|
||||
{...register('edns_cs_custom_ip', {
|
||||
required: t('form_error_required'),
|
||||
validate: (value) => validateIp(value) || validateRequiredValue(value),
|
||||
})}
|
||||
<Controller
|
||||
name="edns_cs_custom_ip"
|
||||
control={control}
|
||||
rules={{
|
||||
validate: {
|
||||
required: validateRequiredValue,
|
||||
id: validateIp,
|
||||
},
|
||||
}}
|
||||
render={({ field, fieldState }) => (
|
||||
<Input
|
||||
{...field}
|
||||
data-testid="dns_config_edns_cs_custom_ip"
|
||||
error={fieldState.error?.message}
|
||||
disabled={processing || !edns_cs_enabled}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
@@ -238,6 +287,7 @@ const Form = ({ processing, initialValues, onSubmit }: Props) => {
|
||||
render={({ field }) => (
|
||||
<Checkbox
|
||||
{...field}
|
||||
data-testid={`dns_config_${name}`}
|
||||
title={placeholder}
|
||||
subtitle={subtitle}
|
||||
disabled={processing}
|
||||
@@ -253,53 +303,52 @@ const Form = ({ processing, initialValues, onSubmit }: Props) => {
|
||||
<label className="form__label form__label--with-desc">{t('blocking_mode')}</label>
|
||||
|
||||
<div className="form__desc form__desc--top">
|
||||
{Object.values(BLOCKING_MODES).map((mode: any) => (
|
||||
<li key={mode}>{t(`blocking_mode_${mode}`)}</li>
|
||||
{blockingModeDescriptions.map((desc: string) => (
|
||||
<li key={desc}>{desc}</li>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div className="custom-controls-stacked">
|
||||
{Object.values(BLOCKING_MODES).map((mode: any) => (
|
||||
<label key={mode} className="custom-control custom-radio">
|
||||
<input
|
||||
type="radio"
|
||||
className="custom-control-input"
|
||||
value={mode}
|
||||
disabled={processing}
|
||||
{...register('blocking_mode')}
|
||||
/>
|
||||
<span className="custom-control-label">{t(mode)}</span>
|
||||
</label>
|
||||
))}
|
||||
<Controller
|
||||
name="blocking_mode"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Radio {...field} options={blockingModeOptions} disabled={processing} />
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{blocking_mode === BLOCKING_MODES.custom_ip && (
|
||||
<>
|
||||
{customIps.map(({ description, name, validateIp }) => (
|
||||
{customIps.map(({ label, description, name, validateIp }) => (
|
||||
<div className="col-12 col-sm-6" key={name}>
|
||||
<div className="form__group form__group--settings">
|
||||
<label className="form__label form__label--with-desc" htmlFor={name}>
|
||||
{t(name)}
|
||||
</label>
|
||||
|
||||
<div className="form__desc form__desc--top">{t(description)}</div>
|
||||
|
||||
<input
|
||||
id={name}
|
||||
type="text"
|
||||
className="form-control"
|
||||
disabled={processing}
|
||||
{...register(name as keyof FormData, {
|
||||
required: t('form_error_required'),
|
||||
validate: (value) => validateIp(value) || validateRequiredValue(value),
|
||||
})}
|
||||
<Controller
|
||||
name={name}
|
||||
control={control}
|
||||
rules={{
|
||||
validate: {
|
||||
required: validateRequiredValue,
|
||||
ip: validateIp,
|
||||
},
|
||||
}}
|
||||
render={({ field, fieldState }) => (
|
||||
<Input
|
||||
{...field}
|
||||
data-testid="dns_config_blocked_response_ttl"
|
||||
type="number"
|
||||
label={label}
|
||||
desc={description}
|
||||
error={fieldState.error?.message}
|
||||
disabled={processing}
|
||||
onChange={(e) => {
|
||||
const { value } = e.target;
|
||||
field.onChange(toNumber(value));
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{errors[name as keyof FormData] && (
|
||||
<div className="form__message form__message--error">
|
||||
{errors[name as keyof FormData]?.message}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
@@ -308,35 +357,35 @@ const Form = ({ processing, initialValues, onSubmit }: Props) => {
|
||||
|
||||
<div className="col-12 col-md-7">
|
||||
<div className="form__group form__group--settings">
|
||||
<label htmlFor="blocked_response_ttl" className="form__label form__label--with-desc">
|
||||
{t('blocked_response_ttl')}
|
||||
</label>
|
||||
|
||||
<div className="form__desc form__desc--top">{t('blocked_response_ttl_desc')}</div>
|
||||
|
||||
<input
|
||||
id="blocked_response_ttl"
|
||||
type="number"
|
||||
className="form-control"
|
||||
disabled={processing}
|
||||
{...register('blocked_response_ttl', {
|
||||
required: t('form_error_required'),
|
||||
valueAsNumber: true,
|
||||
min: UINT32_RANGE.MIN,
|
||||
max: UINT32_RANGE.MAX,
|
||||
})}
|
||||
<Controller
|
||||
name="blocked_response_ttl"
|
||||
control={control}
|
||||
rules={{ validate: validateRequiredValue }}
|
||||
render={({ field, fieldState }) => (
|
||||
<Input
|
||||
{...field}
|
||||
data-testid="dns_config_blocked_response_ttl"
|
||||
type="number"
|
||||
label={t('blocked_response_ttl')}
|
||||
desc={t('blocked_response_ttl_desc')}
|
||||
error={fieldState.error?.message}
|
||||
min={UINT32_RANGE.MIN}
|
||||
max={UINT32_RANGE.MAX}
|
||||
disabled={processing}
|
||||
onChange={(e) => {
|
||||
const { value } = e.target;
|
||||
field.onChange(toNumber(value));
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{errors.blocked_response_ttl && (
|
||||
<div className="form__message form__message--error">
|
||||
{errors.blocked_response_ttl.message}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
data-testid="dns_config_save"
|
||||
className="btn btn-success btn-standard btn-large"
|
||||
disabled={isSubmitting || !isDirty || processing}>
|
||||
{t('save_btn')}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import classnames from 'classnames';
|
||||
import React, { useRef } from 'react';
|
||||
import { Controller, useForm } from 'react-hook-form';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import i18next from 'i18next';
|
||||
import clsx from 'clsx';
|
||||
import { testUpstreamWithFormValues } from '../../../../actions';
|
||||
import { DNS_REQUEST_OPTIONS, UPSTREAM_CONFIGURATION_WIKI_LINK } from '../../../../helpers/constants';
|
||||
import { removeEmptyLines } from '../../../../helpers/helpers';
|
||||
@@ -12,9 +13,10 @@ import { RootState } from '../../../../initialState';
|
||||
import '../../../ui/texareaCommentsHighlight.css';
|
||||
import Examples from './Examples';
|
||||
import { Checkbox } from '../../../ui/Controls/Checkbox';
|
||||
import { Textarea } from '../../../ui/Controls/Textarea';
|
||||
import { Radio } from '../../../ui/Controls/Radio';
|
||||
|
||||
const UPSTREAM_DNS_NAME = 'upstream_dns';
|
||||
const UPSTREAM_MODE_NAME = 'upstream_mode';
|
||||
|
||||
type FormData = {
|
||||
upstream_dns: string;
|
||||
@@ -31,24 +33,21 @@ type FormProps = {
|
||||
onSubmit: (data: FormData) => void;
|
||||
};
|
||||
|
||||
const INPUT_FIELDS = [
|
||||
const upstreamModeOptions = [
|
||||
{
|
||||
name: UPSTREAM_MODE_NAME,
|
||||
label: i18next.t('load_balancing'),
|
||||
desc: i18next.t('load_balancing_desc'),
|
||||
value: DNS_REQUEST_OPTIONS.LOAD_BALANCING,
|
||||
subtitle: 'load_balancing_desc',
|
||||
placeholder: 'load_balancing',
|
||||
},
|
||||
{
|
||||
name: UPSTREAM_MODE_NAME,
|
||||
label: i18next.t('parallel_requests'),
|
||||
desc: i18next.t('upstream_parallel'),
|
||||
value: DNS_REQUEST_OPTIONS.PARALLEL,
|
||||
subtitle: 'upstream_parallel',
|
||||
placeholder: 'parallel_requests',
|
||||
},
|
||||
{
|
||||
name: UPSTREAM_MODE_NAME,
|
||||
label: i18next.t('fastest_addr'),
|
||||
desc: i18next.t('fastest_addr_desc'),
|
||||
value: DNS_REQUEST_OPTIONS.FASTEST_ADDR,
|
||||
subtitle: 'fastest_addr_desc',
|
||||
placeholder: 'fastest_addr',
|
||||
},
|
||||
];
|
||||
|
||||
@@ -91,14 +90,10 @@ const Form = ({ initialValues, onSubmit }: FormProps) => {
|
||||
dispatch(testUpstreamWithFormValues(formValues));
|
||||
};
|
||||
|
||||
const testButtonClass = classnames('btn btn-primary btn-standard mr-2', {
|
||||
'btn-loading': processingTestUpstream,
|
||||
});
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)} className="form--upstream">
|
||||
<div className="row">
|
||||
<label className="col form__label" htmlFor={UPSTREAM_DNS_NAME}>
|
||||
<label className="col form__label" htmlFor="upstream_dns">
|
||||
<Trans
|
||||
components={{
|
||||
a: <a href={UPSTREAM_CONFIGURATION_WIKI_LINK} target="_blank" rel="noopener noreferrer" />,
|
||||
@@ -126,17 +121,16 @@ const Form = ({ initialValues, onSubmit }: FormProps) => {
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<>
|
||||
<textarea
|
||||
<Textarea
|
||||
{...field}
|
||||
id={UPSTREAM_DNS_NAME}
|
||||
className="form-control form-control--textarea font-monospace text-input"
|
||||
data-testid="upstream_dns"
|
||||
className="form-control--textarea-large text-input"
|
||||
wrapperClassName="mb-0"
|
||||
placeholder={t('upstream_dns')}
|
||||
disabled={!!upstream_dns_file || processingSetConfig || processingTestUpstream}
|
||||
onScroll={(e) => syncScroll(e, textareaRef)}
|
||||
onBlur={(e) => {
|
||||
const value = removeEmptyLines(e.target.value);
|
||||
field.onChange(value);
|
||||
}}
|
||||
trimOnBlur
|
||||
/>
|
||||
{getTextareaCommentsHighlight(textareaRef, upstream_dns)}
|
||||
</>
|
||||
@@ -150,31 +144,19 @@ const Form = ({ initialValues, onSubmit }: FormProps) => {
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
{INPUT_FIELDS.map(({ name, value, subtitle, placeholder }) => (
|
||||
<div key={value} className="col-12 mb-4">
|
||||
<Controller
|
||||
name="upstream_mode"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<div className="custom-control custom-radio">
|
||||
<input
|
||||
{...field}
|
||||
type="radio"
|
||||
className="custom-control-input"
|
||||
id={`${name}_${value}`}
|
||||
value={value}
|
||||
checked={field.value === value}
|
||||
disabled={processingSetConfig || processingTestUpstream}
|
||||
/>
|
||||
<label className="custom-control-label" htmlFor={`${name}_${value}`}>
|
||||
<span className="custom-control-label__title">{t(placeholder)}</span>
|
||||
<span className="custom-control-label__subtitle">{t(subtitle)}</span>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
<div className="col-12 mb-4">
|
||||
<Controller
|
||||
name="upstream_mode"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Radio
|
||||
{...field}
|
||||
options={upstreamModeOptions}
|
||||
disabled={processingSetConfig || processingTestUpstream}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="col-12">
|
||||
<label className="form__label form__label--with-desc" htmlFor="fallback_dns">
|
||||
@@ -187,16 +169,14 @@ const Form = ({ initialValues, onSubmit }: FormProps) => {
|
||||
name="fallback_dns"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<textarea
|
||||
<Textarea
|
||||
{...field}
|
||||
id="fallback_dns"
|
||||
className="form-control form-control--textarea form-control--textarea-small font-monospace"
|
||||
data-testid="fallback_dns"
|
||||
wrapperClassName="mb-0"
|
||||
placeholder={t('fallback_dns_placeholder')}
|
||||
disabled={processingSetConfig}
|
||||
onBlur={(e) => {
|
||||
const value = removeEmptyLines(e.target.value);
|
||||
field.onChange(value);
|
||||
}}
|
||||
trimOnBlur
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
@@ -206,7 +186,7 @@ const Form = ({ initialValues, onSubmit }: FormProps) => {
|
||||
<hr />
|
||||
</div>
|
||||
|
||||
<div className="col-12 mb-2">
|
||||
<div className="col-12">
|
||||
<label className="form__label form__label--with-desc" htmlFor="bootstrap_dns">
|
||||
{t('bootstrap_dns')}
|
||||
</label>
|
||||
@@ -217,11 +197,12 @@ const Form = ({ initialValues, onSubmit }: FormProps) => {
|
||||
name="bootstrap_dns"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<textarea
|
||||
<Textarea
|
||||
{...field}
|
||||
id="bootstrap_dns"
|
||||
className="form-control form-control--textarea form-control--textarea-small font-monospace"
|
||||
data-testid="bootstrap_dns"
|
||||
placeholder={t('bootstrap_dns')}
|
||||
wrapperClassName="mb-0"
|
||||
disabled={processingSetConfig}
|
||||
onBlur={(e) => {
|
||||
const value = removeEmptyLines(e.target.value);
|
||||
@@ -255,16 +236,13 @@ const Form = ({ initialValues, onSubmit }: FormProps) => {
|
||||
name="local_ptr_upstreams"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<textarea
|
||||
<Textarea
|
||||
{...field}
|
||||
id="local_ptr_upstreams"
|
||||
className="form-control form-control--textarea form-control--textarea-small font-monospace"
|
||||
data-testid="local_ptr_upstreams"
|
||||
placeholder={t('local_ptr_placeholder')}
|
||||
disabled={processingSetConfig}
|
||||
onBlur={(e) => {
|
||||
const value = removeEmptyLines(e.target.value);
|
||||
field.onChange(value);
|
||||
}}
|
||||
trimOnBlur
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
@@ -276,6 +254,7 @@ const Form = ({ initialValues, onSubmit }: FormProps) => {
|
||||
render={({ field }) => (
|
||||
<Checkbox
|
||||
{...field}
|
||||
data-testid="dns_use_private_ptr_resolvers"
|
||||
title={t('use_private_ptr_resolvers_title')}
|
||||
subtitle={t('use_private_ptr_resolvers_desc')}
|
||||
disabled={processingSetConfig}
|
||||
@@ -296,6 +275,7 @@ const Form = ({ initialValues, onSubmit }: FormProps) => {
|
||||
render={({ field }) => (
|
||||
<Checkbox
|
||||
{...field}
|
||||
data-testid="dns_resolve_clients"
|
||||
title={t('resolve_clients_title')}
|
||||
subtitle={t('resolve_clients_desc')}
|
||||
disabled={processingSetConfig}
|
||||
@@ -309,7 +289,10 @@ const Form = ({ initialValues, onSubmit }: FormProps) => {
|
||||
<div className="btn-list">
|
||||
<button
|
||||
type="button"
|
||||
className={testButtonClass}
|
||||
data-testid="dns_upstream_test"
|
||||
className={clsx('btn btn-primary btn-standard mr-2', {
|
||||
'btn-loading': processingTestUpstream,
|
||||
})}
|
||||
onClick={handleUpstreamTest}
|
||||
disabled={!upstream_dns || processingTestUpstream}>
|
||||
{t('test_upstream_btn')}
|
||||
@@ -317,6 +300,7 @@ const Form = ({ initialValues, onSubmit }: FormProps) => {
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
data-testid="dns_upstream_save"
|
||||
className="btn btn-success btn-standard"
|
||||
disabled={isSubmitting || !isDirty || processingSetConfig || processingTestUpstream}>
|
||||
{t('apply_btn')}
|
||||
|
||||
@@ -6,6 +6,7 @@ import i18next from 'i18next';
|
||||
import { toNumber } from '../../../helpers/form';
|
||||
import { DAY, FILTERS_INTERVALS_HOURS, FILTERS_RELATIVE_LINK } from '../../../helpers/constants';
|
||||
import { Checkbox } from '../../ui/Controls/Checkbox';
|
||||
import { Select } from '../../ui/Controls/Select';
|
||||
|
||||
const THREE_DAYS_INTERVAL = DAY * 3;
|
||||
const SEVEN_DAYS_INTERVAL = DAY * 7;
|
||||
@@ -37,7 +38,7 @@ export const FiltersConfig = ({ initialValues, setFiltersConfig, processing }: P
|
||||
const { t } = useTranslation();
|
||||
const prevFormValuesRef = useRef<FormValues>(initialValues);
|
||||
|
||||
const { register, watch, control } = useForm({
|
||||
const { watch, control } = useForm({
|
||||
mode: 'onBlur',
|
||||
defaultValues: initialValues,
|
||||
});
|
||||
@@ -68,6 +69,7 @@ export const FiltersConfig = ({ initialValues, setFiltersConfig, processing }: P
|
||||
render={({ field }) => (
|
||||
<Checkbox
|
||||
{...field}
|
||||
data-testid="filters_enabled"
|
||||
title={t('block_domain_use_filters_and_hosts')}
|
||||
disabled={processing}
|
||||
/>
|
||||
@@ -85,18 +87,26 @@ export const FiltersConfig = ({ initialValues, setFiltersConfig, processing }: P
|
||||
<label className="form__label">
|
||||
<Trans>filters_interval</Trans>
|
||||
</label>
|
||||
<select
|
||||
{...register('interval', {
|
||||
setValueAs: toNumber,
|
||||
})}
|
||||
className="custom-select"
|
||||
disabled={processing}>
|
||||
{FILTERS_INTERVALS_HOURS.map((interval) => (
|
||||
<option value={interval} key={interval}>
|
||||
{getTitleForInterval(interval)}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<Controller
|
||||
name="interval"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Select
|
||||
{...field}
|
||||
data-testid="filters_interval"
|
||||
disabled={processing}
|
||||
onChange={(e) => {
|
||||
const { value } = e.target;
|
||||
field.onChange(toNumber(value));
|
||||
}}>
|
||||
{FILTERS_INTERVALS_HOURS.map((interval) => (
|
||||
<option value={interval} key={interval}>
|
||||
{getTitleForInterval(interval)}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -86,7 +86,14 @@ export const Form = ({ initialValues, processing, processingReset, onSubmit, onR
|
||||
<Controller
|
||||
name="enabled"
|
||||
control={control}
|
||||
render={({ field }) => <Checkbox {...field} title={t('query_log_enable')} disabled={processing} />}
|
||||
render={({ field }) => (
|
||||
<Checkbox
|
||||
{...field}
|
||||
data-testid="logs_enabled"
|
||||
title={t('query_log_enable')}
|
||||
disabled={processing}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -97,6 +104,7 @@ export const Form = ({ initialValues, processing, processingReset, onSubmit, onR
|
||||
render={({ field }) => (
|
||||
<Checkbox
|
||||
{...field}
|
||||
data-testid="logs_anonymize_client_ip"
|
||||
title={t('anonymize_client_ip')}
|
||||
subtitle={t('anonymize_client_ip_desc')}
|
||||
disabled={processing}
|
||||
@@ -114,6 +122,7 @@ export const Form = ({ initialValues, processing, processingReset, onSubmit, onR
|
||||
<label className="custom-control custom-radio">
|
||||
<input
|
||||
type="radio"
|
||||
data-testid="logs_config_interval"
|
||||
className="custom-control-input"
|
||||
disabled={processing}
|
||||
checked={!QUERY_LOG_INTERVALS_DAYS.includes(intervalValue)}
|
||||
@@ -128,7 +137,7 @@ export const Form = ({ initialValues, processing, processingReset, onSubmit, onR
|
||||
|
||||
{!QUERY_LOG_INTERVALS_DAYS.includes(intervalValue) && (
|
||||
<div className="form__group--input">
|
||||
<div className="form__desc form__desc--top">{i18next.t('custom_rotation_input')}</div>
|
||||
<div className="form__desc form__desc--top">{t('custom_rotation_input')}</div>
|
||||
|
||||
<Controller
|
||||
name="customInterval"
|
||||
@@ -136,7 +145,7 @@ export const Form = ({ initialValues, processing, processingReset, onSubmit, onR
|
||||
render={({ field, fieldState }) => (
|
||||
<Input
|
||||
{...field}
|
||||
placeholder={t('encryption_certificates_input')}
|
||||
data-testid="logs_config_custom_interval"
|
||||
disabled={processing}
|
||||
error={fieldState.error?.message}
|
||||
min={RETENTION_RANGE.MIN}
|
||||
@@ -156,6 +165,7 @@ export const Form = ({ initialValues, processing, processingReset, onSubmit, onR
|
||||
<input
|
||||
type="radio"
|
||||
className="custom-control-input"
|
||||
data-testid={`logs_config_${interval}`}
|
||||
disabled={processing}
|
||||
value={interval}
|
||||
checked={intervalValue === interval}
|
||||
@@ -185,6 +195,7 @@ export const Form = ({ initialValues, processing, processingReset, onSubmit, onR
|
||||
render={({ field, fieldState }) => (
|
||||
<Textarea
|
||||
{...field}
|
||||
data-testid="logs_config_ingored"
|
||||
placeholder={t('ignore_domains')}
|
||||
className="text-input"
|
||||
disabled={processing}
|
||||
@@ -196,12 +207,17 @@ export const Form = ({ initialValues, processing, processingReset, onSubmit, onR
|
||||
</div>
|
||||
|
||||
<div className="mt-5">
|
||||
<button type="submit" className="btn btn-success btn-standard btn-large" disabled={disableSubmit}>
|
||||
<button
|
||||
type="submit"
|
||||
data-testid="logs_config_save"
|
||||
className="btn btn-success btn-standard btn-large"
|
||||
disabled={disableSubmit}>
|
||||
<Trans>save_btn</Trans>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
data-testid="logs_config_clear"
|
||||
className="btn btn-outline-secondary btn-standard form__button"
|
||||
onClick={onReset}
|
||||
disabled={processingReset}>
|
||||
|
||||
@@ -82,7 +82,14 @@ export const Form = ({ initialValues, processing, processingReset, onSubmit, onR
|
||||
<Controller
|
||||
name="enabled"
|
||||
control={control}
|
||||
render={({ field }) => <Checkbox {...field} title={t('statistics_enable')} disabled={processing} />}
|
||||
render={({ field }) => (
|
||||
<Checkbox
|
||||
{...field}
|
||||
data-testid="stats_config_enabled"
|
||||
title={t('statistics_enable')}
|
||||
disabled={processing}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -99,6 +106,7 @@ export const Form = ({ initialValues, processing, processingReset, onSubmit, onR
|
||||
<label className="custom-control custom-radio">
|
||||
<input
|
||||
type="radio"
|
||||
data-testid="stats_config_interval"
|
||||
className="custom-control-input"
|
||||
disabled={processing}
|
||||
checked={!STATS_INTERVALS_DAYS.includes(intervalValue)}
|
||||
@@ -121,7 +129,7 @@ export const Form = ({ initialValues, processing, processingReset, onSubmit, onR
|
||||
render={({ field, fieldState }) => (
|
||||
<Input
|
||||
{...field}
|
||||
placeholder={t('encryption_certificates_input')}
|
||||
data-testid="stats_config_custom_interval"
|
||||
disabled={processing}
|
||||
error={fieldState.error?.message}
|
||||
min={RETENTION_RANGE.MIN}
|
||||
@@ -169,6 +177,7 @@ export const Form = ({ initialValues, processing, processingReset, onSubmit, onR
|
||||
render={({ field, fieldState }) => (
|
||||
<Textarea
|
||||
{...field}
|
||||
data-testid="stats_config_ignored"
|
||||
placeholder={t('ignore_domains')}
|
||||
className="text-input"
|
||||
disabled={processing}
|
||||
@@ -180,12 +189,17 @@ export const Form = ({ initialValues, processing, processingReset, onSubmit, onR
|
||||
</div>
|
||||
|
||||
<div className="mt-5">
|
||||
<button type="submit" className="btn btn-success btn-standard btn-large" disabled={disableSubmit}>
|
||||
<button
|
||||
type="submit"
|
||||
data-testid="stats_config_save"
|
||||
className="btn btn-success btn-standard btn-large"
|
||||
disabled={disableSubmit}>
|
||||
<Trans>save_btn</Trans>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
data-testid="stats_config_clear"
|
||||
className="btn btn-outline-secondary btn-standard form__button"
|
||||
onClick={onReset}
|
||||
disabled={processingReset}>
|
||||
|
||||
@@ -3,6 +3,7 @@ import clsx from 'clsx';
|
||||
|
||||
type Props = ComponentProps<'input'> & {
|
||||
label?: string;
|
||||
desc?: string;
|
||||
leftAddon?: ReactNode;
|
||||
rightAddon?: ReactNode;
|
||||
error?: string;
|
||||
@@ -10,13 +11,14 @@ type Props = ComponentProps<'input'> & {
|
||||
};
|
||||
|
||||
export const Input = forwardRef<HTMLInputElement, Props>(
|
||||
({ name, label, className, leftAddon, rightAddon, error, trimOnBlur, onBlur, ...rest }, ref) => (
|
||||
({ name, label, desc, className, leftAddon, rightAddon, error, trimOnBlur, onBlur, ...rest }, ref) => (
|
||||
<div className={clsx('form-group', { 'has-error': !!error })}>
|
||||
{label && (
|
||||
<label className="form__label" htmlFor={name}>
|
||||
<label className={clsx('form__label', { 'form__label--with-desc': !!desc })} htmlFor={name}>
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
{desc && <div className="form__desc form__desc--top">{desc}</div>}
|
||||
<div className="input-group">
|
||||
{leftAddon && <div>{leftAddon}</div>}
|
||||
<input
|
||||
|
||||
@@ -25,6 +25,7 @@ export const Radio = forwardRef<HTMLInputElement, Props<string | boolean | numbe
|
||||
className="custom-control custom-radio">
|
||||
<input
|
||||
id={getId(o.label)}
|
||||
data-testid={o.value}
|
||||
type="radio"
|
||||
className="custom-control-input"
|
||||
onChange={() => onChange(o.value)}
|
||||
|
||||
@@ -4,19 +4,22 @@ import { trimLinesAndRemoveEmpty } from '../../../helpers/helpers';
|
||||
|
||||
type Props = ComponentProps<'textarea'> & {
|
||||
className?: string;
|
||||
wrapperClassName?: string;
|
||||
label?: string;
|
||||
desc?: string;
|
||||
error?: string;
|
||||
trimOnBlur?: boolean;
|
||||
};
|
||||
|
||||
export const Textarea = forwardRef<HTMLTextAreaElement, Props>(
|
||||
({ name, label, className, error, trimOnBlur, onBlur, ...rest }, ref) => (
|
||||
<div className={clsx('form-group', { 'has-error': !!error })}>
|
||||
({ name, label, desc, className, wrapperClassName, error, trimOnBlur, onBlur, ...rest }, ref) => (
|
||||
<div className={clsx('form-group', wrapperClassName, { 'has-error': !!error })}>
|
||||
{label && (
|
||||
<label className="form__label" htmlFor={name}>
|
||||
<label className={clsx('form__label', { 'form__label--with-desc': !!desc })} htmlFor={name}>
|
||||
{label}
|
||||
</label>
|
||||
)}
|
||||
{desc && <div className="form__desc form__desc--top">{desc}</div>}
|
||||
<textarea
|
||||
className={clsx(
|
||||
'form-control form-control--textarea form-control--textarea-small font-monospace',
|
||||
|
||||
@@ -142,7 +142,7 @@ export const MobileConfigForm = ({ initialValues }: Props) => {
|
||||
|
||||
<div className="form__group form__group--settings">
|
||||
<label htmlFor="clientId" className="form__label form__label--with-desc">
|
||||
{i18next.t('client_id')}
|
||||
{t('client_id')}
|
||||
</label>
|
||||
|
||||
<div className="form__desc form__desc--top">
|
||||
@@ -176,8 +176,8 @@ export const MobileConfigForm = ({ initialValues }: Props) => {
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<Select {...field} label={t('protocol')} data-testid="mobile_config_protocol">
|
||||
<option value={MOBILE_CONFIG_LINKS.DOT}>{i18next.t('dns_over_tls')}</option>
|
||||
<option value={MOBILE_CONFIG_LINKS.DOH}>{i18next.t('dns_over_https')}</option>
|
||||
<option value={MOBILE_CONFIG_LINKS.DOT}>{t('dns_over_tls')}</option>
|
||||
<option value={MOBILE_CONFIG_LINKS.DOH}>{t('dns_over_https')}</option>
|
||||
</Select>
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -320,7 +320,7 @@ export type DnsConfigData = {
|
||||
ratelimit_subnet_len_ipv4?: number;
|
||||
ratelimit_subnet_len_ipv6?: number;
|
||||
edns_cs_use_custom?: boolean;
|
||||
edns_cs_custom_ip?: boolean;
|
||||
edns_cs_custom_ip?: string;
|
||||
cache_size?: number;
|
||||
cache_ttl_max?: number;
|
||||
cache_ttl_min?: number;
|
||||
|
||||
Reference in New Issue
Block a user