From 92cebd5b3133a39c62500678ec7313f6a9f09807 Mon Sep 17 00:00:00 2001 From: Ildar Kamalov Date: Thu, 18 Jul 2019 14:52:47 +0300 Subject: [PATCH] + client: handle blocked services --- client/package-lock.json | 6 +- client/package.json | 2 +- client/src/__locales/en.json | 11 +- client/src/actions/services.js | 37 +++ client/src/api/Api.js | 18 ++ client/src/components/Logs/Logs.css | 6 + client/src/components/Logs/index.js | 24 +- .../Settings/Clients/ClientsTable.js | 36 ++- .../src/components/Settings/Clients/Form.js | 238 ++++++++++-------- .../src/components/Settings/Clients/Modal.js | 23 +- .../components/Settings/Clients/Service.css | 79 ++++++ .../src/components/Settings/Services/Form.js | 90 +++++++ .../src/components/Settings/Services/index.js | 69 +++++ client/src/components/Settings/Settings.css | 9 + client/src/components/Settings/index.js | 12 +- client/src/components/ui/Icons.js | 64 +++++ client/src/components/ui/Popover.css | 2 + client/src/components/ui/PopoverFilter.js | 34 ++- client/src/components/ui/Tabs.css | 14 ++ client/src/components/ui/Tabs.js | 10 +- client/src/containers/Settings.js | 6 +- client/src/helpers/constants.js | 63 +++++ client/src/helpers/form.js | 39 +++ client/src/helpers/helpers.js | 6 + client/src/reducers/index.js | 2 + client/src/reducers/services.js | 29 +++ 26 files changed, 803 insertions(+), 126 deletions(-) create mode 100644 client/src/actions/services.js create mode 100644 client/src/components/Settings/Clients/Service.css create mode 100644 client/src/components/Settings/Services/Form.js create mode 100644 client/src/components/Settings/Services/index.js create mode 100644 client/src/reducers/services.js diff --git a/client/package-lock.json b/client/package-lock.json index cedee41d..0c596c27 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -945,9 +945,9 @@ } }, "axios": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.18.1.tgz", - "integrity": "sha512-0BfJq4NSfQXd+SkFdrvFbG7addhYSBA2mQwISr46pD6E5iqkWg02RAs8vyTT/j0RTnoYmeXauBuSv1qKwR179g==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.0.tgz", + "integrity": "sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ==", "requires": { "follow-redirects": "1.5.10", "is-buffer": "^2.0.2" diff --git a/client/package.json b/client/package.json index dc64d590..10080eb5 100644 --- a/client/package.json +++ b/client/package.json @@ -10,7 +10,7 @@ }, "dependencies": { "@nivo/line": "^0.49.1", - "axios": "^0.18.1", + "axios": "^0.19.0", "classnames": "^2.2.6", "date-fns": "^1.29.0", "file-saver": "^1.3.8", diff --git a/client/src/__locales/en.json b/client/src/__locales/en.json index 4aaac0e0..6a013cf6 100644 --- a/client/src/__locales/en.json +++ b/client/src/__locales/en.json @@ -344,5 +344,14 @@ "form_answer": "Enter IP address or domain name", "form_error_domain_format": "Invalid domain format", "form_error_answer_format": "Invalid answer format", - "configure": "Configure" + "configure": "Configure", + "main_settings": "Main settings", + "block_services": "Block specific services", + "blocked_services": "Blocked services", + "blocked_services_desc": "Allows to quickly block popular sites and services.", + "blocked_services_saved": "Blocked services successfully saved", + "blocked_services_global": "Use global blocked services", + "blocked_service": "Blocked service", + "block_all": "Block all", + "unblock_all": "Unblock all" } diff --git a/client/src/actions/services.js b/client/src/actions/services.js new file mode 100644 index 00000000..7aae500f --- /dev/null +++ b/client/src/actions/services.js @@ -0,0 +1,37 @@ +import { createAction } from 'redux-actions'; +import Api from '../api/Api'; +import { addErrorToast, addSuccessToast } from './index'; + +const apiClient = new Api(); + +export const getBlockedServicesRequest = createAction('GET_BLOCKED_SERVICES_REQUEST'); +export const getBlockedServicesFailure = createAction('GET_BLOCKED_SERVICES_FAILURE'); +export const getBlockedServicesSuccess = createAction('GET_BLOCKED_SERVICES_SUCCESS'); + +export const getBlockedServices = () => async (dispatch) => { + dispatch(getBlockedServicesRequest()); + try { + const data = await apiClient.getBlockedServices(); + dispatch(getBlockedServicesSuccess(data)); + } catch (error) { + dispatch(addErrorToast({ error })); + dispatch(getBlockedServicesFailure()); + } +}; + +export const setBlockedServicesRequest = createAction('SET_BLOCKED_SERVICES_REQUEST'); +export const setBlockedServicesFailure = createAction('SET_BLOCKED_SERVICES_FAILURE'); +export const setBlockedServicesSuccess = createAction('SET_BLOCKED_SERVICES_SUCCESS'); + +export const setBlockedServices = values => async (dispatch) => { + dispatch(setBlockedServicesRequest()); + try { + await apiClient.setBlockedServices(values); + dispatch(setBlockedServicesSuccess()); + dispatch(getBlockedServices()); + dispatch(addSuccessToast('blocked_services_saved')); + } catch (error) { + dispatch(addErrorToast({ error })); + dispatch(setBlockedServicesFailure()); + } +}; diff --git a/client/src/api/Api.js b/client/src/api/Api.js index 766cd499..a857766c 100644 --- a/client/src/api/Api.js +++ b/client/src/api/Api.js @@ -509,4 +509,22 @@ export default class Api { }; return this.makeRequest(path, method, parameters); } + + // Blocked services + BLOCKED_SERVICES_LIST = { path: 'blocked_services/list', method: 'GET' }; + BLOCKED_SERVICES_SET = { path: 'blocked_services/set', method: 'POST' }; + + getBlockedServices() { + const { path, method } = this.BLOCKED_SERVICES_LIST; + return this.makeRequest(path, method); + } + + setBlockedServices(config) { + const { path, method } = this.BLOCKED_SERVICES_SET; + const parameters = { + data: config, + headers: { 'Content-Type': 'application/json' }, + }; + return this.makeRequest(path, method, parameters); + } } diff --git a/client/src/components/Logs/Logs.css b/client/src/components/Logs/Logs.css index 3205e424..cd105626 100644 --- a/client/src/components/Logs/Logs.css +++ b/client/src/components/Logs/Logs.css @@ -19,6 +19,11 @@ overflow: hidden; } +.logs__row--icons { + max-width: 180px; + flex-flow: row wrap; +} + .logs__row .list-unstyled { margin-bottom: 0; overflow: hidden; @@ -26,6 +31,7 @@ .logs__text, .logs__row .list-unstyled li { + padding: 0 1px; text-overflow: ellipsis; white-space: nowrap; overflow: hidden; diff --git a/client/src/components/Logs/index.js b/client/src/components/Logs/index.js index f8891206..93a1e420 100644 --- a/client/src/components/Logs/index.js +++ b/client/src/components/Logs/index.js @@ -8,6 +8,7 @@ import { Trans, withNamespaces } from 'react-i18next'; import { HashLink as Link } from 'react-router-hash-link'; import { formatTime, getClientName } from '../../helpers/helpers'; +import { SERVICES } from '../../helpers/constants'; import { getTrackerData } from '../../helpers/trackers/trackers'; import PageTitle from '../ui/PageTitle'; import Card from '../ui/Card'; @@ -39,12 +40,8 @@ class Logs extends Component { } } - renderTooltip(isFiltered, rule, filter) { - if (rule) { - return (isFiltered && ); - } - return ''; - } + renderTooltip = (isFiltered, rule, filter, service) => + isFiltered && ; toggleBlocking = (type, domain) => { const { userRules } = this.props.filtering; @@ -146,6 +143,21 @@ class Logs extends Component { } } + if (reason === 'FilteredBlockedService') { + const getService = SERVICES + .find(service => service.id === row.original.serviceName); + const serviceName = getService && getService.name; + + return ( +
+ + {parsedFilteredReason} + + {this.renderTooltip(isFiltered, '', '', serviceName)} +
+ ); + } + if (isFiltered) { return (
diff --git a/client/src/components/Settings/Clients/ClientsTable.js b/client/src/components/Settings/Clients/ClientsTable.js index c12fa1e3..1dae9146 100644 --- a/client/src/components/Settings/Clients/ClientsTable.js +++ b/client/src/components/Settings/Clients/ClientsTable.js @@ -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 settings_global; + } + + return ( +
+ {value && value.length > 0 ? value.map(service => ( + + + + )) : '–'} +
+ ); + }, + }, { Header: this.props.t('table_statistics'), accessor: 'statistics', diff --git a/client/src/components/Settings/Clients/Form.js b/client/src/components/Settings/Clients/Form.js index b10487c4..ee267d94 100644 --- a/client/src/components/Settings/Clients/Form.js +++ b/client/src/components/Settings/Clients/Form.js @@ -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) => {
-
+
client_identifier -
- {clientIdentifier === CLIENT_ID.IP && ( -
+
+
+ {clientIdentifier === CLIENT_ID.IP && ( +
+ +
+ )} + {clientIdentifier === CLIENT_ID.MAC && ( +
+ +
+ )} +
+
- )} - {clientIdentifier === CLIENT_ID.MAC && ( -
- -
- )} +
{
-
- -
- -
- - settings - -
- -
- -
- -
- -
- -
- -
- -
- -
- -
- -
+ +
+ {settingsCheckboxes.map(setting => ( +
+ +
+ ))} +
+
+
+ +
+
+ +
+
+ +
+
+
+ {SERVICES.map(service => ( + + ))} +
+
+
+
@@ -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); diff --git a/client/src/components/Settings/Clients/Modal.js b/client/src/components/Settings/Clients/Modal.js index 49e3483e..d5687344 100644 --- a/client/src/components/Settings/Clients/Modal.js +++ b/client/src/components/Settings/Clients/Modal.js @@ -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 ( {
{ + const { + handleSubmit, + change, + pristine, + submitting, + processing, + processingSet, + } = props; + + return ( + +
+
+
+ +
+
+ +
+
+
+ {SERVICES.map(service => ( + + ))} +
+
+ +
+ +
+ + ); +}; + +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); diff --git a/client/src/components/Settings/Services/index.js b/client/src/components/Settings/Services/index.js new file mode 100644 index 00000000..362f1c96 --- /dev/null +++ b/client/src/components/Settings/Services/index.js @@ -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 ( + +
+
+
+
+ ); + } +} + +Services.propTypes = { + t: PropTypes.func.isRequired, + services: PropTypes.object.isRequired, + setBlockedServices: PropTypes.func.isRequired, +}; + +export default withNamespaces()(Services); diff --git a/client/src/components/Settings/Settings.css b/client/src/components/Settings/Settings.css index 48acf4eb..7f12dbbe 100644 --- a/client/src/components/Settings/Settings.css +++ b/client/src/components/Settings/Settings.css @@ -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; } diff --git a/client/src/components/Settings/index.js b/client/src/components/Settings/index.js index 1e072311..7391cbaf 100644 --- a/client/src/components/Settings/index.js +++ b/client/src/components/Settings/index.js @@ -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 ( @@ -74,6 +78,12 @@ class Settings extends Component {
+
+ +
)} diff --git a/client/src/components/ui/Icons.js b/client/src/components/ui/Icons.js index ac9a2a5b..01f410c6 100644 --- a/client/src/components/ui/Icons.js +++ b/client/src/components/ui/Icons.js @@ -63,6 +63,70 @@ const Icons = () => ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); diff --git a/client/src/components/ui/Popover.css b/client/src/components/ui/Popover.css index f7e23836..707affbc 100644 --- a/client/src/components/ui/Popover.css +++ b/client/src/components/ui/Popover.css @@ -109,9 +109,11 @@ width: 20px; height: 20px; stroke: #9aa0ac; + color: #9aa0ac; } .popover__icon--green { + color: #66b574; stroke: #66b574; } diff --git a/client/src/components/ui/PopoverFilter.js b/client/src/components/ui/PopoverFilter.js index e54cf204..f10cc8a1 100644 --- a/client/src/components/ui/PopoverFilter.js +++ b/client/src/components/ui/PopoverFilter.js @@ -6,19 +6,36 @@ import './Popover.css'; class PopoverFilter extends Component { render() { + const { rule, filter, service } = this.props; + + if (!rule && !service) { + return ''; + } + return (
- + + +
-
- rule_label: {this.props.rule} -
- {this.props.filter &&
- filter_label: {this.props.filter} -
} + {rule && ( +
+ rule_label: {rule} +
+ )} + {filter && ( +
+ filter_label: {filter} +
+ )} + {service && ( +
+ blocked_service: {service} +
+ )}
@@ -27,8 +44,9 @@ class PopoverFilter extends Component { } PopoverFilter.propTypes = { - rule: PropTypes.string.isRequired, + rule: PropTypes.string, filter: PropTypes.string, + service: PropTypes.string, }; export default withNamespaces()(PopoverFilter); diff --git a/client/src/components/ui/Tabs.css b/client/src/components/ui/Tabs.css index cd1671e9..1e6f75c6 100644 --- a/client/src/components/ui/Tabs.css +++ b/client/src/components/ui/Tabs.css @@ -6,6 +6,20 @@ border-bottom: 1px solid #e8e8e8; } +.tabs__controls--form { + justify-content: flex-start; +} + +.tabs__controls--form .tab__control { + min-width: initial; + margin-right: 25px; + font-size: 14px; +} + +.tabs__controls--form .tab__icon { + display: none; +} + .tab__control { display: flex; flex-direction: column; diff --git a/client/src/components/ui/Tabs.js b/client/src/components/ui/Tabs.js index a15b0ee6..7da68fba 100644 --- a/client/src/components/ui/Tabs.js +++ b/client/src/components/ui/Tabs.js @@ -1,5 +1,6 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; +import classnames from 'classnames'; import Tab from './Tab'; import './Tabs.css'; @@ -16,6 +17,7 @@ class Tabs extends Component { render() { const { props: { + controlClass, children, }, state: { @@ -23,9 +25,14 @@ class Tabs extends Component { }, } = this; + const getControlClass = classnames({ + tabs__controls: true, + [`tabs__controls--${controlClass}`]: controlClass, + }); + return (
-
+
{children.map((child) => { const { label, title } = child.props; @@ -54,6 +61,7 @@ class Tabs extends Component { } Tabs.propTypes = { + controlClass: PropTypes.string, children: PropTypes.array.isRequired, }; diff --git a/client/src/containers/Settings.js b/client/src/containers/Settings.js index e50290f1..054d1d1b 100644 --- a/client/src/containers/Settings.js +++ b/client/src/containers/Settings.js @@ -1,11 +1,13 @@ import { connect } from 'react-redux'; import { initSettings, toggleSetting } from '../actions'; +import { getBlockedServices, setBlockedServices } from '../actions/services'; import Settings from '../components/Settings'; const mapStateToProps = (state) => { - const { settings } = state; + const { settings, services } = state; const props = { settings, + services, }; return props; }; @@ -13,6 +15,8 @@ const mapStateToProps = (state) => { const mapDispatchToProps = { initSettings, toggleSetting, + getBlockedServices, + setBlockedServices, }; export default connect( diff --git a/client/src/helpers/constants.js b/client/src/helpers/constants.js index 896d873e..a5c8e922 100644 --- a/client/src/helpers/constants.js +++ b/client/src/helpers/constants.js @@ -181,3 +181,66 @@ export const CLIENT_ID = { }; export const SETTINGS_URLS = ['/encryption', '/dhcp', '/dns', '/settings', '/clients']; + +export const SERVICES = [ + { + id: 'facebook', + name: 'Facebook', + }, + { + id: 'whatsapp', + name: 'WhatsApp', + }, + { + id: 'instagram', + name: 'Instagram', + }, + { + id: 'twitter', + name: 'Twitter', + }, + { + id: 'youtube', + name: 'YouTube', + }, + { + id: 'netflix', + name: 'Netflix', + }, + { + id: 'snapchat', + name: 'Snapchat', + }, + { + id: 'messenger', + name: 'Messenger', + }, + { + id: 'twitch', + name: 'Twitch', + }, + { + id: 'discord', + name: 'Discord', + }, + { + id: 'skype', + name: 'Skype', + }, + { + id: 'steam', + name: 'Steam', + }, + { + id: 'ok', + name: 'OK', + }, + { + id: 'vk', + name: 'VK', + }, + { + id: 'mail_ru', + name: 'mail.ru', + }, +]; diff --git a/client/src/helpers/form.js b/client/src/helpers/form.js index c4ffcb73..e3b6e46c 100644 --- a/client/src/helpers/form.js +++ b/client/src/helpers/form.js @@ -27,6 +27,23 @@ export const renderField = ({ ); +export const renderRadioField = ({ + input, placeholder, disabled, meta: { touched, error }, +}) => ( + + + {!disabled && touched && (error && {error})} + +); + export const renderSelectField = ({ input, placeholder, disabled, meta: { touched, error }, }) => ( @@ -46,6 +63,28 @@ export const renderSelectField = ({ ); +export const renderServiceField = ({ + input, placeholder, disabled, modifier, icon, meta: { touched, error }, +}) => ( + + + {!disabled && touched && (error && {error})} + +); + export const required = (value) => { if (value || value === 0) { return false; diff --git a/client/src/helpers/helpers.js b/client/src/helpers/helpers.js index 031747f3..0fd5baea 100644 --- a/client/src/helpers/helpers.js +++ b/client/src/helpers/helpers.js @@ -27,6 +27,7 @@ export const normalizeLogs = logs => logs.map((log) => { client, filterId, rule, + service_name, } = log; const { host: domain, type } = question; const responsesArray = response ? response.map((response) => { @@ -42,6 +43,7 @@ export const normalizeLogs = logs => logs.map((log) => { client, filterId, rule, + serviceName: service_name, }; }); @@ -225,3 +227,7 @@ export const sortClients = (clients) => { return clients.sort(compare); }; + +export const toggleAllServices = (services, change, isSelected) => { + services.forEach(service => change(`blocked_services.${service.id}`, isSelected)); +}; diff --git a/client/src/reducers/index.js b/client/src/reducers/index.js index 32026a08..2913f5cc 100644 --- a/client/src/reducers/index.js +++ b/client/src/reducers/index.js @@ -10,6 +10,7 @@ import encryption from './encryption'; import clients from './clients'; import access from './access'; import rewrites from './rewrites'; +import services from './services'; const settings = handleActions({ [actions.initSettingsRequest]: state => ({ ...state, processing: true }), @@ -424,6 +425,7 @@ export default combineReducers({ clients, access, rewrites, + services, loadingBar: loadingBarReducer, form: formReducer, }); diff --git a/client/src/reducers/services.js b/client/src/reducers/services.js new file mode 100644 index 00000000..d91cadf5 --- /dev/null +++ b/client/src/reducers/services.js @@ -0,0 +1,29 @@ +import { handleActions } from 'redux-actions'; + +import * as actions from '../actions/services'; + +const services = handleActions( + { + [actions.getBlockedServicesRequest]: state => ({ ...state, processing: true }), + [actions.getBlockedServicesFailure]: state => ({ ...state, processing: false }), + [actions.getBlockedServicesSuccess]: (state, { payload }) => ({ + ...state, + list: payload, + processing: false, + }), + + [actions.setBlockedServicesRequest]: state => ({ ...state, processingSet: true }), + [actions.setBlockedServicesFailure]: state => ({ ...state, processingSet: false }), + [actions.setBlockedServicesSuccess]: state => ({ + ...state, + processingSet: false, + }), + }, + { + processing: true, + processingSet: false, + list: [], + }, +); + +export default services;