all: rm files
This commit is contained in:
26
CHANGELOG.md
26
CHANGELOG.md
@@ -9,23 +9,15 @@ The format is based on [*Keep a Changelog*](https://keepachangelog.com/en/1.0.0/
|
|||||||
<!--
|
<!--
|
||||||
## [v0.108.0] – TBA
|
## [v0.108.0] – TBA
|
||||||
|
|
||||||
## [v0.107.59] - 2025-04-01 (APPROX.)
|
## [v0.107.58] - 2025-03-11 (APPROX.)
|
||||||
|
|
||||||
See also the [v0.107.59 GitHub milestone][ms-v0.107.59].
|
See also the [v0.107.58 GitHub milestone][ms-v0.107.58].
|
||||||
|
|
||||||
[ms-v0.107.59]: https://github.com/AdguardTeam/AdGuardHome/milestone/94?closed=1
|
[ms-v0.107.58]: https://github.com/AdguardTeam/AdGuardHome/milestone/93?closed=1
|
||||||
|
|
||||||
NOTE: Add new changes BELOW THIS COMMENT.
|
NOTE: Add new changes BELOW THIS COMMENT.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
<!--
|
|
||||||
NOTE: Add new changes ABOVE THIS COMMENT.
|
|
||||||
-->
|
|
||||||
|
|
||||||
## [v0.107.58] - 2025-03-13
|
|
||||||
|
|
||||||
See also the [v0.107.58 GitHub milestone][ms-v0.107.58].
|
|
||||||
|
|
||||||
### Security
|
### Security
|
||||||
|
|
||||||
- Go version has been updated to prevent the possibility of exploiting the Go vulnerabilities fixed in [1.24.1][go-1.24.1].
|
- Go version has been updated to prevent the possibility of exploiting the Go vulnerabilities fixed in [1.24.1][go-1.24.1].
|
||||||
@@ -46,7 +38,10 @@ See also the [v0.107.58 GitHub milestone][ms-v0.107.58].
|
|||||||
[#7583]: https://github.com/AdguardTeam/AdGuardHome/issues/7583
|
[#7583]: https://github.com/AdguardTeam/AdGuardHome/issues/7583
|
||||||
|
|
||||||
[go-1.24.1]: https://groups.google.com/g/golang-announce/c/4t3lzH3I0eI
|
[go-1.24.1]: https://groups.google.com/g/golang-announce/c/4t3lzH3I0eI
|
||||||
[ms-v0.107.58]: https://github.com/AdguardTeam/AdGuardHome/milestone/93?closed=1
|
|
||||||
|
<!--
|
||||||
|
NOTE: Add new changes ABOVE THIS COMMENT.
|
||||||
|
-->
|
||||||
|
|
||||||
## [v0.107.57] - 2025-02-20
|
## [v0.107.57] - 2025-02-20
|
||||||
|
|
||||||
@@ -3044,12 +3039,11 @@ See also the [v0.104.2 GitHub milestone][ms-v0.104.2].
|
|||||||
[ms-v0.104.2]: https://github.com/AdguardTeam/AdGuardHome/milestone/28?closed=1
|
[ms-v0.104.2]: https://github.com/AdguardTeam/AdGuardHome/milestone/28?closed=1
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.59...HEAD
|
|
||||||
[v0.107.59]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.58...v0.107.59
|
|
||||||
-->
|
|
||||||
|
|
||||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.58...HEAD
|
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.58...HEAD
|
||||||
[v0.107.58]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.57...v0.107.58
|
[v0.107.58]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.57...v0.107.58
|
||||||
|
-->
|
||||||
|
|
||||||
|
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.57...HEAD
|
||||||
[v0.107.57]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.56...v0.107.57
|
[v0.107.57]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.56...v0.107.57
|
||||||
[v0.107.56]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.55...v0.107.56
|
[v0.107.56]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.55...v0.107.56
|
||||||
[v0.107.55]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.54...v0.107.55
|
[v0.107.55]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.54...v0.107.55
|
||||||
|
|||||||
6
client/jest.config.mjs
vendored
6
client/jest.config.mjs
vendored
@@ -1,6 +0,0 @@
|
|||||||
export default {
|
|
||||||
testEnvironment: 'jsdom',
|
|
||||||
transform: {
|
|
||||||
'^.+\\.tsx?$': 'babel-jest',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@@ -1,514 +0,0 @@
|
|||||||
import React, { useState } from 'react';
|
|
||||||
import { connect, useSelector } from 'react-redux';
|
|
||||||
import { Field, FieldArray, reduxForm, formValueSelector, FormErrors } from 'redux-form';
|
|
||||||
import { Trans, withTranslation } from 'react-i18next';
|
|
||||||
import flow from 'lodash/flow';
|
|
||||||
|
|
||||||
import Select from 'react-select';
|
|
||||||
|
|
||||||
import i18n from '../../../i18n';
|
|
||||||
|
|
||||||
import Tabs from '../../ui/Tabs';
|
|
||||||
|
|
||||||
import Examples from '../Dns/Upstream/Examples';
|
|
||||||
|
|
||||||
import { ScheduleForm } from '../../Filters/Services/ScheduleForm';
|
|
||||||
import { toggleAllServices, trimLinesAndRemoveEmpty, captitalizeWords } from '../../../helpers/helpers';
|
|
||||||
import {
|
|
||||||
toNumber,
|
|
||||||
renderInputField,
|
|
||||||
renderGroupField,
|
|
||||||
CheckboxField,
|
|
||||||
renderServiceField,
|
|
||||||
renderTextareaField,
|
|
||||||
} from '../../../helpers/form';
|
|
||||||
import { validateClientId, validateRequiredValue } from '../../../helpers/validators';
|
|
||||||
import { CLIENT_ID_LINK, FORM_NAME, UINT32_RANGE } from '../../../helpers/constants';
|
|
||||||
import './Service.css';
|
|
||||||
import { RootState } from '../../../initialState';
|
|
||||||
|
|
||||||
const settingsCheckboxes = [
|
|
||||||
{
|
|
||||||
name: 'use_global_settings',
|
|
||||||
placeholder: 'client_global_settings',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'filtering_enabled',
|
|
||||||
placeholder: 'block_domain_use_filters_and_hosts',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'safebrowsing_enabled',
|
|
||||||
placeholder: 'use_adguard_browsing_sec',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'parental_enabled',
|
|
||||||
placeholder: 'use_adguard_parental',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const logAndStatsCheckboxes = [
|
|
||||||
{
|
|
||||||
name: 'ignore_querylog',
|
|
||||||
placeholder: 'ignore_query_log',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'ignore_statistics',
|
|
||||||
placeholder: 'ignore_statistics',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
const validate = (values: any): FormErrors<any, string> => {
|
|
||||||
const errors: {
|
|
||||||
name?: string;
|
|
||||||
ids?: string[];
|
|
||||||
} = {};
|
|
||||||
const { name, ids } = values;
|
|
||||||
|
|
||||||
errors.name = validateRequiredValue(name);
|
|
||||||
|
|
||||||
if (ids && ids.length) {
|
|
||||||
const idArrayErrors: any = [];
|
|
||||||
ids.forEach((id: any, idx: any) => {
|
|
||||||
idArrayErrors[idx] = validateRequiredValue(id) || validateClientId(id);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (idArrayErrors.length) {
|
|
||||||
errors.ids = idArrayErrors;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// @ts-expect-error FIXME: ts migration
|
|
||||||
return errors;
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderFieldsWrapper = (placeholder: any, buttonTitle: any) =>
|
|
||||||
function cell(row: any) {
|
|
||||||
const { fields } = row;
|
|
||||||
return (
|
|
||||||
<div className="form__group">
|
|
||||||
{fields.map((ip: any, index: any) => (
|
|
||||||
<div key={index} className="mb-1">
|
|
||||||
<Field
|
|
||||||
name={ip}
|
|
||||||
component={renderGroupField}
|
|
||||||
type="text"
|
|
||||||
className="form-control"
|
|
||||||
placeholder={placeholder}
|
|
||||||
isActionAvailable={index !== 0}
|
|
||||||
removeField={() => fields.remove(index)}
|
|
||||||
normalizeOnBlur={(data: any) => data.trim()}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="btn btn-link btn-block btn-sm"
|
|
||||||
onClick={() => fields.push()}
|
|
||||||
title={buttonTitle}>
|
|
||||||
<svg className="icon icon--24">
|
|
||||||
<use xlinkHref="#plus" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Should create function outside of component to prevent component re-renders
|
|
||||||
const renderFields = renderFieldsWrapper(i18n.t('form_enter_id'), i18n.t('form_add_id'));
|
|
||||||
|
|
||||||
interface renderMultiselectProps {
|
|
||||||
input: {
|
|
||||||
name: string;
|
|
||||||
value: string;
|
|
||||||
checked: boolean;
|
|
||||||
onChange: (...args: unknown[]) => unknown;
|
|
||||||
onBlur: (...args: unknown[]) => unknown;
|
|
||||||
};
|
|
||||||
placeholder?: string;
|
|
||||||
options?: unknown[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const renderMultiselect = (props: renderMultiselectProps) => {
|
|
||||||
const { input, placeholder, options } = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Select
|
|
||||||
{...input}
|
|
||||||
options={options}
|
|
||||||
className="basic-multi-select"
|
|
||||||
classNamePrefix="select"
|
|
||||||
onChange={(value: any) => input.onChange(value)}
|
|
||||||
onBlur={() => input.onBlur(input.value)}
|
|
||||||
placeholder={placeholder}
|
|
||||||
blurInputOnSelect={false}
|
|
||||||
isMulti
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
interface FormProps {
|
|
||||||
pristine: boolean;
|
|
||||||
handleSubmit: (...args: unknown[]) => string;
|
|
||||||
reset: (...args: unknown[]) => string;
|
|
||||||
change: (...args: unknown[]) => unknown;
|
|
||||||
submitting: boolean;
|
|
||||||
handleClose: (...args: unknown[]) => unknown;
|
|
||||||
useGlobalSettings?: boolean;
|
|
||||||
useGlobalServices?: boolean;
|
|
||||||
blockedServicesSchedule?: {
|
|
||||||
time_zone: string;
|
|
||||||
};
|
|
||||||
t: (...args: unknown[]) => string;
|
|
||||||
processingAdding: boolean;
|
|
||||||
processingUpdating: boolean;
|
|
||||||
invalid: boolean;
|
|
||||||
tagsOptions: unknown[];
|
|
||||||
initialValues?: {
|
|
||||||
safe_search: any;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let Form = (props: FormProps) => {
|
|
||||||
const {
|
|
||||||
t,
|
|
||||||
handleSubmit,
|
|
||||||
reset,
|
|
||||||
change,
|
|
||||||
submitting,
|
|
||||||
useGlobalSettings,
|
|
||||||
useGlobalServices,
|
|
||||||
blockedServicesSchedule,
|
|
||||||
handleClose,
|
|
||||||
processingAdding,
|
|
||||||
processingUpdating,
|
|
||||||
invalid,
|
|
||||||
tagsOptions,
|
|
||||||
initialValues,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const services = useSelector((store: RootState) => store?.services);
|
|
||||||
const { safe_search } = initialValues;
|
|
||||||
const safeSearchServices = { ...safe_search };
|
|
||||||
delete safeSearchServices.enabled;
|
|
||||||
|
|
||||||
const [activeTabLabel, setActiveTabLabel] = useState('settings');
|
|
||||||
|
|
||||||
const handleScheduleSubmit = (values: any) => {
|
|
||||||
change('blocked_services_schedule', { ...values });
|
|
||||||
};
|
|
||||||
|
|
||||||
const tabs = {
|
|
||||||
settings: {
|
|
||||||
title: 'settings',
|
|
||||||
|
|
||||||
component: (
|
|
||||||
<div title={props.t('main_settings')}>
|
|
||||||
<div className="form__label--bot form__label--bold">{t('protection_section_label')}</div>
|
|
||||||
{settingsCheckboxes.map((setting) => (
|
|
||||||
<div className="form__group" key={setting.name}>
|
|
||||||
<Field
|
|
||||||
name={setting.name}
|
|
||||||
type="checkbox"
|
|
||||||
component={CheckboxField}
|
|
||||||
placeholder={t(setting.placeholder)}
|
|
||||||
disabled={setting.name !== 'use_global_settings' ? useGlobalSettings : false}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
|
|
||||||
<div className="form__group">
|
|
||||||
<Field
|
|
||||||
name="safe_search.enabled"
|
|
||||||
type="checkbox"
|
|
||||||
component={CheckboxField}
|
|
||||||
placeholder={t('enforce_safe_search')}
|
|
||||||
disabled={useGlobalSettings}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="form__group--inner">
|
|
||||||
{Object.keys(safeSearchServices).map((searchKey) => (
|
|
||||||
<div key={searchKey}>
|
|
||||||
<Field
|
|
||||||
name={`safe_search.${searchKey}`}
|
|
||||||
type="checkbox"
|
|
||||||
component={CheckboxField}
|
|
||||||
placeholder={captitalizeWords(searchKey)}
|
|
||||||
disabled={useGlobalSettings}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="form__label--bold form__label--top form__label--bot">
|
|
||||||
{t('log_and_stats_section_label')}
|
|
||||||
</div>
|
|
||||||
{logAndStatsCheckboxes.map((setting) => (
|
|
||||||
<div className="form__group" key={setting.name}>
|
|
||||||
<Field
|
|
||||||
name={setting.name}
|
|
||||||
type="checkbox"
|
|
||||||
component={CheckboxField}
|
|
||||||
placeholder={t(setting.placeholder)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
block_services: {
|
|
||||||
title: 'block_services',
|
|
||||||
|
|
||||||
component: (
|
|
||||||
<div title={props.t('block_services')}>
|
|
||||||
<div className="form__group">
|
|
||||||
<Field
|
|
||||||
name="use_global_blocked_services"
|
|
||||||
type="checkbox"
|
|
||||||
component={renderServiceField}
|
|
||||||
placeholder={t('blocked_services_global')}
|
|
||||||
modifier="service--global"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="row mb-4">
|
|
||||||
<div className="col-6">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="btn btn-secondary btn-block"
|
|
||||||
disabled={useGlobalServices}
|
|
||||||
onClick={() => toggleAllServices(services.allServices, change, true)}>
|
|
||||||
<Trans>block_all</Trans>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="col-6">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="btn btn-secondary btn-block"
|
|
||||||
disabled={useGlobalServices}
|
|
||||||
onClick={() => toggleAllServices(services.allServices, change, false)}>
|
|
||||||
<Trans>unblock_all</Trans>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{services.allServices.length > 0 && (
|
|
||||||
<div className="services">
|
|
||||||
{services.allServices.map((service: any) => (
|
|
||||||
<Field
|
|
||||||
key={service.id}
|
|
||||||
icon={service.icon_svg}
|
|
||||||
name={`blocked_services.${service.id}`}
|
|
||||||
type="checkbox"
|
|
||||||
component={renderServiceField}
|
|
||||||
placeholder={service.name}
|
|
||||||
disabled={useGlobalServices}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
schedule_services: {
|
|
||||||
title: 'schedule_services',
|
|
||||||
component: (
|
|
||||||
<>
|
|
||||||
<div className="form__desc mb-4">
|
|
||||||
<Trans>schedule_services_desc_client</Trans>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ScheduleForm
|
|
||||||
schedule={blockedServicesSchedule}
|
|
||||||
onScheduleSubmit={handleScheduleSubmit}
|
|
||||||
clientForm
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
upstream_dns: {
|
|
||||||
title: 'upstream_dns',
|
|
||||||
|
|
||||||
component: (
|
|
||||||
<div title={props.t('upstream_dns')}>
|
|
||||||
<div className="form__desc mb-3">
|
|
||||||
<Trans
|
|
||||||
components={[
|
|
||||||
<a href="#dns" key="0">
|
|
||||||
link
|
|
||||||
</a>,
|
|
||||||
]}>
|
|
||||||
upstream_dns_client_desc
|
|
||||||
</Trans>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Field
|
|
||||||
id="upstreams"
|
|
||||||
name="upstreams"
|
|
||||||
component={renderTextareaField}
|
|
||||||
type="text"
|
|
||||||
className="form-control form-control--textarea mb-5"
|
|
||||||
placeholder={t('upstream_dns')}
|
|
||||||
normalizeOnBlur={trimLinesAndRemoveEmpty}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Examples />
|
|
||||||
|
|
||||||
<div className="form__label--bold mt-5 mb-3">{t('upstream_dns_cache_configuration')}</div>
|
|
||||||
|
|
||||||
<div className="form__group mb-2">
|
|
||||||
<Field
|
|
||||||
name="upstreams_cache_enabled"
|
|
||||||
type="checkbox"
|
|
||||||
component={CheckboxField}
|
|
||||||
placeholder={t('enable_upstream_dns_cache')}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="form__group form__group--settings">
|
|
||||||
<label htmlFor="upstreams_cache_size" className="form__label">
|
|
||||||
{t('dns_cache_size')}
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<Field
|
|
||||||
name="upstreams_cache_size"
|
|
||||||
type="number"
|
|
||||||
component={renderInputField}
|
|
||||||
placeholder={t('enter_cache_size')}
|
|
||||||
className="form-control"
|
|
||||||
normalize={toNumber}
|
|
||||||
min={0}
|
|
||||||
max={UINT32_RANGE.MAX}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const activeTab = tabs[activeTabLabel].component;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<form onSubmit={handleSubmit}>
|
|
||||||
<div className="modal-body">
|
|
||||||
<div className="form__group mb-0">
|
|
||||||
<div className="form__group">
|
|
||||||
<Field
|
|
||||||
id="name"
|
|
||||||
name="name"
|
|
||||||
component={renderInputField}
|
|
||||||
type="text"
|
|
||||||
className="form-control"
|
|
||||||
placeholder={t('form_client_name')}
|
|
||||||
normalizeOnBlur={(data: any) => data.trim()}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="form__group mb-4">
|
|
||||||
<div className="form__label">
|
|
||||||
<strong className="mr-3">
|
|
||||||
<Trans>tags_title</Trans>
|
|
||||||
</strong>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="form__desc mt-0 mb-2">
|
|
||||||
<Trans
|
|
||||||
components={[
|
|
||||||
<a
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
href="https://link.adtidy.org/forward.html?action=dns_kb_filtering_syntax_ctag&from=ui&app=home"
|
|
||||||
key="0">
|
|
||||||
link
|
|
||||||
</a>,
|
|
||||||
]}>
|
|
||||||
tags_desc
|
|
||||||
</Trans>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Field
|
|
||||||
name="tags"
|
|
||||||
component={renderMultiselect}
|
|
||||||
placeholder={t('form_select_tags')}
|
|
||||||
options={tagsOptions}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="form__group">
|
|
||||||
<div className="form__label">
|
|
||||||
<strong className="mr-3">
|
|
||||||
<Trans>client_identifier</Trans>
|
|
||||||
</strong>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="form__desc mt-0">
|
|
||||||
<Trans
|
|
||||||
components={[
|
|
||||||
<a href={CLIENT_ID_LINK} target="_blank" rel="noopener noreferrer" key="0">
|
|
||||||
text
|
|
||||||
</a>,
|
|
||||||
]}>
|
|
||||||
client_identifier_desc
|
|
||||||
</Trans>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="form__group">
|
|
||||||
<FieldArray name="ids" component={renderFields} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Tabs
|
|
||||||
controlClass="form"
|
|
||||||
tabs={tabs}
|
|
||||||
activeTabLabel={activeTabLabel}
|
|
||||||
setActiveTabLabel={setActiveTabLabel}>
|
|
||||||
{activeTab}
|
|
||||||
</Tabs>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="modal-footer">
|
|
||||||
<div className="btn-list">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="btn btn-secondary btn-standard"
|
|
||||||
disabled={submitting}
|
|
||||||
onClick={() => {
|
|
||||||
reset();
|
|
||||||
handleClose();
|
|
||||||
}}>
|
|
||||||
<Trans>cancel_btn</Trans>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
className="btn btn-success btn-standard"
|
|
||||||
disabled={submitting || invalid || processingAdding || processingUpdating}>
|
|
||||||
<Trans>save_btn</Trans>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const selector = formValueSelector(FORM_NAME.CLIENT);
|
|
||||||
|
|
||||||
Form = connect((state) => {
|
|
||||||
const useGlobalSettings = selector(state, 'use_global_settings');
|
|
||||||
const useGlobalServices = selector(state, 'use_global_blocked_services');
|
|
||||||
const blockedServicesSchedule = selector(state, 'blocked_services_schedule');
|
|
||||||
return {
|
|
||||||
useGlobalSettings,
|
|
||||||
useGlobalServices,
|
|
||||||
blockedServicesSchedule,
|
|
||||||
};
|
|
||||||
})(Form);
|
|
||||||
|
|
||||||
export default flow([
|
|
||||||
withTranslation(),
|
|
||||||
reduxForm({
|
|
||||||
form: FORM_NAME.CLIENT,
|
|
||||||
enableReinitialize: true,
|
|
||||||
validate,
|
|
||||||
}),
|
|
||||||
])(Form);
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
|
|
||||||
import { Field, reduxForm } from 'redux-form';
|
|
||||||
import { Trans, withTranslation } from 'react-i18next';
|
|
||||||
import flow from 'lodash/flow';
|
|
||||||
|
|
||||||
import { CheckboxField, toNumber } from '../../../helpers/form';
|
|
||||||
import { FILTERS_INTERVALS_HOURS, FILTERS_RELATIVE_LINK, FORM_NAME } from '../../../helpers/constants';
|
|
||||||
|
|
||||||
const getTitleForInterval = (interval: any, t: any) => {
|
|
||||||
if (interval === 0) {
|
|
||||||
return t('disabled');
|
|
||||||
}
|
|
||||||
if (interval === 72 || interval === 168) {
|
|
||||||
return t('interval_days', { count: interval / 24 });
|
|
||||||
}
|
|
||||||
|
|
||||||
return t('interval_hours', { count: interval });
|
|
||||||
};
|
|
||||||
|
|
||||||
const getIntervalSelect = (processing: any, t: any, handleChange: any, toNumber: any) => (
|
|
||||||
<Field
|
|
||||||
name="interval"
|
|
||||||
className="custom-select"
|
|
||||||
component="select"
|
|
||||||
onChange={handleChange}
|
|
||||||
normalize={toNumber}
|
|
||||||
disabled={processing}>
|
|
||||||
{FILTERS_INTERVALS_HOURS.map((interval) => (
|
|
||||||
<option value={interval} key={interval}>
|
|
||||||
{getTitleForInterval(interval, t)}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</Field>
|
|
||||||
);
|
|
||||||
|
|
||||||
interface FormProps {
|
|
||||||
handleSubmit: (...args: unknown[]) => string;
|
|
||||||
handleChange?: (...args: unknown[]) => unknown;
|
|
||||||
change: (...args: unknown[]) => unknown;
|
|
||||||
submitting: boolean;
|
|
||||||
invalid: boolean;
|
|
||||||
processing: boolean;
|
|
||||||
t: (...args: unknown[]) => string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Form = (props: FormProps) => {
|
|
||||||
const { handleSubmit, handleChange, processing, t } = props;
|
|
||||||
|
|
||||||
const components = {
|
|
||||||
a: <a href={FILTERS_RELATIVE_LINK} rel="noopener noreferrer" />,
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<form onSubmit={handleSubmit}>
|
|
||||||
<div className="row">
|
|
||||||
<div className="col-12">
|
|
||||||
<div className="form__group form__group--settings">
|
|
||||||
<Field
|
|
||||||
name="enabled"
|
|
||||||
type="checkbox"
|
|
||||||
modifier="checkbox--settings"
|
|
||||||
component={CheckboxField}
|
|
||||||
placeholder={t('block_domain_use_filters_and_hosts')}
|
|
||||||
subtitle={<Trans components={components}>filters_block_toggle_hint</Trans>}
|
|
||||||
onChange={handleChange}
|
|
||||||
disabled={processing}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="col-12 col-md-5">
|
|
||||||
<div className="form__group form__group--inner mb-5">
|
|
||||||
<label className="form__label">
|
|
||||||
<Trans>filters_interval</Trans>
|
|
||||||
</label>
|
|
||||||
{getIntervalSelect(processing, t, handleChange, toNumber)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default flow([withTranslation(), reduxForm({ form: FORM_NAME.FILTER_CONFIG })])(Form);
|
|
||||||
@@ -1,113 +0,0 @@
|
|||||||
.checkbox {
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox--single {
|
|
||||||
display: block;
|
|
||||||
margin: 2px auto 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox--single .checkbox__label:before {
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox--settings .checkbox__label:before {
|
|
||||||
top: 2px;
|
|
||||||
margin-right: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox--settings .checkbox__label-title {
|
|
||||||
margin-bottom: 2px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox--form .checkbox__label:before {
|
|
||||||
top: 1px;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox__label {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 400;
|
|
||||||
user-select: none;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox__label:before {
|
|
||||||
content: '';
|
|
||||||
position: relative;
|
|
||||||
top: 1px;
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: middle;
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
min-width: 20px;
|
|
||||||
margin-right: 10px;
|
|
||||||
background-color: var(--checkbox-bg);
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: center center;
|
|
||||||
background-size: 12px 10px;
|
|
||||||
border-radius: 3px;
|
|
||||||
transition:
|
|
||||||
0.3s ease-in-out box-shadow,
|
|
||||||
0.3s ease-in-out opacity;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox__label .checkbox__label-text {
|
|
||||||
line-height: 1.3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox__label .checkbox__label-text .md__paragraph {
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: baseline;
|
|
||||||
margin: 0;
|
|
||||||
text-align: left;
|
|
||||||
line-height: 1.3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox__input {
|
|
||||||
position: absolute;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox__input:checked + .checkbox__label:before {
|
|
||||||
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMi4zIDkuMiIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiPjxwYXRoIGQ9Ik0xMS44IDAuNUw1LjMgOC41IDAuNSA0LjIiLz48L3N2Zz4=);
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox__input:focus + .checkbox__label:before {
|
|
||||||
box-shadow: 0 0 1px 1px rgba(74, 74, 74, 0.32);
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox__input:disabled + .checkbox__label {
|
|
||||||
cursor: default;
|
|
||||||
color: var(--gray);
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox__input:disabled + .checkbox__label:before {
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox__label-text {
|
|
||||||
max-width: 515px;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox__label-text--long {
|
|
||||||
max-width: initial;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox__label-title {
|
|
||||||
display: block;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.checkbox__label-subtitle {
|
|
||||||
display: block;
|
|
||||||
line-height: 1.5;
|
|
||||||
color: var(--scolor);
|
|
||||||
}
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
import React, { Component } from 'react';
|
|
||||||
import { withTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
import './Checkbox.css';
|
|
||||||
|
|
||||||
interface CheckboxProps {
|
|
||||||
title: string;
|
|
||||||
subtitle: string;
|
|
||||||
enabled: boolean;
|
|
||||||
handleChange: (...args: unknown[]) => unknown;
|
|
||||||
disabled?: boolean;
|
|
||||||
t?: (...args: unknown[]) => string;
|
|
||||||
}
|
|
||||||
|
|
||||||
class Checkbox extends Component<CheckboxProps> {
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
title,
|
|
||||||
|
|
||||||
subtitle,
|
|
||||||
|
|
||||||
enabled,
|
|
||||||
|
|
||||||
handleChange,
|
|
||||||
|
|
||||||
disabled,
|
|
||||||
|
|
||||||
t,
|
|
||||||
} = this.props;
|
|
||||||
return (
|
|
||||||
<div className="form__group form__group--checkbox">
|
|
||||||
<label className="checkbox checkbox--settings">
|
|
||||||
<span className="checkbox__marker" />
|
|
||||||
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
className="checkbox__input"
|
|
||||||
onChange={handleChange}
|
|
||||||
checked={enabled}
|
|
||||||
disabled={disabled}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<span className="checkbox__label">
|
|
||||||
<span className="checkbox__label-text">
|
|
||||||
<span className="checkbox__label-title">{t(title)}</span>
|
|
||||||
|
|
||||||
<span
|
|
||||||
className="checkbox__label-subtitle"
|
|
||||||
dangerouslySetInnerHTML={{ __html: t(subtitle) }}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default withTranslation()(Checkbox);
|
|
||||||
Reference in New Issue
Block a user