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
|
## 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
|
- The new optional `tls.override_tls_ciphers` property, which allows
|
||||||
overriding TLS ciphers used by AdGuard Home ([#4925], [#4990]).
|
overriding TLS ciphers used by AdGuard Home ([#4925], [#4990]).
|
||||||
- The ability to serve DNS on link-local IPv6 addresses ([#2926]).
|
- 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.
|
- 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
|
### Fixed
|
||||||
|
|
||||||
- ClientIDs not working when using DNS-over-HTTPS with HTTP/3.
|
- 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
|
[#2926]: https://github.com/AdguardTeam/AdGuardHome/issues/2926
|
||||||
[#3418]: https://github.com/AdguardTeam/AdGuardHome/issues/3418
|
[#3418]: https://github.com/AdguardTeam/AdGuardHome/issues/3418
|
||||||
|
[#3972]: https://github.com/AdguardTeam/AdGuardHome/issues/3972
|
||||||
[#4916]: https://github.com/AdguardTeam/AdGuardHome/issues/4916
|
[#4916]: https://github.com/AdguardTeam/AdGuardHome/issues/4916
|
||||||
[#4925]: https://github.com/AdguardTeam/AdGuardHome/issues/4925
|
[#4925]: https://github.com/AdguardTeam/AdGuardHome/issues/4925
|
||||||
[#4942]: https://github.com/AdguardTeam/AdGuardHome/issues/4942
|
[#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 setBlockedServicesRequest = createAction('SET_BLOCKED_SERVICES_REQUEST');
|
||||||
export const setBlockedServicesFailure = createAction('SET_BLOCKED_SERVICES_FAILURE');
|
export const setBlockedServicesFailure = createAction('SET_BLOCKED_SERVICES_FAILURE');
|
||||||
export const setBlockedServicesSuccess = createAction('SET_BLOCKED_SERVICES_SUCCESS');
|
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_SET = { path: 'blocked_services/set', method: 'POST' };
|
||||||
|
|
||||||
|
BLOCKED_SERVICES_ALL = { path: 'blocked_services/all', method: 'GET' };
|
||||||
|
|
||||||
getBlockedServicesAvailableServices() {
|
getBlockedServicesAvailableServices() {
|
||||||
const { path, method } = this.BLOCKED_SERVICES_SERVICES;
|
const { path, method } = this.BLOCKED_SERVICES_SERVICES;
|
||||||
return this.makeRequest(path, method);
|
return this.makeRequest(path, method);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getAllBlockedServices() {
|
||||||
|
const { path, method } = this.BLOCKED_SERVICES_ALL;
|
||||||
|
return this.makeRequest(path, method);
|
||||||
|
}
|
||||||
|
|
||||||
getBlockedServices() {
|
getBlockedServices() {
|
||||||
const { path, method } = this.BLOCKED_SERVICES_LIST;
|
const { path, method } = this.BLOCKED_SERVICES_LIST;
|
||||||
return this.makeRequest(path, method);
|
return this.makeRequest(path, method);
|
||||||
|
|
|
@ -6,10 +6,11 @@ import flow from 'lodash/flow';
|
||||||
|
|
||||||
import { toggleAllServices } from '../../../helpers/helpers';
|
import { toggleAllServices } from '../../../helpers/helpers';
|
||||||
import { renderServiceField } from '../../../helpers/form';
|
import { renderServiceField } from '../../../helpers/form';
|
||||||
import { FORM_NAME, SERVICES } from '../../../helpers/constants';
|
import { FORM_NAME } from '../../../helpers/constants';
|
||||||
|
|
||||||
const Form = (props) => {
|
const Form = (props) => {
|
||||||
const {
|
const {
|
||||||
|
blockedServices,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
change,
|
change,
|
||||||
pristine,
|
pristine,
|
||||||
|
@ -27,7 +28,7 @@ const Form = (props) => {
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-secondary btn-block"
|
className="btn btn-secondary btn-block"
|
||||||
disabled={processing || processingSet}
|
disabled={processing || processingSet}
|
||||||
onClick={() => toggleAllServices(SERVICES, change, true)}
|
onClick={() => toggleAllServices(blockedServices, change, true)}
|
||||||
>
|
>
|
||||||
<Trans>block_all</Trans>
|
<Trans>block_all</Trans>
|
||||||
</button>
|
</button>
|
||||||
|
@ -37,17 +38,17 @@ const Form = (props) => {
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-secondary btn-block"
|
className="btn btn-secondary btn-block"
|
||||||
disabled={processing || processingSet}
|
disabled={processing || processingSet}
|
||||||
onClick={() => toggleAllServices(SERVICES, change, false)}
|
onClick={() => toggleAllServices(blockedServices, change, false)}
|
||||||
>
|
>
|
||||||
<Trans>unblock_all</Trans>
|
<Trans>unblock_all</Trans>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="services">
|
<div className="services">
|
||||||
{SERVICES.map((service) => (
|
{blockedServices.map((service) => (
|
||||||
<Field
|
<Field
|
||||||
key={service.id}
|
key={service.id}
|
||||||
icon={`service_${service.id}`}
|
icon={service.icon_svg}
|
||||||
name={`blocked_services.${service.id}`}
|
name={`blocked_services.${service.id}`}
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
component={renderServiceField}
|
component={renderServiceField}
|
||||||
|
@ -72,6 +73,7 @@ const Form = (props) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
Form.propTypes = {
|
Form.propTypes = {
|
||||||
|
blockedServices: PropTypes.array.isRequired,
|
||||||
pristine: PropTypes.bool.isRequired,
|
pristine: PropTypes.bool.isRequired,
|
||||||
handleSubmit: PropTypes.func.isRequired,
|
handleSubmit: PropTypes.func.isRequired,
|
||||||
change: PropTypes.func.isRequired,
|
change: PropTypes.func.isRequired,
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { useTranslation } from 'react-i18next';
|
||||||
import { useDispatch, useSelector } from 'react-redux';
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import Form from './Form';
|
import Form from './Form';
|
||||||
import Card from '../../ui/Card';
|
import Card from '../../ui/Card';
|
||||||
import { getBlockedServices, setBlockedServices } from '../../../actions/services';
|
import { getBlockedServices, getAllBlockedServices, setBlockedServices } from '../../../actions/services';
|
||||||
import PageTitle from '../../ui/PageTitle';
|
import PageTitle from '../../ui/PageTitle';
|
||||||
|
|
||||||
const getInitialDataForServices = (initial) => (initial ? initial.reduce(
|
const getInitialDataForServices = (initial) => (initial ? initial.reduce(
|
||||||
|
@ -21,6 +21,7 @@ const Services = () => {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(getBlockedServices());
|
dispatch(getBlockedServices());
|
||||||
|
dispatch(getAllBlockedServices());
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleSubmit = (values) => {
|
const handleSubmit = (values) => {
|
||||||
|
@ -49,6 +50,7 @@ const Services = () => {
|
||||||
<div className="form">
|
<div className="form">
|
||||||
<Form
|
<Form
|
||||||
initialValues={initialValues}
|
initialValues={initialValues}
|
||||||
|
blockedServices={services.allServices}
|
||||||
processing={services.processing}
|
processing={services.processing}
|
||||||
processingSet={services.processingSet}
|
processingSet={services.processingSet}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
|
|
|
@ -27,6 +27,7 @@ const ResponseCell = ({
|
||||||
const filters = useSelector((state) => state.filtering.filters, shallowEqual);
|
const filters = useSelector((state) => state.filtering.filters, shallowEqual);
|
||||||
const whitelistFilters = useSelector((state) => state.filtering.whitelistFilters, shallowEqual);
|
const whitelistFilters = useSelector((state) => state.filtering.whitelistFilters, shallowEqual);
|
||||||
const isDetailed = useSelector((state) => state.queryLogs.isDetailed);
|
const isDetailed = useSelector((state) => state.queryLogs.isDetailed);
|
||||||
|
const services = useSelector((store) => store?.services);
|
||||||
|
|
||||||
const formattedElapsedMs = formatElapsedMs(elapsedMs, t);
|
const formattedElapsedMs = formatElapsedMs(elapsedMs, t);
|
||||||
|
|
||||||
|
@ -60,8 +61,8 @@ const ResponseCell = ({
|
||||||
install_settings_dns: upstreamString,
|
install_settings_dns: upstreamString,
|
||||||
elapsed: formattedElapsedMs,
|
elapsed: formattedElapsedMs,
|
||||||
response_code: status,
|
response_code: status,
|
||||||
...(service_name
|
...(service_name && services.allServices
|
||||||
&& { service_name: getServiceName(service_name) }
|
&& { service_name: getServiceName(services.allServices, service_name) }
|
||||||
),
|
),
|
||||||
...(rules.length > 0
|
...(rules.length > 0
|
||||||
&& { rule_label: getRulesToFilterList(rules, filters, whitelistFilters) }
|
&& { rule_label: getRulesToFilterList(rules, filters, whitelistFilters) }
|
||||||
|
@ -80,10 +81,10 @@ const ResponseCell = ({
|
||||||
const getDetailedInfo = (reason) => {
|
const getDetailedInfo = (reason) => {
|
||||||
switch (reason) {
|
switch (reason) {
|
||||||
case FILTERED_STATUS.FILTERED_BLOCKED_SERVICE:
|
case FILTERED_STATUS.FILTERED_BLOCKED_SERVICE:
|
||||||
if (!service_name) {
|
if (!service_name || !services.allServices) {
|
||||||
return formattedElapsedMs;
|
return formattedElapsedMs;
|
||||||
}
|
}
|
||||||
return getServiceName(service_name);
|
return getServiceName(services.allServices, service_name);
|
||||||
case FILTERED_STATUS.FILTERED_BLACK_LIST:
|
case FILTERED_STATUS.FILTERED_BLACK_LIST:
|
||||||
case FILTERED_STATUS.NOT_FILTERED_WHITE_LIST:
|
case FILTERED_STATUS.NOT_FILTERED_WHITE_LIST:
|
||||||
return getFilterNames(rules, filters, whitelistFilters).join(', ');
|
return getFilterNames(rules, filters, whitelistFilters).join(', ');
|
||||||
|
|
|
@ -52,6 +52,7 @@ const Row = memo(({
|
||||||
const autoClients = useSelector((state) => state.dashboard.autoClients, shallowEqual);
|
const autoClients = useSelector((state) => state.dashboard.autoClients, shallowEqual);
|
||||||
const processingSet = useSelector((state) => state.access.processingSet);
|
const processingSet = useSelector((state) => state.access.processingSet);
|
||||||
const allowedСlients = useSelector((state) => state.access.allowed_clients, shallowEqual);
|
const allowedСlients = useSelector((state) => state.access.allowed_clients, shallowEqual);
|
||||||
|
const services = useSelector((store) => store?.services);
|
||||||
|
|
||||||
const clients = useSelector((state) => state.dashboard.clients);
|
const clients = useSelector((state) => state.dashboard.clients);
|
||||||
|
|
||||||
|
@ -175,8 +176,8 @@ const Row = memo(({
|
||||||
date: formatDateTime(time, DEFAULT_SHORT_DATE_FORMAT_OPTIONS),
|
date: formatDateTime(time, DEFAULT_SHORT_DATE_FORMAT_OPTIONS),
|
||||||
encryption_status: isBlocked
|
encryption_status: isBlocked
|
||||||
? <div className="bg--danger">{requestStatus}</div> : requestStatus,
|
? <div className="bg--danger">{requestStatus}</div> : requestStatus,
|
||||||
...(FILTERED_STATUS.FILTERED_BLOCKED_SERVICE && service_name
|
...(FILTERED_STATUS.FILTERED_BLOCKED_SERVICE && service_name && services.allServices
|
||||||
&& { service_name: getServiceName(service_name) }),
|
&& { service_name: getServiceName(services.allServices, service_name) }),
|
||||||
domain,
|
domain,
|
||||||
type_table_header: type,
|
type_table_header: type,
|
||||||
protocol,
|
protocol,
|
||||||
|
|
|
@ -16,6 +16,7 @@ import { getFilteringStatus } from '../../actions/filtering';
|
||||||
import { getClients } from '../../actions';
|
import { getClients } from '../../actions';
|
||||||
import { getDnsConfig } from '../../actions/dnsConfig';
|
import { getDnsConfig } from '../../actions/dnsConfig';
|
||||||
import { getAccessList } from '../../actions/access';
|
import { getAccessList } from '../../actions/access';
|
||||||
|
import { getAllBlockedServices } from '../../actions/services';
|
||||||
import {
|
import {
|
||||||
getLogsConfig,
|
getLogsConfig,
|
||||||
resetFilteredLogs,
|
resetFilteredLogs,
|
||||||
|
@ -130,6 +131,7 @@ const Logs = () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
dispatch(getFilteringStatus());
|
dispatch(getFilteringStatus());
|
||||||
dispatch(getClients());
|
dispatch(getClients());
|
||||||
|
dispatch(getAllBlockedServices());
|
||||||
try {
|
try {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
dispatch(getLogsConfig()),
|
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 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 ReactTable from 'react-table';
|
||||||
|
|
||||||
import { MODAL_TYPE } from '../../../helpers/constants';
|
import { getAllBlockedServices } from '../../../../actions/services';
|
||||||
import { splitByNewLine, countClientsStatistics, sortIp } from '../../../helpers/helpers';
|
import {
|
||||||
import Card from '../../ui/Card';
|
splitByNewLine,
|
||||||
import Modal from './Modal';
|
countClientsStatistics,
|
||||||
import CellWrap from '../../ui/CellWrap';
|
sortIp,
|
||||||
import LogsSearchLink from '../../ui/LogsSearchLink';
|
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 {
|
const ClientsTable = ({
|
||||||
handleFormAdd = (values) => {
|
clients,
|
||||||
this.props.addClient(values);
|
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) => {
|
const handleFormUpdate = (values, name) => {
|
||||||
this.props.updateClient(values, name);
|
updateClient(values, name);
|
||||||
};
|
};
|
||||||
|
|
||||||
handleSubmit = (values) => {
|
const handleSubmit = (values) => {
|
||||||
const config = values;
|
const config = values;
|
||||||
|
|
||||||
if (values) {
|
if (values) {
|
||||||
|
@ -42,21 +74,21 @@ class ClientsTable extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.props.modalType === MODAL_TYPE.EDIT_FILTERS) {
|
if (modalType === MODAL_TYPE.EDIT_FILTERS) {
|
||||||
this.handleFormUpdate(config, this.props.modalClientName);
|
handleFormUpdate(config, modalClientName);
|
||||||
} else {
|
} else {
|
||||||
this.handleFormAdd(config);
|
handleFormAdd(config);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
getOptionsWithLabels = (options) => (
|
const getOptionsWithLabels = (options) => (
|
||||||
options.map((option) => ({
|
options.map((option) => ({
|
||||||
value: option,
|
value: option,
|
||||||
label: option,
|
label: option,
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|
||||||
getClient = (name, clients) => {
|
const getClient = (name, clients) => {
|
||||||
const client = clients.find((item) => name === item.name);
|
const client = clients.find((item) => name === item.name);
|
||||||
|
|
||||||
if (client) {
|
if (client) {
|
||||||
|
@ -65,7 +97,7 @@ class ClientsTable extends Component {
|
||||||
} = client;
|
} = client;
|
||||||
return {
|
return {
|
||||||
upstreams: (upstreams && upstreams.join('\n')) || '',
|
upstreams: (upstreams && upstreams.join('\n')) || '',
|
||||||
tags: (tags && this.getOptionsWithLabels(tags)) || [],
|
tags: (tags && getOptionsWithLabels(tags)) || [],
|
||||||
...values,
|
...values,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -78,17 +110,17 @@ class ClientsTable extends Component {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
handleDelete = (data) => {
|
const handleDelete = (data) => {
|
||||||
// eslint-disable-next-line no-alert
|
// eslint-disable-next-line no-alert
|
||||||
if (window.confirm(this.props.t('client_confirm_delete', { key: data.name }))) {
|
if (window.confirm(t('client_confirm_delete', { key: data.name }))) {
|
||||||
this.props.deleteClient(data);
|
deleteClient(data);
|
||||||
this.props.getStats();
|
getStats();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
columns = [
|
const columns = [
|
||||||
{
|
{
|
||||||
Header: this.props.t('table_client'),
|
Header: t('table_client'),
|
||||||
accessor: 'ids',
|
accessor: 'ids',
|
||||||
minWidth: 150,
|
minWidth: 150,
|
||||||
Cell: (row) => {
|
Cell: (row) => {
|
||||||
|
@ -109,13 +141,13 @@ class ClientsTable extends Component {
|
||||||
sortMethod: sortIp,
|
sortMethod: sortIp,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: this.props.t('table_name'),
|
Header: t('table_name'),
|
||||||
accessor: 'name',
|
accessor: 'name',
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
Cell: CellWrap,
|
Cell: CellWrap,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: this.props.t('settings'),
|
Header: t('settings'),
|
||||||
accessor: 'use_global_settings',
|
accessor: 'use_global_settings',
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
Cell: ({ value }) => {
|
Cell: ({ value }) => {
|
||||||
|
@ -133,7 +165,7 @@ class ClientsTable extends Component {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: this.props.t('blocked_services'),
|
Header: t('blocked_services'),
|
||||||
accessor: 'blocked_services',
|
accessor: 'blocked_services',
|
||||||
minWidth: 180,
|
minWidth: 180,
|
||||||
Cell: (row) => {
|
Cell: (row) => {
|
||||||
|
@ -143,25 +175,40 @@ class ClientsTable extends Component {
|
||||||
return <Trans>settings_global</Trans>;
|
return <Trans>settings_global</Trans>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (value && services.allServices) {
|
||||||
return (
|
return (
|
||||||
<div className="logs__row logs__row--icons">
|
<div className="logs__row logs__row--icons">
|
||||||
{value && value.length > 0
|
{value.map((service) => {
|
||||||
? value.map((service) => (
|
const serviceInfo = getService(services.allServices, service);
|
||||||
<svg
|
|
||||||
|
if (serviceInfo?.icon_svg) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={serviceInfo.name}
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: window.atob(serviceInfo.icon_svg),
|
||||||
|
}}
|
||||||
className="service__icon service__icon--table"
|
className="service__icon service__icon--table"
|
||||||
title={service}
|
title={serviceInfo.name}
|
||||||
key={service}
|
/>
|
||||||
>
|
);
|
||||||
<use xlinkHref={`#service_${service}`} />
|
}
|
||||||
</svg>
|
|
||||||
))
|
return null;
|
||||||
: '–'}
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="logs__row logs__row--icons">
|
||||||
|
–
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: this.props.t('upstreams'),
|
Header: t('upstreams'),
|
||||||
accessor: 'upstreams',
|
accessor: 'upstreams',
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
Cell: ({ value }) => {
|
Cell: ({ value }) => {
|
||||||
|
@ -179,7 +226,7 @@ class ClientsTable extends Component {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: this.props.t('tags_title'),
|
Header: t('tags_title'),
|
||||||
accessor: 'tags',
|
accessor: 'tags',
|
||||||
minWidth: 140,
|
minWidth: 140,
|
||||||
Cell: (row) => {
|
Cell: (row) => {
|
||||||
|
@ -203,11 +250,11 @@ class ClientsTable extends Component {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: this.props.t('requests_count'),
|
Header: t('requests_count'),
|
||||||
id: 'statistics',
|
id: 'statistics',
|
||||||
accessor: (row) => countClientsStatistics(
|
accessor: (row) => countClientsStatistics(
|
||||||
row.ids,
|
row.ids,
|
||||||
this.props.normalizedTopClients.auto,
|
normalizedTopClients.auto,
|
||||||
),
|
),
|
||||||
sortMethod: (a, b) => b - a,
|
sortMethod: (a, b) => b - a,
|
||||||
minWidth: 120,
|
minWidth: 120,
|
||||||
|
@ -222,16 +269,13 @@ class ClientsTable extends Component {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: this.props.t('actions_table_header'),
|
Header: t('actions_table_header'),
|
||||||
accessor: 'actions',
|
accessor: 'actions',
|
||||||
maxWidth: 100,
|
maxWidth: 100,
|
||||||
sortable: false,
|
sortable: false,
|
||||||
resizable: false,
|
resizable: false,
|
||||||
Cell: (row) => {
|
Cell: (row) => {
|
||||||
const clientName = row.original.name;
|
const clientName = row.original.name;
|
||||||
const {
|
|
||||||
toggleClientModal, processingDeleting, processingUpdating, t,
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="logs__row logs__row--center">
|
<div className="logs__row logs__row--center">
|
||||||
|
@ -253,7 +297,7 @@ class ClientsTable extends Component {
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-icon btn-outline-secondary btn-sm"
|
className="btn btn-icon btn-outline-secondary btn-sm"
|
||||||
onClick={() => this.handleDelete({ name: clientName })}
|
onClick={() => handleDelete({ name: clientName })}
|
||||||
disabled={processingDeleting}
|
disabled={processingDeleting}
|
||||||
title={t('delete_table_action')}
|
title={t('delete_table_action')}
|
||||||
>
|
>
|
||||||
|
@ -267,21 +311,8 @@ class ClientsTable extends Component {
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
render() {
|
const currentClientData = getClient(modalClientName, clients);
|
||||||
const {
|
const tagsOptions = getOptionsWithLabels(supportedTags);
|
||||||
t,
|
|
||||||
clients,
|
|
||||||
isModalOpen,
|
|
||||||
modalType,
|
|
||||||
modalClientName,
|
|
||||||
toggleClientModal,
|
|
||||||
processingAdding,
|
|
||||||
processingUpdating,
|
|
||||||
supportedTags,
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const currentClientData = this.getClient(modalClientName, clients);
|
|
||||||
const tagsOptions = this.getOptionsWithLabels(supportedTags);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
|
@ -289,10 +320,10 @@ class ClientsTable extends Component {
|
||||||
subtitle={t('clients_desc')}
|
subtitle={t('clients_desc')}
|
||||||
bodyType="card-body box-body--settings"
|
bodyType="card-body box-body--settings"
|
||||||
>
|
>
|
||||||
<Fragment>
|
<>
|
||||||
<ReactTable
|
<ReactTable
|
||||||
data={clients || []}
|
data={clients || []}
|
||||||
columns={this.columns}
|
columns={columns}
|
||||||
defaultSorted={[
|
defaultSorted={[
|
||||||
{
|
{
|
||||||
id: 'statistics',
|
id: 'statistics',
|
||||||
|
@ -324,19 +355,17 @@ class ClientsTable extends Component {
|
||||||
modalType={modalType}
|
modalType={modalType}
|
||||||
toggleClientModal={toggleClientModal}
|
toggleClientModal={toggleClientModal}
|
||||||
currentClientData={currentClientData}
|
currentClientData={currentClientData}
|
||||||
handleSubmit={this.handleSubmit}
|
handleSubmit={handleSubmit}
|
||||||
processingAdding={processingAdding}
|
processingAdding={processingAdding}
|
||||||
processingUpdating={processingUpdating}
|
processingUpdating={processingUpdating}
|
||||||
tagsOptions={tagsOptions}
|
tagsOptions={tagsOptions}
|
||||||
/>
|
/>
|
||||||
</Fragment>
|
</>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
|
||||||
ClientsTable.propTypes = {
|
ClientsTable.propTypes = {
|
||||||
t: PropTypes.func.isRequired,
|
|
||||||
clients: PropTypes.array.isRequired,
|
clients: PropTypes.array.isRequired,
|
||||||
normalizedTopClients: PropTypes.object.isRequired,
|
normalizedTopClients: PropTypes.object.isRequired,
|
||||||
toggleClientModal: PropTypes.func.isRequired,
|
toggleClientModal: PropTypes.func.isRequired,
|
||||||
|
@ -353,4 +382,4 @@ ClientsTable.propTypes = {
|
||||||
supportedTags: PropTypes.array.isRequired,
|
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 React, { useState } from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect, useSelector } from 'react-redux';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import {
|
import {
|
||||||
Field, FieldArray, reduxForm, formValueSelector,
|
Field, FieldArray, reduxForm, formValueSelector,
|
||||||
|
@ -19,7 +19,7 @@ import {
|
||||||
renderServiceField,
|
renderServiceField,
|
||||||
} from '../../../helpers/form';
|
} from '../../../helpers/form';
|
||||||
import { validateClientId, validateRequiredValue } from '../../../helpers/validators';
|
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';
|
import './Service.css';
|
||||||
|
|
||||||
const settingsCheckboxes = [
|
const settingsCheckboxes = [
|
||||||
|
@ -139,6 +139,7 @@ let Form = (props) => {
|
||||||
invalid,
|
invalid,
|
||||||
tagsOptions,
|
tagsOptions,
|
||||||
} = props;
|
} = props;
|
||||||
|
const services = useSelector((store) => store?.services);
|
||||||
|
|
||||||
const [activeTabLabel, setActiveTabLabel] = useState('settings');
|
const [activeTabLabel, setActiveTabLabel] = useState('settings');
|
||||||
|
|
||||||
|
@ -180,7 +181,9 @@ let Form = (props) => {
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-secondary btn-block"
|
className="btn btn-secondary btn-block"
|
||||||
disabled={useGlobalServices}
|
disabled={useGlobalServices}
|
||||||
onClick={() => toggleAllServices(SERVICES, change, true)}
|
onClick={() => (
|
||||||
|
toggleAllServices(services.allServices, change, true)
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<Trans>block_all</Trans>
|
<Trans>block_all</Trans>
|
||||||
</button>
|
</button>
|
||||||
|
@ -190,17 +193,20 @@ let Form = (props) => {
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-secondary btn-block"
|
className="btn btn-secondary btn-block"
|
||||||
disabled={useGlobalServices}
|
disabled={useGlobalServices}
|
||||||
onClick={() => toggleAllServices(SERVICES, change, false)}
|
onClick={() => (
|
||||||
|
toggleAllServices(services.allServices, change, false)
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<Trans>unblock_all</Trans>
|
<Trans>unblock_all</Trans>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{services.allServices.length > 0 && (
|
||||||
<div className="services">
|
<div className="services">
|
||||||
{SERVICES.map((service) => (
|
{services.allServices.map((service) => (
|
||||||
<Field
|
<Field
|
||||||
key={service.id}
|
key={service.id}
|
||||||
icon={`service_${service.id}`}
|
icon={service.icon_svg}
|
||||||
name={`blocked_services.${service.id}`}
|
name={`blocked_services.${service.id}`}
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
component={renderServiceField}
|
component={renderServiceField}
|
||||||
|
@ -209,6 +215,7 @@ let Form = (props) => {
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>,
|
</div>,
|
||||||
},
|
},
|
||||||
|
|
|
@ -9,6 +9,12 @@
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.service__text {
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 768px) {
|
@media screen and (min-width: 768px) {
|
||||||
.services {
|
.services {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
@ -2,7 +2,7 @@ import React, { Component, Fragment } from 'react';
|
||||||
import { withTranslation } from 'react-i18next';
|
import { withTranslation } from 'react-i18next';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import ClientsTable from './ClientsTable';
|
import { ClientsTable } from './ClientsTable';
|
||||||
import AutoClients from './AutoClients';
|
import AutoClients from './AutoClients';
|
||||||
import PageTitle from '../../ui/PageTitle';
|
import PageTitle from '../../ui/PageTitle';
|
||||||
import Loading from '../../ui/Loading';
|
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 {
|
.custom-switch-indicator {
|
||||||
|
flex-shrink: 0;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
height: 1.25rem;
|
height: 1.25rem;
|
||||||
width: 2.25rem;
|
width: 2.25rem;
|
||||||
|
|
|
@ -202,158 +202,6 @@ export const FILTERS_URLS = {
|
||||||
blocked_services: '/blocked_services',
|
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 = {
|
export const ENCRYPTION_SOURCE = {
|
||||||
PATH: 'path',
|
PATH: 'path',
|
||||||
CONTENT: 'content',
|
CONTENT: 'content',
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import React, { Fragment } from 'react';
|
import React, { Fragment } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Trans } from 'react-i18next';
|
import { Trans } from 'react-i18next';
|
||||||
|
import cn from 'classnames';
|
||||||
|
|
||||||
import { createOnBlurHandler } from './helpers';
|
import { createOnBlurHandler } from './helpers';
|
||||||
import { R_MAC_WITHOUT_COLON, R_UNIX_ABSOLUTE_PATH, R_WIN_ABSOLUTE_PATH } from './constants';
|
import { R_MAC_WITHOUT_COLON, R_UNIX_ABSOLUTE_PATH, R_WIN_ABSOLUTE_PATH } from './constants';
|
||||||
|
|
||||||
|
@ -229,8 +231,9 @@ export const renderServiceField = ({
|
||||||
modifier,
|
modifier,
|
||||||
icon,
|
icon,
|
||||||
meta: { touched, error },
|
meta: { touched, error },
|
||||||
}) => <Fragment>
|
}) => (
|
||||||
<label className={`service custom-switch ${modifier}`}>
|
<>
|
||||||
|
<label className={cn('service custom-switch', { [modifier]: modifier })}>
|
||||||
<input
|
<input
|
||||||
{...input}
|
{...input}
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
|
@ -239,14 +242,23 @@ export const renderServiceField = ({
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
/>
|
/>
|
||||||
<span className="service__switch custom-switch-indicator"></span>
|
<span className="service__switch custom-switch-indicator"></span>
|
||||||
<span className="service__text">{placeholder}</span>
|
<span className="service__text" title={placeholder}>
|
||||||
<svg className="service__icon">
|
{placeholder}
|
||||||
<use xlinkHref={`#${icon}`} />
|
</span>
|
||||||
</svg>
|
{icon && (
|
||||||
|
<div
|
||||||
|
dangerouslySetInnerHTML={{ __html: window.atob(icon) }}
|
||||||
|
className="service__icon"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</label>
|
</label>
|
||||||
{!disabled && touched && error
|
{!disabled && touched && error && (
|
||||||
&& <span className="form__message form__message--error"><Trans>{error}</Trans></span>}
|
<span className="form__message form__message--error">
|
||||||
</Fragment>;
|
<Trans>{error}</Trans>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
renderServiceField.propTypes = {
|
renderServiceField.propTypes = {
|
||||||
input: PropTypes.object.isRequired,
|
input: PropTypes.object.isRequired,
|
||||||
|
|
|
@ -21,7 +21,6 @@ import {
|
||||||
FILTERED,
|
FILTERED,
|
||||||
FILTERED_STATUS,
|
FILTERED_STATUS,
|
||||||
R_CLIENT_ID,
|
R_CLIENT_ID,
|
||||||
SERVICES_ID_NAME_MAP,
|
|
||||||
STANDARD_DNS_PORT,
|
STANDARD_DNS_PORT,
|
||||||
STANDARD_HTTPS_PORT,
|
STANDARD_HTTPS_PORT,
|
||||||
STANDARD_WEB_PORT,
|
STANDARD_WEB_PORT,
|
||||||
|
@ -991,7 +990,22 @@ export const filterOutComments = (lines) => lines
|
||||||
.filter((line) => !line.startsWith(COMMENT_LINE_DEFAULT_TOKEN));
|
.filter((line) => !line.startsWith(COMMENT_LINE_DEFAULT_TOKEN));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} serviceId
|
* @param {array} services
|
||||||
|
* @param {string} id
|
||||||
* @returns {string}
|
* @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,
|
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.setBlockedServicesRequest]: (state) => ({ ...state, processingSet: true }),
|
||||||
[actions.setBlockedServicesFailure]: (state) => ({ ...state, processingSet: false }),
|
[actions.setBlockedServicesFailure]: (state) => ({ ...state, processingSet: false }),
|
||||||
[actions.setBlockedServicesSuccess]: (state) => ({
|
[actions.setBlockedServicesSuccess]: (state) => ({
|
||||||
|
@ -21,8 +29,10 @@ const services = handleActions(
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
processing: true,
|
processing: true,
|
||||||
|
processingAll: true,
|
||||||
processingSet: false,
|
processingSet: false,
|
||||||
list: [],
|
list: [],
|
||||||
|
allServices: [],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -10,382 +10,6 @@ import (
|
||||||
"golang.org/x/exp/slices"
|
"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.
|
// serviceRules maps a service ID to its filtering rules.
|
||||||
var serviceRules map[string][]*rules.NetworkRule
|
var serviceRules map[string][]*rules.NetworkRule
|
||||||
|
|
||||||
|
@ -394,16 +18,16 @@ var serviceIDs []string
|
||||||
|
|
||||||
// initBlockedServices initializes package-level blocked service data.
|
// initBlockedServices initializes package-level blocked service data.
|
||||||
func initBlockedServices() {
|
func initBlockedServices() {
|
||||||
l := len(servicesData)
|
l := len(blockedServices)
|
||||||
serviceIDs = make([]string, l)
|
serviceIDs = make([]string, l)
|
||||||
serviceRules = make(map[string][]*rules.NetworkRule, l)
|
serviceRules = make(map[string][]*rules.NetworkRule, l)
|
||||||
|
|
||||||
for i, s := range servicesData {
|
for i, s := range blockedServices {
|
||||||
netRules := make([]*rules.NetworkRule, 0, len(s.rules))
|
netRules := make([]*rules.NetworkRule, 0, len(s.Rules))
|
||||||
for _, text := range s.rules {
|
for _, text := range s.Rules {
|
||||||
rule, err := rules.NewNetworkRule(text, BlockedSvcsListID)
|
rule, err := rules.NewNetworkRule(text, BlockedSvcsListID)
|
||||||
if err != nil {
|
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
|
continue
|
||||||
}
|
}
|
||||||
|
@ -411,8 +35,8 @@ func initBlockedServices() {
|
||||||
netRules = append(netRules, rule)
|
netRules = append(netRules, rule)
|
||||||
}
|
}
|
||||||
|
|
||||||
serviceIDs[i] = s.name
|
serviceIDs[i] = s.ID
|
||||||
serviceRules[s.name] = netRules
|
serviceRules[s.ID] = netRules
|
||||||
}
|
}
|
||||||
|
|
||||||
slices.Sort(serviceIDs)
|
slices.Sort(serviceIDs)
|
||||||
|
@ -420,7 +44,7 @@ func initBlockedServices() {
|
||||||
log.Debug("filtering: initialized %d services", l)
|
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) {
|
func BlockedSvcKnown(s string) (ok bool) {
|
||||||
_, ok = serviceRules[s]
|
_, 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)
|
_ = 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) {
|
func (d *DNSFilter) handleBlockedServicesList(w http.ResponseWriter, r *http.Request) {
|
||||||
d.confLock.RLock()
|
d.confLock.RLock()
|
||||||
list := d.Config.BlockedServices
|
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))
|
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/add", d.handleRewriteAdd)
|
||||||
registerHTTP(http.MethodPost, "/control/rewrite/delete", d.handleRewriteDelete)
|
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.MethodGet, "/control/blocked_services/list", d.handleBlockedServicesList)
|
||||||
registerHTTP(http.MethodPost, "/control/blocked_services/set", d.handleBlockedServicesSet)
|
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
|
## v0.107.15: `POST` Requests Without Bodies
|
||||||
|
|
||||||
As an additional CSRF protection measure, AdGuard Home now ensures that requests
|
As an additional CSRF protection measure, AdGuard Home now ensures that requests
|
||||||
|
|
|
@ -862,6 +862,9 @@
|
||||||
- 'clients'
|
- 'clients'
|
||||||
'/blocked_services/services':
|
'/blocked_services/services':
|
||||||
'get':
|
'get':
|
||||||
|
'deprecated': true
|
||||||
|
'description': >
|
||||||
|
Deprecated: Use `GET /blocked_services/all` instead.
|
||||||
'tags':
|
'tags':
|
||||||
- 'blocked_services'
|
- 'blocked_services'
|
||||||
'operationId': 'blockedServicesAvailableServices'
|
'operationId': 'blockedServicesAvailableServices'
|
||||||
|
@ -873,6 +876,19 @@
|
||||||
'application/json':
|
'application/json':
|
||||||
'schema':
|
'schema':
|
||||||
'$ref': '#/components/schemas/BlockedServicesArray'
|
'$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':
|
'/blocked_services/list':
|
||||||
'get':
|
'get':
|
||||||
'tags':
|
'tags':
|
||||||
|
@ -2539,6 +2555,42 @@
|
||||||
'type': 'array'
|
'type': 'array'
|
||||||
'items':
|
'items':
|
||||||
'type': 'string'
|
'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':
|
'CheckConfigRequestBeta':
|
||||||
'type': 'object'
|
'type': 'object'
|
||||||
'description': 'Configuration to be checked'
|
'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
|
## `vetted-filters/`: Vetted Filters Updater
|
||||||
|
|
||||||
A simple script that downloads and updates the vetted filtering list data from
|
Similar to the one above, a script that downloads and updates the vetted
|
||||||
AdGuard's [Hostlists Registry][reg].
|
filtering list data from AdGuard's [Hostlists Registry][reg].
|
||||||
|
|
||||||
Optional environment:
|
Optional environment:
|
||||||
|
|
||||||
|
@ -223,5 +243,3 @@ Optional environment:
|
||||||
```sh
|
```sh
|
||||||
go run ./scripts/vetted-filters/main.go
|
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
|
// vetted-filters fetches the most recent Hostlists Registry filtering rule list
|
||||||
// transforms the filters from it to AdGuard Home's format.
|
// index and transforms the filters from it to AdGuard Home's format.
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -91,7 +91,7 @@ func main() {
|
||||||
check(err)
|
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
|
// reader that the file is generated and disables some style-related eslint
|
||||||
// checks.
|
// checks.
|
||||||
const jsHeader = `// Code generated by go run ./scripts/vetted-filters/main.go; DO NOT EDIT.
|
const jsHeader = `// Code generated by go run ./scripts/vetted-filters/main.go; DO NOT EDIT.
|
||||||
|
|
Loading…
Reference in New Issue