Pull request: 3972-hostlists-services

Updates #3972.

Squashed commit of the following:

commit 9dc0efe2453cb6c738d97d39b02c86eccb18a42c
Merge: 239550f8 8a935d4f
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Oct 27 14:42:38 2022 +0300

    Merge branch 'master' into 3972-hostlists-services

commit 239550f84228e7c7a6f4ae6b1cadcc47e01f54d5
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Thu Oct 27 14:41:42 2022 +0300

    filtering: upd service list

commit b8bf3a6a4b1333059b886be95a1419612aebac39
Author: Ildar Kamalov <ik@adguard.com>
Date:   Thu Oct 27 13:41:09 2022 +0300

    client: remove todo

commit caa504b482befb804db2a1ca0b6d4834aa4da49a
Author: Ildar Kamalov <ik@adguard.com>
Date:   Thu Oct 27 12:54:45 2022 +0300

    fix build

commit 511797c305d9eef84a20553dab795414e00da51a
Author: Ildar Kamalov <ik@adguard.com>
Date:   Thu Oct 27 12:40:33 2022 +0300

    client: add titles with service names to the clients table

commit 79ed3157a85b489a0b13381cff867a8c73ba60e9
Author: Ildar Kamalov <ik@adguard.com>
Date:   Thu Oct 27 12:36:59 2022 +0300

    client: fix empty icons

commit ab69b95784de87665d5a1a3683f28e3b3df1c210
Author: Ildar Kamalov <ik@adguard.com>
Date:   Thu Oct 27 11:55:48 2022 +0300

    client: use all blocked services

commit 9a4a87665c8463224d8e93f1e162988107f6c7ca
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Oct 25 19:25:20 2022 +0300

    all: fix json response

commit 86eb4493ce305cd5991176bd4cd8f7f5afdea330
Author: Ainar Garipov <A.Garipov@AdGuard.COM>
Date:   Tue Oct 25 19:09:44 2022 +0300

    all: use hostslists registry for blocked svcs
This commit is contained in:
Ainar Garipov
2022-10-27 15:46:25 +03:00
parent 8a935d4ffb
commit 9951d861d1
27 changed files with 987 additions and 922 deletions

View File

@@ -32,6 +32,21 @@ export const getBlockedServices = () => async (dispatch) => {
}
};
export const getAllBlockedServicesRequest = createAction('GET_ALL_BLOCKED_SERVICES_REQUEST');
export const getAllBlockedServicesFailure = createAction('GET_ALL_BLOCKED_SERVICES_FAILURE');
export const getAllBlockedServicesSuccess = createAction('GET_ALL_BLOCKED_SERVICES_SUCCESS');
export const getAllBlockedServices = () => async (dispatch) => {
dispatch(getAllBlockedServicesRequest());
try {
const data = await apiClient.getAllBlockedServices();
dispatch(getAllBlockedServicesSuccess(data));
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(getAllBlockedServicesFailure());
}
};
export const setBlockedServicesRequest = createAction('SET_BLOCKED_SERVICES_REQUEST');
export const setBlockedServicesFailure = createAction('SET_BLOCKED_SERVICES_FAILURE');
export const setBlockedServicesSuccess = createAction('SET_BLOCKED_SERVICES_SUCCESS');

View File

@@ -465,11 +465,18 @@ class Api {
BLOCKED_SERVICES_SET = { path: 'blocked_services/set', method: 'POST' };
BLOCKED_SERVICES_ALL = { path: 'blocked_services/all', method: 'GET' };
getBlockedServicesAvailableServices() {
const { path, method } = this.BLOCKED_SERVICES_SERVICES;
return this.makeRequest(path, method);
}
getAllBlockedServices() {
const { path, method } = this.BLOCKED_SERVICES_ALL;
return this.makeRequest(path, method);
}
getBlockedServices() {
const { path, method } = this.BLOCKED_SERVICES_LIST;
return this.makeRequest(path, method);

View File

@@ -6,10 +6,11 @@ import flow from 'lodash/flow';
import { toggleAllServices } from '../../../helpers/helpers';
import { renderServiceField } from '../../../helpers/form';
import { FORM_NAME, SERVICES } from '../../../helpers/constants';
import { FORM_NAME } from '../../../helpers/constants';
const Form = (props) => {
const {
blockedServices,
handleSubmit,
change,
pristine,
@@ -27,7 +28,7 @@ const Form = (props) => {
type="button"
className="btn btn-secondary btn-block"
disabled={processing || processingSet}
onClick={() => toggleAllServices(SERVICES, change, true)}
onClick={() => toggleAllServices(blockedServices, change, true)}
>
<Trans>block_all</Trans>
</button>
@@ -37,17 +38,17 @@ const Form = (props) => {
type="button"
className="btn btn-secondary btn-block"
disabled={processing || processingSet}
onClick={() => toggleAllServices(SERVICES, change, false)}
onClick={() => toggleAllServices(blockedServices, change, false)}
>
<Trans>unblock_all</Trans>
</button>
</div>
</div>
<div className="services">
{SERVICES.map((service) => (
{blockedServices.map((service) => (
<Field
key={service.id}
icon={`service_${service.id}`}
icon={service.icon_svg}
name={`blocked_services.${service.id}`}
type="checkbox"
component={renderServiceField}
@@ -72,6 +73,7 @@ const Form = (props) => {
};
Form.propTypes = {
blockedServices: PropTypes.array.isRequired,
pristine: PropTypes.bool.isRequired,
handleSubmit: PropTypes.func.isRequired,
change: PropTypes.func.isRequired,

View File

@@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import Form from './Form';
import Card from '../../ui/Card';
import { getBlockedServices, setBlockedServices } from '../../../actions/services';
import { getBlockedServices, getAllBlockedServices, setBlockedServices } from '../../../actions/services';
import PageTitle from '../../ui/PageTitle';
const getInitialDataForServices = (initial) => (initial ? initial.reduce(
@@ -21,6 +21,7 @@ const Services = () => {
useEffect(() => {
dispatch(getBlockedServices());
dispatch(getAllBlockedServices());
}, []);
const handleSubmit = (values) => {
@@ -49,6 +50,7 @@ const Services = () => {
<div className="form">
<Form
initialValues={initialValues}
blockedServices={services.allServices}
processing={services.processing}
processingSet={services.processingSet}
onSubmit={handleSubmit}

View File

@@ -27,6 +27,7 @@ const ResponseCell = ({
const filters = useSelector((state) => state.filtering.filters, shallowEqual);
const whitelistFilters = useSelector((state) => state.filtering.whitelistFilters, shallowEqual);
const isDetailed = useSelector((state) => state.queryLogs.isDetailed);
const services = useSelector((store) => store?.services);
const formattedElapsedMs = formatElapsedMs(elapsedMs, t);
@@ -60,8 +61,8 @@ const ResponseCell = ({
install_settings_dns: upstreamString,
elapsed: formattedElapsedMs,
response_code: status,
...(service_name
&& { service_name: getServiceName(service_name) }
...(service_name && services.allServices
&& { service_name: getServiceName(services.allServices, service_name) }
),
...(rules.length > 0
&& { rule_label: getRulesToFilterList(rules, filters, whitelistFilters) }
@@ -80,10 +81,10 @@ const ResponseCell = ({
const getDetailedInfo = (reason) => {
switch (reason) {
case FILTERED_STATUS.FILTERED_BLOCKED_SERVICE:
if (!service_name) {
if (!service_name || !services.allServices) {
return formattedElapsedMs;
}
return getServiceName(service_name);
return getServiceName(services.allServices, service_name);
case FILTERED_STATUS.FILTERED_BLACK_LIST:
case FILTERED_STATUS.NOT_FILTERED_WHITE_LIST:
return getFilterNames(rules, filters, whitelistFilters).join(', ');

View File

@@ -52,6 +52,7 @@ const Row = memo(({
const autoClients = useSelector((state) => state.dashboard.autoClients, shallowEqual);
const processingSet = useSelector((state) => state.access.processingSet);
const allowedСlients = useSelector((state) => state.access.allowed_clients, shallowEqual);
const services = useSelector((store) => store?.services);
const clients = useSelector((state) => state.dashboard.clients);
@@ -175,8 +176,8 @@ const Row = memo(({
date: formatDateTime(time, DEFAULT_SHORT_DATE_FORMAT_OPTIONS),
encryption_status: isBlocked
? <div className="bg--danger">{requestStatus}</div> : requestStatus,
...(FILTERED_STATUS.FILTERED_BLOCKED_SERVICE && service_name
&& { service_name: getServiceName(service_name) }),
...(FILTERED_STATUS.FILTERED_BLOCKED_SERVICE && service_name && services.allServices
&& { service_name: getServiceName(services.allServices, service_name) }),
domain,
type_table_header: type,
protocol,

View File

@@ -16,6 +16,7 @@ import { getFilteringStatus } from '../../actions/filtering';
import { getClients } from '../../actions';
import { getDnsConfig } from '../../actions/dnsConfig';
import { getAccessList } from '../../actions/access';
import { getAllBlockedServices } from '../../actions/services';
import {
getLogsConfig,
resetFilteredLogs,
@@ -130,6 +131,7 @@ const Logs = () => {
setIsLoading(true);
dispatch(getFilteringStatus());
dispatch(getClients());
dispatch(getAllBlockedServices());
try {
await Promise.all([
dispatch(getLogsConfig()),

View File

@@ -1,25 +1,57 @@
import React, { Component, Fragment } from 'react';
/* eslint-disable react/display-name */
/* eslint-disable react/prop-types */
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { Trans, withTranslation } from 'react-i18next';
import { Trans, useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import ReactTable from 'react-table';
import { MODAL_TYPE } from '../../../helpers/constants';
import { splitByNewLine, countClientsStatistics, sortIp } from '../../../helpers/helpers';
import Card from '../../ui/Card';
import Modal from './Modal';
import CellWrap from '../../ui/CellWrap';
import LogsSearchLink from '../../ui/LogsSearchLink';
import { getAllBlockedServices } from '../../../../actions/services';
import {
splitByNewLine,
countClientsStatistics,
sortIp,
getService,
} from '../../../../helpers/helpers';
import { MODAL_TYPE } from '../../../../helpers/constants';
import Card from '../../../ui/Card';
import CellWrap from '../../../ui/CellWrap';
import LogsSearchLink from '../../../ui/LogsSearchLink';
import Modal from '../Modal';
class ClientsTable extends Component {
handleFormAdd = (values) => {
this.props.addClient(values);
const ClientsTable = ({
clients,
normalizedTopClients,
isModalOpen,
modalClientName,
modalType,
addClient,
updateClient,
deleteClient,
toggleClientModal,
processingAdding,
processingDeleting,
processingUpdating,
getStats,
supportedTags,
}) => {
const [t] = useTranslation();
const dispatch = useDispatch();
const services = useSelector((store) => store?.services);
useEffect(() => {
dispatch(getAllBlockedServices());
}, []);
const handleFormAdd = (values) => {
addClient(values);
};
handleFormUpdate = (values, name) => {
this.props.updateClient(values, name);
const handleFormUpdate = (values, name) => {
updateClient(values, name);
};
handleSubmit = (values) => {
const handleSubmit = (values) => {
const config = values;
if (values) {
@@ -42,21 +74,21 @@ class ClientsTable extends Component {
}
}
if (this.props.modalType === MODAL_TYPE.EDIT_FILTERS) {
this.handleFormUpdate(config, this.props.modalClientName);
if (modalType === MODAL_TYPE.EDIT_FILTERS) {
handleFormUpdate(config, modalClientName);
} else {
this.handleFormAdd(config);
handleFormAdd(config);
}
};
getOptionsWithLabels = (options) => (
const getOptionsWithLabels = (options) => (
options.map((option) => ({
value: option,
label: option,
}))
);
getClient = (name, clients) => {
const getClient = (name, clients) => {
const client = clients.find((item) => name === item.name);
if (client) {
@@ -65,7 +97,7 @@ class ClientsTable extends Component {
} = client;
return {
upstreams: (upstreams && upstreams.join('\n')) || '',
tags: (tags && this.getOptionsWithLabels(tags)) || [],
tags: (tags && getOptionsWithLabels(tags)) || [],
...values,
};
}
@@ -78,17 +110,17 @@ class ClientsTable extends Component {
};
};
handleDelete = (data) => {
const handleDelete = (data) => {
// eslint-disable-next-line no-alert
if (window.confirm(this.props.t('client_confirm_delete', { key: data.name }))) {
this.props.deleteClient(data);
this.props.getStats();
if (window.confirm(t('client_confirm_delete', { key: data.name }))) {
deleteClient(data);
getStats();
}
};
columns = [
const columns = [
{
Header: this.props.t('table_client'),
Header: t('table_client'),
accessor: 'ids',
minWidth: 150,
Cell: (row) => {
@@ -109,13 +141,13 @@ class ClientsTable extends Component {
sortMethod: sortIp,
},
{
Header: this.props.t('table_name'),
Header: t('table_name'),
accessor: 'name',
minWidth: 120,
Cell: CellWrap,
},
{
Header: this.props.t('settings'),
Header: t('settings'),
accessor: 'use_global_settings',
minWidth: 120,
Cell: ({ value }) => {
@@ -133,7 +165,7 @@ class ClientsTable extends Component {
},
},
{
Header: this.props.t('blocked_services'),
Header: t('blocked_services'),
accessor: 'blocked_services',
minWidth: 180,
Cell: (row) => {
@@ -143,25 +175,40 @@ class ClientsTable extends Component {
return <Trans>settings_global</Trans>;
}
if (value && services.allServices) {
return (
<div className="logs__row logs__row--icons">
{value.map((service) => {
const serviceInfo = getService(services.allServices, service);
if (serviceInfo?.icon_svg) {
return (
<div
key={serviceInfo.name}
dangerouslySetInnerHTML={{
__html: window.atob(serviceInfo.icon_svg),
}}
className="service__icon service__icon--table"
title={serviceInfo.name}
/>
);
}
return null;
})}
</div>
);
}
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_${service}`} />
</svg>
))
: ''}
</div>
);
},
},
{
Header: this.props.t('upstreams'),
Header: t('upstreams'),
accessor: 'upstreams',
minWidth: 120,
Cell: ({ value }) => {
@@ -179,7 +226,7 @@ class ClientsTable extends Component {
},
},
{
Header: this.props.t('tags_title'),
Header: t('tags_title'),
accessor: 'tags',
minWidth: 140,
Cell: (row) => {
@@ -203,11 +250,11 @@ class ClientsTable extends Component {
},
},
{
Header: this.props.t('requests_count'),
Header: t('requests_count'),
id: 'statistics',
accessor: (row) => countClientsStatistics(
row.ids,
this.props.normalizedTopClients.auto,
normalizedTopClients.auto,
),
sortMethod: (a, b) => b - a,
minWidth: 120,
@@ -222,16 +269,13 @@ class ClientsTable extends Component {
},
},
{
Header: this.props.t('actions_table_header'),
Header: t('actions_table_header'),
accessor: 'actions',
maxWidth: 100,
sortable: false,
resizable: false,
Cell: (row) => {
const clientName = row.original.name;
const {
toggleClientModal, processingDeleting, processingUpdating, t,
} = this.props;
return (
<div className="logs__row logs__row--center">
@@ -253,7 +297,7 @@ class ClientsTable extends Component {
<button
type="button"
className="btn btn-icon btn-outline-secondary btn-sm"
onClick={() => this.handleDelete({ name: clientName })}
onClick={() => handleDelete({ name: clientName })}
disabled={processingDeleting}
title={t('delete_table_action')}
>
@@ -267,76 +311,61 @@ class ClientsTable extends Component {
},
];
render() {
const {
t,
clients,
isModalOpen,
modalType,
modalClientName,
toggleClientModal,
processingAdding,
processingUpdating,
supportedTags,
} = this.props;
const currentClientData = getClient(modalClientName, clients);
const tagsOptions = getOptionsWithLabels(supportedTags);
const currentClientData = this.getClient(modalClientName, clients);
const tagsOptions = this.getOptionsWithLabels(supportedTags);
return (
<Card
title={t('clients_title')}
subtitle={t('clients_desc')}
bodyType="card-body box-body--settings"
>
<Fragment>
<ReactTable
data={clients || []}
columns={this.columns}
defaultSorted={[
{
id: 'statistics',
asc: true,
},
]}
className="-striped -highlight card-table-overflow"
showPagination
defaultPageSize={10}
minRows={5}
ofText="/"
previousText={t('previous_btn')}
nextText={t('next_btn')}
pageText={t('page_table_footer_text')}
rowsText={t('rows_table_footer_text')}
loadingText={t('loading_table_status')}
noDataText={t('clients_not_found')}
/>
<button
type="button"
className="btn btn-success btn-standard mt-3"
onClick={() => toggleClientModal(MODAL_TYPE.ADD_FILTERS)}
disabled={processingAdding}
>
<Trans>client_add</Trans>
</button>
<Modal
isModalOpen={isModalOpen}
modalType={modalType}
toggleClientModal={toggleClientModal}
currentClientData={currentClientData}
handleSubmit={this.handleSubmit}
processingAdding={processingAdding}
processingUpdating={processingUpdating}
tagsOptions={tagsOptions}
/>
</Fragment>
</Card>
);
}
}
return (
<Card
title={t('clients_title')}
subtitle={t('clients_desc')}
bodyType="card-body box-body--settings"
>
<>
<ReactTable
data={clients || []}
columns={columns}
defaultSorted={[
{
id: 'statistics',
asc: true,
},
]}
className="-striped -highlight card-table-overflow"
showPagination
defaultPageSize={10}
minRows={5}
ofText="/"
previousText={t('previous_btn')}
nextText={t('next_btn')}
pageText={t('page_table_footer_text')}
rowsText={t('rows_table_footer_text')}
loadingText={t('loading_table_status')}
noDataText={t('clients_not_found')}
/>
<button
type="button"
className="btn btn-success btn-standard mt-3"
onClick={() => toggleClientModal(MODAL_TYPE.ADD_FILTERS)}
disabled={processingAdding}
>
<Trans>client_add</Trans>
</button>
<Modal
isModalOpen={isModalOpen}
modalType={modalType}
toggleClientModal={toggleClientModal}
currentClientData={currentClientData}
handleSubmit={handleSubmit}
processingAdding={processingAdding}
processingUpdating={processingUpdating}
tagsOptions={tagsOptions}
/>
</>
</Card>
);
};
ClientsTable.propTypes = {
t: PropTypes.func.isRequired,
clients: PropTypes.array.isRequired,
normalizedTopClients: PropTypes.object.isRequired,
toggleClientModal: PropTypes.func.isRequired,
@@ -353,4 +382,4 @@ ClientsTable.propTypes = {
supportedTags: PropTypes.array.isRequired,
};
export default withTranslation()(ClientsTable);
export default ClientsTable;

View File

@@ -0,0 +1 @@
export { default as ClientsTable } from './ClientsTable';

View File

@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { connect } from 'react-redux';
import { connect, useSelector } from 'react-redux';
import PropTypes from 'prop-types';
import {
Field, FieldArray, reduxForm, formValueSelector,
@@ -19,7 +19,7 @@ import {
renderServiceField,
} from '../../../helpers/form';
import { validateClientId, validateRequiredValue } from '../../../helpers/validators';
import { CLIENT_ID_LINK, FORM_NAME, SERVICES } from '../../../helpers/constants';
import { CLIENT_ID_LINK, FORM_NAME } from '../../../helpers/constants';
import './Service.css';
const settingsCheckboxes = [
@@ -139,6 +139,7 @@ let Form = (props) => {
invalid,
tagsOptions,
} = props;
const services = useSelector((store) => store?.services);
const [activeTabLabel, setActiveTabLabel] = useState('settings');
@@ -180,7 +181,9 @@ let Form = (props) => {
type="button"
className="btn btn-secondary btn-block"
disabled={useGlobalServices}
onClick={() => toggleAllServices(SERVICES, change, true)}
onClick={() => (
toggleAllServices(services.allServices, change, true)
)}
>
<Trans>block_all</Trans>
</button>
@@ -190,25 +193,29 @@ let Form = (props) => {
type="button"
className="btn btn-secondary btn-block"
disabled={useGlobalServices}
onClick={() => toggleAllServices(SERVICES, change, false)}
onClick={() => (
toggleAllServices(services.allServices, 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>
{services.allServices.length > 0 && (
<div className="services">
{services.allServices.map((service) => (
<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>,
},

View File

@@ -9,6 +9,12 @@
cursor: pointer;
}
.service__text {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
@media screen and (min-width: 768px) {
.services {
display: flex;
@@ -33,7 +39,7 @@
margin-right: 30px;
margin-left: 0;
}
.service:nth-child(3n) {
margin-right: 0;
margin-left: auto;

View File

@@ -2,7 +2,7 @@ import React, { Component, Fragment } from 'react';
import { withTranslation } from 'react-i18next';
import PropTypes from 'prop-types';
import ClientsTable from './ClientsTable';
import { ClientsTable } from './ClientsTable';
import AutoClients from './AutoClients';
import PageTitle from '../../ui/PageTitle';
import Loading from '../../ui/Loading';

File diff suppressed because one or more lines are too long

View File

@@ -15452,6 +15452,7 @@ a.tag-addon:hover {
}
.custom-switch-indicator {
flex-shrink: 0;
display: inline-block;
height: 1.25rem;
width: 2.25rem;

View File

@@ -202,158 +202,6 @@ export const FILTERS_URLS = {
blocked_services: '/blocked_services',
};
export const SERVICES = [
{
id: '9gag',
name: '9GAG',
},
{
id: 'amazon',
name: 'Amazon',
},
{
id: 'bilibili',
name: 'Bilibili',
},
{
id: 'cloudflare',
name: 'CloudFlare',
},
{
id: 'dailymotion',
name: 'Dailymotion',
},
{
id: 'discord',
name: 'Discord',
},
{
id: 'disneyplus',
name: 'Disney+',
},
{
id: 'ebay',
name: 'EBay',
},
{
id: 'epic_games',
name: 'Epic Games',
},
{
id: 'facebook',
name: 'Facebook',
},
{
id: 'hulu',
name: 'Hulu',
},
{
id: 'imgur',
name: 'Imgur',
},
{
id: 'instagram',
name: 'Instagram',
},
{
id: 'mail_ru',
name: 'Mail.ru',
},
{
id: 'netflix',
name: 'Netflix',
},
{
id: 'ok',
name: 'OK.ru',
},
{
id: 'origin',
name: 'Origin',
},
{
id: 'pinterest',
name: 'Pinterest',
},
{
id: 'qq',
name: 'QQ',
},
{
id: 'reddit',
name: 'Reddit',
},
{
id: 'skype',
name: 'Skype',
},
{
id: 'snapchat',
name: 'Snapchat',
},
{
id: 'spotify',
name: 'Spotify',
},
{
id: 'steam',
name: 'Steam',
},
{
id: 'telegram',
name: 'Telegram',
},
{
id: 'tiktok',
name: 'TikTok',
},
{
id: 'tinder',
name: 'Tinder',
},
{
id: 'twitch',
name: 'Twitch',
},
{
id: 'twitter',
name: 'Twitter',
},
{
id: 'viber',
name: 'Viber',
},
{
id: 'vimeo',
name: 'Vimeo',
},
{
id: 'vk',
name: 'VK.com',
},
{
id: 'wechat',
name: 'WeChat',
},
{
id: 'weibo',
name: 'Weibo',
},
{
id: 'whatsapp',
name: 'WhatsApp',
},
{
id: 'youtube',
name: 'YouTube',
},
];
export const SERVICES_ID_NAME_MAP = SERVICES.reduce((acc, { id, name }) => {
acc[id] = name;
return acc;
}, {});
export const ENCRYPTION_SOURCE = {
PATH: 'path',
CONTENT: 'content',

View File

@@ -1,6 +1,8 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { Trans } from 'react-i18next';
import cn from 'classnames';
import { createOnBlurHandler } from './helpers';
import { R_MAC_WITHOUT_COLON, R_UNIX_ABSOLUTE_PATH, R_WIN_ABSOLUTE_PATH } from './constants';
@@ -229,24 +231,34 @@ export const renderServiceField = ({
modifier,
icon,
meta: { touched, error },
}) => <Fragment>
<label className={`service custom-switch ${modifier}`}>
<input
{...input}
type="checkbox"
className="custom-switch-input"
value={placeholder.toLowerCase()}
disabled={disabled}
/>
<span className="service__switch custom-switch-indicator"></span>
<span className="service__text">{placeholder}</span>
<svg className="service__icon">
<use xlinkHref={`#${icon}`} />
</svg>
</label>
{!disabled && touched && error
&& <span className="form__message form__message--error"><Trans>{error}</Trans></span>}
</Fragment>;
}) => (
<>
<label className={cn('service custom-switch', { [modifier]: modifier })}>
<input
{...input}
type="checkbox"
className="custom-switch-input"
value={placeholder.toLowerCase()}
disabled={disabled}
/>
<span className="service__switch custom-switch-indicator"></span>
<span className="service__text" title={placeholder}>
{placeholder}
</span>
{icon && (
<div
dangerouslySetInnerHTML={{ __html: window.atob(icon) }}
className="service__icon"
/>
)}
</label>
{!disabled && touched && error && (
<span className="form__message form__message--error">
<Trans>{error}</Trans>
</span>
)}
</>
);
renderServiceField.propTypes = {
input: PropTypes.object.isRequired,

View File

@@ -21,7 +21,6 @@ import {
FILTERED,
FILTERED_STATUS,
R_CLIENT_ID,
SERVICES_ID_NAME_MAP,
STANDARD_DNS_PORT,
STANDARD_HTTPS_PORT,
STANDARD_WEB_PORT,
@@ -991,7 +990,22 @@ export const filterOutComments = (lines) => lines
.filter((line) => !line.startsWith(COMMENT_LINE_DEFAULT_TOKEN));
/**
* @param {string} serviceId
* @param {array} services
* @param {string} id
* @returns {string}
*/
export const getServiceName = (serviceId) => SERVICES_ID_NAME_MAP[serviceId] || serviceId;
export const getService = (services, id) => services.find((s) => s.id === id);
/**
* @param {array} services
* @param {string} id
* @returns {string}
*/
export const getServiceName = (services, id) => getService(services, id)?.name;
/**
* @param {array} services
* @param {string} id
* @returns {string}
*/
export const getServiceIcon = (services, id) => getService(services, id)?.icon_svg;

View File

@@ -12,6 +12,14 @@ const services = handleActions(
processing: false,
}),
[actions.getAllBlockedServicesRequest]: (state) => ({ ...state, processingAll: true }),
[actions.getAllBlockedServicesFailure]: (state) => ({ ...state, processingAll: false }),
[actions.getAllBlockedServicesSuccess]: (state, { payload }) => ({
...state,
allServices: payload.blocked_services,
processingAll: false,
}),
[actions.setBlockedServicesRequest]: (state) => ({ ...state, processingSet: true }),
[actions.setBlockedServicesFailure]: (state) => ({ ...state, processingSet: false }),
[actions.setBlockedServicesSuccess]: (state) => ({
@@ -21,8 +29,10 @@ const services = handleActions(
},
{
processing: true,
processingAll: true,
processingSet: false,
list: [],
allServices: [],
},
);