Merge branch 'master' of ssh://bit.adguard.com:7999/dns/adguard-home
This commit is contained in:
commit
4df02714fd
|
@ -0,0 +1,12 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -e;
|
||||||
|
git diff --cached --name-only | grep -q '.js$' && make lint-js;
|
||||||
|
|
||||||
|
found=0
|
||||||
|
git diff --cached --name-only | grep -q '.go$' && found=1
|
||||||
|
if [ $found == 1 ]; then
|
||||||
|
make lint-go || exit 1
|
||||||
|
go test ./... || exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0;
|
|
@ -344,10 +344,14 @@ Response:
|
||||||
|
|
||||||
If `can_autoupdate` is true, then the server can automatically upgrade to a new version.
|
If `can_autoupdate` is true, then the server can automatically upgrade to a new version.
|
||||||
|
|
||||||
Response with empty body:
|
Response when auto-update is disabled by command-line argument:
|
||||||
|
|
||||||
200 OK
|
200 OK
|
||||||
|
|
||||||
|
{
|
||||||
|
"disabled":true
|
||||||
|
}
|
||||||
|
|
||||||
It means that update check is disabled by user. UI should do nothing.
|
It means that update check is disabled by user. UI should do nothing.
|
||||||
|
|
||||||
|
|
||||||
|
|
18
Makefile
18
Makefile
|
@ -88,9 +88,12 @@ ifndef DOCKER_IMAGE_NAME
|
||||||
$(error DOCKER_IMAGE_NAME value is not set)
|
$(error DOCKER_IMAGE_NAME value is not set)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
.PHONY: all build client client-watch docker lint test dependencies clean release docker-multi-arch
|
.PHONY: all build client client-watch docker lint lint-js lint-go test dependencies clean release docker-multi-arch
|
||||||
all: build
|
all: build
|
||||||
|
|
||||||
|
init:
|
||||||
|
git config core.hooksPath .githooks
|
||||||
|
|
||||||
build: dependencies client
|
build: dependencies client
|
||||||
PATH=$(GOPATH)/bin:$(PATH) go generate ./...
|
PATH=$(GOPATH)/bin:$(PATH) go generate ./...
|
||||||
CGO_ENABLED=0 go build -ldflags="-s -w -X main.version=$(VERSION) -X main.channel=$(CHANNEL) -X main.goarm=$(GOARM)"
|
CGO_ENABLED=0 go build -ldflags="-s -w -X main.version=$(VERSION) -X main.channel=$(CHANNEL) -X main.goarm=$(GOARM)"
|
||||||
|
@ -116,11 +119,16 @@ docker:
|
||||||
@echo Now you can run the docker image:
|
@echo Now you can run the docker image:
|
||||||
@echo docker run --name "adguard-home" -p 53:53/tcp -p 53:53/udp -p 80:80/tcp -p 443:443/tcp -p 853:853/tcp -p 3000:3000/tcp $(DOCKER_IMAGE_NAME)
|
@echo docker run --name "adguard-home" -p 53:53/tcp -p 53:53/udp -p 80:80/tcp -p 443:443/tcp -p 853:853/tcp -p 3000:3000/tcp $(DOCKER_IMAGE_NAME)
|
||||||
|
|
||||||
lint:
|
lint: lint-js lint-go
|
||||||
@echo Running linters
|
|
||||||
golangci-lint run ./...
|
lint-js:
|
||||||
|
@echo Running js linter
|
||||||
npm --prefix client run lint
|
npm --prefix client run lint
|
||||||
|
|
||||||
|
lint-go:
|
||||||
|
@echo Running go linter
|
||||||
|
golangci-lint run
|
||||||
|
|
||||||
test:
|
test:
|
||||||
@echo Running unit-tests
|
@echo Running unit-tests
|
||||||
go test -race -v -bench=. -coverprofile=coverage.txt -covermode=atomic ./...
|
go test -race -v -bench=. -coverprofile=coverage.txt -covermode=atomic ./...
|
||||||
|
@ -223,4 +231,4 @@ define write_version_file
|
||||||
|
|
||||||
# Finish
|
# Finish
|
||||||
echo "}" >> $(DIST_DIR)/version.json
|
echo "}" >> $(DIST_DIR)/version.json
|
||||||
endef
|
endef
|
||||||
|
|
|
@ -150,11 +150,14 @@ Is there a chance to handle this in the future? DNS will never be enough to do t
|
||||||
|
|
||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
|
Run `make init` to prepare the development environment.
|
||||||
|
|
||||||
You will need this to build AdGuard Home:
|
You will need this to build AdGuard Home:
|
||||||
|
|
||||||
* [go](https://golang.org/dl/) v1.14 or later.
|
* [go](https://golang.org/dl/) v1.14 or later.
|
||||||
* [node.js](https://nodejs.org/en/download/) v10 or later.
|
* [node.js](https://nodejs.org/en/download/) v10 or later.
|
||||||
|
* [golangci-lint](https://github.com/golangci/golangci-lint)
|
||||||
|
|
||||||
### Building
|
### Building
|
||||||
|
|
||||||
Open Terminal and execute these commands:
|
Open Terminal and execute these commands:
|
||||||
|
|
|
@ -2,9 +2,9 @@ import { createAction } from 'redux-actions';
|
||||||
import i18next from 'i18next';
|
import i18next from 'i18next';
|
||||||
|
|
||||||
import apiClient from '../api/Api';
|
import apiClient from '../api/Api';
|
||||||
import { normalizeTextarea } from '../helpers/helpers';
|
|
||||||
import { addErrorToast, addSuccessToast } from './toasts';
|
import { addErrorToast, addSuccessToast } from './toasts';
|
||||||
import { BLOCK_ACTIONS } from '../helpers/constants';
|
import { BLOCK_ACTIONS } from '../helpers/constants';
|
||||||
|
import { splitByNewLine } from '../helpers/helpers';
|
||||||
|
|
||||||
export const getAccessListRequest = createAction('GET_ACCESS_LIST_REQUEST');
|
export const getAccessListRequest = createAction('GET_ACCESS_LIST_REQUEST');
|
||||||
export const getAccessListFailure = createAction('GET_ACCESS_LIST_FAILURE');
|
export const getAccessListFailure = createAction('GET_ACCESS_LIST_FAILURE');
|
||||||
|
@ -31,9 +31,9 @@ export const setAccessList = (config) => async (dispatch) => {
|
||||||
const { allowed_clients, disallowed_clients, blocked_hosts } = config;
|
const { allowed_clients, disallowed_clients, blocked_hosts } = config;
|
||||||
|
|
||||||
const values = {
|
const values = {
|
||||||
allowed_clients: normalizeTextarea(allowed_clients),
|
allowed_clients: splitByNewLine(allowed_clients),
|
||||||
disallowed_clients: normalizeTextarea(disallowed_clients),
|
disallowed_clients: splitByNewLine(disallowed_clients),
|
||||||
blocked_hosts: normalizeTextarea(blocked_hosts),
|
blocked_hosts: splitByNewLine(blocked_hosts),
|
||||||
};
|
};
|
||||||
|
|
||||||
await apiClient.setAccessList(values);
|
await apiClient.setAccessList(values);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { createAction } from 'redux-actions';
|
import { createAction } from 'redux-actions';
|
||||||
|
|
||||||
import apiClient from '../api/Api';
|
import apiClient from '../api/Api';
|
||||||
import { normalizeTextarea } from '../helpers/helpers';
|
import { splitByNewLine } from '../helpers/helpers';
|
||||||
import { addErrorToast, addSuccessToast } from './toasts';
|
import { addErrorToast, addSuccessToast } from './toasts';
|
||||||
|
|
||||||
export const getDnsConfigRequest = createAction('GET_DNS_CONFIG_REQUEST');
|
export const getDnsConfigRequest = createAction('GET_DNS_CONFIG_REQUEST');
|
||||||
|
@ -30,11 +30,11 @@ export const setDnsConfig = (config) => async (dispatch) => {
|
||||||
|
|
||||||
let hasDnsSettings = false;
|
let hasDnsSettings = false;
|
||||||
if (Object.prototype.hasOwnProperty.call(data, 'bootstrap_dns')) {
|
if (Object.prototype.hasOwnProperty.call(data, 'bootstrap_dns')) {
|
||||||
data.bootstrap_dns = normalizeTextarea(config.bootstrap_dns);
|
data.bootstrap_dns = splitByNewLine(config.bootstrap_dns);
|
||||||
hasDnsSettings = true;
|
hasDnsSettings = true;
|
||||||
}
|
}
|
||||||
if (Object.prototype.hasOwnProperty.call(data, 'upstream_dns')) {
|
if (Object.prototype.hasOwnProperty.call(data, 'upstream_dns')) {
|
||||||
data.upstream_dns = normalizeTextarea(config.upstream_dns);
|
data.upstream_dns = splitByNewLine(config.upstream_dns);
|
||||||
hasDnsSettings = true;
|
hasDnsSettings = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { createAction } from 'redux-actions';
|
||||||
import i18next from 'i18next';
|
import i18next from 'i18next';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
import { isVersionGreater, normalizeTextarea, sortClients } from '../helpers/helpers';
|
import { isVersionGreater, splitByNewLine, sortClients } from '../helpers/helpers';
|
||||||
import { CHECK_TIMEOUT, SETTINGS_NAMES } from '../helpers/constants';
|
import { CHECK_TIMEOUT, SETTINGS_NAMES } from '../helpers/constants';
|
||||||
import { getTlsStatus } from './encryption';
|
import { getTlsStatus } from './encryption';
|
||||||
import apiClient from '../api/Api';
|
import apiClient from '../api/Api';
|
||||||
|
@ -279,8 +279,8 @@ export const testUpstream = (config) => async (dispatch) => {
|
||||||
dispatch(testUpstreamRequest());
|
dispatch(testUpstreamRequest());
|
||||||
try {
|
try {
|
||||||
const values = { ...config };
|
const values = { ...config };
|
||||||
values.bootstrap_dns = normalizeTextarea(values.bootstrap_dns);
|
values.bootstrap_dns = splitByNewLine(values.bootstrap_dns);
|
||||||
values.upstream_dns = normalizeTextarea(values.upstream_dns);
|
values.upstream_dns = splitByNewLine(values.upstream_dns);
|
||||||
|
|
||||||
const upstreamResponse = await apiClient.testUpstream(values);
|
const upstreamResponse = await apiClient.testUpstream(values);
|
||||||
const testMessages = Object.keys(upstreamResponse)
|
const testMessages = Object.keys(upstreamResponse)
|
||||||
|
|
|
@ -62,7 +62,7 @@ const clientCell = (t, toggleClientStatus, processing, disallowedClients) => fun
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="logs__row logs__row--overflow logs__row--column">
|
<div className="logs__row logs__row--overflow logs__row--column">
|
||||||
{formatClientCell(row, t)}
|
{formatClientCell(row, true)}
|
||||||
</div>
|
</div>
|
||||||
{ipMatchListStatus !== IP_MATCH_LIST_STATUS.CIDR
|
{ipMatchListStatus !== IP_MATCH_LIST_STATUS.CIDR
|
||||||
&& renderBlockingButton(ipMatchListStatus, value, toggleClientStatus, processing)}
|
&& renderBlockingButton(ipMatchListStatus, value, toggleClientStatus, processing)}
|
||||||
|
|
|
@ -9,7 +9,7 @@ const DomainCell = ({ value }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="logs__row">
|
<div className="logs__row">
|
||||||
<div className="logs__text logs__text--domain" title={value}>
|
<div className="logs__text" title={value}>
|
||||||
{value}
|
{value}
|
||||||
</div>
|
</div>
|
||||||
{trackerData && <Popover data={trackerData} />}
|
{trackerData && <Popover data={trackerData} />}
|
||||||
|
|
|
@ -33,7 +33,7 @@ const getClientCell = ({
|
||||||
const isFiltered = checkFiltered(reason);
|
const isFiltered = checkFiltered(reason);
|
||||||
|
|
||||||
const nameClass = classNames('w-90 o-hidden d-flex flex-column', {
|
const nameClass = classNames('w-90 o-hidden d-flex flex-column', {
|
||||||
'mt-2': isDetailed && !name,
|
'mt-2': isDetailed && !name && !whois_info,
|
||||||
'white-space--nowrap': isDetailed,
|
'white-space--nowrap': isDetailed,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -80,9 +80,9 @@ const getClientCell = ({
|
||||||
})}
|
})}
|
||||||
<div
|
<div
|
||||||
className={nameClass}>
|
className={nameClass}>
|
||||||
<div data-tip={true} data-for={id}>{formatClientCell(row, t, isDetailed)}</div>
|
<div data-tip={true} data-for={id}>{formatClientCell(row, isDetailed)}</div>
|
||||||
{isDetailed && name
|
{isDetailed && name
|
||||||
&& <div className="detailed-info d-none d-sm-block logs__text"
|
&& !whois_info && <div className="detailed-info d-none d-sm-block logs__text"
|
||||||
title={name}>{name}</div>}
|
title={name}>{name}</div>}
|
||||||
</div>
|
</div>
|
||||||
{renderBlockingButton(isFiltered, domain)}
|
{renderBlockingButton(isFiltered, domain)}
|
||||||
|
|
|
@ -9,8 +9,7 @@ import getHintElement from './getHintElement';
|
||||||
|
|
||||||
const getResponseCell = (row, filtering, t, isDetailed, getFilterName) => {
|
const getResponseCell = (row, filtering, t, isDetailed, getFilterName) => {
|
||||||
const {
|
const {
|
||||||
reason, filterId, rule, status, upstream, elapsedMs,
|
reason, filterId, rule, status, upstream, elapsedMs, response, originalResponse,
|
||||||
domain, response, originalResponse,
|
|
||||||
} = row.original;
|
} = row.original;
|
||||||
|
|
||||||
const { filters, whitelistFilters } = filtering;
|
const { filters, whitelistFilters } = filtering;
|
||||||
|
@ -41,7 +40,6 @@ const getResponseCell = (row, filtering, t, isDetailed, getFilterName) => {
|
||||||
|
|
||||||
const FILTERED_STATUS_TO_FIELDS_MAP = {
|
const FILTERED_STATUS_TO_FIELDS_MAP = {
|
||||||
[FILTERED_STATUS.NOT_FILTERED_NOT_FOUND]: {
|
[FILTERED_STATUS.NOT_FILTERED_NOT_FOUND]: {
|
||||||
domain,
|
|
||||||
encryption_status: boldStatusLabel,
|
encryption_status: boldStatusLabel,
|
||||||
install_settings_dns: upstream,
|
install_settings_dns: upstream,
|
||||||
elapsed: formattedElapsedMs,
|
elapsed: formattedElapsedMs,
|
||||||
|
@ -49,7 +47,6 @@ const getResponseCell = (row, filtering, t, isDetailed, getFilterName) => {
|
||||||
response_table_header: renderResponses(response),
|
response_table_header: renderResponses(response),
|
||||||
},
|
},
|
||||||
[FILTERED_STATUS.FILTERED_BLOCKED_SERVICE]: {
|
[FILTERED_STATUS.FILTERED_BLOCKED_SERVICE]: {
|
||||||
domain,
|
|
||||||
encryption_status: boldStatusLabel,
|
encryption_status: boldStatusLabel,
|
||||||
install_settings_dns: upstream,
|
install_settings_dns: upstream,
|
||||||
elapsed: formattedElapsedMs,
|
elapsed: formattedElapsedMs,
|
||||||
|
@ -59,7 +56,6 @@ const getResponseCell = (row, filtering, t, isDetailed, getFilterName) => {
|
||||||
original_response: renderResponses(originalResponse),
|
original_response: renderResponses(originalResponse),
|
||||||
},
|
},
|
||||||
[FILTERED_STATUS.NOT_FILTERED_WHITE_LIST]: {
|
[FILTERED_STATUS.NOT_FILTERED_WHITE_LIST]: {
|
||||||
domain,
|
|
||||||
encryption_status: boldStatusLabel,
|
encryption_status: boldStatusLabel,
|
||||||
install_settings_dns: upstream,
|
install_settings_dns: upstream,
|
||||||
elapsed: formattedElapsedMs,
|
elapsed: formattedElapsedMs,
|
||||||
|
@ -68,21 +64,19 @@ const getResponseCell = (row, filtering, t, isDetailed, getFilterName) => {
|
||||||
response_code: status,
|
response_code: status,
|
||||||
},
|
},
|
||||||
[FILTERED_STATUS.NOT_FILTERED_WHITE_LIST]: {
|
[FILTERED_STATUS.NOT_FILTERED_WHITE_LIST]: {
|
||||||
domain,
|
|
||||||
encryption_status: boldStatusLabel,
|
encryption_status: boldStatusLabel,
|
||||||
filter,
|
filter,
|
||||||
rule_label: rule,
|
rule_label: rule,
|
||||||
response_code: status,
|
response_code: status,
|
||||||
},
|
},
|
||||||
[FILTERED_STATUS.FILTERED_SAFE_SEARCH]: {
|
[FILTERED_STATUS.FILTERED_SAFE_SEARCH]: {
|
||||||
domain,
|
|
||||||
encryption_status: boldStatusLabel,
|
encryption_status: boldStatusLabel,
|
||||||
install_settings_dns: upstream,
|
install_settings_dns: upstream,
|
||||||
elapsed: formattedElapsedMs,
|
elapsed: formattedElapsedMs,
|
||||||
response_code: status,
|
response_code: status,
|
||||||
|
response_table_header: renderResponses(response),
|
||||||
},
|
},
|
||||||
[FILTERED_STATUS.FILTERED_BLACK_LIST]: {
|
[FILTERED_STATUS.FILTERED_BLACK_LIST]: {
|
||||||
domain,
|
|
||||||
encryption_status: boldStatusLabel,
|
encryption_status: boldStatusLabel,
|
||||||
filter,
|
filter,
|
||||||
rule_label: rule,
|
rule_label: rule,
|
||||||
|
@ -93,7 +87,7 @@ const getResponseCell = (row, filtering, t, isDetailed, getFilterName) => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const fields = FILTERED_STATUS_TO_FIELDS_MAP[reason]
|
const content = FILTERED_STATUS_TO_FIELDS_MAP[reason]
|
||||||
? Object.entries(FILTERED_STATUS_TO_FIELDS_MAP[reason])
|
? Object.entries(FILTERED_STATUS_TO_FIELDS_MAP[reason])
|
||||||
: Object.entries(FILTERED_STATUS_TO_FIELDS_MAP.NotFilteredNotFound);
|
: Object.entries(FILTERED_STATUS_TO_FIELDS_MAP.NotFilteredNotFound);
|
||||||
|
|
||||||
|
@ -108,7 +102,7 @@ const getResponseCell = (row, filtering, t, isDetailed, getFilterName) => {
|
||||||
contentItemClass: 'text-truncate key-colon o-hidden',
|
contentItemClass: 'text-truncate key-colon o-hidden',
|
||||||
xlinkHref: 'question',
|
xlinkHref: 'question',
|
||||||
title: 'response_details',
|
title: 'response_details',
|
||||||
content: fields,
|
content,
|
||||||
placement: 'bottom',
|
placement: 'bottom',
|
||||||
})}
|
})}
|
||||||
<div className="text-truncate">
|
<div className="text-truncate">
|
||||||
|
|
|
@ -144,18 +144,20 @@ const Form = (props) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Field
|
<div className="field__search">
|
||||||
id={FORM_NAMES.search}
|
<Field
|
||||||
name={FORM_NAMES.search}
|
id={FORM_NAMES.search}
|
||||||
component={renderFilterField}
|
name={FORM_NAMES.search}
|
||||||
type="text"
|
component={renderFilterField}
|
||||||
className={classNames('form-control--search form-control--transparent', className)}
|
type="text"
|
||||||
placeholder={t('domain_or_client')}
|
className={classNames('form-control--search form-control--transparent', className)}
|
||||||
tooltip={t('query_log_strict_search')}
|
placeholder={t('domain_or_client')}
|
||||||
onClearInputClick={onInputClear}
|
tooltip={t('query_log_strict_search')}
|
||||||
onKeyDown={onEnterPress}
|
onClearInputClick={onInputClear}
|
||||||
normalizeOnBlur={normalizeOnBlur}
|
onKeyDown={onEnterPress}
|
||||||
/>
|
normalizeOnBlur={normalizeOnBlur}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div className="field__select">
|
<div className="field__select">
|
||||||
<Field
|
<Field
|
||||||
name={FORM_NAMES.response_status}
|
name={FORM_NAMES.response_status}
|
||||||
|
|
|
@ -13,7 +13,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-table .logs__row {
|
.card-table .logs__row {
|
||||||
overflow: visible;
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logs__row--center {
|
.logs__row--center {
|
||||||
|
@ -57,10 +58,6 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logs__text--domain {
|
|
||||||
max-width: 285px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logs__text--wrap,
|
.logs__text--wrap,
|
||||||
.logs__text--whois {
|
.logs__text--whois {
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
|
@ -202,6 +199,7 @@
|
||||||
.logs__whois {
|
.logs__whois {
|
||||||
display: inline;
|
display: inline;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logs__whois::after {
|
.logs__whois::after {
|
||||||
|
@ -455,15 +453,11 @@
|
||||||
color: var(--danger);
|
color: var(--danger);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ml-small {
|
|
||||||
margin-left: 3.3125rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-control--search {
|
.form-control--search {
|
||||||
width: 39.125rem;
|
|
||||||
box-shadow: 0 1px 0 #ddd;
|
box-shadow: 0 1px 0 #ddd;
|
||||||
padding: 0 2.5rem;
|
padding: 0 2.5rem;
|
||||||
height: 2.25rem;
|
height: 2.25rem;
|
||||||
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-control--transparent {
|
.form-control--transparent {
|
||||||
|
@ -493,31 +487,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-control--container {
|
.form-control--container {
|
||||||
max-width: 100%;
|
flex: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1279.98px) {
|
.field__search {
|
||||||
.form-control--search {
|
display: flex;
|
||||||
max-width: 30.125rem;
|
flex-grow: 1;
|
||||||
}
|
|
||||||
|
|
||||||
.form-control--container {
|
|
||||||
max-width: 70%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-control--search {
|
|
||||||
max-width: 50%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 991.98px) {
|
|
||||||
.form-control--search {
|
|
||||||
max-width: 40%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-control--container {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 767.98px) {
|
@media (max-width: 767.98px) {
|
||||||
|
@ -528,6 +503,19 @@
|
||||||
.ml-small {
|
.ml-small {
|
||||||
margin-left: 1.5rem;
|
margin-left: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form-control--container {
|
||||||
|
width: 100%;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control--search {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field__select {
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 575px) {
|
@media (max-width: 575px) {
|
||||||
|
@ -544,16 +532,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 500px) {
|
|
||||||
.form-control--search {
|
|
||||||
max-width: 85%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.field__select {
|
|
||||||
margin-top: 1.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading__container > .-loading-inner {
|
.loading__container > .-loading-inner {
|
||||||
top: 10rem !important;
|
top: 10rem !important;
|
||||||
bottom: initial !important;
|
bottom: initial !important;
|
||||||
|
|
|
@ -25,7 +25,7 @@ import {
|
||||||
formatDateTime,
|
formatDateTime,
|
||||||
formatElapsedMs,
|
formatElapsedMs,
|
||||||
formatTime,
|
formatTime,
|
||||||
|
processContent,
|
||||||
} from '../../helpers/helpers';
|
} from '../../helpers/helpers';
|
||||||
import Loading from '../ui/Loading';
|
import Loading from '../ui/Loading';
|
||||||
import { getSourceData } from '../../helpers/trackers/trackers';
|
import { getSourceData } from '../../helpers/trackers/trackers';
|
||||||
|
@ -302,6 +302,7 @@ const Table = (props) => {
|
||||||
filterId,
|
filterId,
|
||||||
rule,
|
rule,
|
||||||
originalResponse,
|
originalResponse,
|
||||||
|
status,
|
||||||
} = rowInfo.original;
|
} = rowInfo.original;
|
||||||
|
|
||||||
const hasTracker = !!tracker;
|
const hasTracker = !!tracker;
|
||||||
|
@ -328,17 +329,20 @@ const Table = (props) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const isBlockedByResponse = originalResponse.length > 0 && isBlocked;
|
const isBlockedByResponse = originalResponse.length > 0 && isBlocked;
|
||||||
const status = t(isBlockedByResponse ? 'blocked_by_cname_or_ip' : FILTERED_STATUS_TO_META_MAP[reason]?.label || reason);
|
const requestStatus = t(isBlockedByResponse ? 'blocked_by_cname_or_ip' : FILTERED_STATUS_TO_META_MAP[reason]?.label || reason);
|
||||||
const statusBlocked = <div className="bg--danger">{status}</div>;
|
|
||||||
|
|
||||||
const protocol = t(SCHEME_TO_PROTOCOL_MAP[client_proto]) || '';
|
const protocol = t(SCHEME_TO_PROTOCOL_MAP[client_proto]) || '';
|
||||||
|
|
||||||
const sourceData = getSourceData(tracker);
|
const sourceData = getSourceData(tracker);
|
||||||
|
|
||||||
|
const { filters, whitelistFilters } = filtering;
|
||||||
|
const filter = getFilterName(filters, whitelistFilters, filterId, t);
|
||||||
|
|
||||||
const detailedData = {
|
const detailedData = {
|
||||||
time_table_header: formatTime(time, LONG_TIME_FORMAT),
|
time_table_header: formatTime(time, LONG_TIME_FORMAT),
|
||||||
date: formatDateTime(time, DEFAULT_SHORT_DATE_FORMAT_OPTIONS),
|
date: formatDateTime(time, DEFAULT_SHORT_DATE_FORMAT_OPTIONS),
|
||||||
encryption_status: status,
|
encryption_status: isBlocked
|
||||||
|
? <div className="bg--danger">{requestStatus}</div> : requestStatus,
|
||||||
domain,
|
domain,
|
||||||
type_table_header: type,
|
type_table_header: type,
|
||||||
protocol,
|
protocol,
|
||||||
|
@ -346,12 +350,19 @@ const Table = (props) => {
|
||||||
table_name: tracker?.name,
|
table_name: tracker?.name,
|
||||||
category_label: hasTracker && captitalizeWords(tracker.category),
|
category_label: hasTracker && captitalizeWords(tracker.category),
|
||||||
tracker_source: hasTracker && sourceData
|
tracker_source: hasTracker && sourceData
|
||||||
&& <a href={sourceData.url} target="_blank" rel="noopener noreferrer"
|
&& <a
|
||||||
className="link--green">{sourceData.name}</a>,
|
href={sourceData.url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="link--green">{sourceData.name}
|
||||||
|
</a>,
|
||||||
response_details: 'title',
|
response_details: 'title',
|
||||||
install_settings_dns: upstream,
|
install_settings_dns: upstream,
|
||||||
elapsed: formattedElapsedMs,
|
elapsed: formattedElapsedMs,
|
||||||
|
filter: isBlocked ? filter : null,
|
||||||
|
rule_label: rule,
|
||||||
response_table_header: response?.join('\n'),
|
response_table_header: response?.join('\n'),
|
||||||
|
response_code: status,
|
||||||
client_details: 'title',
|
client_details: 'title',
|
||||||
ip_address: client,
|
ip_address: client,
|
||||||
name: info?.name,
|
name: info?.name,
|
||||||
|
@ -360,41 +371,14 @@ const Table = (props) => {
|
||||||
network,
|
network,
|
||||||
source_label: source,
|
source_label: source,
|
||||||
validated_with_dnssec: dnssec_enabled ? Boolean(answer_dnssec) : false,
|
validated_with_dnssec: dnssec_enabled ? Boolean(answer_dnssec) : false,
|
||||||
[buttonType]: <div onClick={onToggleBlock}
|
|
||||||
className="title--border bg--danger text-center">{t(buttonType)}</div>,
|
|
||||||
};
|
|
||||||
|
|
||||||
const { filters, whitelistFilters } = filtering;
|
|
||||||
|
|
||||||
const filter = getFilterName(filters, whitelistFilters, filterId, t);
|
|
||||||
|
|
||||||
const detailedDataBlocked = {
|
|
||||||
time_table_header: formatTime(time, LONG_TIME_FORMAT),
|
|
||||||
date: formatDateTime(time, DEFAULT_SHORT_DATE_FORMAT_OPTIONS),
|
|
||||||
encryption_status: statusBlocked,
|
|
||||||
domain,
|
|
||||||
type_table_header: type,
|
|
||||||
protocol,
|
|
||||||
known_tracker: 'title',
|
|
||||||
table_name: tracker?.name,
|
|
||||||
category_label: hasTracker && captitalizeWords(tracker.category),
|
|
||||||
source_label: hasTracker && sourceData
|
|
||||||
&& <a href={sourceData.url} target="_blank" rel="noopener noreferrer"
|
|
||||||
className="link--green">{sourceData.name}</a>,
|
|
||||||
response_details: 'title',
|
|
||||||
install_settings_dns: upstream,
|
|
||||||
elapsed: formattedElapsedMs,
|
|
||||||
filter,
|
|
||||||
rule_label: rule,
|
|
||||||
response_table_header: response?.join('\n'),
|
|
||||||
original_response: originalResponse?.join('\n'),
|
original_response: originalResponse?.join('\n'),
|
||||||
[buttonType]: <div onClick={onToggleBlock}
|
[buttonType]: <div onClick={onToggleBlock}
|
||||||
className="title--border text-center">{t(buttonType)}</div>,
|
className={classNames('title--border text-center', {
|
||||||
|
'bg--danger': isBlocked,
|
||||||
|
})}>{t(buttonType)}</div>,
|
||||||
};
|
};
|
||||||
|
|
||||||
const detailedDataCurrent = isBlocked ? detailedDataBlocked : detailedData;
|
setDetailedDataCurrent(processContent(detailedData));
|
||||||
|
|
||||||
setDetailedDataCurrent(detailedDataCurrent);
|
|
||||||
setButtonType(buttonType);
|
setButtonType(buttonType);
|
||||||
setModalOpened(true);
|
setModalOpened(true);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import Modal from 'react-modal';
|
||||||
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||||
import { useHistory } from 'react-router-dom';
|
import { useHistory } from 'react-router-dom';
|
||||||
import queryString from 'query-string';
|
import queryString from 'query-string';
|
||||||
|
import classNames from 'classnames';
|
||||||
import {
|
import {
|
||||||
BLOCK_ACTIONS,
|
BLOCK_ACTIONS,
|
||||||
TABLE_DEFAULT_PAGE_SIZE,
|
TABLE_DEFAULT_PAGE_SIZE,
|
||||||
|
@ -27,7 +28,7 @@ import {
|
||||||
import { addSuccessToast } from '../../actions/toasts';
|
import { addSuccessToast } from '../../actions/toasts';
|
||||||
import './Logs.css';
|
import './Logs.css';
|
||||||
|
|
||||||
export const processContent = (data, buttonType) => Object.entries(data)
|
const processContent = (data, buttonType) => Object.entries(data)
|
||||||
.map(([key, value]) => {
|
.map(([key, value]) => {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -49,7 +50,9 @@ export const processContent = (data, buttonType) => Object.entries(data)
|
||||||
|
|
||||||
return isHidden ? null : <Fragment key={key}>
|
return isHidden ? null : <Fragment key={key}>
|
||||||
<div
|
<div
|
||||||
className={`key__${key} ${keyClass} ${(isBoolean && value === true) ? 'font-weight-bold' : ''}`}>
|
className={classNames(`key__${key}`, keyClass, {
|
||||||
|
'font-weight-bold': isBoolean && value === true,
|
||||||
|
})}>
|
||||||
<Trans>{isButton ? value : key}</Trans>
|
<Trans>{isButton ? value : key}</Trans>
|
||||||
</div>
|
</div>
|
||||||
<div className={`value__${key} text-pre text-truncate`}>
|
<div className={`value__${key} text-pre text-truncate`}>
|
||||||
|
@ -133,7 +136,16 @@ const Logs = (props) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
mediaQuery.addEventListener('change', mediaQueryHandler);
|
try {
|
||||||
|
mediaQuery.addEventListener('change', mediaQueryHandler);
|
||||||
|
} catch (e1) {
|
||||||
|
try {
|
||||||
|
// Safari 13.1 do not support mediaQuery.addEventListener('change', handler)
|
||||||
|
mediaQuery.addListener(mediaQueryHandler);
|
||||||
|
} catch (e2) {
|
||||||
|
console.error(e2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
@ -153,7 +165,16 @@ const Logs = (props) => {
|
||||||
})();
|
})();
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
mediaQuery.removeEventListener('change', mediaQueryHandler);
|
try {
|
||||||
|
mediaQuery.removeEventListener('change', mediaQueryHandler);
|
||||||
|
} catch (e1) {
|
||||||
|
try {
|
||||||
|
mediaQuery.removeListener(mediaQueryHandler);
|
||||||
|
} catch (e2) {
|
||||||
|
console.error(e2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dispatch(resetFilteredLogs());
|
dispatch(resetFilteredLogs());
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { Trans, withTranslation } from 'react-i18next';
|
||||||
import ReactTable from 'react-table';
|
import ReactTable from 'react-table';
|
||||||
|
|
||||||
import { MODAL_TYPE } from '../../../helpers/constants';
|
import { MODAL_TYPE } from '../../../helpers/constants';
|
||||||
import { normalizeTextarea } from '../../../helpers/helpers';
|
import { splitByNewLine } from '../../../helpers/helpers';
|
||||||
import Card from '../../ui/Card';
|
import Card from '../../ui/Card';
|
||||||
import Modal from './Modal';
|
import Modal from './Modal';
|
||||||
import CellWrap from '../../ui/CellWrap';
|
import CellWrap from '../../ui/CellWrap';
|
||||||
|
@ -30,7 +30,7 @@ class ClientsTable extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (values.upstreams && typeof values.upstreams === 'string') {
|
if (values.upstreams && typeof values.upstreams === 'string') {
|
||||||
config.upstreams = normalizeTextarea(values.upstreams);
|
config.upstreams = splitByNewLine(values.upstreams);
|
||||||
} else {
|
} else {
|
||||||
config.upstreams = [];
|
config.upstreams = [];
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,10 @@ import { Field, reduxForm } from 'redux-form';
|
||||||
import { Trans, withTranslation } from 'react-i18next';
|
import { Trans, withTranslation } from 'react-i18next';
|
||||||
import flow from 'lodash/flow';
|
import flow from 'lodash/flow';
|
||||||
import { renderTextareaField } from '../../../../helpers/form';
|
import { renderTextareaField } from '../../../../helpers/form';
|
||||||
import { normalizeMultiline } from '../../../../helpers/helpers';
|
import {
|
||||||
|
trimMultilineString,
|
||||||
|
removeEmptyLines,
|
||||||
|
} from '../../../../helpers/helpers';
|
||||||
import { FORM_NAME } from '../../../../helpers/constants';
|
import { FORM_NAME } from '../../../../helpers/constants';
|
||||||
|
|
||||||
const fields = [
|
const fields = [
|
||||||
|
@ -12,16 +15,19 @@ const fields = [
|
||||||
id: 'allowed_clients',
|
id: 'allowed_clients',
|
||||||
title: 'access_allowed_title',
|
title: 'access_allowed_title',
|
||||||
subtitle: 'access_allowed_desc',
|
subtitle: 'access_allowed_desc',
|
||||||
|
normalizeOnBlur: removeEmptyLines,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'disallowed_clients',
|
id: 'disallowed_clients',
|
||||||
title: 'access_disallowed_title',
|
title: 'access_disallowed_title',
|
||||||
subtitle: 'access_disallowed_desc',
|
subtitle: 'access_disallowed_desc',
|
||||||
|
normalizeOnBlur: trimMultilineString,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'blocked_hosts',
|
id: 'blocked_hosts',
|
||||||
title: 'access_blocked_title',
|
title: 'access_blocked_title',
|
||||||
subtitle: 'access_blocked_desc',
|
subtitle: 'access_blocked_desc',
|
||||||
|
normalizeOnBlur: removeEmptyLines,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -31,7 +37,7 @@ const Form = (props) => {
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const renderField = ({
|
const renderField = ({
|
||||||
id, title, subtitle, disabled = processingSet,
|
id, title, subtitle, disabled = processingSet, normalizeOnBlur,
|
||||||
}) => <div key={id} className="form__group mb-5">
|
}) => <div key={id} className="form__group mb-5">
|
||||||
<label className="form__label form__label--with-desc" htmlFor={id}>
|
<label className="form__label form__label--with-desc" htmlFor={id}>
|
||||||
<Trans>{title}</Trans>
|
<Trans>{title}</Trans>
|
||||||
|
@ -46,7 +52,7 @@ const Form = (props) => {
|
||||||
type="text"
|
type="text"
|
||||||
className="form-control form-control--textarea font-monospace"
|
className="form-control form-control--textarea font-monospace"
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
normalizeOnBlur={id === 'disallowed_clients' ? normalizeMultiline : undefined}
|
normalizeOnBlur={normalizeOnBlur}
|
||||||
/>
|
/>
|
||||||
</div>;
|
</div>;
|
||||||
|
|
||||||
|
@ -55,6 +61,7 @@ const Form = (props) => {
|
||||||
title: PropTypes.string,
|
title: PropTypes.string,
|
||||||
subtitle: PropTypes.string,
|
subtitle: PropTypes.string,
|
||||||
disabled: PropTypes.bool,
|
disabled: PropTypes.bool,
|
||||||
|
normalizeOnBlur: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -6,9 +6,10 @@ import { Trans, useTranslation } from 'react-i18next';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
|
||||||
import Examples from './Examples';
|
import Examples from './Examples';
|
||||||
import { renderRadioField } from '../../../../helpers/form';
|
import { renderRadioField, renderTextareaField } from '../../../../helpers/form';
|
||||||
import { DNS_REQUEST_OPTIONS, FORM_NAME } from '../../../../helpers/constants';
|
import { DNS_REQUEST_OPTIONS, FORM_NAME } from '../../../../helpers/constants';
|
||||||
import { testUpstream } from '../../../../actions';
|
import { testUpstream } from '../../../../actions';
|
||||||
|
import { removeEmptyLines } from '../../../../helpers/helpers';
|
||||||
|
|
||||||
const getInputFields = () => [{
|
const getInputFields = () => [{
|
||||||
// eslint-disable-next-line react/display-name
|
// eslint-disable-next-line react/display-name
|
||||||
|
@ -17,9 +18,10 @@ const getInputFields = () => [{
|
||||||
</label>,
|
</label>,
|
||||||
name: 'upstream_dns',
|
name: 'upstream_dns',
|
||||||
type: 'text',
|
type: 'text',
|
||||||
component: 'textarea',
|
component: renderTextareaField,
|
||||||
className: 'form-control form-control--textarea font-monospace',
|
className: 'form-control form-control--textarea font-monospace',
|
||||||
placeholder: 'upstream_dns',
|
placeholder: 'upstream_dns',
|
||||||
|
normalizeOnBlur: removeEmptyLines,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'upstream_mode',
|
name: 'upstream_mode',
|
||||||
|
@ -69,7 +71,8 @@ const Form = ({
|
||||||
return <form onSubmit={handleSubmit}>
|
return <form onSubmit={handleSubmit}>
|
||||||
<div className="row">
|
<div className="row">
|
||||||
{INPUT_FIELDS.map(({
|
{INPUT_FIELDS.map(({
|
||||||
name, component, type, className, placeholder, getTitle, subtitle, disabled, value,
|
name, component, type, className, placeholder,
|
||||||
|
getTitle, subtitle, disabled, value, normalizeOnBlur,
|
||||||
}) => <div className="col-12 mb-4" key={placeholder}>
|
}) => <div className="col-12 mb-4" key={placeholder}>
|
||||||
{typeof getTitle === 'function' && getTitle()}
|
{typeof getTitle === 'function' && getTitle()}
|
||||||
<Field
|
<Field
|
||||||
|
@ -82,6 +85,7 @@ const Form = ({
|
||||||
placeholder={t(placeholder)}
|
placeholder={t(placeholder)}
|
||||||
subtitle={t(subtitle)}
|
subtitle={t(subtitle)}
|
||||||
disabled={processingSetConfig || processingTestUpstream || disabled}
|
disabled={processingSetConfig || processingTestUpstream || disabled}
|
||||||
|
normalizeOnBlur={normalizeOnBlur}
|
||||||
/>
|
/>
|
||||||
</div>)}
|
</div>)}
|
||||||
<div className="col-12">
|
<div className="col-12">
|
||||||
|
@ -101,11 +105,12 @@ const Form = ({
|
||||||
<Field
|
<Field
|
||||||
id="bootstrap_dns"
|
id="bootstrap_dns"
|
||||||
name="bootstrap_dns"
|
name="bootstrap_dns"
|
||||||
component="textarea"
|
component={renderTextareaField}
|
||||||
type="text"
|
type="text"
|
||||||
className="form-control form-control--textarea form-control--textarea-small font-monospace"
|
className="form-control form-control--textarea form-control--textarea-small font-monospace"
|
||||||
placeholder={t('bootstrap_dns')}
|
placeholder={t('bootstrap_dns')}
|
||||||
disabled={processingSetConfig}
|
disabled={processingSetConfig}
|
||||||
|
normalizeOnBlur={removeEmptyLines}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -45,7 +45,10 @@ const Dns = (props) => {
|
||||||
dnsConfig={dnsConfig}
|
dnsConfig={dnsConfig}
|
||||||
setDnsConfig={setDnsConfig}
|
setDnsConfig={setDnsConfig}
|
||||||
/>
|
/>
|
||||||
<Access access={access} setAccessList={setAccessList} />
|
<Access
|
||||||
|
access={access}
|
||||||
|
setAccessList={setAccessList}
|
||||||
|
/>
|
||||||
</>}
|
</>}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -2,14 +2,14 @@ import React from 'react';
|
||||||
import { normalizeWhois } from './helpers';
|
import { normalizeWhois } from './helpers';
|
||||||
import { WHOIS_ICONS } from './constants';
|
import { WHOIS_ICONS } from './constants';
|
||||||
|
|
||||||
const getFormattedWhois = (whois, t) => {
|
const getFormattedWhois = (whois) => {
|
||||||
const whoisInfo = normalizeWhois(whois);
|
const whoisInfo = normalizeWhois(whois);
|
||||||
return (
|
return (
|
||||||
Object.keys(whoisInfo)
|
Object.keys(whoisInfo)
|
||||||
.map((key) => {
|
.map((key) => {
|
||||||
const icon = WHOIS_ICONS[key];
|
const icon = WHOIS_ICONS[key];
|
||||||
return (
|
return (
|
||||||
<span className="logs__whois text-muted" key={key} title={t(key)}>
|
<span className="logs__whois text-muted " key={key} title={whoisInfo[key]}>
|
||||||
{icon && (
|
{icon && (
|
||||||
<>
|
<>
|
||||||
<svg className="logs__whois-icon icons">
|
<svg className="logs__whois-icon icons">
|
||||||
|
@ -24,7 +24,7 @@ const getFormattedWhois = (whois, t) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const formatClientCell = (row, t, isDetailed = false) => {
|
export const formatClientCell = (row, isDetailed = false) => {
|
||||||
const { value, original: { info } } = row;
|
const { value, original: { info } } = row;
|
||||||
let whoisContainer = '';
|
let whoisContainer = '';
|
||||||
let nameContainer = value;
|
let nameContainer = value;
|
||||||
|
@ -33,7 +33,7 @@ export const formatClientCell = (row, t, isDetailed = false) => {
|
||||||
const { name, whois_info } = info;
|
const { name, whois_info } = info;
|
||||||
|
|
||||||
if (name) {
|
if (name) {
|
||||||
nameContainer = isDetailed
|
nameContainer = !whois_info && isDetailed
|
||||||
? <small title={value}>{value}</small>
|
? <small title={value}>{value}</small>
|
||||||
: <div className="logs__text logs__text--nowrap" title={`${name} (${value})`}>
|
: <div className="logs__text logs__text--nowrap" title={`${name} (${value})`}>
|
||||||
{name}
|
{name}
|
||||||
|
@ -42,10 +42,10 @@ export const formatClientCell = (row, t, isDetailed = false) => {
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (whois_info) {
|
if (whois_info && isDetailed) {
|
||||||
whoisContainer = (
|
whoisContainer = (
|
||||||
<div className="logs__text logs__text--wrap logs__text--whois">
|
<div className="logs__text logs__text--wrap logs__text--whois">
|
||||||
{getFormattedWhois(whois_info, t)}
|
{getFormattedWhois(whois_info)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -306,15 +306,27 @@ export const redirectToCurrentProtocol = (values, httpPort = 80) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const normalizeTextarea = (text) => {
|
/**
|
||||||
if (!text) {
|
* @param {string} text
|
||||||
return [];
|
* @returns []string
|
||||||
}
|
*/
|
||||||
|
export const splitByNewLine = (text) => text.split('\n')
|
||||||
|
.filter((n) => n.trim());
|
||||||
|
|
||||||
return text.replace(/[;, ]/g, '\n')
|
/**
|
||||||
.split('\n')
|
* @param {string} text
|
||||||
.filter((n) => n);
|
* @returns {string}
|
||||||
};
|
*/
|
||||||
|
export const trimMultilineString = (text) => splitByNewLine(text)
|
||||||
|
.map((line) => line.trim())
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} text
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
export const removeEmptyLines = (text) => splitByNewLine(text)
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalizes the topClients array
|
* Normalizes the topClients array
|
||||||
|
@ -533,10 +545,6 @@ export const getMap = (arr, key, value) => arr.reduce((acc, curr) => {
|
||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
export const normalizeMultiline = (multiline) => `${normalizeTextarea(multiline)
|
|
||||||
.map((line) => line.trim())
|
|
||||||
.join('\n')}\n`;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param parsedIp {object} ipaddr.js IPv4 or IPv6 object
|
* @param parsedIp {object} ipaddr.js IPv4 or IPv6 object
|
||||||
* @param cidr {array} ipaddr.js CIDR array
|
* @param cidr {array} ipaddr.js CIDR array
|
||||||
|
@ -629,7 +637,6 @@ export const getLogsUrlParams = (search, response_status) => `?${queryString.str
|
||||||
response_status,
|
response_status,
|
||||||
})}`;
|
})}`;
|
||||||
|
|
||||||
|
|
||||||
export const processContent = (content) => (Array.isArray(content)
|
export const processContent = (content) => (Array.isArray(content)
|
||||||
? content.filter(([, value]) => value)
|
? content.filter(([, value]) => value)
|
||||||
.flat() : content);
|
.flat() : content);
|
||||||
|
|
|
@ -82,7 +82,7 @@ const dashboard = handleActions(
|
||||||
[actions.getVersionSuccess]: (state, { payload }) => {
|
[actions.getVersionSuccess]: (state, { payload }) => {
|
||||||
const currentVersion = state.dnsVersion === 'undefined' ? 0 : state.dnsVersion;
|
const currentVersion = state.dnsVersion === 'undefined' ? 0 : state.dnsVersion;
|
||||||
|
|
||||||
if (payload && isVersionGreater(currentVersion, payload.new_version)) {
|
if (!payload.disabled && isVersionGreater(currentVersion, payload.new_version)) {
|
||||||
const {
|
const {
|
||||||
announcement_url: announcementUrl,
|
announcement_url: announcementUrl,
|
||||||
new_version: newVersion,
|
new_version: newVersion,
|
||||||
|
@ -96,7 +96,7 @@ const dashboard = handleActions(
|
||||||
canAutoUpdate,
|
canAutoUpdate,
|
||||||
isUpdateAvailable: true,
|
isUpdateAvailable: true,
|
||||||
processingVersion: false,
|
processingVersion: false,
|
||||||
checkUpdateFlag: !!payload,
|
checkUpdateFlag: !payload.disabled,
|
||||||
};
|
};
|
||||||
return newState;
|
return newState;
|
||||||
}
|
}
|
||||||
|
@ -104,6 +104,7 @@ const dashboard = handleActions(
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
processingVersion: false,
|
processingVersion: false,
|
||||||
|
checkUpdateFlag: !payload.disabled,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -395,6 +395,7 @@ func optionalAuth(handler func(http.ResponseWriter, *http.Request)) func(http.Re
|
||||||
|
|
||||||
if glProcessCookie(r) {
|
if glProcessCookie(r) {
|
||||||
log.Debug("Auth: authentification was handled by GL-Inet submodule")
|
log.Debug("Auth: authentification was handled by GL-Inet submodule")
|
||||||
|
ok = true
|
||||||
|
|
||||||
} else if err == nil {
|
} else if err == nil {
|
||||||
r := Context.auth.CheckSession(cookie.Value)
|
r := Context.auth.CheckSession(cookie.Value)
|
||||||
|
|
|
@ -41,6 +41,10 @@ type getVersionJSONRequest struct {
|
||||||
// Get the latest available version from the Internet
|
// Get the latest available version from the Internet
|
||||||
func handleGetVersionJSON(w http.ResponseWriter, r *http.Request) {
|
func handleGetVersionJSON(w http.ResponseWriter, r *http.Request) {
|
||||||
if Context.disableUpdate {
|
if Context.disableUpdate {
|
||||||
|
resp := make(map[string]interface{})
|
||||||
|
resp["disabled"] = true
|
||||||
|
d, _ := json.Marshal(resp)
|
||||||
|
_, _ = w.Write(d)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue