fix forms
This commit is contained in:
@@ -48,6 +48,7 @@ const Check = ({ onSubmit }: Props) => {
|
|||||||
<Input
|
<Input
|
||||||
{...field}
|
{...field}
|
||||||
type="text"
|
type="text"
|
||||||
|
data-testid="check_domain_name"
|
||||||
placeholder={t('form_enter_host')}
|
placeholder={t('form_enter_host')}
|
||||||
error={fieldState.error?.message}
|
error={fieldState.error?.message}
|
||||||
rightAddon={
|
rightAddon={
|
||||||
@@ -55,6 +56,7 @@ const Check = ({ onSubmit }: Props) => {
|
|||||||
<button
|
<button
|
||||||
className="btn btn-success btn-standard btn-large"
|
className="btn btn-success btn-standard btn-large"
|
||||||
type="submit"
|
type="submit"
|
||||||
|
data-testid="check_domain_submit"
|
||||||
disabled={!isDirty || !isValid || processingCheck}>
|
disabled={!isDirty || !isValid || processingCheck}>
|
||||||
{t('check')}
|
{t('check')}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -74,7 +74,12 @@ export const FiltersList = ({ categories, filters, selectedSources }: Props) =>
|
|||||||
name={id}
|
name={id}
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<Checkbox {...field} title={name} disabled={isSelected} />
|
<Checkbox
|
||||||
|
{...field}
|
||||||
|
data-testid={`filters_${id}`}
|
||||||
|
title={name}
|
||||||
|
disabled={isSelected}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{renderIcons(iconsData)}
|
{renderIcons(iconsData)}
|
||||||
|
|||||||
@@ -15,13 +15,13 @@ type FormValues = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
closeModal: (...args: unknown[]) => void;
|
closeModal: () => void;
|
||||||
onSubmit: (values: FormValues) => void;
|
onSubmit: (values: FormValues) => void;
|
||||||
processingAddFilter: boolean;
|
processingAddFilter: boolean;
|
||||||
processingConfigFilter: boolean;
|
processingConfigFilter: boolean;
|
||||||
whitelist?: boolean;
|
whitelist?: boolean;
|
||||||
modalType: string;
|
modalType: string;
|
||||||
toggleFilteringModal: (...args: unknown[]) => void;
|
toggleFilteringModal: ({ type }: { type?: keyof typeof MODAL_TYPE }) => void;
|
||||||
selectedSources?: Record<string, boolean>;
|
selectedSources?: Record<string, boolean>;
|
||||||
initialValues?: FormValues;
|
initialValues?: FormValues;
|
||||||
};
|
};
|
||||||
@@ -42,14 +42,14 @@ export const Form = ({
|
|||||||
const methods = useForm({ defaultValues: initialValues });
|
const methods = useForm({ defaultValues: initialValues });
|
||||||
const { handleSubmit, control } = methods;
|
const { handleSubmit, control } = methods;
|
||||||
|
|
||||||
const openModal = (modalType: any, timeout = MODAL_OPEN_TIMEOUT) => {
|
const openModal = (modalType: keyof typeof MODAL_TYPE, timeout = MODAL_OPEN_TIMEOUT) => {
|
||||||
toggleFilteringModal();
|
toggleFilteringModal(undefined);
|
||||||
setTimeout(() => toggleFilteringModal({ type: modalType }), timeout);
|
setTimeout(() => toggleFilteringModal({ type: modalType }), timeout);
|
||||||
};
|
};
|
||||||
|
|
||||||
const openFilteringListModal = () => openModal(MODAL_TYPE.CHOOSE_FILTERING_LIST);
|
const openFilteringListModal = () => openModal('CHOOSE_FILTERING_LIST');
|
||||||
|
|
||||||
const openAddFiltersModal = () => openModal(MODAL_TYPE.ADD_FILTERS);
|
const openAddFiltersModal = () => openModal('ADD_FILTERS');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FormProvider {...methods}>
|
<FormProvider {...methods}>
|
||||||
@@ -81,8 +81,15 @@ export const Form = ({
|
|||||||
<Controller
|
<Controller
|
||||||
name="name"
|
name="name"
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field }) => (
|
render={({ field, fieldState }) => (
|
||||||
<Input {...field} type="text" placeholder={t('enter_name_hint')} trimOnBlur />
|
<Input
|
||||||
|
{...field}
|
||||||
|
type="text"
|
||||||
|
data-testid="filters_name"
|
||||||
|
placeholder={t('enter_name_hint')}
|
||||||
|
error={fieldState.error?.message}
|
||||||
|
trimOnBlur
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -92,11 +99,13 @@ export const Form = ({
|
|||||||
name="url"
|
name="url"
|
||||||
control={control}
|
control={control}
|
||||||
rules={{ validate: { validateRequiredValue, validatePath } }}
|
rules={{ validate: { validateRequiredValue, validatePath } }}
|
||||||
render={({ field }) => (
|
render={({ field, fieldState }) => (
|
||||||
<Input
|
<Input
|
||||||
{...field}
|
{...field}
|
||||||
type="text"
|
type="text"
|
||||||
|
data-testid="filters_url"
|
||||||
placeholder={t('enter_url_or_path_hint')}
|
placeholder={t('enter_url_or_path_hint')}
|
||||||
|
error={fieldState.error?.message}
|
||||||
trimOnBlur
|
trimOnBlur
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -118,6 +127,7 @@ export const Form = ({
|
|||||||
{modalType !== MODAL_TYPE.SELECT_MODAL_TYPE && (
|
{modalType !== MODAL_TYPE.SELECT_MODAL_TYPE && (
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
|
data-testid="filters_save"
|
||||||
className="btn btn-success"
|
className="btn btn-success"
|
||||||
disabled={processingAddFilter || processingConfigFilter}>
|
disabled={processingAddFilter || processingConfigFilter}>
|
||||||
{t('save_btn')}
|
{t('save_btn')}
|
||||||
|
|||||||
@@ -5,16 +5,16 @@ import { Trans, useTranslation } from 'react-i18next';
|
|||||||
import { validateAnswer, validateDomain, validateRequiredValue } from '../../../helpers/validators';
|
import { validateAnswer, validateDomain, validateRequiredValue } from '../../../helpers/validators';
|
||||||
import { Input } from '../../ui/Controls/Input';
|
import { Input } from '../../ui/Controls/Input';
|
||||||
|
|
||||||
interface FormValues {
|
interface RewriteFormValues {
|
||||||
domain: string;
|
domain: string;
|
||||||
answer: string;
|
answer: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
processingAdd: boolean;
|
processingAdd: boolean;
|
||||||
currentRewrite?: { answer: string; domain: string };
|
currentRewrite?: RewriteFormValues;
|
||||||
toggleRewritesModal: () => void;
|
toggleRewritesModal: () => void;
|
||||||
onSubmit?: (data: FormValues) => Promise<void> | void;
|
onSubmit?: (data: RewriteFormValues) => Promise<void> | void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Form = ({ processingAdd, currentRewrite, toggleRewritesModal, onSubmit }: Props) => {
|
const Form = ({ processingAdd, currentRewrite, toggleRewritesModal, onSubmit }: Props) => {
|
||||||
@@ -25,7 +25,7 @@ const Form = ({ processingAdd, currentRewrite, toggleRewritesModal, onSubmit }:
|
|||||||
reset,
|
reset,
|
||||||
control,
|
control,
|
||||||
formState: { isDirty, isSubmitting },
|
formState: { isDirty, isSubmitting },
|
||||||
} = useForm<FormValues>({
|
} = useForm<RewriteFormValues>({
|
||||||
mode: 'onBlur',
|
mode: 'onBlur',
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
domain: currentRewrite?.domain || '',
|
domain: currentRewrite?.domain || '',
|
||||||
@@ -33,7 +33,7 @@ const Form = ({ processingAdd, currentRewrite, toggleRewritesModal, onSubmit }:
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleFormSubmit = async (data: FormValues) => {
|
const handleFormSubmit = async (data: RewriteFormValues) => {
|
||||||
if (onSubmit) {
|
if (onSubmit) {
|
||||||
await onSubmit(data);
|
await onSubmit(data);
|
||||||
}
|
}
|
||||||
@@ -59,6 +59,7 @@ const Form = ({ processingAdd, currentRewrite, toggleRewritesModal, onSubmit }:
|
|||||||
<Input
|
<Input
|
||||||
{...field}
|
{...field}
|
||||||
type="text"
|
type="text"
|
||||||
|
data-testid="rewrites_domain"
|
||||||
placeholder={t('form_domain')}
|
placeholder={t('form_domain')}
|
||||||
error={fieldState.error?.message}
|
error={fieldState.error?.message}
|
||||||
/>
|
/>
|
||||||
@@ -91,6 +92,7 @@ const Form = ({ processingAdd, currentRewrite, toggleRewritesModal, onSubmit }:
|
|||||||
<Input
|
<Input
|
||||||
{...field}
|
{...field}
|
||||||
type="text"
|
type="text"
|
||||||
|
data-testid="rewrites_answer"
|
||||||
placeholder={t('form_answer')}
|
placeholder={t('form_answer')}
|
||||||
error={fieldState.error?.message}
|
error={fieldState.error?.message}
|
||||||
/>
|
/>
|
||||||
@@ -111,6 +113,7 @@ const Form = ({ processingAdd, currentRewrite, toggleRewritesModal, onSubmit }:
|
|||||||
<div className="btn-list">
|
<div className="btn-list">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
data-testid="rewrites_cancel"
|
||||||
className="btn btn-secondary btn-standard"
|
className="btn btn-secondary btn-standard"
|
||||||
disabled={isSubmitting || processingAdd}
|
disabled={isSubmitting || processingAdd}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -122,6 +125,7 @@ const Form = ({ processingAdd, currentRewrite, toggleRewritesModal, onSubmit }:
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
|
data-testid="rewrites_save"
|
||||||
className="btn btn-success btn-standard"
|
className="btn btn-success btn-standard"
|
||||||
disabled={isSubmitting || !isDirty || processingAdd}>
|
disabled={isSubmitting || !isDirty || processingAdd}>
|
||||||
<Trans>save_btn</Trans>
|
<Trans>save_btn</Trans>
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ export const Form = ({ initialValues, blockedServices, processing, processingSet
|
|||||||
<div className="col-6">
|
<div className="col-6">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
data-testid="blocked_services_block_all"
|
||||||
className="btn btn-secondary btn-block"
|
className="btn btn-secondary btn-block"
|
||||||
disabled={processing || processingSet}
|
disabled={processing || processingSet}
|
||||||
onClick={() => handleToggleAllServices(true)}>
|
onClick={() => handleToggleAllServices(true)}>
|
||||||
@@ -56,6 +57,7 @@ export const Form = ({ initialValues, blockedServices, processing, processingSet
|
|||||||
<div className="col-6">
|
<div className="col-6">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
data-testid="blocked_services_unblock_all"
|
||||||
className="btn btn-secondary btn-block"
|
className="btn btn-secondary btn-block"
|
||||||
disabled={processing || processingSet}
|
disabled={processing || processingSet}
|
||||||
onClick={() => handleToggleAllServices(false)}>
|
onClick={() => handleToggleAllServices(false)}>
|
||||||
@@ -65,7 +67,7 @@ export const Form = ({ initialValues, blockedServices, processing, processingSet
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="services">
|
<div className="services">
|
||||||
{blockedServices.map((service: any) => (
|
{blockedServices.map((service: BlockedService) => (
|
||||||
<Controller
|
<Controller
|
||||||
key={service.id}
|
key={service.id}
|
||||||
name={`blocked_services.${service.id}`}
|
name={`blocked_services.${service.id}`}
|
||||||
@@ -73,6 +75,7 @@ export const Form = ({ initialValues, blockedServices, processing, processingSet
|
|||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<ServiceField
|
<ServiceField
|
||||||
{...field}
|
{...field}
|
||||||
|
data-testid={`blocked_services_${service.id}`}
|
||||||
placeholder={service.name}
|
placeholder={service.name}
|
||||||
disabled={processing || processingSet}
|
disabled={processing || processingSet}
|
||||||
icon={service.icon_svg}
|
icon={service.icon_svg}
|
||||||
@@ -86,6 +89,7 @@ export const Form = ({ initialValues, blockedServices, processing, processingSet
|
|||||||
<div className="btn-list">
|
<div className="btn-list">
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
|
data-testid="blocked_services_save"
|
||||||
className="btn btn-success btn-standard btn-large"
|
className="btn btn-success btn-standard btn-large"
|
||||||
disabled={isSubmitting || !isDirty || processing || processingSet}>
|
disabled={isSubmitting || !isDirty || processing || processingSet}>
|
||||||
<Trans>save_btn</Trans>
|
<Trans>save_btn</Trans>
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ type Props = ControllerRenderProps<FieldValues> & {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const ServiceField = React.forwardRef<HTMLInputElement, Props>(
|
export const ServiceField = React.forwardRef<HTMLInputElement, Props>(
|
||||||
({ name, value, onChange, onBlur, placeholder, disabled, className, icon, error }, ref) => (
|
({ name, value, onChange, onBlur, placeholder, disabled, className, icon, error, ...rest }, ref) => (
|
||||||
<>
|
<>
|
||||||
<label className={cn('service custom-switch', className)}>
|
<label className={cn('service custom-switch', className)}>
|
||||||
<input
|
<input
|
||||||
@@ -23,6 +23,7 @@ export const ServiceField = React.forwardRef<HTMLInputElement, Props>(
|
|||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
{...rest}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<span className="service__switch custom-switch-indicator"></span>
|
<span className="service__switch custom-switch-indicator"></span>
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ export const BlockedServices = ({ services }: Props) => {
|
|||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<ServiceField
|
<ServiceField
|
||||||
{...field}
|
{...field}
|
||||||
|
data-testid="clients_use_global_blocked_services"
|
||||||
placeholder={t('blocked_services_global')}
|
placeholder={t('blocked_services_global')}
|
||||||
className="service--global"
|
className="service--global"
|
||||||
/>
|
/>
|
||||||
@@ -38,6 +39,7 @@ export const BlockedServices = ({ services }: Props) => {
|
|||||||
<div className="col-6">
|
<div className="col-6">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
data-testid="clients_block_all"
|
||||||
className="btn btn-secondary btn-block"
|
className="btn btn-secondary btn-block"
|
||||||
disabled={useGlobalServices}
|
disabled={useGlobalServices}
|
||||||
onClick={() => handleToggleAllServices(true)}>
|
onClick={() => handleToggleAllServices(true)}>
|
||||||
@@ -48,6 +50,7 @@ export const BlockedServices = ({ services }: Props) => {
|
|||||||
<div className="col-6">
|
<div className="col-6">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
data-testid="clients_unblock_all"
|
||||||
className="btn btn-secondary btn-block"
|
className="btn btn-secondary btn-block"
|
||||||
disabled={useGlobalServices}
|
disabled={useGlobalServices}
|
||||||
onClick={() => handleToggleAllServices(false)}>
|
onClick={() => handleToggleAllServices(false)}>
|
||||||
@@ -65,6 +68,7 @@ export const BlockedServices = ({ services }: Props) => {
|
|||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<ServiceField
|
<ServiceField
|
||||||
{...field}
|
{...field}
|
||||||
|
data-testid={`clients_service_${service.id}`}
|
||||||
placeholder={service.name}
|
placeholder={service.name}
|
||||||
disabled={useGlobalServices}
|
disabled={useGlobalServices}
|
||||||
icon={service.icon_svg}
|
icon={service.icon_svg}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export const ClientIds = () => {
|
|||||||
<Input
|
<Input
|
||||||
{...field}
|
{...field}
|
||||||
type="text"
|
type="text"
|
||||||
|
data-testid={`clients_id_${index}`}
|
||||||
placeholder={t('form_enter_id')}
|
placeholder={t('form_enter_id')}
|
||||||
error={fieldState.error?.message}
|
error={fieldState.error?.message}
|
||||||
onBlur={(event) => {
|
onBlur={(event) => {
|
||||||
@@ -43,6 +44,7 @@ export const ClientIds = () => {
|
|||||||
<span className="input-group-append">
|
<span className="input-group-append">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
data-testid={`clients_id_remove_${index}`}
|
||||||
className="btn btn-secondary btn-icon btn-icon--green"
|
className="btn btn-secondary btn-icon btn-icon--green"
|
||||||
onClick={() => remove(index)}>
|
onClick={() => remove(index)}>
|
||||||
<svg className="icon icon--24">
|
<svg className="icon icon--24">
|
||||||
@@ -59,6 +61,7 @@ export const ClientIds = () => {
|
|||||||
))}
|
))}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
data-testid="clients_id_add"
|
||||||
className="btn btn-link btn-block btn-sm"
|
className="btn btn-link btn-block btn-sm"
|
||||||
onClick={() => append({ name: '' })}
|
onClick={() => append({ name: '' })}
|
||||||
title={t('form_add_id')}>
|
title={t('form_add_id')}>
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ export const MainSettings = ({ safeSearchServices }: Props) => {
|
|||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<Checkbox
|
<Checkbox
|
||||||
{...field}
|
{...field}
|
||||||
|
data-testid={`clients_${setting.name}`}
|
||||||
title={setting.placeholder}
|
title={setting.placeholder}
|
||||||
disabled={setting.name !== 'use_global_settings' ? useGlobalSettings : false}
|
disabled={setting.name !== 'use_global_settings' ? useGlobalSettings : false}
|
||||||
/>
|
/>
|
||||||
@@ -77,7 +78,12 @@ export const MainSettings = ({ safeSearchServices }: Props) => {
|
|||||||
name="safe_search.enabled"
|
name="safe_search.enabled"
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<Checkbox {...field} title={t('enforce_safe_search')} disabled={useGlobalSettings} />
|
<Checkbox
|
||||||
|
data-testid="clients_safe_search"
|
||||||
|
{...field}
|
||||||
|
title={t('enforce_safe_search')}
|
||||||
|
disabled={useGlobalSettings}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -89,7 +95,12 @@ export const MainSettings = ({ safeSearchServices }: Props) => {
|
|||||||
name={`safe_search.${searchKey}`}
|
name={`safe_search.${searchKey}`}
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<Checkbox {...field} title={captitalizeWords(searchKey)} disabled={useGlobalSettings} />
|
<Checkbox
|
||||||
|
{...field}
|
||||||
|
data-testid={`clients_safe_search_${searchKey}`}
|
||||||
|
title={captitalizeWords(searchKey)}
|
||||||
|
disabled={useGlobalSettings}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -104,7 +115,9 @@ export const MainSettings = ({ safeSearchServices }: Props) => {
|
|||||||
<Controller
|
<Controller
|
||||||
name={setting.name}
|
name={setting.name}
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field }) => <Checkbox {...field} title={setting.placeholder} />}
|
render={({ field }) => (
|
||||||
|
<Checkbox {...field} data-testid={`clients_${setting.name}`} title={setting.placeholder} />
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { Textarea } from '../../../../ui/Controls/Textarea';
|
|||||||
import { ClientForm } from '../types';
|
import { ClientForm } from '../types';
|
||||||
import { Checkbox } from '../../../../ui/Controls/Checkbox';
|
import { Checkbox } from '../../../../ui/Controls/Checkbox';
|
||||||
import { Input } from '../../../../ui/Controls/Input';
|
import { Input } from '../../../../ui/Controls/Input';
|
||||||
import { trimLinesAndRemoveEmpty } from '../../../../../helpers/helpers';
|
import { toNumber } from '../../../../../helpers/form';
|
||||||
|
|
||||||
export const UpstreamDns = () => {
|
export const UpstreamDns = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -18,14 +18,7 @@ export const UpstreamDns = () => {
|
|||||||
return (
|
return (
|
||||||
<div title={t('upstream_dns')}>
|
<div title={t('upstream_dns')}>
|
||||||
<div className="form__desc mb-3">
|
<div className="form__desc mb-3">
|
||||||
<Trans
|
<Trans components={[<a href="#dns" key="0" />]}>upstream_dns_client_desc</Trans>
|
||||||
components={[
|
|
||||||
<a href="#dns" key="0">
|
|
||||||
link
|
|
||||||
</a>,
|
|
||||||
]}>
|
|
||||||
upstream_dns_client_desc
|
|
||||||
</Trans>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Controller
|
<Controller
|
||||||
@@ -34,13 +27,10 @@ export const UpstreamDns = () => {
|
|||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<Textarea
|
<Textarea
|
||||||
{...field}
|
{...field}
|
||||||
|
data-testid="clients_upstreams"
|
||||||
className="form-control form-control--textarea mb-5"
|
className="form-control form-control--textarea mb-5"
|
||||||
placeholder={t('upstream_dns')}
|
placeholder={t('upstream_dns')}
|
||||||
onBlur={(event) => {
|
trimOnBlur
|
||||||
const normalizedValue = trimLinesAndRemoveEmpty(event.target.value);
|
|
||||||
field.onBlur();
|
|
||||||
field.onChange(normalizedValue);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -53,7 +43,13 @@ export const UpstreamDns = () => {
|
|||||||
<Controller
|
<Controller
|
||||||
name="upstreams_cache_enabled"
|
name="upstreams_cache_enabled"
|
||||||
control={control}
|
control={control}
|
||||||
render={({ field }) => <Checkbox {...field} title={t('enable_upstream_dns_cache')} />}
|
render={({ field }) => (
|
||||||
|
<Checkbox
|
||||||
|
{...field}
|
||||||
|
data-testid="clients_upstreams_cache_enabled"
|
||||||
|
title={t('enable_upstream_dns_cache')}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -69,10 +65,15 @@ export const UpstreamDns = () => {
|
|||||||
<Input
|
<Input
|
||||||
{...field}
|
{...field}
|
||||||
type="number"
|
type="number"
|
||||||
|
data-testid="clients_upstreams_cache_size"
|
||||||
placeholder={t('enter_cache_size')}
|
placeholder={t('enter_cache_size')}
|
||||||
error={fieldState.error?.message}
|
error={fieldState.error?.message}
|
||||||
min={0}
|
min={0}
|
||||||
max={UINT32_RANGE.MAX}
|
max={UINT32_RANGE.MAX}
|
||||||
|
onChange={(e) => {
|
||||||
|
const { value } = e.target;
|
||||||
|
field.onChange(toNumber(value));
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -70,7 +70,6 @@ export const Form = ({
|
|||||||
handleSubmit,
|
handleSubmit,
|
||||||
reset,
|
reset,
|
||||||
control,
|
control,
|
||||||
setValue,
|
|
||||||
formState: { isSubmitting, isValid },
|
formState: { isSubmitting, isValid },
|
||||||
} = methods;
|
} = methods;
|
||||||
|
|
||||||
@@ -116,6 +115,7 @@ export const Form = ({
|
|||||||
<Input
|
<Input
|
||||||
{...field}
|
{...field}
|
||||||
type="text"
|
type="text"
|
||||||
|
data-testid="clients_name"
|
||||||
placeholder={t('form_client_name')}
|
placeholder={t('form_client_name')}
|
||||||
error={fieldState.error?.message}
|
error={fieldState.error?.message}
|
||||||
onBlur={(event) => {
|
onBlur={(event) => {
|
||||||
@@ -155,13 +155,11 @@ export const Form = ({
|
|||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<Select
|
<Select
|
||||||
{...field}
|
{...field}
|
||||||
|
data-testid="clients_tags"
|
||||||
options={tagsOptions}
|
options={tagsOptions}
|
||||||
className="basic-multi-select"
|
className="basic-multi-select"
|
||||||
classNamePrefix="select"
|
classNamePrefix="select"
|
||||||
isMulti
|
isMulti
|
||||||
onChange={(selectedOptions) => {
|
|
||||||
setValue('tags', selectedOptions);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import { Radio } from '../../ui/Controls/Radio';
|
|||||||
import { Input } from '../../ui/Controls/Input';
|
import { Input } from '../../ui/Controls/Input';
|
||||||
import { Textarea } from '../../ui/Controls/Textarea';
|
import { Textarea } from '../../ui/Controls/Textarea';
|
||||||
import { EncryptionData } from '../../../initialState';
|
import { EncryptionData } from '../../../initialState';
|
||||||
|
import { toNumber } from '../../../helpers/form';
|
||||||
|
|
||||||
const certificateSourceOptions = [
|
const certificateSourceOptions = [
|
||||||
{
|
{
|
||||||
@@ -335,6 +336,10 @@ export const Form = ({
|
|||||||
placeholder={t('encryption_https')}
|
placeholder={t('encryption_https')}
|
||||||
error={fieldState.error?.message}
|
error={fieldState.error?.message}
|
||||||
disabled={!isEnabled}
|
disabled={!isEnabled}
|
||||||
|
onChange={(e) => {
|
||||||
|
const { value } = e.target;
|
||||||
|
field.onChange(toNumber(value));
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -362,6 +367,10 @@ export const Form = ({
|
|||||||
placeholder={t('encryption_dot')}
|
placeholder={t('encryption_dot')}
|
||||||
error={fieldState.error?.message}
|
error={fieldState.error?.message}
|
||||||
disabled={!isEnabled}
|
disabled={!isEnabled}
|
||||||
|
onChange={(e) => {
|
||||||
|
const { value } = e.target;
|
||||||
|
field.onChange(toNumber(value));
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -389,6 +398,10 @@ export const Form = ({
|
|||||||
placeholder={t('encryption_doq')}
|
placeholder={t('encryption_doq')}
|
||||||
error={fieldState.error?.message}
|
error={fieldState.error?.message}
|
||||||
disabled={!isEnabled}
|
disabled={!isEnabled}
|
||||||
|
onChange={(e) => {
|
||||||
|
const { value } = e.target;
|
||||||
|
field.onChange(toNumber(value));
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import i18next from 'i18next';
|
|||||||
import { Controller, useForm } from 'react-hook-form';
|
import { Controller, useForm } from 'react-hook-form';
|
||||||
import { STATS_INTERVALS_DAYS, DAY, RETENTION_CUSTOM, RETENTION_RANGE } from '../../../helpers/constants';
|
import { STATS_INTERVALS_DAYS, DAY, RETENTION_CUSTOM, RETENTION_RANGE } from '../../../helpers/constants';
|
||||||
|
|
||||||
import { trimLinesAndRemoveEmpty } from '../../../helpers/helpers';
|
|
||||||
import '../FormButton.css';
|
import '../FormButton.css';
|
||||||
import { Checkbox } from '../../ui/Controls/Checkbox';
|
import { Checkbox } from '../../ui/Controls/Checkbox';
|
||||||
import { Input } from '../../ui/Controls/Input';
|
import { Input } from '../../ui/Controls/Input';
|
||||||
@@ -75,11 +74,6 @@ export const Form = ({ initialValues, processing, processingReset, onSubmit, onR
|
|||||||
onSubmit(data);
|
onSubmit(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleIgnoredBlur = (e: React.FocusEvent<HTMLTextAreaElement>) => {
|
|
||||||
const trimmed = trimLinesAndRemoveEmpty(e.target.value);
|
|
||||||
setValue('ignored', trimmed);
|
|
||||||
};
|
|
||||||
|
|
||||||
const disableSubmit = isSubmitting || processing || (intervalValue === RETENTION_CUSTOM && !customIntervalValue);
|
const disableSubmit = isSubmitting || processing || (intervalValue === RETENTION_CUSTOM && !customIntervalValue);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -179,7 +173,7 @@ export const Form = ({ initialValues, processing, processingReset, onSubmit, onR
|
|||||||
className="text-input"
|
className="text-input"
|
||||||
disabled={processing}
|
disabled={processing}
|
||||||
error={fieldState.error?.message}
|
error={fieldState.error?.message}
|
||||||
onBlur={handleIgnoredBlur}
|
trimOnBlur
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ type Props = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const Checkbox = forwardRef<HTMLInputElement, Props>(
|
export const Checkbox = forwardRef<HTMLInputElement, Props>(
|
||||||
({ title, subtitle, value, name, disabled, error, className = 'checkbox--form', onChange }, ref) => (
|
({ title, subtitle, value, name, disabled, error, className = 'checkbox--form', onChange, ...rest }, ref) => (
|
||||||
<>
|
<>
|
||||||
<label className={clsx('checkbox', className)}>
|
<label className={clsx('checkbox', className)}>
|
||||||
<span className="checkbox__marker" />
|
<span className="checkbox__marker" />
|
||||||
@@ -27,6 +27,7 @@ export const Checkbox = forwardRef<HTMLInputElement, Props>(
|
|||||||
checked={value}
|
checked={value}
|
||||||
onChange={(e) => onChange(e.target.checked)}
|
onChange={(e) => onChange(e.target.checked)}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
{...rest}
|
||||||
/>
|
/>
|
||||||
<span className="checkbox__label">
|
<span className="checkbox__label">
|
||||||
<span className="checkbox__label-text checkbox__label-text--long">
|
<span className="checkbox__label-text checkbox__label-text--long">
|
||||||
|
|||||||
@@ -1,29 +1,42 @@
|
|||||||
import React, { ComponentProps, forwardRef } from 'react';
|
import React, { ComponentProps, forwardRef } from 'react';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
|
import { trimLinesAndRemoveEmpty } from '../../../helpers/helpers';
|
||||||
|
|
||||||
type Props = ComponentProps<'textarea'> & {
|
type Props = ComponentProps<'textarea'> & {
|
||||||
className?: string;
|
className?: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
error?: string;
|
error?: string;
|
||||||
|
trimOnBlur?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Textarea = forwardRef<HTMLTextAreaElement, Props>(({ name, label, className, error, ...rest }, ref) => (
|
export const Textarea = forwardRef<HTMLTextAreaElement, Props>(
|
||||||
<div className={clsx('form-group', { 'has-error': !!error })}>
|
({ name, label, className, error, trimOnBlur, onBlur, ...rest }, ref) => (
|
||||||
{label && (
|
<div className={clsx('form-group', { 'has-error': !!error })}>
|
||||||
<label className="form__label" htmlFor={name}>
|
{label && (
|
||||||
{label}
|
<label className="form__label" htmlFor={name}>
|
||||||
</label>
|
{label}
|
||||||
)}
|
</label>
|
||||||
<textarea
|
|
||||||
className={clsx(
|
|
||||||
'form-control form-control--textarea form-control--textarea-small font-monospace',
|
|
||||||
className,
|
|
||||||
)}
|
)}
|
||||||
ref={ref}
|
<textarea
|
||||||
{...rest}
|
className={clsx(
|
||||||
/>
|
'form-control form-control--textarea form-control--textarea-small font-monospace',
|
||||||
{error && <div className="form__message form__message--error">{error}</div>}
|
className,
|
||||||
</div>
|
)}
|
||||||
));
|
ref={ref}
|
||||||
|
onBlur={(e) => {
|
||||||
|
if (trimOnBlur) {
|
||||||
|
const normalizedValue = trimLinesAndRemoveEmpty(e.target.value);
|
||||||
|
rest.onChange(normalizedValue);
|
||||||
|
}
|
||||||
|
if (onBlur) {
|
||||||
|
onBlur(e);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
{error && <div className="form__message form__message--error">{error}</div>}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
Textarea.displayName = 'Textarea';
|
Textarea.displayName = 'Textarea';
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
validateIsSafePort,
|
validateIsSafePort,
|
||||||
} from '../../../helpers/validators';
|
} from '../../../helpers/validators';
|
||||||
import { Input } from '../Controls/Input';
|
import { Input } from '../Controls/Input';
|
||||||
|
import { Select } from '../Controls/Select';
|
||||||
|
|
||||||
const getDownloadLink = (host: string, clientId: string, protocol: string, invalid: boolean) => {
|
const getDownloadLink = (host: string, clientId: string, protocol: string, invalid: boolean) => {
|
||||||
if (!host || invalid) {
|
if (!host || invalid) {
|
||||||
@@ -62,7 +63,6 @@ export const MobileConfigForm = ({ initialValues }: Props) => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
register,
|
|
||||||
watch,
|
watch,
|
||||||
control,
|
control,
|
||||||
formState: { isValid },
|
formState: { isValid },
|
||||||
@@ -101,6 +101,7 @@ export const MobileConfigForm = ({ initialValues }: Props) => {
|
|||||||
<Input
|
<Input
|
||||||
{...field}
|
{...field}
|
||||||
type="text"
|
type="text"
|
||||||
|
data-testid="mobile_config_host"
|
||||||
label={t('dhcp_table_hostname')}
|
label={t('dhcp_table_hostname')}
|
||||||
placeholder={t('form_enter_hostname')}
|
placeholder={t('form_enter_hostname')}
|
||||||
error={fieldState.error?.message}
|
error={fieldState.error?.message}
|
||||||
@@ -123,6 +124,7 @@ export const MobileConfigForm = ({ initialValues }: Props) => {
|
|||||||
<Input
|
<Input
|
||||||
{...field}
|
{...field}
|
||||||
type="number"
|
type="number"
|
||||||
|
data-testid="mobile_config_port"
|
||||||
label={t('encryption_https')}
|
label={t('encryption_https')}
|
||||||
placeholder={t('encryption_https')}
|
placeholder={t('encryption_https')}
|
||||||
error={fieldState.error?.message}
|
error={fieldState.error?.message}
|
||||||
@@ -160,6 +162,7 @@ export const MobileConfigForm = ({ initialValues }: Props) => {
|
|||||||
<Input
|
<Input
|
||||||
{...field}
|
{...field}
|
||||||
type="text"
|
type="text"
|
||||||
|
data-testid="mobile_config_client_id"
|
||||||
placeholder={t('client_id_placeholder')}
|
placeholder={t('client_id_placeholder')}
|
||||||
error={fieldState.error?.message}
|
error={fieldState.error?.message}
|
||||||
/>
|
/>
|
||||||
@@ -168,14 +171,16 @@ export const MobileConfigForm = ({ initialValues }: Props) => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="form__group form__group--settings">
|
<div className="form__group form__group--settings">
|
||||||
<label htmlFor="protocol" className="form__label">
|
<Controller
|
||||||
{i18next.t('protocol')}
|
name="protocol"
|
||||||
</label>
|
control={control}
|
||||||
|
render={({ field }) => (
|
||||||
<select id="protocol" className="form-control" {...register('protocol')}>
|
<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.DOT}>{i18next.t('dns_over_tls')}</option>
|
||||||
<option value={MOBILE_CONFIG_LINKS.DOH}>{i18next.t('dns_over_https')}</option>
|
<option value={MOBILE_CONFIG_LINKS.DOH}>{i18next.t('dns_over_https')}</option>
|
||||||
</select>
|
</Select>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user