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:
parent
8a935d4ffb
commit
9951d861d1
10
CHANGELOG.md
10
CHANGELOG.md
|
@ -17,6 +17,10 @@ and this project adheres to
|
|||
|
||||
## Added
|
||||
|
||||
- Several new blockable services ([#3972]). Those will now be more in sync with
|
||||
the services that are already blockable in AdGuard DNS.
|
||||
- A new HTTP API, `GET /control/blocked_services/all`, that lists all available
|
||||
blocked services and their data, such as SVG icons ([#3972]).
|
||||
- The new optional `tls.override_tls_ciphers` property, which allows
|
||||
overriding TLS ciphers used by AdGuard Home ([#4925], [#4990]).
|
||||
- The ability to serve DNS on link-local IPv6 addresses ([#2926]).
|
||||
|
@ -28,6 +32,11 @@ and this project adheres to
|
|||
|
||||
- Responses with `SERVFAIL` code are now cached for at least 30 seconds.
|
||||
|
||||
### Deprecated
|
||||
|
||||
- The `GET /control/blocked_services/services` HTTP API; use the new
|
||||
`GET /control/blocked_services/all` API instead ([#3972]).
|
||||
|
||||
### Fixed
|
||||
|
||||
- ClientIDs not working when using DNS-over-HTTPS with HTTP/3.
|
||||
|
@ -41,6 +50,7 @@ and this project adheres to
|
|||
|
||||
[#2926]: https://github.com/AdguardTeam/AdGuardHome/issues/2926
|
||||
[#3418]: https://github.com/AdguardTeam/AdGuardHome/issues/3418
|
||||
[#3972]: https://github.com/AdguardTeam/AdGuardHome/issues/3972
|
||||
[#4916]: https://github.com/AdguardTeam/AdGuardHome/issues/4916
|
||||
[#4925]: https://github.com/AdguardTeam/AdGuardHome/issues/4925
|
||||
[#4942]: https://github.com/AdguardTeam/AdGuardHome/issues/4942
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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(', ');
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()),
|
||||
|
|
|
@ -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;
|
|
@ -0,0 +1 @@
|
|||
export { default as ClientsTable } from './ClientsTable';
|
|
@ -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>,
|
||||
},
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
@ -15452,6 +15452,7 @@ a.tag-addon:hover {
|
|||
}
|
||||
|
||||
.custom-switch-indicator {
|
||||
flex-shrink: 0;
|
||||
display: inline-block;
|
||||
height: 1.25rem;
|
||||
width: 2.25rem;
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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: [],
|
||||
},
|
||||
);
|
||||
|
||||
|
|
|
@ -10,382 +10,6 @@ import (
|
|||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// svc represents a single blocked service.
|
||||
type svc struct {
|
||||
name string
|
||||
rules []string
|
||||
}
|
||||
|
||||
// servicesData contains raw blocked service data.
|
||||
//
|
||||
// Keep in sync with:
|
||||
// - client/src/helpers/constants.js
|
||||
// - client/src/components/ui/Icons.js
|
||||
var servicesData = []svc{{
|
||||
name: "whatsapp",
|
||||
rules: []string{
|
||||
"||wa.me^",
|
||||
"||whatsapp.com^",
|
||||
"||whatsapp.net^",
|
||||
},
|
||||
}, {
|
||||
name: "facebook",
|
||||
rules: []string{
|
||||
"||facebook.com^",
|
||||
"||facebook.net^",
|
||||
"||fbcdn.net^",
|
||||
"||accountkit.com^",
|
||||
"||fb.me^",
|
||||
"||fb.com^",
|
||||
"||fb.gg^",
|
||||
"||fbsbx.com^",
|
||||
"||fbwat.ch^",
|
||||
"||messenger.com^",
|
||||
"||facebookcorewwwi.onion^",
|
||||
"||fbcdn.com^",
|
||||
"||fb.watch^",
|
||||
},
|
||||
}, {
|
||||
name: "twitter",
|
||||
rules: []string{
|
||||
"||t.co^",
|
||||
"||twimg.com^",
|
||||
"||twitter.com^",
|
||||
"||twttr.com^",
|
||||
},
|
||||
}, {
|
||||
name: "youtube",
|
||||
rules: []string{
|
||||
"||googlevideo.com^",
|
||||
"||wide-youtube.l.google.com^",
|
||||
"||youtu.be^",
|
||||
"||youtube",
|
||||
"||youtube-nocookie.com^",
|
||||
"||youtube.com^",
|
||||
"||youtubei.googleapis.com^",
|
||||
"||youtubekids.com^",
|
||||
"||ytimg.com^",
|
||||
},
|
||||
}, {
|
||||
name: "twitch",
|
||||
rules: []string{
|
||||
"||jtvnw.net^",
|
||||
"||ttvnw.net^",
|
||||
"||twitch.tv^",
|
||||
"||twitchcdn.net^",
|
||||
},
|
||||
}, {
|
||||
name: "netflix",
|
||||
rules: []string{
|
||||
"||nflxext.com^",
|
||||
"||netflix.com^",
|
||||
"||nflximg.net^",
|
||||
"||nflxvideo.net^",
|
||||
"||nflxso.net^",
|
||||
},
|
||||
}, {
|
||||
name: "instagram",
|
||||
rules: []string{"||instagram.com^", "||cdninstagram.com^"},
|
||||
}, {
|
||||
name: "snapchat",
|
||||
rules: []string{
|
||||
"||snapchat.com^",
|
||||
"||sc-cdn.net^",
|
||||
"||snap-dev.net^",
|
||||
"||snapkit.co",
|
||||
"||snapads.com^",
|
||||
"||impala-media-production.s3.amazonaws.com^",
|
||||
},
|
||||
}, {
|
||||
name: "discord",
|
||||
rules: []string{
|
||||
"||discord.gg^",
|
||||
"||discordapp.net^",
|
||||
"||discordapp.com^",
|
||||
"||discord.com^",
|
||||
"||discord.gift",
|
||||
"||discord.media^",
|
||||
},
|
||||
}, {
|
||||
name: "ok",
|
||||
rules: []string{"||ok.ru^"},
|
||||
}, {
|
||||
name: "skype",
|
||||
rules: []string{
|
||||
"||edge-skype-com.s-0001.s-msedge.net^",
|
||||
"||skype-edf.akadns.net^",
|
||||
"||skype.com^",
|
||||
"||skypeassets.com^",
|
||||
"||skypedata.akadns.net^",
|
||||
},
|
||||
}, {
|
||||
name: "vk",
|
||||
rules: []string{
|
||||
"||userapi.com^",
|
||||
"||vk-cdn.net^",
|
||||
"||vk.com^",
|
||||
"||vkuservideo.net^",
|
||||
},
|
||||
}, {
|
||||
name: "origin",
|
||||
rules: []string{
|
||||
"||accounts.ea.com^",
|
||||
"||origin.com^",
|
||||
"||signin.ea.com^",
|
||||
},
|
||||
}, {
|
||||
name: "steam",
|
||||
rules: []string{
|
||||
"||steam.com^",
|
||||
"||steampowered.com^",
|
||||
"||steamcommunity.com^",
|
||||
"||steamstatic.com^",
|
||||
"||steamstore-a.akamaihd.net^",
|
||||
"||steamcdn-a.akamaihd.net^",
|
||||
},
|
||||
}, {
|
||||
name: "epic_games",
|
||||
rules: []string{"||epicgames.com^", "||easyanticheat.net^", "||easy.ac^", "||eac-cdn.com^"},
|
||||
}, {
|
||||
name: "reddit",
|
||||
rules: []string{"||reddit.com^", "||redditstatic.com^", "||redditmedia.com^", "||redd.it^"},
|
||||
}, {
|
||||
name: "mail_ru",
|
||||
rules: []string{"||mail.ru^"},
|
||||
}, {
|
||||
name: "cloudflare",
|
||||
rules: []string{
|
||||
"||1.1.1.1^",
|
||||
"||argotunnel.com^",
|
||||
"||cloudflare-dns.com^",
|
||||
"||cloudflare-ipfs.com^",
|
||||
"||cloudflare-quic.com^",
|
||||
"||cloudflare.cn^",
|
||||
"||cloudflare.com^",
|
||||
"||cloudflare.net^",
|
||||
"||cloudflareaccess.com^",
|
||||
"||cloudflareapps.com^",
|
||||
"||cloudflarebolt.com^",
|
||||
"||cloudflareclient.com^",
|
||||
"||cloudflareinsights.com^",
|
||||
"||cloudflareresolve.com^",
|
||||
"||cloudflarestatus.com^",
|
||||
"||cloudflarestream.com^",
|
||||
"||cloudflarewarp.com^",
|
||||
"||dns4torpnlfs2ifuz2s2yf3fc7rdmsbhm6rw75euj35pac6ap25zgqad.onion^",
|
||||
"||one.one^",
|
||||
"||pages.dev^",
|
||||
"||trycloudflare.com^",
|
||||
"||videodelivery.net^",
|
||||
"||warp.plus^",
|
||||
"||workers.dev^",
|
||||
},
|
||||
}, {
|
||||
name: "amazon",
|
||||
rules: []string{
|
||||
"||amazon.com^",
|
||||
"||media-amazon.com^",
|
||||
"||primevideo.com^",
|
||||
"||amazontrust.com^",
|
||||
"||images-amazon.com^",
|
||||
"||ssl-images-amazon.com^",
|
||||
"||amazonpay.com^",
|
||||
"||amazonpay.in^",
|
||||
"||amazon-adsystem.com^",
|
||||
"||a2z.com^",
|
||||
"||amazon.ae^",
|
||||
"||amazon.ca^",
|
||||
"||amazon.cn^",
|
||||
"||amazon.de^",
|
||||
"||amazon.es^",
|
||||
"||amazon.fr^",
|
||||
"||amazon.in^",
|
||||
"||amazon.it^",
|
||||
"||amazon.nl^",
|
||||
"||amazon.com.au^",
|
||||
"||amazon.com.br^",
|
||||
"||amazon.co.jp^",
|
||||
"||amazon.com.mx^",
|
||||
"||amazon.com.tr^",
|
||||
"||amazon.co.uk^",
|
||||
"||createspace.com^",
|
||||
"||aws",
|
||||
},
|
||||
}, {
|
||||
name: "ebay",
|
||||
rules: []string{
|
||||
"||ebay.com^",
|
||||
"||ebayimg.com^",
|
||||
"||ebaystatic.com^",
|
||||
"||ebaycdn.net^",
|
||||
"||ebayinc.com^",
|
||||
"||ebay.at^",
|
||||
"||ebay.be^",
|
||||
"||ebay.ca^",
|
||||
"||ebay.ch^",
|
||||
"||ebay.cn^",
|
||||
"||ebay.de^",
|
||||
"||ebay.es^",
|
||||
"||ebay.fr^",
|
||||
"||ebay.ie^",
|
||||
"||ebay.in^",
|
||||
"||ebay.it^",
|
||||
"||ebay.ph^",
|
||||
"||ebay.pl^",
|
||||
"||ebay.nl^",
|
||||
"||ebay.com.au^",
|
||||
"||ebay.com.cn^",
|
||||
"||ebay.com.hk^",
|
||||
"||ebay.com.my^",
|
||||
"||ebay.com.sg^",
|
||||
"||ebay.co.uk^",
|
||||
},
|
||||
}, {
|
||||
name: "tiktok",
|
||||
rules: []string{
|
||||
"||amemv.com^",
|
||||
"||bdurl.com^",
|
||||
"||bytecdn.cn^",
|
||||
"||bytedance.map.fastly.net^",
|
||||
"||bytedapm.com^",
|
||||
"||byteimg.com^",
|
||||
"||byteoversea.com^",
|
||||
"||douyin.com^",
|
||||
"||douyincdn.com^",
|
||||
"||douyinpic.com^",
|
||||
"||douyinstatic.com^",
|
||||
"||douyinvod.com^",
|
||||
"||ixigua.com^",
|
||||
"||ixiguavideo.com^",
|
||||
"||muscdn.com^",
|
||||
"||musical.ly^",
|
||||
"||pstatp.com^",
|
||||
"||snssdk.com^",
|
||||
"||tiktok.com^",
|
||||
"||tiktokcdn.com^",
|
||||
"||tiktokv.com^",
|
||||
"||toutiao.com^",
|
||||
"||toutiaocloud.com^",
|
||||
"||toutiaocloud.net^",
|
||||
"||toutiaovod.com^",
|
||||
},
|
||||
}, {
|
||||
name: "vimeo",
|
||||
rules: []string{
|
||||
"*vod-adaptive.akamaized.net^",
|
||||
"||vimeo.com^",
|
||||
"||vimeocdn.com^",
|
||||
},
|
||||
}, {
|
||||
name: "pinterest",
|
||||
rules: []string{
|
||||
"||pinimg.com^",
|
||||
"||pinterest.*^",
|
||||
},
|
||||
}, {
|
||||
name: "imgur",
|
||||
rules: []string{"||imgur.com^"},
|
||||
}, {
|
||||
name: "dailymotion",
|
||||
rules: []string{
|
||||
"||dailymotion.com^",
|
||||
"||dm-event.net^",
|
||||
"||dmcdn.net^",
|
||||
},
|
||||
}, {
|
||||
name: "qq",
|
||||
rules: []string{
|
||||
// Block qq.com and subdomains excluding WeChat's domains.
|
||||
"||qq.com^$denyallow=wx.qq.com|weixin.qq.com",
|
||||
"||qqzaixian.com^",
|
||||
"||qq-video.cdn-go.cn^",
|
||||
"||url.cn^",
|
||||
},
|
||||
}, {
|
||||
name: "wechat",
|
||||
rules: []string{
|
||||
"||wechat.com^",
|
||||
"||weixin.qq.com.cn^",
|
||||
"||weixin.qq.com^",
|
||||
"||weixinbridge.com^",
|
||||
"||wx.qq.com^",
|
||||
},
|
||||
}, {
|
||||
name: "viber",
|
||||
rules: []string{"||viber.com^"},
|
||||
}, {
|
||||
name: "weibo",
|
||||
rules: []string{
|
||||
"||weibo.cn^",
|
||||
"||weibo.com^",
|
||||
"||weibocdn.com^",
|
||||
},
|
||||
}, {
|
||||
name: "9gag",
|
||||
rules: []string{
|
||||
"||9cache.com^",
|
||||
"||9gag.com^",
|
||||
},
|
||||
}, {
|
||||
name: "telegram",
|
||||
rules: []string{
|
||||
"||t.me^",
|
||||
"||telegram.me^",
|
||||
"||telegram.org^",
|
||||
},
|
||||
}, {
|
||||
name: "disneyplus",
|
||||
rules: []string{
|
||||
"||disney-plus.net^",
|
||||
"||disney.playback.edge.bamgrid.com^",
|
||||
"||disneynow.com^",
|
||||
"||disneyplus.com^",
|
||||
"||hotstar.com^",
|
||||
"||media.dssott.com^",
|
||||
"||star.playback.edge.bamgrid.com^",
|
||||
"||starplus.com^",
|
||||
},
|
||||
}, {
|
||||
name: "hulu",
|
||||
rules: []string{"||hulu.com^"},
|
||||
}, {
|
||||
name: "spotify",
|
||||
rules: []string{
|
||||
"/_spotify-connect._tcp.local/",
|
||||
"||spotify.com^",
|
||||
"||scdn.co^",
|
||||
"||spotify.com.edgesuite.net^",
|
||||
"||spotify.map.fastly.net^",
|
||||
"||spotify.map.fastlylb.net^",
|
||||
"||spotifycdn.net^",
|
||||
"||audio-ak-spotify-com.akamaized.net^",
|
||||
"||audio4-ak-spotify-com.akamaized.net^",
|
||||
"||heads-ak-spotify-com.akamaized.net^",
|
||||
"||heads4-ak-spotify-com.akamaized.net^",
|
||||
},
|
||||
}, {
|
||||
name: "tinder",
|
||||
rules: []string{
|
||||
"||gotinder.com^",
|
||||
"||tinder.com^",
|
||||
"||tindersparks.com^",
|
||||
},
|
||||
}, {
|
||||
name: "bilibili",
|
||||
rules: []string{
|
||||
"||b23.tv^",
|
||||
"||biliapi.net^",
|
||||
"||bilibili.com^",
|
||||
"||bilicdn1.com^",
|
||||
"||bilicdn2.com^",
|
||||
"||biligame.com^",
|
||||
"||bilivideo.cn^",
|
||||
"||bilivideo.com^",
|
||||
"||dreamcast.hk^",
|
||||
"||hdslb.com^",
|
||||
},
|
||||
}}
|
||||
|
||||
// serviceRules maps a service ID to its filtering rules.
|
||||
var serviceRules map[string][]*rules.NetworkRule
|
||||
|
||||
|
@ -394,16 +18,16 @@ var serviceIDs []string
|
|||
|
||||
// initBlockedServices initializes package-level blocked service data.
|
||||
func initBlockedServices() {
|
||||
l := len(servicesData)
|
||||
l := len(blockedServices)
|
||||
serviceIDs = make([]string, l)
|
||||
serviceRules = make(map[string][]*rules.NetworkRule, l)
|
||||
|
||||
for i, s := range servicesData {
|
||||
netRules := make([]*rules.NetworkRule, 0, len(s.rules))
|
||||
for _, text := range s.rules {
|
||||
for i, s := range blockedServices {
|
||||
netRules := make([]*rules.NetworkRule, 0, len(s.Rules))
|
||||
for _, text := range s.Rules {
|
||||
rule, err := rules.NewNetworkRule(text, BlockedSvcsListID)
|
||||
if err != nil {
|
||||
log.Error("parsing blocked service %q rule %q: %s", s.name, text, err)
|
||||
log.Error("parsing blocked service %q rule %q: %s", s.ID, text, err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
@ -411,8 +35,8 @@ func initBlockedServices() {
|
|||
netRules = append(netRules, rule)
|
||||
}
|
||||
|
||||
serviceIDs[i] = s.name
|
||||
serviceRules[s.name] = netRules
|
||||
serviceIDs[i] = s.ID
|
||||
serviceRules[s.ID] = netRules
|
||||
}
|
||||
|
||||
slices.Sort(serviceIDs)
|
||||
|
@ -420,7 +44,7 @@ func initBlockedServices() {
|
|||
log.Debug("filtering: initialized %d services", l)
|
||||
}
|
||||
|
||||
// BlockedSvcKnown - return TRUE if a blocked service name is known
|
||||
// BlockedSvcKnown returns true if a blocked service ID is known.
|
||||
func BlockedSvcKnown(s string) (ok bool) {
|
||||
_, ok = serviceRules[s]
|
||||
|
||||
|
@ -452,10 +76,18 @@ func (d *DNSFilter) ApplyBlockedServices(setts *Settings, list []string) {
|
|||
}
|
||||
}
|
||||
|
||||
func (d *DNSFilter) handleBlockedServicesAvailableServices(w http.ResponseWriter, r *http.Request) {
|
||||
func (d *DNSFilter) handleBlockedServicesIDs(w http.ResponseWriter, r *http.Request) {
|
||||
_ = aghhttp.WriteJSONResponse(w, r, serviceIDs)
|
||||
}
|
||||
|
||||
func (d *DNSFilter) handleBlockedServicesAll(w http.ResponseWriter, r *http.Request) {
|
||||
_ = aghhttp.WriteJSONResponse(w, r, struct {
|
||||
BlockedServices []blockedService `json:"blocked_services"`
|
||||
}{
|
||||
BlockedServices: blockedServices,
|
||||
})
|
||||
}
|
||||
|
||||
func (d *DNSFilter) handleBlockedServicesList(w http.ResponseWriter, r *http.Request) {
|
||||
d.confLock.RLock()
|
||||
list := d.Config.BlockedServices
|
||||
|
@ -479,5 +111,5 @@ func (d *DNSFilter) handleBlockedServicesSet(w http.ResponseWriter, r *http.Requ
|
|||
|
||||
log.Debug("Updated blocked services list: %d", len(list))
|
||||
|
||||
d.ConfigModified()
|
||||
d.Config.ConfigModified()
|
||||
}
|
||||
|
|
|
@ -466,7 +466,8 @@ func (d *DNSFilter) RegisterFilteringHandlers() {
|
|||
registerHTTP(http.MethodPost, "/control/rewrite/add", d.handleRewriteAdd)
|
||||
registerHTTP(http.MethodPost, "/control/rewrite/delete", d.handleRewriteDelete)
|
||||
|
||||
registerHTTP(http.MethodGet, "/control/blocked_services/services", d.handleBlockedServicesAvailableServices)
|
||||
registerHTTP(http.MethodGet, "/control/blocked_services/services", d.handleBlockedServicesIDs)
|
||||
registerHTTP(http.MethodGet, "/control/blocked_services/all", d.handleBlockedServicesAll)
|
||||
registerHTTP(http.MethodGet, "/control/blocked_services/list", d.handleBlockedServicesList)
|
||||
registerHTTP(http.MethodPost, "/control/blocked_services/set", d.handleBlockedServicesSet)
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -6,6 +6,19 @@
|
|||
|
||||
|
||||
|
||||
## v0.107.17: API Changes
|
||||
|
||||
### `GET /control/blocked_services/services` is deprecated
|
||||
|
||||
Use `GET /control/blocked_services/all`.
|
||||
|
||||
### `GET /control/blocked_services/all`
|
||||
|
||||
* The new `GET /control/blocked_services/all` HTTP API allows inspecting all
|
||||
available services and their data, such as SVG icons and human-readable names.
|
||||
|
||||
|
||||
|
||||
## v0.107.15: `POST` Requests Without Bodies
|
||||
|
||||
As an additional CSRF protection measure, AdGuard Home now ensures that requests
|
||||
|
|
|
@ -862,6 +862,9 @@
|
|||
- 'clients'
|
||||
'/blocked_services/services':
|
||||
'get':
|
||||
'deprecated': true
|
||||
'description': >
|
||||
Deprecated: Use `GET /blocked_services/all` instead.
|
||||
'tags':
|
||||
- 'blocked_services'
|
||||
'operationId': 'blockedServicesAvailableServices'
|
||||
|
@ -873,6 +876,19 @@
|
|||
'application/json':
|
||||
'schema':
|
||||
'$ref': '#/components/schemas/BlockedServicesArray'
|
||||
'/blocked_services/all':
|
||||
'get':
|
||||
'tags':
|
||||
- 'blocked_services'
|
||||
'operationId': 'blockedServicesAll'
|
||||
'summary': 'Get available services to use for blocking'
|
||||
'responses':
|
||||
'200':
|
||||
'description': 'OK.'
|
||||
'content':
|
||||
'application/json':
|
||||
'schema':
|
||||
'$ref': '#/components/schemas/BlockedServicesAll'
|
||||
'/blocked_services/list':
|
||||
'get':
|
||||
'tags':
|
||||
|
@ -2539,6 +2555,42 @@
|
|||
'type': 'array'
|
||||
'items':
|
||||
'type': 'string'
|
||||
'BlockedServicesAll':
|
||||
'properties':
|
||||
'blocked_services':
|
||||
'items':
|
||||
'$ref': '#/components/schemas/BlockedService'
|
||||
'type': 'array'
|
||||
'required':
|
||||
- 'blocked_services'
|
||||
'type': 'object'
|
||||
'BlockedService':
|
||||
'properties':
|
||||
'icon_svg':
|
||||
'description': >
|
||||
The SVG icon as a Base64-encoded string to make it easier to embed
|
||||
it into a data URL.
|
||||
'type': 'string'
|
||||
'id':
|
||||
'description': >
|
||||
The ID of this service.
|
||||
'type': 'string'
|
||||
'name':
|
||||
'description': >
|
||||
The human-readable name of this service.
|
||||
'type': 'string'
|
||||
'rules':
|
||||
'description': >
|
||||
The array of the filtering rules.
|
||||
'items':
|
||||
'type': 'string'
|
||||
'type': 'array'
|
||||
'required':
|
||||
- 'icon_svg'
|
||||
- 'id'
|
||||
- 'name'
|
||||
- 'rules'
|
||||
'type': 'object'
|
||||
'CheckConfigRequestBeta':
|
||||
'type': 'object'
|
||||
'description': 'Configuration to be checked'
|
||||
|
|
|
@ -208,10 +208,30 @@ code from [the repo][companiesrepo].
|
|||
|
||||
|
||||
|
||||
## `blocked-services/`: Blocked Services Updater
|
||||
|
||||
A simple script that downloads and updates the blocked services index from
|
||||
AdGuard's [Hostlists Registry][reg].
|
||||
|
||||
Optional environment:
|
||||
|
||||
* `URL`: the URL of the index file. By default it's
|
||||
`https://adguardteam.github.io/HostlistsRegistry/assets/services.json`.
|
||||
|
||||
### Usage
|
||||
|
||||
```sh
|
||||
go run ./scripts/blocked-services/main.go
|
||||
```
|
||||
|
||||
[reg]: https://github.com/AdguardTeam/HostlistsRegistry
|
||||
|
||||
|
||||
|
||||
## `vetted-filters/`: Vetted Filters Updater
|
||||
|
||||
A simple script that downloads and updates the vetted filtering list data from
|
||||
AdGuard's [Hostlists Registry][reg].
|
||||
Similar to the one above, a script that downloads and updates the vetted
|
||||
filtering list data from AdGuard's [Hostlists Registry][reg].
|
||||
|
||||
Optional environment:
|
||||
|
||||
|
@ -223,5 +243,3 @@ Optional environment:
|
|||
```sh
|
||||
go run ./scripts/vetted-filters/main.go
|
||||
```
|
||||
|
||||
[reg]: https://github.com/AdguardTeam/HostlistsRegistry
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
// blocked services fetches the most recent Hostlists Registry blocked service
|
||||
// index and transforms the filters from it to AdGuard Home's data and code
|
||||
// formats.
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
func main() {
|
||||
urlStr := "https://adguardteam.github.io/HostlistsRegistry/assets/services.json"
|
||||
if v, ok := os.LookupEnv("URL"); ok {
|
||||
urlStr = v
|
||||
}
|
||||
|
||||
// Validate the URL.
|
||||
_, err := url.Parse(urlStr)
|
||||
check(err)
|
||||
|
||||
c := &http.Client{
|
||||
Timeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
resp, err := c.Get(urlStr)
|
||||
check(err)
|
||||
defer log.OnCloserError(resp.Body, log.ERROR)
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
panic(fmt.Errorf("expected code %d, got %d", http.StatusOK, resp.StatusCode))
|
||||
}
|
||||
|
||||
hlSvcs := &hlServices{}
|
||||
err = json.NewDecoder(resp.Body).Decode(hlSvcs)
|
||||
check(err)
|
||||
|
||||
// Sort all services and rules to make the output more predictable.
|
||||
slices.SortStableFunc(hlSvcs.BlockedServices, func(a, b *hlServicesService) (less bool) {
|
||||
return a.ID < b.ID
|
||||
})
|
||||
for _, s := range hlSvcs.BlockedServices {
|
||||
slices.Sort(s.Rules)
|
||||
}
|
||||
|
||||
// Use another set of delimiters to prevent them interfering with the Go
|
||||
// code.
|
||||
tmpl, err := template.New("main").Delims("<%", "%>").Funcs(template.FuncMap{
|
||||
"isnotlast": func(idx, sliceLen int) (ok bool) { return idx != sliceLen-1 },
|
||||
}).Parse(tmplStr)
|
||||
check(err)
|
||||
|
||||
f, err := os.OpenFile(
|
||||
"./internal/filtering/servicelist.go",
|
||||
os.O_CREATE|os.O_TRUNC|os.O_WRONLY,
|
||||
0o644,
|
||||
)
|
||||
check(err)
|
||||
defer log.OnCloserError(f, log.ERROR)
|
||||
|
||||
err = tmpl.Execute(f, hlSvcs)
|
||||
check(err)
|
||||
}
|
||||
|
||||
// tmplStr is the template for the Go source file with the services.
|
||||
const tmplStr = `// Code generated by go run ./scripts/blocked-services/main.go; DO NOT EDIT.
|
||||
|
||||
package filtering
|
||||
|
||||
// blockedService represents a single blocked service.
|
||||
type blockedService struct {
|
||||
ID string ` + "`" + `json:"id"` + "`" + `
|
||||
Name string ` + "`" + `json:"name"` + "`" + `
|
||||
IconSVG []byte ` + "`" + `json:"icon_svg"` + "`" + `
|
||||
Rules []string ` + "`" + `json:"rules"` + "`" + `
|
||||
}
|
||||
|
||||
// blockedServices contains raw blocked service data.
|
||||
var blockedServices = []blockedService{<% $l := len .BlockedServices %>
|
||||
<%- range $i, $s := .BlockedServices %>{
|
||||
ID: <% printf "%q" $s.ID %>,
|
||||
Name: <% printf "%q" $s.Name %>,
|
||||
IconSVG: []byte(<% printf "%q" $s.IconSVG %>),
|
||||
Rules: []string{<% range $s.Rules %>
|
||||
<% printf "%q" . %>,<% end %>
|
||||
},
|
||||
}<% if isnotlast $i $l %>, <% end %><% end %>}
|
||||
`
|
||||
|
||||
// check is a simple error-checking helper for scripts.
|
||||
func check(err error) {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// hlServices is the JSON structure for the Hostlists Registry blocked service
|
||||
// index.
|
||||
type hlServices struct {
|
||||
BlockedServices []*hlServicesService `json:"blocked_services"`
|
||||
}
|
||||
|
||||
// hlServicesService is the JSON structure for a service in the Hostlists
|
||||
// Registry.
|
||||
type hlServicesService struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
IconSVG string `json:"icon_svg"`
|
||||
Rules []string `json:"rules"`
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
// vetted-filters fetches the most recent Hostlists Registry index and
|
||||
// transforms the filters from it to AdGuard Home's format.
|
||||
// vetted-filters fetches the most recent Hostlists Registry filtering rule list
|
||||
// index and transforms the filters from it to AdGuard Home's format.
|
||||
package main
|
||||
|
||||
import (
|
||||
|
@ -91,7 +91,7 @@ func main() {
|
|||
check(err)
|
||||
}
|
||||
|
||||
// jsHeader is the header for the generates JavaScript file. It informs the
|
||||
// jsHeader is the header for the generated JavaScript file. It informs the
|
||||
// reader that the file is generated and disables some style-related eslint
|
||||
// checks.
|
||||
const jsHeader = `// Code generated by go run ./scripts/vetted-filters/main.go; DO NOT EDIT.
|
||||
|
|
Loading…
Reference in New Issue