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 | Bin 12633 -> 27149 bytes 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, 739 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 ac9a2a5b6bc102d1ede1ca0ace64332f075d96e3..01f410c6c26739ed3cb29bb8702ec397d17d25ba 100644 GIT binary patch literal 27149 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 b|8P3}`J?~0PajTySp9S{|DQSi`K$i{^5hpD delta 12 TcmeCZ!gw=jgNfnf7n!O6CwB$B 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;