badguardhome/client/src/components/Settings/Clients/Form.js
Artem Baskal a79dda5566 - client: Add default mode in the DNS settings: Merge pull request #650 in DNS/adguard-home from fix/1770 to master
Close #1770

Squashed commit of the following:

commit c1f75ea643623af78de020bd1bc49aa5b66e25c4
Merge: a5df78ad a869ec4c
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Wed Jun 10 18:56:24 2020 +0300

    Merge branch 'master' into fix/1770

commit a5df78ad303305efcafcfa2a170ff567a3a06db5
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Wed Jun 10 12:50:50 2020 +0300

    Revert "Update locales"

    This reverts commit 4b2b4499dea12949c53bce4ceeed595c17df84c6.

commit 4b2b4499dea12949c53bce4ceeed595c17df84c6
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Tue Jun 9 19:56:34 2020 +0300

    Update locales

commit 790cff0db84b5905362d2e2702b2cbca5c3c90b0
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Fri Jun 5 17:56:01 2020 +0300

    Update Upstream component with new api, extract reduxForm HOC names in constant

commit 72de3d5a92cc33d5b234c837879fc6990291e07b
Merge: 92a4a6ae 501a4e06
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Fri Jun 5 15:55:39 2020 +0300

    Merge remote-tracking branch 'origin/update-dnsproxy' into fix/1770

commit 501a4e06ab350e46ff78656141d925de9f2e4996
Author: Simon Zolin <s.zolin@adguard.com>
Date:   Fri Jun 5 12:47:13 2020 +0300

    openapi

commit 3930bd196572924f164ed011629356a0ac0ec631
Author: Simon Zolin <s.zolin@adguard.com>
Date:   Fri Jun 5 12:21:32 2020 +0300

    * DNS API: new setting "upstream_mode"; remove "fastest_addr", "parallel_requests"

    * use dnsproxy v0.29.0

commit 92a4a6ae24793a2a9ca05ad3ef2078573fd4d059
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Thu Jun 4 18:53:42 2020 +0300

    - client: Add default mode in the DNS settings
2020-06-10 19:04:19 +03:00

360 lines
13 KiB
JavaScript

import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import {
Field, FieldArray, reduxForm, formValueSelector,
} 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 { toggleAllServices } from '../../../helpers/helpers';
import {
required,
clientId,
renderInputField,
renderGroupField,
renderSelectField,
renderServiceField,
} from '../../../helpers/form';
import { FORM_NAME, SERVICES } from '../../../helpers/constants';
import './Service.css';
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',
},
{
name: 'safesearch_enabled',
placeholder: 'enforce_safe_search',
},
];
const validate = (values) => {
const errors = {};
const { name, ids } = values;
errors.name = required(name);
if (ids && ids.length) {
const idArrayErrors = [];
ids.forEach((id, idx) => {
idArrayErrors[idx] = required(id) || clientId(id);
});
if (idArrayErrors.length) {
errors.ids = idArrayErrors;
}
}
return errors;
};
const renderFieldsWrapper = (placeholder, buttonTitle) => function cell(row) {
const {
fields,
} = row;
return (
<div className="form__group">
{fields.map((ip, index) => (
<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) => data.trim()}
/>
</div>
))}
<button
type="button"
className="btn btn-link btn-block btn-sm"
onClick={() => fields.push()}
title={buttonTitle}
>
<svg className="icon icon--close">
<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'));
const renderMultiselect = (props) => {
const { input, placeholder, options } = props;
return (
<Select
{...input}
options={options}
className="basic-multi-select"
classNamePrefix="select"
onChange={(value) => input.onChange(value)}
onBlur={() => input.onBlur(input.value)}
placeholder={placeholder}
blurInputOnSelect={false}
isMulti
/>
);
};
renderMultiselect.propTypes = {
input: PropTypes.object.isRequired,
placeholder: PropTypes.string,
options: PropTypes.object,
};
let Form = (props) => {
const {
t,
handleSubmit,
reset,
change,
pristine,
submitting,
useGlobalSettings,
useGlobalServices,
toggleClientModal,
processingAdding,
processingUpdating,
invalid,
tagsOptions,
} = props;
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) => 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 href="https://github.com/AdguardTeam/AdGuardHome/wiki/Hosts-Blocklists#ctag"
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="#dhcp" key="0">
link
</a>,
]}
>
client_identifier_desc
</Trans>
</div>
</div>
<div className="form__group">
<FieldArray
name="ids"
component={renderFields}
/>
</div>
</div>
<Tabs controlClass="form">
<div label="settings" title={props.t('main_settings')}>
{settingsCheckboxes.map((setting) => (
<div className="form__group" key={setting.name}>
<Field
name={setting.name}
type="checkbox"
component={renderSelectField}
placeholder={t(setting.placeholder)}
disabled={
setting.name !== 'use_global_settings'
? useGlobalSettings
: false
}
/>
</div>
))}
</div>
<div label="services" 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, 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, change, false)}
>
<Trans>unblock_all</Trans>
</button>
</div>
</div>
<div className="services">
{SERVICES.map((service) => (
<Field
key={service.id}
icon={`service_${service.id}`}
name={`blocked_services.${service.id}`}
type="checkbox"
component={renderServiceField}
placeholder={service.name}
disabled={useGlobalServices}
/>
))}
</div>
</div>
</div>
<div label="upstream" 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="textarea"
type="text"
className="form-control form-control--textarea mb-5"
placeholder={t('upstream_dns')}
/>
<Examples />
</div>
</Tabs>
</div>
<div className="modal-footer">
<div className="btn-list">
<button
type="button"
className="btn btn-secondary btn-standard"
disabled={submitting}
onClick={() => {
reset();
toggleClientModal();
}}
>
<Trans>cancel_btn</Trans>
</button>
<button
type="submit"
className="btn btn-success btn-standard"
disabled={
submitting
|| invalid
|| pristine
|| processingAdding
|| processingUpdating
}
>
<Trans>save_btn</Trans>
</button>
</div>
</div>
</form>
);
};
Form.propTypes = {
pristine: PropTypes.bool.isRequired,
handleSubmit: PropTypes.func.isRequired,
reset: PropTypes.func.isRequired,
change: PropTypes.func.isRequired,
submitting: PropTypes.bool.isRequired,
toggleClientModal: PropTypes.func.isRequired,
useGlobalSettings: PropTypes.bool,
useGlobalServices: PropTypes.bool,
t: PropTypes.func.isRequired,
processingAdding: PropTypes.bool.isRequired,
processingUpdating: PropTypes.bool.isRequired,
invalid: PropTypes.bool.isRequired,
tagsOptions: PropTypes.array.isRequired,
};
const selector = formValueSelector(FORM_NAME.CLIENT);
Form = connect((state) => {
const useGlobalSettings = selector(state, 'use_global_settings');
const useGlobalServices = selector(state, 'use_global_blocked_services');
return {
useGlobalSettings,
useGlobalServices,
};
})(Form);
export default flow([
withTranslation(),
reduxForm({
form: FORM_NAME.CLIENT,
enableReinitialize: true,
validate,
}),
])(Form);