Compare commits

...

22 Commits

Author SHA1 Message Date
Andrey Meshkov
ddb9a2e872 readme - update stable -> edge 2020-07-24 19:40:03 +03:00
Ildar Kamalov
cfdfd250a0 Merge: - client: show filter name on mobile for whitelisted entry
Closes #1934

Squashed commit of the following:

commit 5127467acfeec1d8a37736ecce7e92ff9f7b30c9
Author: Ildar Kamalov <ik@adguard.com>
Date:   Fri Jul 24 19:10:10 2020 +0300

    - client: show filter name if rule present

commit f3a3399682b2d842ec399f856e2b586aae9b3ec5
Merge: 63f4c965 3c91b8c7
Author: Ildar Kamalov <ik@adguard.com>
Date:   Fri Jul 24 19:09:32 2020 +0300

    Merge branch 'fix/1934' of ssh://bit.adguard.com:7999/dns/adguard-home into fix/1934

commit 3c91b8c70c0e0830a180ed1445e7c0994bc300ad
Author: Ildar Kamalov <ik@adguard.com>
Date:   Fri Jul 24 18:54:01 2020 +0300

    - client: show filter name for whitelisted entry

commit 63f4c9655ed5efe2bf35f1174c6c2b0af52403b5
Author: Ildar Kamalov <ik@adguard.com>
Date:   Fri Jul 24 18:54:01 2020 +0300

    - client: show filter name for whitelisted entry
2020-07-24 19:19:51 +03:00
Ildar Kamalov
cb01d05ef4 Merge: - client: fix host validation rule
Closes #1926

* commit 'c7e7b76cecb0ba14666ebf55dd665809a6df9612':
  - client: fix escape
  - client: fix host validation rule
  - client: fix host validation rule
  - client: fix host validation rule
2020-07-24 18:54:45 +03:00
Ildar Kamalov
c7e7b76cec - client: fix escape 2020-07-24 18:42:49 +03:00
Ildar Kamalov
f5128d27f1 - client: fix host validation rule 2020-07-24 18:36:59 +03:00
Ildar Kamalov
005f8fb279 - client: fix host validation rule 2020-07-24 18:35:03 +03:00
Ildar Kamalov
244fe093cd - client: fix host validation rule 2020-07-24 17:45:24 +03:00
Ildar Kamalov
ff9d1c234c Merge: - client: fix ui issues with table pagination and whois
Closes #1927

* commit '1c9d3acaa8864bca3e17dc3a313630dbc9ebea60':
  - client: fix whois wrap and vertical alignment
  - client: revert table paginations
2020-07-24 17:28:42 +03:00
Ildar Kamalov
1c9d3acaa8 - client: fix whois wrap and vertical alignment 2020-07-24 17:17:11 +03:00
Ildar Kamalov
0dab36a108 - client: revert table paginations 2020-07-24 16:45:59 +03:00
Ildar Kamalov
611ed94884 Merge: - client: show tooltips on tap for mobile devices
Closes #1922

* commit 'dd2c9d96e7f164e5295cf9ca7b68695dd7bc46c1':
  - client: show tooltips on tap for mobile devices
2020-07-24 16:29:14 +03:00
Ildar Kamalov
22935c5fed Merge: - client: show all available information in the response tooltip
Closes #1916

* commit '1ab650bb86f4b3103b8c650c6af862c6f1881694':
  - client: show all available information in the response tooltip
2020-07-24 16:27:58 +03:00
Simon Zolin
4a8dcbeeed Merge: - rewrites: return NOERROR without A records instead of NXDOMAIN
Close #1918

* commit 'ad4e85d8f50e1e905329f687c2748d28449d39c4':
  add test
  - rewrites: return NOERROR without A records instead of NXDOMAIN
2020-07-24 16:26:52 +03:00
Ildar Kamalov
1ab650bb86 - client: show all available information in the response tooltip 2020-07-24 16:14:21 +03:00
Ildar Kamalov
4743743b1f Merge: - client: fix container padding
Closes #1921

Squashed commit of the following:

commit b893ddf31e2251c9d0a77d14c0bc86d1d8608c0a
Author: Ildar Kamalov <ik@adguard.com>
Date:   Fri Jul 24 14:41:50 2020 +0300

    - client: fix container padding

commit 111f53bed2b944febc1f55de24e99cd49801120b
Author: Ildar Kamalov <ik@adguard.com>
Date:   Fri Jul 24 14:40:36 2020 +0300

    - client: npm audit fix
2020-07-24 16:13:58 +03:00
Ildar Kamalov
dd2c9d96e7 - client: show tooltips on tap for mobile devices 2020-07-24 16:11:45 +03:00
Andrey Meshkov
946bda37a3 - (ui): improved client access check performance
This is still not ideal and if the disallowed clients list is huge
enough, the slowdown is considerable. But it's at least x10 or x100
times faster than it was.

Closes: #1920
2020-07-24 13:45:46 +03:00
Simon Zolin
ad4e85d8f5 add test 2020-07-24 13:27:14 +03:00
Simon Zolin
4b9ab97271 - rewrites: return NOERROR without A records instead of NXDOMAIN
For rule "host -> ipv6" we return "ipv6" address for AAAA request
 and empty answer for A request
2020-07-24 13:18:05 +03:00
Andrey Meshkov
d2bf1e176e Update urlfilter to v0.11.2 2020-07-23 20:48:50 +03:00
Simon Zolin
ffeb88ac0c * openapi: update changelog
Squashed commit of the following:

commit a42d1762c2ffbfe65d728a5e76a3d0c5cc03a9c5
Author: Simon Zolin <s.zolin@adguard.com>
Date:   Thu Jul 23 19:17:05 2020 +0300

    * openapi: update changelog
2020-07-23 19:25:42 +03:00
Simon Zolin
c71b8d3ad2 * use urlfilter v0.11.1
Squashed commit of the following:

commit 35d2c34355b093ec1daa40f156809a7cae089c20
Author: Simon Zolin <s.zolin@adguard.com>
Date:   Thu Jul 23 18:00:43 2020 +0300

    * use urlfilter v0.11.1
2020-07-23 18:09:39 +03:00
27 changed files with 268 additions and 284 deletions

View File

@@ -134,7 +134,9 @@ lint-go:
golangci-lint run
test:
@echo Running unit-tests
@echo Running JS unit-tests
npm run test --prefix client
@echo Running Go unit-tests
go test -race -v -bench=. -coverprofile=coverage.txt -covermode=atomic ./...
ci: dependencies client test

View File

@@ -230,7 +230,7 @@ There are three options how you can install an unstable version:
There are three options how you can install an unstable version.
1. You can either install a beta version of AdGuard Home which we update periodically.
1. You can either install a beta or edge version of AdGuard Home which we update periodically. If you're already using stable version of AdGuard Home, just replace the executable with a new one.
2. You can use the Docker image from the `edge` tag, which is synced with the repo master branch.
3. You can install AdGuard Home from `beta` or `edge` channels on the Snap Store.

9
client/package-lock.json generated vendored
View File

@@ -10168,9 +10168,9 @@
}
},
"lodash": {
"version": "4.17.15",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
"version": "4.17.19",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
"integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ=="
},
"lodash-es": {
"version": "4.17.15",
@@ -13491,8 +13491,7 @@
},
"kind-of": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
"integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==",
"resolved": "",
"dev": true
}
}

2
client/package.json vendored
View File

@@ -19,7 +19,7 @@
"i18next": "^19.4.4",
"i18next-browser-languagedetector": "^4.2.0",
"ipaddr.js": "^1.9.1",
"lodash": "^4.17.15",
"lodash": "^4.17.19",
"nanoid": "^3.1.9",
"prop-types": "^15.7.2",
"query-string": "^6.13.1",

View File

@@ -28,10 +28,6 @@ body {
}
@media screen and (max-width: 992px) {
.container {
padding: 0 !important;
}
.container--wrap {
min-height: calc(100vh);
}

View File

@@ -21,7 +21,7 @@ const Row = ({
<Trans components={translationComponents}>{label}</Trans>
<Tooltip content={tooltipTitle} placement="top"
className="tooltip-container tooltip-custom--narrow text-center">
<svg className="icons icon--20 icon--lightgray ml-1">
<svg className="icons icon--20 icon--lightgray ml-2">
<use xlinkHref="#question" />
</svg>
</Tooltip>

View File

@@ -62,23 +62,13 @@ class Table extends Component {
showPagination
defaultPageSize={10}
minRows={5}
previousText={
<svg className="icons icon--24 icon--gray">
<use xlinkHref="#arrow-left" />
</svg>}
nextText={
<svg className="icons icon--24 icon--gray">
<use xlinkHref="#arrow-right" />
</svg>}
loadingText={t('loading_table_status')}
pageText=''
ofText=''
ofText="/"
previousText={t('previous_btn')}
nextText={t('next_btn')}
pageText={t('page_table_footer_text')}
rowsText={t('rows_table_footer_text')}
loadingText={t('loading_table_status')}
noDataText={t('rewrite_not_found')}
showPageSizeOptions={false}
showPageJump={false}
renderTotalPagesCount={() => false}
getPaginationProps={() => ({ className: 'custom-pagination' })}
/>
);
}

View File

@@ -128,24 +128,15 @@ class Table extends Component {
columns={this.columns}
showPagination
defaultPageSize={10}
showPageSizeOptions={false}
showPageJump={false}
renderTotalPagesCount={() => false}
loading={loading}
minRows={6}
pageText=''
ofText=''
ofText="/"
previousText={t('previous_btn')}
nextText={t('next_btn')}
pageText={t('page_table_footer_text')}
rowsText={t('rows_table_footer_text')}
loadingText={t('loading_table_status')}
noDataText={whitelist ? t('no_whitelist_added') : t('no_blocklist_added')}
getPaginationProps={() => ({ className: 'custom-pagination' })}
previousText={
<svg className="icons icon--24 icon--gray w-100 h-100">
<use xlinkHref="#arrow-left" />
</svg>}
nextText={
<svg className="icons icon--24 icon--gray w-100 h-100">
<use xlinkHref="#arrow-right" />
</svg>}
/>
);
}

View File

@@ -16,6 +16,7 @@ const getClientCell = ({
const autoClient = autoClients.find((autoClient) => autoClient.name === client);
const source = autoClient?.source;
const whoisAvailable = whois_info && Object.keys(whois_info).length > 0;
const id = nanoid();
@@ -33,7 +34,7 @@ const getClientCell = ({
const isFiltered = checkFiltered(reason);
const nameClass = classNames('w-90 o-hidden d-flex flex-column', {
'mt-2': isDetailed && !name && !whois_info,
'mt-2': isDetailed && !name && !whoisAvailable,
'white-space--nowrap': isDetailed,
});
@@ -78,12 +79,19 @@ const getClientCell = ({
content: processedData,
placement: 'bottom',
})}
<div
className={nameClass}>
<div data-tip={true} data-for={id}>{formatClientCell(row, isDetailed)}</div>
{isDetailed && name
&& !whois_info && <div className="detailed-info d-none d-sm-block logs__text"
title={name}>{name}</div>}
<div className={nameClass}>
<div data-tip={true} data-for={id}>
{formatClientCell(row, isDetailed)}
</div>
{isDetailed && name && !whoisAvailable && (
<div
className="detailed-info d-none d-sm-block logs__text"
title={name}
>
{name}
</div>
)}
</div>
{renderBlockingButton(isFiltered, domain)}
</div>

View File

@@ -38,59 +38,20 @@ const getResponseCell = (row, filtering, t, isDetailed, getFilterName) => {
})}</div>;
};
const FILTERED_STATUS_TO_FIELDS_MAP = {
[FILTERED_STATUS.NOT_FILTERED_NOT_FOUND]: {
encryption_status: boldStatusLabel,
install_settings_dns: upstream,
elapsed: formattedElapsedMs,
response_code: status,
response_table_header: renderResponses(response),
},
[FILTERED_STATUS.FILTERED_BLOCKED_SERVICE]: {
encryption_status: boldStatusLabel,
install_settings_dns: upstream,
elapsed: formattedElapsedMs,
filter,
rule_label: rule,
response_code: status,
original_response: renderResponses(originalResponse),
},
[FILTERED_STATUS.NOT_FILTERED_WHITE_LIST]: {
encryption_status: boldStatusLabel,
install_settings_dns: upstream,
elapsed: formattedElapsedMs,
filter,
rule_label: rule,
response_code: status,
},
[FILTERED_STATUS.NOT_FILTERED_WHITE_LIST]: {
encryption_status: boldStatusLabel,
filter,
rule_label: rule,
response_code: status,
},
[FILTERED_STATUS.FILTERED_SAFE_SEARCH]: {
encryption_status: boldStatusLabel,
install_settings_dns: upstream,
elapsed: formattedElapsedMs,
response_code: status,
response_table_header: renderResponses(response),
},
[FILTERED_STATUS.FILTERED_BLACK_LIST]: {
encryption_status: boldStatusLabel,
filter,
rule_label: rule,
install_settings_dns: upstream,
elapsed: formattedElapsedMs,
response_code: status,
original_response: renderResponses(originalResponse),
},
const COMMON_CONTENT = {
encryption_status: boldStatusLabel,
install_settings_dns: upstream,
elapsed: formattedElapsedMs,
response_code: status,
filter,
rule_label: rule,
response_table_header: renderResponses(response),
original_response: renderResponses(originalResponse),
};
const content = FILTERED_STATUS_TO_FIELDS_MAP[reason]
? Object.entries(FILTERED_STATUS_TO_FIELDS_MAP[reason])
: Object.entries(FILTERED_STATUS_TO_FIELDS_MAP.NotFilteredNotFound);
const content = rule
? Object.entries(COMMON_CONTENT)
: Object.entries({ ...COMMON_CONTENT, filter: '' });
const detailedInfo = isBlocked ? filter : formattedElapsedMs;
return (

View File

@@ -143,10 +143,11 @@ const Form = (props) => {
const normalizeOnBlur = (data) => data.trim();
return (
<form className="d-flex flex-wrap form-control--container"
onSubmit={(e) => {
e.preventDefault();
}}
<form
className="d-flex flex-wrap form-control--container"
onSubmit={(e) => {
e.preventDefault();
}}
>
<div className="field__search">
<Field
@@ -166,13 +167,21 @@ const Form = (props) => {
<Field
name={FORM_NAMES.response_status}
component="select"
className={classNames('form-control custom-select custom-select--logs custom-select__arrow--left ml-small form-control--transparent', responseStatusClass)}
className={classNames('form-control custom-select custom-select--logs custom-select__arrow--left form-control--transparent', responseStatusClass)}
>
{Object.values(RESPONSE_FILTER)
.map(({
query, label, disabled,
}) => <option key={label} value={query}
disabled={disabled}>{t(label)}</option>)}
}) => (
<option
key={label}
value={query}
disabled={disabled}
>
{t(label)}
</option>
))
}
</Field>
</div>
</form>

View File

@@ -58,8 +58,7 @@
width: 100%;
}
.logs__text--wrap,
.logs__text--whois {
.logs__text--wrap {
line-height: 1.4;
white-space: normal;
}
@@ -71,6 +70,7 @@
.logs__text--whois {
line-height: 1.2;
color: #9aa0ac;
}
.logs__row .tooltip-custom {
@@ -362,7 +362,7 @@
display: flex !important;
}
.-pageInfo {
.logs__table .-pageInfo {
--side-size: 2rem;
font-variant-numeric: tabular-nums !important;
background-color: transparent !important;
@@ -376,18 +376,18 @@
align-items: center;
}
.pagination-bottom {
.logs__table .pagination-bottom {
justify-content: center !important;
display: flex !important;
}
.-center:before {
.logs__table .-center:before {
content: '...';
transform: translateY(-0.25rem);
margin: auto;
}
.-center:after {
.logs__table .-center:after {
content: '...';
transform: translateY(-0.25rem);
margin: auto;
@@ -437,12 +437,13 @@
}
.custom-select__arrow--left {
background: #fff url('./chevron-down.svg') no-repeat left 0.2rem center;
background-size: 1.5rem;
background: #fff url('./chevron-down.svg') no-repeat;
background-position: 5px 9px;
background-size: 22px;
}
.custom-select--logs {
padding: 0.5rem 0.75rem 0.5rem 1.75rem !important;
padding: 0.5rem 0.75rem 0.5rem 2rem !important;
}
.bg--danger {
@@ -511,6 +512,8 @@
.field__select {
margin-top: 1.5rem;
padding-left: 24px;
padding-right: 24px;
}
}

View File

@@ -355,7 +355,7 @@ const Table = (props) => {
response_details: 'title',
install_settings_dns: upstream,
elapsed: formattedElapsedMs,
filter: isBlocked ? filter : null,
filter: rule ? filter : null,
rule_label: rule,
response_table_header: response?.join('\n'),
response_code: status,

View File

@@ -10,7 +10,7 @@ import {
BLOCK_ACTIONS,
TABLE_DEFAULT_PAGE_SIZE,
TABLE_FIRST_PAGE,
smallScreenSize,
SMALL_SCREEN_SIZE,
} from '../../helpers/constants';
import Loading from '../ui/Loading';
import Filters from './Filters';
@@ -76,7 +76,7 @@ const Logs = (props) => {
const search = filter?.search || search_url_param;
const response_status = filter?.response_status || response_status_url_param;
const [isSmallScreen, setIsSmallScreen] = useState(window.innerWidth < smallScreenSize);
const [isSmallScreen, setIsSmallScreen] = useState(window.innerWidth < SMALL_SCREEN_SIZE);
const [detailedDataCurrent, setDetailedDataCurrent] = useState({});
const [buttonType, setButtonType] = useState(BLOCK_ACTIONS.BLOCK);
const [isModalOpened, setModalOpened] = useState(false);
@@ -114,7 +114,7 @@ const Logs = (props) => {
},
} = props;
const mediaQuery = window.matchMedia(`(max-width: ${smallScreenSize}px)`);
const mediaQuery = window.matchMedia(`(max-width: ${SMALL_SCREEN_SIZE}px)`);
const mediaQueryHandler = (e) => {
setIsSmallScreen(e.matches);
if (e.matches) {

View File

@@ -85,23 +85,13 @@ class AutoClients extends Component {
showPagination
defaultPageSize={10}
minRows={5}
showPageSizeOptions={false}
showPageJump={false}
renderTotalPagesCount={() => false}
previousText={
<svg className="icons icon--24 icon--gray w-100 h-100">
<use xlinkHref="#arrow-left" />
</svg>}
nextText={
<svg className="icons icon--24 icon--gray w-100 h-100">
<use xlinkHref="#arrow-right" />
</svg>}
loadingText={t('loading_table_status')}
pageText=''
ofText=''
ofText="/"
previousText={t('previous_btn')}
nextText={t('next_btn')}
pageText={t('page_table_footer_text')}
rowsText={t('rows_table_footer_text')}
loadingText={t('loading_table_status')}
noDataText={t('clients_not_found')}
getPaginationProps={() => ({ className: 'custom-pagination' })}
/>
</Card>
);

View File

@@ -297,23 +297,13 @@ class ClientsTable extends Component {
showPagination
defaultPageSize={10}
minRows={5}
showPageSizeOptions={false}
showPageJump={false}
renderTotalPagesCount={() => false}
previousText={
<svg className="icons icon--24 icon--gray w-100 h-100">
<use xlinkHref="#arrow-left" />
</svg>}
nextText={
<svg className="icons icon--24 icon--gray w-100 h-100">
<use xlinkHref="#arrow-right" />
</svg>}
loadingText={t('loading_table_status')}
pageText=''
ofText=''
ofText="/"
previousText={t('previous_btn')}
nextText={t('next_btn')}
pageText={t('page_table_footer_text')}
rowsText={t('rows_table_footer_text')}
loadingText={t('loading_table_status')}
noDataText={t('clients_not_found')}
getPaginationProps={() => ({ className: 'custom-pagination' })}
/>
<button
type="button"

View File

@@ -1,5 +1,11 @@
.tooltip-container {
border: 0;
padding: 0.7rem;
box-shadow: 1px 1px 6px rgba(0, 0, 0, 0.2);
}
.tooltip-custom--narrow {
max-width: 13.75rem;
max-width: 14rem;
}
.tooltip-custom--wide {

View File

@@ -2,7 +2,12 @@ import React from 'react';
import TooltipTrigger from 'react-popper-tooltip';
import propTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { HIDE_TOOLTIP_DELAY } from '../../helpers/constants';
import {
HIDE_TOOLTIP_DELAY,
HIDE_TOOLTIP_CLICK_DELAY,
MEDIUM_SCREEN_SIZE,
} from '../../helpers/constants';
import 'react-popper-tooltip/dist/styles.css';
import './Tooltip.css';
@@ -16,27 +21,42 @@ const Tooltip = ({
delayHide = HIDE_TOOLTIP_DELAY,
}) => {
const { t } = useTranslation();
let triggerValue = trigger;
let delayValue = delayHide;
return <TooltipTrigger
placement={placement}
trigger={trigger}
delayHide={delayHide}
tooltip={({
tooltipRef,
getTooltipProps,
}) => <div {...getTooltipProps({
ref: tooltipRef,
className,
})}>
{typeof content === 'string' ? t(content) : content}
</div>
}>{({ getTriggerProps, triggerRef }) => <span
{...getTriggerProps({
ref: triggerRef,
className: triggerClass,
})}
>{children}</span>}
</TooltipTrigger>;
if (window.matchMedia(`(max-width: ${MEDIUM_SCREEN_SIZE}px)`).matches) {
triggerValue = 'click';
delayValue = HIDE_TOOLTIP_CLICK_DELAY;
}
return (
<TooltipTrigger
placement={placement}
trigger={triggerValue}
delayHide={delayValue}
tooltip={({ tooltipRef, getTooltipProps }) => (
<div
{...getTooltipProps({
ref: tooltipRef,
className,
})}
>
{typeof content === 'string' ? t(content) : content}
</div>
)}
>
{({ getTriggerProps, triggerRef }) => (
<span
{...getTriggerProps({
ref: triggerRef,
className: triggerClass,
})}
>
{children}
</span>
)}
</TooltipTrigger>
);
};
Tooltip.propTypes = {

View File

@@ -1,13 +1,23 @@
export const R_URL_REQUIRES_PROTOCOL = /^https?:\/\/[^/\s]+(\/.*)?$/;
export const R_HOST = /^(\*\.)?([\w-]+\.)+[\w-]+$/;
// matches hostname or *.wildcard
export const R_HOST = /^(\*\.)?[\w.-]+$/;
export const R_IPV4 = /^(?:(?:^|\.)(?:2(?:5[0-5]|[0-4]\d)|1?\d?\d)){4}$/;
export const R_IPV6 = /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/;
export const R_CIDR = /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$/;
export const R_MAC = /^((([a-fA-F0-9][a-fA-F0-9]+[-]){5}|([a-fA-F0-9][a-fA-F0-9]+[:]){5})([a-fA-F0-9][a-fA-F0-9])$)|(^([a-fA-F0-9][a-fA-F0-9][a-fA-F0-9][a-fA-F0-9]+[.]){2}([a-fA-F0-9][a-fA-F0-9][a-fA-F0-9][a-fA-F0-9]))$/;
export const R_CIDR_IPV6 = /^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(\/(12[0-8]|1[0-1][0-9]|[1-9][0-9]|[0-9]))$/;
export const R_PATH_LAST_PART = /\/[^/]*$/;
// eslint-disable-next-line no-control-regex
export const R_UNIX_ABSOLUTE_PATH = /^(\/[^/\x00]+)+$/;
// eslint-disable-next-line no-control-regex
export const R_WIN_ABSOLUTE_PATH = /^([a-zA-Z]:)?(\\|\/)(?:[^\\/:*?"<>|\x00]+\\)*[^\\/:*?"<>|\x00]*$/;
@@ -62,6 +72,7 @@ export const CHECK_TIMEOUT = 1000;
export const SUCCESS_TOAST_TIMEOUT = 5000;
export const FAILURE_TOAST_TIMEOUT = 30000;
export const HIDE_TOOLTIP_DELAY = 300;
export const HIDE_TOOLTIP_CLICK_DELAY = 100;
export const MODAL_OPEN_TIMEOUT = 150;
export const UNSAFE_PORTS = [
@@ -288,50 +299,6 @@ export const WHOIS_ICONS = {
descr: '',
};
export const DNS_RECORD_TYPES = [
'A',
'AAAA',
'AFSDB',
'APL',
'CAA',
'CDNSKEY',
'CDS',
'CERT',
'CNAME',
'CSYNC',
'DHCID',
'DLV',
'DNAME',
'DNSKEY',
'DS',
'HIP',
'IPSECKEY',
'KEY',
'KX',
'LOC',
'MX',
'NAPTR',
'NS',
'NSEC',
'NSEC3',
'NSEC3PARAM',
'OPENPGPKEY',
'PTR',
'RRSIG',
'RP',
'SIG',
'SMIMEA',
'SOA',
'SRV',
'SSHFP',
'TA',
'TKEY',
'TLSA',
'TSIG',
'TXT',
'URI',
];
export const DEFAULT_LOGS_FILTER = {
search: '',
response_status: '',
@@ -339,7 +306,7 @@ export const DEFAULT_LOGS_FILTER = {
export const DEFAULT_LANGUAGE = 'en';
export const TABLE_DEFAULT_PAGE_SIZE = 50;
export const TABLE_DEFAULT_PAGE_SIZE = 25;
export const TABLE_FIRST_PAGE = 0;
@@ -516,6 +483,7 @@ export const FORM_NAME = {
CACHE: 'cache',
};
export const smallScreenSize = 767;
export const SMALL_SCREEN_SIZE = 767;
export const MEDIUM_SCREEN_SIZE = 1023;
export const SECONDS_IN_HOUR = 60 * 60;

View File

@@ -31,10 +31,11 @@ export const formatClientCell = (row, isDetailed = false, isLogs = true) => {
if (info) {
const { name, whois_info } = info;
const whoisAvailable = whois_info && Object.keys(whois_info).length > 0;
if (name) {
if (isLogs) {
nameContainer = !whois_info && isDetailed
nameContainer = !whoisAvailable && isDetailed
? (
<small title={value}>{value}</small>
) : (
@@ -54,7 +55,7 @@ export const formatClientCell = (row, isDetailed = false, isLogs = true) => {
}
}
if (whois_info && isDetailed) {
if (whoisAvailable && isDetailed) {
whoisContainer = (
<div className="logs__text logs__text--wrap logs__text--whois">
{getFormattedWhois(whois_info)}

View File

@@ -5,7 +5,6 @@ import subHours from 'date-fns/sub_hours';
import addHours from 'date-fns/add_hours';
import addDays from 'date-fns/add_days';
import subDays from 'date-fns/sub_days';
import isSameDay from 'date-fns/is_same_day';
import round from 'lodash/round';
import axios from 'axios';
import i18n from 'i18next';
@@ -20,7 +19,6 @@ import {
DEFAULT_LANGUAGE,
DEFAULT_TIME_FORMAT,
DETAILED_DATE_FORMAT_OPTIONS,
DNS_RECORD_TYPES,
FILTERED,
FILTERED_STATUS,
IP_MATCH_LIST_STATUS,
@@ -31,6 +29,7 @@ import {
/**
* @param time {string} The time to format
* @param options {string}
* @returns {string} Returns the time in the format HH:mm:ss
*/
export const formatTime = (time, options = DEFAULT_TIME_FORMAT) => {
@@ -60,12 +59,6 @@ export const formatDetailedDateTime = (dateTime) => formatDateTime(
dateTime, DETAILED_DATE_FORMAT_OPTIONS,
);
/**
* @param date {string}
* @returns {boolean}
*/
export const isToday = (date) => isSameDay(new Date(date), new Date());
export const normalizeLogs = (logs) => logs.map((log) => {
const {
answer,
@@ -351,39 +344,6 @@ export const normalizeTopClients = (topClients) => topClients.reduce(
},
);
export const getClientInfo = (clients, ip) => {
const client = clients
.find((item) => item.ip_addrs?.find((clientIp) => clientIp === ip));
if (!client) {
return '';
}
const { name, whois_info } = client;
const whois = Object.keys(whois_info).length > 0 ? whois_info : '';
return {
name,
whois,
};
};
export const getAutoClientInfo = (clients, ip) => {
const client = clients.find((item) => ip === item.ip);
if (!client) {
return '';
}
const { name, whois_info } = client;
const whois = Object.keys(whois_info).length > 0 ? whois_info : '';
return {
name,
whois,
};
};
export const sortClients = (clients) => {
const compare = (a, b) => {
const nameA = a.name.toUpperCase();
@@ -443,8 +403,6 @@ export const normalizeWhois = (whois) => {
return whois;
};
export const isValidQuestionType = (type) => type && DNS_RECORD_TYPES.includes(type.toUpperCase());
export const getPathWithQueryString = (path, params) => {
const searchParams = new URLSearchParams(params);
@@ -542,10 +500,10 @@ export const getMap = (arr, key, value) => arr.reduce((acc, curr) => {
/**
* @param parsedIp {object} ipaddr.js IPv4 or IPv6 object
* @param cidr {array} ipaddr.js CIDR array
* @param parsedCidr {array} ipaddr.js CIDR array
* @returns {boolean}
*/
export const isIpMatchCidr = (parsedIp, parsedCidr) => {
const isIpMatchCidr = (parsedIp, parsedCidr) => {
try {
const cidrIpVersion = parsedCidr[0].kind();
const ipVersion = parsedIp.kind();
@@ -556,6 +514,75 @@ export const isIpMatchCidr = (parsedIp, parsedCidr) => {
}
};
/**
* The purpose of this method is to quickly check
* if this IP can possibly be in the specified CIDR range.
*
* @param ip {string}
* @param listItem {string}
* @returns {boolean}
*/
const isIpQuickMatchCIDR = (ip, listItem) => {
const ipv6 = ip.indexOf(':') !== -1;
const cidrIpv6 = listItem.indexOf(':') !== -1;
if (ipv6 !== cidrIpv6) {
// CIDR is for a different IP type
return false;
}
if (cidrIpv6) {
// We don't do quick check for IPv6 addresses
return true;
}
const idx = listItem.indexOf('/');
if (idx === -1) {
// Not a CIDR, return false immediately
return false;
}
const cidrIp = listItem.substring(0, idx);
const cidrRange = parseInt(listItem.substring(idx + 1), 10);
if (Number.isNaN(cidrRange)) {
// Not a valid CIDR
return false;
}
const parts = cidrIp.split('.');
if (parts.length !== 4) {
// Invalid IP, return immediately
return false;
}
// Now depending on the range we check if the IP can possibly be in that range
if (cidrRange < 8) {
// Use the slow approach
return true;
}
if (cidrRange < 16) {
// Check the first part
// Example: 0.0.0.0/8 matches 0.*.*.*
return ip.indexOf(`${parts[0]}.`) === 0;
}
if (cidrRange < 24) {
// Check the first two parts
// Example: 0.0.0.0/16 matches 0.0.*.*
return ip.indexOf(`${parts[0]}.${parts[1]}.`) === 0;
}
if (cidrRange <= 32) {
// Check the first two parts
// Example: 0.0.0.0/16 matches 0.0.*.*
return ip.indexOf(`${parts[0]}.${parts[1]}.${parts[2]}.`) === 0;
}
// range for IPv4 CIDR cannot be more than 32
// no need to check further, this CIDR is invalid
return false;
};
/**
* @param ip {string}
* @param list {string}
@@ -573,20 +600,29 @@ export const getIpMatchListStatus = (ip, list) => {
for (let i = 0; i < listArr.length; i += 1) {
const listItem = listArr[i];
const parsedIp = ipaddr.parse(ip);
const isItemAnIp = ipaddr.isValid(listItem);
const parsedItem = isItemAnIp ? ipaddr.parse(listItem) : ipaddr.parseCIDR(listItem);
if (isItemAnIp && parsedIp.toString() === parsedItem.toString()) {
if (ip === listItem.trim()) {
return IP_MATCH_LIST_STATUS.EXACT;
}
if (!isItemAnIp && isIpMatchCidr(parsedIp, parsedItem)) {
return IP_MATCH_LIST_STATUS.CIDR;
// Using ipaddr.js is quite slow so we first do a quick check
// to see if it's possible that this IP may be in the specified CIDR range
if (isIpQuickMatchCIDR(ip, listItem)) {
const parsedIp = ipaddr.parse(ip);
const isItemAnIp = ipaddr.isValid(listItem);
const parsedItem = isItemAnIp ? ipaddr.parse(listItem) : ipaddr.parseCIDR(listItem);
if (isItemAnIp && parsedIp.toString() === parsedItem.toString()) {
return IP_MATCH_LIST_STATUS.EXACT;
}
if (!isItemAnIp && isIpMatchCidr(parsedIp, parsedItem)) {
return IP_MATCH_LIST_STATUS.CIDR;
}
}
}
return IP_MATCH_LIST_STATUS.NOT_FOUND;
} catch (e) {
console.error(e);
return IP_MATCH_LIST_STATUS.NOT_FOUND;
}
};

View File

@@ -670,6 +670,11 @@ func TestRewrite(t *testing.T) {
assert.True(t, ok)
assert.Equal(t, "1.2.3.4", a.A.String())
req = createTestMessageWithType("test.com.", dns.TypeAAAA)
reply, err = dns.Exchange(req, addr.String())
assert.Nil(t, err)
assert.Equal(t, 0, len(reply.Answer))
req = createTestMessageWithType("alias.test.com.", dns.TypeA)
reply, err = dns.Exchange(req, addr.String())
assert.Nil(t, err)

View File

@@ -54,8 +54,12 @@ func (s *Server) filterDNSRequest(ctx *dnsContext) (*dnsfilter.Result, error) {
// log.Tracef("Host %s is filtered, reason - '%s', matched rule: '%s'", host, res.Reason, res.Rule)
d.Res = s.genDNSFilterMessage(d, &res)
} else if (res.Reason == dnsfilter.ReasonRewrite || res.Reason == dnsfilter.RewriteEtcHosts) &&
len(res.IPList) != 0 {
} else if res.Reason == dnsfilter.ReasonRewrite && len(res.CanonName) != 0 && len(res.IPList) == 0 {
ctx.origQuestion = d.Req.Question[0]
// resolve canonical name, not the original host name
d.Req.Question[0].Name = dns.Fqdn(res.CanonName)
} else if res.Reason == dnsfilter.ReasonRewrite || res.Reason == dnsfilter.RewriteEtcHosts {
resp := s.makeResponse(req)
name := host
@@ -78,11 +82,6 @@ func (s *Server) filterDNSRequest(ctx *dnsContext) (*dnsfilter.Result, error) {
d.Res = resp
} else if res.Reason == dnsfilter.ReasonRewrite && len(res.CanonName) != 0 {
ctx.origQuestion = d.Req.Question[0]
// resolve canonical name, not the original host name
d.Req.Question[0].Name = dns.Fqdn(res.CanonName)
} else if res.Reason == dnsfilter.RewriteEtcHosts && len(res.ReverseHost) != 0 {
resp := s.makeResponse(req)

2
go.mod
View File

@@ -5,7 +5,7 @@ go 1.14
require (
github.com/AdguardTeam/dnsproxy v0.29.1
github.com/AdguardTeam/golibs v0.4.2
github.com/AdguardTeam/urlfilter v0.11.0
github.com/AdguardTeam/urlfilter v0.11.2
github.com/NYTimes/gziphandler v1.1.1
github.com/fsnotify/fsnotify v1.4.7
github.com/gobuffalo/packr v1.30.1

4
go.sum
View File

@@ -4,8 +4,8 @@ github.com/AdguardTeam/golibs v0.4.0/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKU
github.com/AdguardTeam/golibs v0.4.2 h1:7M28oTZFoFwNmp8eGPb3ImmYbxGaJLyQXeIFVHjME0o=
github.com/AdguardTeam/golibs v0.4.2/go.mod h1:skKsDKIBB7kkFflLJBpfGX+G8QFTx0WKUzB6TIgtUj4=
github.com/AdguardTeam/gomitmproxy v0.2.0/go.mod h1:Qdv0Mktnzer5zpdpi5rAwixNJzW2FN91LjKJCkVbYGU=
github.com/AdguardTeam/urlfilter v0.11.0 h1:tgZss6uZs1UZAaxpovD/QuX+VVIQLDOlKc7rdF8dwNw=
github.com/AdguardTeam/urlfilter v0.11.0/go.mod h1:aMuejlNxpWppOVjiEV87X6z0eMf7wsXHTAIWQuylfZY=
github.com/AdguardTeam/urlfilter v0.11.2 h1:gCrWGh63Yqw3z4yi9pgikfsbshIEyvAu/KYV3MvTBlc=
github.com/AdguardTeam/urlfilter v0.11.2/go.mod h1:aMuejlNxpWppOVjiEV87X6z0eMf7wsXHTAIWQuylfZY=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=

View File

@@ -242,7 +242,7 @@ func applyAdditionalFiltering(clientAddr string, setts *dnsfilter.RequestFilteri
return
}
log.Debug("Using settings for client with IP %s", clientAddr)
log.Debug("Using settings for client %s with IP %s", c.Name, clientAddr)
if c.UseOwnBlockedServices {
Context.dnsFilter.ApplyBlockedServices(setts, c.BlockedServices, false)

View File

@@ -2,12 +2,22 @@
## v0.103: API changes
### API: replace settings in GET /control/dns_info & POST /control/dns_config
* added "upstream_mode"
"upstream_mode": "" | "parallel" | "fastest_addr"
* removed "fastest_addr", "parallel_requests"
### API: Get querylog: GET /control/querylog
* Added optional "offset" and "limit" parameters
We are still using "older_than" approach in AdGuard Home UI, but we realize that it's easier to use offset/limit so here is this option now.
## v0.102: API changes
### API: Get general status: GET /control/status