+ client: handle blocked services

This commit is contained in:
Ildar Kamalov
2019-07-18 14:52:47 +03:00
committed by Simon Zolin
parent 3c684d1f85
commit 92cebd5b31
26 changed files with 803 additions and 126 deletions

View File

@@ -17,10 +17,19 @@ class ClientsTable extends Component {
};
handleSubmit = (values) => {
let config = values;
if (values && values.blocked_services) {
const blocked_services = Object
.keys(values.blocked_services)
.filter(service => values.blocked_services[service]);
config = { ...values, blocked_services };
}
if (this.props.modalType === MODAL_TYPE.EDIT) {
this.handleFormUpdate(values, this.props.modalClientName);
this.handleFormUpdate(config, this.props.modalClientName);
} else {
this.handleFormAdd(values);
this.handleFormAdd(config);
}
};
@@ -41,6 +50,7 @@ class ClientsTable extends Component {
return {
identifier,
use_global_settings: true,
use_global_blocked_services: true,
...client,
};
}
@@ -48,6 +58,7 @@ class ClientsTable extends Component {
return {
identifier: CLIENT_ID.IP,
use_global_settings: true,
use_global_blocked_services: true,
};
};
@@ -116,6 +127,27 @@ class ClientsTable extends Component {
);
},
},
{
Header: this.props.t('blocked_services'),
accessor: 'blocked_services',
Cell: (row) => {
const { value, original } = row;
if (original.use_global_blocked_services) {
return <Trans>settings_global</Trans>;
}
return (
<div className="logs__row logs__row--icons">
{value && value.length > 0 ? value.map(service => (
<svg className="service__icon service__icon--table" title={service} key={service}>
<use xlinkHref={`#${service}`} />
</svg>
)) : ''}
</div>
);
},
},
{
Header: this.props.t('table_statistics'),
accessor: 'statistics',

View File

@@ -5,18 +5,46 @@ import { Field, reduxForm, formValueSelector } from 'redux-form';
import { Trans, withNamespaces } from 'react-i18next';
import flow from 'lodash/flow';
import { renderField, renderSelectField, ipv4, mac, required } from '../../../helpers/form';
import { CLIENT_ID } from '../../../helpers/constants';
import Tabs from '../../ui/Tabs';
import { toggleAllServices } from '../../../helpers/helpers';
import { renderField, renderRadioField, renderSelectField, renderServiceField, ipv4, mac, required } from '../../../helpers/form';
import { CLIENT_ID, 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',
},
];
let Form = (props) => {
const {
t,
handleSubmit,
reset,
change,
pristine,
submitting,
clientIdentifier,
useGlobalSettings,
useGlobalServices,
toggleClientModal,
processingAdding,
processingUpdating,
@@ -26,57 +54,70 @@ let Form = (props) => {
<form onSubmit={handleSubmit}>
<div className="modal-body">
<div className="form__group">
<div className="form-inline mb-3">
<div className="form__inline mb-2">
<strong className="mr-3">
<Trans>client_identifier</Trans>
</strong>
<label className="mr-3">
<div className="custom-controls-stacked">
<Field
name="identifier"
component={renderField}
component={renderRadioField}
type="radio"
className="form-control mr-2"
value="ip"
/>{' '}
<Trans>ip_address</Trans>
</label>
<label>
placeholder={t('ip_address')}
/>
<Field
name="identifier"
component={renderField}
component={renderRadioField}
type="radio"
className="form-control mr-2"
value="mac"
/>{' '}
MAC
</label>
placeholder="MAC"
/>
</div>
</div>
{clientIdentifier === CLIENT_ID.IP && (
<div className="form__group">
<div className="row">
<div className="col col-sm-6">
{clientIdentifier === CLIENT_ID.IP && (
<div className="form__group">
<Field
id="ip"
name="ip"
component={renderField}
type="text"
className="form-control"
placeholder={t('form_enter_ip')}
validate={[ipv4, required]}
/>
</div>
)}
{clientIdentifier === CLIENT_ID.MAC && (
<div className="form__group">
<Field
id="mac"
name="mac"
component={renderField}
type="text"
className="form-control"
placeholder={t('form_enter_mac')}
validate={[mac, required]}
/>
</div>
)}
</div>
<div className="col col-sm-6">
<Field
id="ip"
name="ip"
id="name"
name="name"
component={renderField}
type="text"
className="form-control"
placeholder={t('form_enter_ip')}
validate={[ipv4, required]}
placeholder={t('form_client_name')}
validate={[required]}
/>
</div>
)}
{clientIdentifier === CLIENT_ID.MAC && (
<div className="form__group">
<Field
id="mac"
name="mac"
component={renderField}
type="text"
className="form-control"
placeholder={t('form_enter_mac')}
validate={[mac, required]}
/>
</div>
)}
</div>
<div className="form__desc">
<Trans
components={[
@@ -90,72 +131,67 @@ let Form = (props) => {
</div>
</div>
<div className="form__group">
<Field
id="name"
name="name"
component={renderField}
type="text"
className="form-control"
placeholder={t('form_client_name')}
validate={[required]}
/>
</div>
<div className="mb-4">
<strong>
<Trans>settings</Trans>
</strong>
</div>
<div className="form__group">
<Field
name="use_global_settings"
type="checkbox"
component={renderSelectField}
placeholder={t('client_global_settings')}
/>
</div>
<div className="form__group">
<Field
name="filtering_enabled"
type="checkbox"
component={renderSelectField}
placeholder={t('block_domain_use_filters_and_hosts')}
disabled={useGlobalSettings}
/>
</div>
<div className="form__group">
<Field
name="safebrowsing_enabled"
type="checkbox"
component={renderSelectField}
placeholder={t('use_adguard_browsing_sec')}
disabled={useGlobalSettings}
/>
</div>
<div className="form__group">
<Field
name="parental_enabled"
type="checkbox"
component={renderSelectField}
placeholder={t('use_adguard_parental')}
disabled={useGlobalSettings}
/>
</div>
<div className="form__group">
<Field
name="safesearch_enabled"
type="checkbox"
component={renderSelectField}
placeholder={t('enforce_safe_search')}
disabled={useGlobalSettings}
/>
</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.id}
name={`blocked_services.${service.id}`}
type="checkbox"
component={renderServiceField}
placeholder={service.name}
disabled={useGlobalServices}
/>
))}
</div>
</div>
</div>
</Tabs>
</div>
<div className="modal-footer">
@@ -188,10 +224,12 @@ 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,
clientIdentifier: PropTypes.string,
useGlobalSettings: PropTypes.bool,
useGlobalServices: PropTypes.bool,
t: PropTypes.func.isRequired,
processingAdding: PropTypes.bool.isRequired,
processingUpdating: PropTypes.bool.isRequired,
@@ -202,9 +240,11 @@ const selector = formValueSelector('clientForm');
Form = connect((state) => {
const clientIdentifier = selector(state, 'identifier');
const useGlobalSettings = selector(state, 'use_global_settings');
const useGlobalServices = selector(state, 'use_global_blocked_services');
return {
clientIdentifier,
useGlobalSettings,
useGlobalServices,
};
})(Form);

View File

@@ -6,6 +6,24 @@ import ReactModal from 'react-modal';
import { MODAL_TYPE } from '../../../helpers/constants';
import Form from './Form';
const getInitialData = (initial) => {
if (initial && initial.blocked_services) {
const { blocked_services } = initial;
const blocked = {};
blocked_services.forEach((service) => {
blocked[service] = true;
});
return {
...initial,
blocked_services: blocked,
};
}
return initial;
};
const Modal = (props) => {
const {
isModalOpen,
@@ -16,6 +34,7 @@ const Modal = (props) => {
processingAdding,
processingUpdating,
} = props;
const initialData = getInitialData(currentClientData);
return (
<ReactModal
@@ -38,9 +57,7 @@ const Modal = (props) => {
</button>
</div>
<Form
initialValues={{
...currentClientData,
}}
initialValues={{ ...initialData }}
onSubmit={handleSubmit}
toggleClientModal={toggleClientModal}
processingAdding={processingAdding}

View File

@@ -0,0 +1,79 @@
.service {
display: flex;
flex-direction: row-reverse;
align-items: center;
margin-bottom: 15px;
padding: 10px 15px;
border: 1px solid #eee;
border-radius: 4px;
cursor: pointer;
}
@media screen and (min-width: 768px) {
.services {
display: flex;
flex-flow: row wrap;
}
.service {
flex-grow: 0;
flex-shrink: 0;
flex-basis: calc(99.9% * 4/12 - (30px - 30px * 4/12));
max-width: calc(99.9% * 4/12 - (30px - 30px * 4/12));
width: calc(99.9% * 4/12 - (30px - 30px * 4/12));
}
.service--global {
flex-basis: 1;
max-width: 100%;
width: 100%;
}
.service:nth-child(1n) {
margin-right: 30px;
margin-left: 0;
}
.service:nth-child(3n) {
margin-right: 0;
margin-left: auto;
}
}
.service__icon {
width: 20px;
height: 20px;
flex-shrink: 0;
margin-right: 10px;
color: #495057;
}
.service--global .service__icon {
display: none;
}
.service__icon--table {
margin-bottom: 3px;
color: #9aa0ac;
}
.service__switch {
margin-left: auto;
border: 1px solid rgba(150, 150, 150, 0.12);
}
.custom-switch-input:checked ~ .service__switch {
background-color: #cd201f;
}
.custom-switch-input:focus ~ .service__switch {
box-shadow: 0 0 0 2px #cd201f3b;
border-color: #ec4241;
}
.custom-switch-input:disabled ~ .service__switch,
.custom-switch-input:disabled ~ .service__text,
.custom-switch-input:disabled ~ .service__icon {
opacity: 0.5;
cursor: pointer;
}

View File

@@ -0,0 +1,90 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Field, reduxForm } from 'redux-form';
import { Trans, withNamespaces } from 'react-i18next';
import flow from 'lodash/flow';
import { toggleAllServices } from '../../../helpers/helpers';
import { renderServiceField } from '../../../helpers/form';
import { SERVICES } from '../../../helpers/constants';
const Form = (props) => {
const {
handleSubmit,
change,
pristine,
submitting,
processing,
processingSet,
} = props;
return (
<form onSubmit={handleSubmit}>
<div className="form__group">
<div className="row mb-4">
<div className="col-6">
<button
type="button"
className="btn btn-secondary btn-block"
disabled={processing || processingSet}
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={processing || processingSet}
onClick={() => toggleAllServices(SERVICES, change, false)}
>
<Trans>unblock_all</Trans>
</button>
</div>
</div>
<div className="services">
{SERVICES.map(service => (
<Field
key={service.id}
icon={service.id}
name={`blocked_services.${service.id}`}
type="checkbox"
component={renderServiceField}
placeholder={service.name}
disabled={processing || processingSet}
/>
))}
</div>
</div>
<div className="btn-list">
<button
type="submit"
className="btn btn-success btn-standard btn-large"
disabled={submitting || pristine || processing || processingSet}
>
<Trans>save_btn</Trans>
</button>
</div>
</form>
);
};
Form.propTypes = {
pristine: PropTypes.bool.isRequired,
handleSubmit: PropTypes.func.isRequired,
change: PropTypes.func.isRequired,
submitting: PropTypes.bool.isRequired,
processing: PropTypes.bool.isRequired,
processingSet: PropTypes.bool.isRequired,
t: PropTypes.func.isRequired,
};
export default flow([
withNamespaces(),
reduxForm({
form: 'servicesForm',
enableReinitialize: true,
}),
])(Form);

View File

@@ -0,0 +1,69 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { withNamespaces } from 'react-i18next';
import Form from './Form';
import Card from '../../ui/Card';
class Services extends Component {
handleSubmit = (values) => {
let config = values;
if (values && values.blocked_services) {
const blocked_services = Object
.keys(values.blocked_services)
.filter(service => values.blocked_services[service]);
config = blocked_services;
}
this.props.setBlockedServices(config);
};
getInitialDataForServices = (initial) => {
if (initial) {
const blocked = {};
initial.forEach((service) => {
blocked[service] = true;
});
return {
blocked_services: blocked,
};
}
return initial;
};
render() {
const { services, t } = this.props;
const initialData = this.getInitialDataForServices(services.list);
return (
<Card
title={t('blocked_services')}
subtitle={t('Allows to quickly block popular sites.')}
bodyType="card-body box-body--settings"
>
<div className="form">
<Form
initialValues={{ ...initialData }}
processing={services.processing}
processingSet={services.processingSet}
onSubmit={this.handleSubmit}
/>
</div>
</Card>
);
}
}
Services.propTypes = {
t: PropTypes.func.isRequired,
services: PropTypes.object.isRequired,
setBlockedServices: PropTypes.func.isRequired,
};
export default withNamespaces()(Services);

View File

@@ -11,11 +11,20 @@
margin-bottom: 20px;
}
.form__inline {
display: flex;
justify-content: flex-start;
}
.btn-standard {
padding-left: 20px;
padding-right: 20px;
}
.btn-large {
min-width: 150px;
}
.form-control--textarea {
min-height: 110px;
}

View File

@@ -2,6 +2,7 @@ import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import { withNamespaces, Trans } from 'react-i18next';
import Services from './Services';
import Checkbox from '../ui/Checkbox';
import Loading from '../ui/Loading';
import PageTitle from '../ui/PageTitle';
@@ -35,6 +36,7 @@ class Settings extends Component {
componentDidMount() {
this.props.initSettings(this.settings);
this.props.getBlockedServices();
}
renderSettings = (settings) => {
@@ -59,7 +61,9 @@ class Settings extends Component {
};
render() {
const { settings, t } = this.props;
const {
settings, services, setBlockedServices, t,
} = this.props;
return (
<Fragment>
<PageTitle title={t('general_settings')} />
@@ -74,6 +78,12 @@ class Settings extends Component {
</div>
</Card>
</div>
<div className="col-md-12">
<Services
services={services}
setBlockedServices={setBlockedServices}
/>
</div>
</div>
</div>
)}