Compare commits

..

83 Commits

Author SHA1 Message Date
Simon Zolin
f85de51452 MITM proxy 2020-08-18 19:23:33 +03:00
Simon Zolin
c3123473cf * DNS: resolve host names from DHCP: improve
#1956

Squashed commit of the following:

commit efeacd92b8b82a9a0a0cce8c5648f2d024b4bc9e
Author: Simon Zolin <s.zolin@adguard.com>
Date:   Tue Aug 18 13:54:15 2020 +0300

    * DNS: resolve host names from DHCP: improve

    . Require a valid host name from DHCP lease
    . Use lower-case names
2020-08-18 17:40:36 +03:00
Simon Zolin
8d0c8ad438 + DNS: resolve host names to IP addresses leased by AGH DHCP server
Close #1956

Squashed commit of the following:

commit 21f11632c871e9c17faa77f9cd6a7aa836559779
Author: Simon Zolin <s.zolin@adguard.com>
Date:   Mon Aug 17 19:54:24 2020 +0300

    + DNS: resolve host names to IP addresses leased by AGH DHCP server
2020-08-18 12:36:52 +03:00
Andrey Meshkov
1e2e965ea7 Merge branch 'master' of ssh://bit.adguard.com:7999/dns/adguard-home 2020-08-14 19:30:44 +03:00
Andrey Meshkov
0b539ced92 * : different version url for edge builds
Closes: #2023
2020-08-14 19:30:40 +03:00
Simon Zolin
9e09dffbc3 - dns: limit the number of active goroutines for incoming requests processing
Close #2015

Squashed commit of the following:

commit 90ba06f1fce22a452b4d61db62bd950b976debd1
Merge: 9494b29b 473d8818
Author: Simon Zolin <s.zolin@adguard.com>
Date:   Fri Aug 14 19:14:26 2020 +0300

    Merge remote-tracking branch 'origin/master' into max-go

commit 9494b29b65ae8fe593a487984bed051aa78e4ff9
Author: Simon Zolin <s.zolin@adguard.com>
Date:   Fri Aug 14 17:03:00 2020 +0300

    + max_goroutines setting

commit 87071a5e0ed43be192a7755fb25764cd4519da5a
Author: Simon Zolin <s.zolin@adguard.com>
Date:   Fri Aug 14 15:29:00 2020 +0300

    - dns: limit the number of active goroutines for incoming requests processing
2020-08-14 19:27:36 +03:00
Andrey Meshkov
473d881871 Merge: - client: Fix DNS settings
Merge in DNS/adguard-home from fix/2021 to master

* commit 'a1ca7862f84b07bde441f4578b1deb025ff79660':
  Fix selector
  - client: Fix DNS settings
2020-08-13 19:09:17 +03:00
ArtemBaskal
a1ca7862f8 Fix selector 2020-08-13 18:15:14 +03:00
ArtemBaskal
8ea1e64c7b - client: Fix DNS settings 2020-08-13 18:12:27 +03:00
Artem Baskal
2f8e34e73b Pull request 734: + client: 1778 Add ip sort function, write unit tests
Close #1778

Squashed commit of the following:

commit ba63e147311799b17deaa97d7a12c2e0ec44a2ed
Merge: 143ba427 705a9d90
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Thu Aug 13 12:00:10 2020 +0300

    Merge branch 'master' into feature/1778

commit 143ba42707da3d7eece9f3e137a8b245f7f7888f
Merge: 483d2ff9 97df1989
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Thu Aug 13 11:16:46 2020 +0300

    Merge branch 'master' into feature/1778

commit 483d2ff9fa3de706d94a647701f1d26a3bcbb217
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Wed Aug 12 13:34:21 2020 +0300

    Always put ipv4 before ipv6 in sort, add invalid input unit tests

commit 26eb41b313785fe7ffaf98a7573cc5eef0dca14f
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Wed Aug 12 11:27:46 2020 +0300

    Rewrite tests: replace ipv4-mapped adresses

commit 4ecf287fd46945effe9ff11cfc9ae49217b9c796
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Tue Aug 11 19:05:15 2020 +0300

    Minor fix

commit 3e5e2a6bb1f2f166619da54e5ade0904fe22a20d
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Tue Aug 11 19:01:26 2020 +0300

    + client: 1778 Add ip sort function, write unit tests
2020-08-13 12:16:52 +03:00
Simon Zolin
705a9d909d * SB/PC: use 4-character hash in request
* use hash prefix as the cache key

Squashed commit of the following:

commit d719a84ee9b9cf43aaab4f53d07451645ea836db
Merge: d9d6d443 97df1989
Author: Simon Zolin <s.zolin@adguard.com>
Date:   Thu Aug 13 11:41:28 2020 +0300

    Merge remote-tracking branch 'origin/master' into sbpc

commit d9d6d44376c44959f2216b08e577d8e5c5f65bff
Merge: 0a8b2483 de92c852
Author: Simon Zolin <s.zolin@adguard.com>
Date:   Thu Aug 13 11:23:14 2020 +0300

    Merge remote-tracking branch 'origin/master' into sbpc

commit 0a8b24839683683a9d327ecf57a7d182b3996b1d
Merge: 0255a24a 9b9902f0
Author: Simon Zolin <s.zolin@adguard.com>
Date:   Wed Aug 12 20:21:22 2020 +0300

    Merge remote-tracking branch 'origin/master' into sbpc

commit 0255a24a191efd2e4ef23d6a00a7a9fed8831730
Author: Simon Zolin <s.zolin@adguard.com>
Date:   Wed Aug 12 17:16:57 2020 +0300

    - TestServerCustomClientUpstream(): fix

commit d2311902f887be9621a9d9312c73f899dd269440
Author: Simon Zolin <s.zolin@adguard.com>
Date:   Wed Aug 12 17:07:12 2020 +0300

    * SB/PC: hard-code Family server IP addresses to prevent from requesting them at runtime

commit ee340108f11f98d49a7af2a7e8a228c25ab1537a
Author: Simon Zolin <s.zolin@adguard.com>
Date:   Wed Aug 12 17:05:57 2020 +0300

    * dnsproxy v0.30.1

commit f5f53ba7116ad525204d00b80352202eee88b78c
Author: Simon Zolin <s.zolin@adguard.com>
Date:   Wed Aug 12 14:53:23 2020 +0300

    minor

commit fb4631e2cd570b0fd5ae26ec2b1890361275a5a8
Author: Simon Zolin <s.zolin@adguard.com>
Date:   Mon Aug 10 20:07:27 2020 +0300

    * SB/PC: implement new cache

commit f9f58461a6efbcfacd798f7640a4645cf1971cb2
Author: Simon Zolin <s.zolin@adguard.com>
Date:   Fri Aug 7 19:31:05 2020 +0300

    doc

commit ed69626a6c119ab1a3b187f5afbd4cef708c3159
Author: Simon Zolin <s.zolin@adguard.com>
Date:   Fri Aug 7 18:25:57 2020 +0300

    * SB/PC: use hostname prefix for cache

commit afa8040c8c0836c7e59e6fb9aaf1caccd132ea8f
Author: Simon Zolin <s.zolin@adguard.com>
Date:   Fri Jul 31 11:19:49 2020 +0300

    * SB/PC: use 4-character hash in request
2020-08-13 11:49:42 +03:00
Artem Baskal
97df19898f Pull request #730: + client: Add Hot Module Replacement
Merge in DNS/adguard-home from feature/hmr to master

Squashed commit of the following:

commit 952ed1955c2a7a32446d99489f137f02eb47c99e
Merge: 83484931 de92c852
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Thu Aug 13 11:02:10 2020 +0300

    Merge branch 'master' into feature/hmr

commit 8348493105d7d63d8b0836a5c272df2b17a6b142
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Wed Aug 5 15:07:31 2020 +0300

    Remove empty prop types, remove Services empty container

commit b2fe4a30b79d91e482318ee5deea8e49c7038f7e
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Wed Aug 5 13:56:35 2020 +0300

    Move constants

commit f8be4c18c35193ad77bf5e25f311ad834c1d6870
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Wed Aug 5 13:19:02 2020 +0300

    Fix Setup bug, update webpack.dev

commit 1d9cc4ddf8af2c979eb707a7f0fc06744eec186c
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Wed Aug 5 12:10:38 2020 +0300

    Review changes

commit a1edb21358def21ed1808b081ffc2f0b6755e3da
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Wed Aug 5 11:46:58 2020 +0300

    Remove lazy loading, fix updated components

commit 0aa2cf55f8d4206ac9e2f99fc1b990ed8a9c7825
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Tue Aug 4 20:32:19 2020 +0300

    Refactor App component, add lazy loading

commit 3c2ba4772a91ff7b06641dba6c6bf3fdcd2fdf7f
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Tue Aug 4 17:12:41 2020 +0300

    Simplify App hot loading boilerplate, setup lazy loading, update Header

commit 8df3221f315372b066f2ac0c9a1687f1677b8415
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Tue Aug 4 15:16:06 2020 +0300

    + client: Add Hot Module Replacement
2020-08-13 11:15:45 +03:00
Artem Baskal
de92c85256 Pull request #726: - client: 1954 Make menu items position stable
- client: 1954 Make menu items position stable
Close #1954

Squashed commit of the following:

commit 24bc6faa1e45cef79e3ba83ad5d595c305e0c816
Merge: a4b07aae d3f5b407
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Thu Aug 13 10:51:47 2020 +0300

    Merge branch 'master' into fix/1954

commit a4b07aae4b3b56d60cc95f669e6c179659d904ce
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Wed Aug 5 15:20:26 2020 +0300

    Review changes

commit 250cdc9b10f93664ed2c1f53d57295dba78e6a99
Merge: 32003f19 39f2d5c4
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Wed Aug 5 15:18:40 2020 +0300

    Merge branch 'master' into fix/1954

commit 32003f19c6e2dda056fa6ae51f6721ea350016d1
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Mon Aug 3 13:36:23 2020 +0300

    - client: 1954 Make menu items position stable
2020-08-13 11:01:23 +03:00
Andrey Meshkov
d3f5b40700 Merge: + Makefile: sign release binaries with gpg
Merge in DNS/adguard-home from sign-binaries-v2 to master

* commit '20a0ba5f608c18eff4022279d8ce2791aca97d1b':
  + Makefile: sign release binaries with gpg
2020-08-12 22:51:06 +03:00
Simon Zolin
20a0ba5f60 + Makefile: sign release binaries with gpg
.tar.gz and .zip archives now contain one more file - .sig

UNIX:
./AdGuardHome/:
 ./AdGuardHome/AdGuardHome
 ./AdGuardHome/AdGuardHome.sig
 ...

Windows:
./AdGuardHome/:
 ./AdGuardHome/AdGuardHome.exe
 ./AdGuardHome/AdGuardHome.exe.sig
 ...
2020-08-12 20:18:21 +03:00
Simon Zolin
9b9902f004 * Revert "Update blocked_services.go"
Squashed commit of the following:

commit 567bb4671ddb4f51c13e51094f96e870212137c8
Author: Simon Zolin <s.zolin@adguard.com>
Date:   Tue Aug 11 19:29:31 2020 +0300

    Revert "Update blocked_services.go"

    This reverts commit 4ca4fb8a11.
2020-08-12 13:03:17 +03:00
Andrey Meshkov
020a30fb6d Merge: * locales: update
Merge in DNS/adguard-home from locales to master

* commit '8d10a269edebb007f49123e96f9f2c9f1834f4aa':
  update ru
  * locales: update
2020-08-07 18:31:31 +03:00
Simon Zolin
39f2d5c4ae * readme: update Prerequisites section
Close #1989

* commit '6ce3c52456feb6f13be976e03c42fa87aa26b099':
  * readme: update Prerequisites section
2020-08-04 15:29:09 +03:00
graysky
6ce3c52456 * readme: update Prerequisites section 2020-08-04 14:30:01 +03:00
Simon Zolin
5188da60cf * Makefile: support running with multiple parallel jobs (e.g. -j8)
* commit 'e57cbc36d944b6c4ebe99f397eb55976d786d5f8':
  * Makefile: support running with multiple parallel jobs (e.g. -j8)
2020-08-03 20:08:31 +03:00
Simon Zolin
e57cbc36d9 * Makefile: support running with multiple parallel jobs (e.g. -j8) 2020-08-03 19:13:51 +03:00
Ildar Kamalov
57e43a66c3 Merge: - client: fix log filters and guide tab styles
Closes #1951

* commit '9fecab8675b3496cb6fa5f03084e0be7c364a263':
  - client: fix log filters styles
  - client: fix guide tab styles
2020-08-03 15:45:53 +03:00
Ildar Kamalov
4f3d503916 Merge: - client: fix query log on edge legacy
Closes #1976

* commit '552280e9a36a15c7c2347af75d97fe2943f80093':
  - client: fix query log not load on edge legacy
2020-08-03 15:45:31 +03:00
Ildar Kamalov
6bb6c700d6 Merge: - client: fix empty log error
Closes #1983

* commit '3ff0c964dc05c0b393b6b7140cf59bffb8f70806':
  - client: fix empty log error
2020-08-03 15:44:55 +03:00
Ildar Kamalov
ed76a3cb8b Merge: - client: check touch events for tooltips
Closes #1922

* commit 'ce21514246dfc89f1178d9346769317f1fff4d05':
  - client: check touch events for tooltips
  - client: tooltip show delay
2020-08-03 15:41:46 +03:00
Simon Zolin
335d62b08e - "set_url": couldn't set a new path for filter local file
Close #1984

* commit '99625da1e44b4e6686f3c4b29526949ef471a100':
  - "set_url": couldn't set a new path for filter local file
2020-08-03 15:19:31 +03:00
Simon Zolin
99625da1e4 - "set_url": couldn't set a new path for filter local file 2020-08-03 14:09:47 +03:00
Ildar Kamalov
9fecab8675 - client: fix log filters styles 2020-08-01 17:13:48 +03:00
Ildar Kamalov
b9aa969a56 - client: fix guide tab styles 2020-08-01 17:12:38 +03:00
Ildar Kamalov
ce21514246 - client: check touch events for tooltips 2020-08-01 16:25:56 +03:00
Ildar Kamalov
3ff0c964dc - client: fix empty log error 2020-08-01 16:14:50 +03:00
Ildar Kamalov
552280e9a3 - client: fix query log not load on edge legacy 2020-08-01 15:35:57 +03:00
Ildar Kamalov
d154456ae5 - client: tooltip show delay 2020-08-01 13:38:26 +03:00
Andrey Meshkov
3cecd6f090 added more info about contribution 2020-07-31 11:31:47 +03:00
Simon Zolin
dc1fc82b9e * client: Corrects the provided homepage for "The Big List of Hacked Malware Web Sites"
Close #1940 Close #1948

* commit 'cecf84836494a432c59157295c1aaf8663df1a42':
  * client: Corrects the provided homepage for "The Big List of Hacked Malware Web Sites"
2020-07-31 10:17:37 +03:00
Simon Zolin
a033b68bfd * Update blocked_services.go
Close #1863

* commit '4ca4fb8a113dda47336038ced905fa87a9fa380f':
  Update blocked_services.go
2020-07-31 10:14:14 +03:00
mHatsune
4ca4fb8a11 Update blocked_services.go 2020-07-30 14:25:01 +03:00
Simon Zolin
8d10a269ed update ru 2020-07-27 13:13:14 +03:00
Simon Zolin
a536357427 * locales: update 2020-07-27 12:59:08 +03:00
Imre Kristoffer Eilertsen
cecf848364 * client: Corrects the provided homepage for "The Big List of Hacked Malware Web Sites" 2020-07-27 12:28:30 +03:00
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
Andrey Meshkov
01957bf503 * updater: cut 'v' prefix when comparing SelfUpdateMinVersion
Fix #1908

* commit '1e5419714ddcaab0913162460474dea8959529be':
  * (ui): fix the version check - strip the v prefix
  * updater: cut 'v' prefix when comparing SelfUpdateMinVersion
2020-07-23 13:21:12 +03:00
Andrey Meshkov
1e5419714d * (ui): fix the version check - strip the v prefix 2020-07-23 12:27:14 +03:00
Simon Zolin
4f4a688ee6 * updater: cut 'v' prefix when comparing SelfUpdateMinVersion 2020-07-23 12:17:20 +03:00
Andrey Meshkov
ccf5494f67 Fix edge channel version string 2020-07-22 21:29:22 +03:00
Andrey Meshkov
f2edcca54b *(global): update anti-ad filter URL
Closes: #1903
2020-07-22 20:59:42 +03:00
Andrey Meshkov
b4aa791513 Merge: Refactor auto-update
Merge in DNS/adguard-home from feature-auto-update to master

* commit '6d5d1833111f65a69b208c609eda548b15db3606':
  Fix minor issues
  * use 'update' package
  minor
  + update package: perform update tasks
  added version cache
  Auto-update interface
2020-07-22 20:38:21 +03:00
Andrey Meshkov
6d5d183311 Fix minor issues 2020-07-22 20:27:20 +03:00
Simon Zolin
e3ea2528be * use 'update' package 2020-07-22 14:20:14 +03:00
Simon Zolin
117ec4dd43 minor 2020-07-21 19:25:29 +03:00
Simon Zolin
0cc0aec5b3 + update package: perform update tasks 2020-07-21 19:10:39 +03:00
Andrey Meshkov
3c53a2162c added version cache 2020-07-21 12:17:23 +03:00
Andrey Meshkov
1bb183c2aa Auto-update interface 2020-07-20 21:14:07 +03:00
Andrey Meshkov
62ccd3fb41 rollback file unpack 2020-07-20 20:15:05 +03:00
Andrey Meshkov
a409cdc2bb fix tar.gz update 2020-07-20 20:04:54 +03:00
Andrey Meshkov
e0aa24e2b7 fix update check 2020-07-20 18:04:44 +03:00
Andrey Meshkov
0662769696 Fix version change check 2020-07-20 17:29:13 +03:00
Andrey Meshkov
dc237f10a8 Merge: Fix unpacking tar files
* commit '793194db67a9b5f8440f77863a170375d3c7612f':
  Fix unpacking tar files
2020-07-20 17:27:15 +03:00
Andrey Meshkov
4fef0c32cb Merge: Update locales
Squashed commit of the following:

commit 4d14f3a3f483a17ddd6aee3e082560d81802e5bc
Author: Andrey Meshkov <am@adguard.com>
Date:   Mon Jul 20 15:46:54 2020 +0300

    update locales

commit 5d70bef97b98f4c7d8c360db2e0d8c1ce8200b8f
Author: Andrey Meshkov <am@adguard.com>
Date:   Mon Jul 20 15:41:41 2020 +0300

    update locales

commit 35b28b1e606885853e55fa8bcf6693e7c07f6151
Merge: 051f6263 c131ac44
Author: Andrey Meshkov <am@adguard.com>
Date:   Mon Jul 20 15:35:07 2020 +0300

    Merge branch 'master' into feature/update_locales

commit 051f62630f33ca06d814d7e646f58a3e0cdaa074
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Fri Jul 17 17:31:51 2020 +0300

    + client: Update locales

commit 7c88f1fbf5721098e92a3765b96b2f169afc7d43
Merge: 6d743bb1 c1e56c83
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Fri Jul 17 17:29:47 2020 +0300

    Merge branch 'master' into feature/update_locales

commit 6d743bb15974a85fedb004a3c639b1f849756e2d
Merge: 1f6c5091 7d2c7a61
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Fri Jul 17 17:10:51 2020 +0300

    Merge branch 'master' into feature/update_locales

commit 1f6c50915e63c18834d63d3f8535d24a4e894647
Merge: 1a861a98 4df02714
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Thu Jul 16 11:23:08 2020 +0300

    Merge branch 'master' into feature/update_locales

commit 1a861a98326a749a347c184bb2b9aff663b3dd09
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Thu Jul 16 11:22:47 2020 +0300

    Update locales

commit 3f4850822c95b1147f39df939eca568f13b2c0dc
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Wed Jul 15 16:07:40 2020 +0300

    Update locales

commit 0f06bd8f188130e97394c8f20f27b66f263612cc
Merge: 5abae336 38366ba8
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Wed Jul 15 12:59:22 2020 +0300

    Merge branch 'master' into feature/update_locales

commit 5abae33603e84dc410f77e7ec622314e85e03951
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Wed Jul 15 12:57:36 2020 +0300

    Update locales

commit 717455ecf42135077a7dae31281b1da0d89b27e3
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Mon Jul 13 17:45:15 2020 +0300

    Rename label

commit b399ef33eed3c0447d5c651d3b678d482dddbb5b
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Mon Jul 13 17:43:08 2020 +0300

    Replace 'All responses' with 'All queries'

commit 1f5a62a3d95906057c862b55d2ba91d67b346604
Author: ArtemBaskal <a.baskal@adguard.com>
Date:   Mon Jul 13 16:18:54 2020 +0300

    + client: Update locales
2020-07-20 15:55:15 +03:00
Ildar Kamalov
c131ac445a Merge: - client: fix names of clients in the top clients list
Closes #1893

Squashed commit of the following:

commit e5de0c4f53558e1ad89dc0069192e534f244f120
Author: Ildar Kamalov <ik@adguard.com>
Date:   Mon Jul 20 14:29:23 2020 +0300

    - client: fix names of clients in the top clients list
2020-07-20 15:25:28 +03:00
Simon Zolin
87789679f5 Merge: - Makefile: repack all release archives so they contain AdGuardHome directory
Squashed commit of the following:

commit f519c82f042670e0bc377ef9f8490edce26085d9
Author: Simon Zolin <s.zolin@adguard.com>
Date:   Mon Jul 20 14:41:46 2020 +0300

    fix

commit 890a2c2b7812c2bc69224f88646ff3ab74e1562f
Author: Simon Zolin <s.zolin@adguard.com>
Date:   Mon Jul 20 14:24:34 2020 +0300

    fix

commit 753ab74b34a5ef010eb8b1d364f62d5a9c904d28
Author: Simon Zolin <s.zolin@adguard.com>
Date:   Mon Jul 20 14:06:49 2020 +0300

    fix

commit 160d0ac83ff656d39597711ab02485c1323f7a0e
Author: Simon Zolin <s.zolin@adguard.com>
Date:   Mon Jul 20 13:19:12 2020 +0300

    - Makefile: repack all release archives so they contain AdGuardHome directory
2020-07-20 14:55:56 +03:00
Andrey Meshkov
793194db67 Fix unpacking tar files 2020-07-20 12:32:14 +03:00
131 changed files with 5759 additions and 2482 deletions

View File

@@ -44,4 +44,4 @@ jobs:
fields: repo,message,commit,author
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

View File

@@ -22,7 +22,8 @@ Contents:
* Update client
* Delete client
* API: Find clients by IP
* Enable DHCP server
* DHCP server
* DHCP server in DNS
* "Show DHCP status" command
* "Check DHCP" command
* "Enable DHCP" command
@@ -51,19 +52,26 @@ Contents:
* API: Get query log
* API: Set querylog parameters
* API: Get querylog parameters
* Filtering
* DNS Filtering
* Filters update mechanism
* API: Get filtering parameters
* API: Set filtering parameters
* API: Refresh filters
* API: Add Filter
* API: Set URL parameters
* API: Delete URL
* API: Set Filter parameters
* API: Delete Filter
* API: Domain Check
* HTTP Proxy
* API: Get Proxy settings
* API: Set Proxy settings
* API: Get Proxy filtering parameters
* API: Add Proxy Filter
* API: Delete Proxy Filter
* Log-in page
* API: Log in
* API: Log out
* API: Get current user info
* Safe services
## Relations between subsystems
@@ -374,9 +382,9 @@ Error response:
UI shows error message "Auto-update has failed"
## Enable DHCP server
## DHCP server
Algorithm:
Enable DHCP server algorithm:
* UI shows DHCP configuration screen with "Enabled DHCP" button disabled, and "Check DHCP" button enabled
* User clicks on "Check DHCP"; UI sends request to server
@@ -388,6 +396,21 @@ Algorithm:
* UI shows the status
### DHCP server in DNS
DHCP leases are used in several ways by DNS module.
* For "A" DNS reqeust we reply with an IP address leased by our DHCP server.
< A bills-notebook.lan.
> A bills-notebook.lan. = 192.168.1.100
* For "PTR" DNS request we reply with a hostname from an active DHCP lease.
< PTR 100.1.168.192.in-addr.arpa.
> PTR 100.1.168.192.in-addr.arpa. = bills-notebook.
### "Show DHCP status" command
Request:
@@ -1460,7 +1483,7 @@ Response:
}
## Filtering
## DNS Filtering
![](doc/agh-filtering.png)
@@ -1531,7 +1554,19 @@ Response:
}
...
],
"user_rules":["...", ...]
"user_rules":["...", ...],
"proxy_filtering_enabled": true | false
"proxy_filters":[
{
"enabled":true,
"url":"https://...",
"name":"...",
"rules_count":1234,
"last_updated":"2019-09-04T18:29:30+00:00",
}
...
],
}
For both arrays `filters` and `whitelist_filters` there are unique values: id, url.
@@ -1546,6 +1581,7 @@ Request:
{
"enabled": true | false
"proxy_filtering_enabled": true | false
"interval": 0 | 1 | 12 | 1*24 || 3*24 || 7*24
}
@@ -1561,7 +1597,7 @@ Request:
POST /control/filtering/refresh
{
"whitelist": true
"type": blocklist | whitelist | proxylist
}
Response:
@@ -1582,7 +1618,7 @@ Request:
{
"name": "..."
"url": "..." // URL or an absolute file path
"whitelist": true
"type": blocklist | whitelist | proxylist
}
Response:
@@ -1590,7 +1626,7 @@ Response:
200 OK
### API: Set URL parameters
### API: Set Filter parameters
Request:
@@ -1598,11 +1634,11 @@ Request:
{
"url": "..."
"whitelist": true
"type": blocklist | whitelist | proxylist
"data": {
"name": "..."
"url": "..."
"enabled": true | false
"enabled": true
}
}
@@ -1611,7 +1647,7 @@ Response:
200 OK
### API: Delete URL
### API: Delete Filter
Request:
@@ -1619,7 +1655,7 @@ Request:
{
"url": "..."
"whitelist": true
"type": blocklist | whitelist | proxylist
}
Response:
@@ -1651,6 +1687,60 @@ Response:
}
## HTTP Proxy
Browser <-(HTTP)-> AGH Proxy <-(HTTP)-> Internet Server
HTTPS MITM:
. Browser --(CONNECT...)-> AGH Proxy --(handshake)-> Internet Server
. Browser <-(handshake,cert/AGH)-- AGH Proxy <-(cert/issuer)-- Internet Server
. Browser <-(TLS/session2)-> AGH Proxy <-(TLS/session1)-> Internet Server
### API: Get Proxy settings
Request:
GET /control/proxy_info
Response:
200 OK
{
"enabled": true|false,
"listen_address": "ip",
"listen_port": 12345,
"auth_username": "",
"auth_password": ""
}
### API: Set Proxy settings
Request:
POST /control/proxy_config
{
"enabled": true|false,
"listen_address": "ip",
"listen_port": 12345,
"auth_username": "",
"auth_password": "",
"cert_data":"...", // user-specified certificate. "": generate new
"pkey_data":"...",
}
Response:
200 OK
## Log-in page
After user completes the steps of installation wizard, he must log in into dashboard using his name and password. After user successfully logs in, he gets the Cookie which allows the server to authenticate him next time without password. After the Cookie is expired, user needs to perform log-in operation again.
@@ -1747,3 +1837,40 @@ Response:
}
If no client is configured then authentication is disabled and server sends an empty response.
### Safe services
Check if host name is blocked by SB/PC service:
* For each host name component, search for the result in cache by the first 2 bytes of SHA-256 hashes of host name components (max. is 4, i.e. sub2.sub1.host.com), excluding TLD:
hashes[] = cache_search(sha256(host.com)[0..1])
...
If hash prefix is found, search for a full hash sum in the cached data.
If found, the host is blocked.
If not found, the host is not blocked - don't request data for this prefix from the Family server again.
If hash prefix is not found, request data for this prefix from the Family server.
* Prepare query string which is generated from the first 2 bytes (converted to a 4-character string) of SHA-256 hashes of host name components (max. is 4, i.e. sub2.sub1.host.com), excluding TLD:
qs = ... + string(sha256(sub.host.com)[0..1]) + "." + string(sha256(host.com)[0..1]) + ".sb.dns.adguard.com."
For PC `.pc.dns.adguard.com` suffix is used.
* Send TXT query to Family server, receive response which contains the array of complete hash sums of the blocked hosts
* Check if one of received hash sums (`hashes[]`) matches hash sums for our host name
hashes[0] <> sha256(host.com)
hashes[0] <> sha256(sub.host.com)
hashes[1] <> sha256(host.com)
hashes[1] <> sha256(sub.host.com)
...
* Store all received hash sums in cache:
sha256(host.com)[0..1] -> hashes[0],hashes[1],...
sha256(sub.host.com)[0..1] -> hashes[2],...
...

120
Makefile
View File

@@ -14,6 +14,13 @@
# Building releases:
#
# * release -- builds AdGuard Home distros. CHANNEL must be specified (edge, release or beta).
# * release_and_sign -- builds AdGuard Home distros and signs the binary files.
# CHANNEL must be specified (edge, release or beta).
# * sign -- Repacks all release archive files and signs the binary files inside them.
# For signing to work, the public+private key pair for $(GPG_KEY) must be imported:
# gpg --import public.txt
# gpg --import private.txt
# GPG_KEY_PASSPHRASE must contain the GPG key passphrase
# * docker-multi-arch -- builds a multi-arch image. If you want it to be pushed to docker hub,
# you must specify:
# * DOCKER_IMAGE_NAME - adguard/adguard-home
@@ -23,6 +30,9 @@ GOPATH := $(shell go env GOPATH)
PWD := $(shell pwd)
TARGET=AdGuardHome
BASE_URL="https://static.adguard.com/adguardhome/$(CHANNEL)"
GPG_KEY := devteam@adguard.com
GPG_KEY_PASSPHRASE :=
GPG_CMD := gpg --detach-sig --default-key $(GPG_KEY) --pinentry-mode loopback --passphrase $(GPG_KEY_PASSPHRASE)
# See release target
DIST_DIR=dist
@@ -39,6 +49,12 @@ endif
endif
endif
# Version history URL (see
VERSION_HISTORY_URL="https://github.com/AdguardTeam/AdGuardHome/releases"
ifeq ($(CHANNEL),edge)
VERSION_HISTORY_URL="https://github.com/AdguardTeam/AdGuardHome/commits/master"
endif
# goreleaser command depends on the $CHANNEL
GORELEASER_COMMAND=goreleaser release --rm-dist --skip-publish --snapshot
ifneq ($(CHANNEL),edge)
@@ -55,7 +71,11 @@ SNAPSHOT_VERSION=$(RELEASE_VERSION)-SNAPSHOT-$(COMMIT)
# Set proper version
VERSION=
ifeq ($(TAG_NAME),$(shell git describe --abbrev=4))
VERSION=$(RELEASE_VERSION)
ifeq ($(CHANNEL),edge)
VERSION=$(SNAPSHOT_VERSION)
else
VERSION=$(RELEASE_VERSION)
endif
else
VERSION=$(SNAPSHOT_VERSION)
endif
@@ -94,7 +114,8 @@ all: build
init:
git config core.hooksPath .githooks
build: dependencies client
build: client_with_deps
go mod download
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)"
PATH=$(GOPATH)/bin:$(PATH) packr clean
@@ -102,6 +123,10 @@ build: dependencies client
client:
npm --prefix client run build-prod
client_with_deps:
npm --prefix client ci
npm --prefix client run build-prod
client-watch:
npm --prefix client run watch
@@ -130,10 +155,14 @@ 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
ci: client_with_deps
go mod download
$(MAKE) test
dependencies:
npm --prefix client ci
@@ -170,12 +199,20 @@ docker-multi-arch:
@echo If the image was pushed to the registry, you can now run it:
@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)
release: dependencies client
release: client_with_deps
go mod download
@echo Starting release build: version $(VERSION), channel $(CHANNEL)
CHANNEL=$(CHANNEL) $(GORELEASER_COMMAND)
$(call write_version_file,$(VERSION))
PATH=$(GOPATH)/bin:$(PATH) packr clean
release_and_sign: client_with_deps
$(MAKE) release
$(call repack_dist)
sign:
$(call repack_dist)
define write_version_file
$(eval version := $(1))
@@ -190,8 +227,8 @@ define write_version_file
echo "{" >> $(DIST_DIR)/version.json
echo " \"version\": \"$(version)\"," >> $(DIST_DIR)/version.json
echo " \"announcement\": \"AdGuard Home $(version) is now available!\"," >> $(DIST_DIR)/version.json
echo " \"announcement_url\": \"https://github.com/AdguardTeam/AdGuardHome/releases\"," >> $(DIST_DIR)/version.json
echo " \"selfupdate_min_version\": \"v0.0\"," >> $(DIST_DIR)/version.json
echo " \"announcement_url\": \"$(VERSION_HISTORY_URL)\"," >> $(DIST_DIR)/version.json
echo " \"selfupdate_min_version\": \"0.0\"," >> $(DIST_DIR)/version.json
# Windows builds
echo " \"download_windows_amd64\": \"$(BASE_URL)/AdGuardHome_windows_amd64.zip\"," >> $(DIST_DIR)/version.json
@@ -232,3 +269,72 @@ define write_version_file
# Finish
echo "}" >> $(DIST_DIR)/version.json
endef
define repack_dist
# Repack archive files
# A temporary solution for our auto-update code to be able to unpack these archive files
# The problem is that goreleaser doesn't add directory AdGuardHome/ to the archive file
# and we can't create it
rm -rf $(DIST_DIR)/AdGuardHome
# Windows builds
$(call zip_repack_windows,AdGuardHome_windows_amd64.zip)
$(call zip_repack_windows,AdGuardHome_windows_386.zip)
# MacOS builds
$(call zip_repack,AdGuardHome_darwin_amd64.zip)
$(call zip_repack,AdGuardHome_darwin_386.zip)
# Linux
$(call tar_repack,AdGuardHome_linux_amd64.tar.gz)
$(call tar_repack,AdGuardHome_linux_386.tar.gz)
# Linux, all kinds of ARM
$(call tar_repack,AdGuardHome_linux_armv5.tar.gz)
$(call tar_repack,AdGuardHome_linux_armv6.tar.gz)
$(call tar_repack,AdGuardHome_linux_armv7.tar.gz)
$(call tar_repack,AdGuardHome_linux_arm64.tar.gz)
# Linux, MIPS
$(call tar_repack,AdGuardHome_linux_mips_softfloat.tar.gz)
$(call tar_repack,AdGuardHome_linux_mipsle_softfloat.tar.gz)
$(call tar_repack,AdGuardHome_linux_mips64_softfloat.tar.gz)
$(call tar_repack,AdGuardHome_linux_mips64le_softfloat.tar.gz)
# FreeBSD
$(call tar_repack,AdGuardHome_freebsd_386.tar.gz)
$(call tar_repack,AdGuardHome_freebsd_amd64.tar.gz)
# FreeBSD, all kinds of ARM
$(call tar_repack,AdGuardHome_freebsd_armv5.tar.gz)
$(call tar_repack,AdGuardHome_freebsd_armv6.tar.gz)
$(call tar_repack,AdGuardHome_freebsd_armv7.tar.gz)
$(call tar_repack,AdGuardHome_freebsd_arm64.tar.gz)
endef
define zip_repack_windows
$(eval ARC := $(1))
cd $(DIST_DIR) && \
unzip $(ARC) && \
$(GPG_CMD) AdGuardHome/AdGuardHome.exe && \
zip -r $(ARC) AdGuardHome/ && \
rm -rf AdGuardHome
endef
define zip_repack
$(eval ARC := $(1))
cd $(DIST_DIR) && \
unzip $(ARC) && \
$(GPG_CMD) AdGuardHome/AdGuardHome && \
zip -r $(ARC) AdGuardHome/ && \
rm -rf AdGuardHome
endef
define tar_repack
$(eval ARC := $(1))
cd $(DIST_DIR) && \
tar xzf $(ARC) && \
$(GPG_CMD) AdGuardHome/AdGuardHome && \
tar czf $(ARC) AdGuardHome/ && \
rm -rf AdGuardHome
endef

View File

@@ -155,7 +155,10 @@ Run `make init` to prepare the development environment.
You will need this to build AdGuard Home:
* [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.16.2 or later.
* [npm](https://www.npmjs.com/) v6.14 or later.
Optionally, for Go devs:
* [golangci-lint](https://github.com/golangci/golangci-lint)
### Building
@@ -214,6 +217,8 @@ You may need to prepare before using these builds:
You are welcome to fork this repository, make your changes and submit a pull request — https://github.com/AdguardTeam/AdGuardHome/pulls
Please note, that we don't expect people to contribute to both UI and golang parts of the program simultaneously. Ideally, the golang part is implemented first, i.e. configuration, API, and the functionality itself. The UI part can be implemented later in a different pull request by a different person.
<a id="test-unstable-versions"></a>
### Test unstable versions
@@ -230,7 +235,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.

View File

@@ -1,9 +1,9 @@
module.exports = {
"disableEmoji": true,
"list": [
"+",
"*",
"-",
"+ ",
"* ",
"- ",
],
"maxMessageLength": 64,
"minMessageLength": 3,
@@ -12,7 +12,7 @@ module.exports = {
"scope",
"subject",
"body",
"issues"
"issues",
],
"scopes": [
"",
@@ -26,20 +26,20 @@ module.exports = {
"documentation",
],
"types": {
"+": {
"+ ": {
"description": "A new feature",
"emoji": "",
"value": "+"
"value": "+ "
},
"*": {
"* ": {
"description": "A code change that neither fixes a bug or adds a feature",
"emoji": "",
"value": "*"
"value": "* "
},
"-": {
"- ": {
"description": "A bug fix",
"emoji": "",
"value": "-"
"value": "- "
}
}
};

View File

@@ -11,6 +11,7 @@ module.exports = (api) => {
'@babel/plugin-proposal-object-rest-spread',
'@babel/plugin-proposal-nullish-coalescing-operator',
'@babel/plugin-proposal-optional-chaining',
'react-hot-loader/babel',
],
};
};

11
client/constants.js vendored Normal file
View File

@@ -0,0 +1,11 @@
const BUILD_ENVS = {
dev: 'development',
prod: 'production',
};
const BASE_URL = '/control';
module.exports = {
BUILD_ENVS,
BASE_URL,
};

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

@@ -1356,6 +1356,17 @@
"resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz",
"integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA=="
},
"@hot-loader/react-dom": {
"version": "16.13.0",
"resolved": "https://registry.npmjs.org/@hot-loader/react-dom/-/react-dom-16.13.0.tgz",
"integrity": "sha512-lJZrmkucz2MrQJTQtJobx5MICXcfQvKihszqv655p557HPi0hMOWxrNpiHv3DWD8ugNWjtWcVWqRnFvwsHq1mQ==",
"requires": {
"loose-envify": "^1.1.0",
"object-assign": "^4.1.1",
"prop-types": "^15.6.2",
"scheduler": "^0.19.0"
}
},
"@istanbuljs/load-nyc-config": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
@@ -2257,6 +2268,12 @@
"@types/istanbul-lib-report": "*"
}
},
"@types/json-schema": {
"version": "7.0.5",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.5.tgz",
"integrity": "sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ==",
"dev": true
},
"@types/minimatch": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
@@ -2728,7 +2745,6 @@
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"dev": true,
"requires": {
"sprintf-js": "~1.0.2"
}
@@ -5108,6 +5124,12 @@
}
}
},
"dom-walk": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz",
"integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==",
"dev": true
},
"domain-browser": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz",
@@ -5201,9 +5223,9 @@
"dev": true
},
"elliptic": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz",
"integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==",
"version": "6.5.3",
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz",
"integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==",
"dev": true,
"requires": {
"bn.js": "^4.4.0",
@@ -5216,9 +5238,9 @@
},
"dependencies": {
"bn.js": {
"version": "4.11.8",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
"integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==",
"version": "4.11.9",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
"dev": true
}
}
@@ -5887,8 +5909,7 @@
"esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
"dev": true
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
},
"esquery": {
"version": "1.3.1",
@@ -6844,6 +6865,16 @@
}
}
},
"global": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz",
"integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==",
"dev": true,
"requires": {
"min-document": "^2.19.0",
"process": "^0.11.10"
}
},
"global-modules": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz",
@@ -7357,18 +7388,6 @@
"requires-port": "^1.0.0"
}
},
"http-proxy-middleware": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz",
"integrity": "sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==",
"dev": true,
"requires": {
"http-proxy": "^1.17.0",
"is-glob": "^4.0.0",
"lodash": "^4.17.11",
"micromatch": "^3.1.10"
}
},
"http-signature": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
@@ -9898,10 +9917,9 @@
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
},
"js-yaml": {
"version": "3.13.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
"integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
"dev": true,
"version": "3.14.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz",
"integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==",
"requires": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
@@ -10168,9 +10186,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",
@@ -10628,6 +10646,15 @@
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
"dev": true
},
"min-document": {
"version": "2.19.0",
"resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz",
"integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=",
"dev": true,
"requires": {
"dom-walk": "^0.1.0"
}
},
"min-indent": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.0.tgz",
@@ -12287,6 +12314,39 @@
"scheduler": "^0.19.1"
}
},
"react-hot-loader": {
"version": "4.12.21",
"resolved": "https://registry.npmjs.org/react-hot-loader/-/react-hot-loader-4.12.21.tgz",
"integrity": "sha512-Ynxa6ROfWUeKWsTHxsrL2KMzujxJVPjs385lmB2t5cHUxdoRPGind9F00tOkdc1l5WBleOF4XEAMILY1KPIIDA==",
"dev": true,
"requires": {
"fast-levenshtein": "^2.0.6",
"global": "^4.3.0",
"hoist-non-react-statics": "^3.3.0",
"loader-utils": "^1.1.0",
"prop-types": "^15.6.1",
"react-lifecycles-compat": "^3.0.4",
"shallowequal": "^1.1.0",
"source-map": "^0.7.3"
},
"dependencies": {
"hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
"dev": true,
"requires": {
"react-is": "^16.7.0"
}
},
"source-map": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
"dev": true
}
}
},
"react-i18next": {
"version": "11.4.0",
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-11.4.0.tgz",
@@ -13328,6 +13388,12 @@
"safe-buffer": "^5.0.1"
}
},
"shallowequal": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
"integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==",
"dev": true
},
"shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -13752,8 +13818,7 @@
"sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
"dev": true
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
},
"sshpk": {
"version": "1.16.1",
@@ -14080,12 +14145,13 @@
}
},
"schema-utils": {
"version": "2.6.6",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.6.tgz",
"integrity": "sha512-wHutF/WPSbIi9x6ctjGGk2Hvl0VOz5l3EKEuKbjPlB30mKZUzb9A5k9yEXRX3pwyqVLPvpfZZEllaFq/M718hA==",
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz",
"integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==",
"dev": true,
"requires": {
"ajv": "^6.12.0",
"@types/json-schema": "^7.0.4",
"ajv": "^6.12.2",
"ajv-keywords": "^3.4.1"
}
}
@@ -15931,6 +15997,18 @@
"ms": "^2.1.1"
}
},
"http-proxy-middleware": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz",
"integrity": "sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==",
"dev": true,
"requires": {
"http-proxy": "^1.17.0",
"is-glob": "^4.0.0",
"lodash": "^4.17.11",
"micromatch": "^3.1.10"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",

8
client/package.json vendored
View File

@@ -4,14 +4,16 @@
"private": true,
"scripts": {
"build-dev": "cross-env BUILD_ENV=dev webpack --config webpack.dev.js",
"watch": "cross-env BUILD_ENV=dev webpack --config webpack.dev.js --watch",
"build-prod": "cross-env BUILD_ENV=prod webpack --config webpack.prod.js",
"watch": "cross-env BUILD_ENV=dev webpack --config webpack.dev.js --watch",
"watch:hot": "cross-env BUILD_ENV=dev webpack-dev-server --config webpack.dev.js",
"lint": "eslint src",
"lint:fix": "eslint src --fix",
"test": "jest",
"test:watch": "jest --watch"
},
"dependencies": {
"@hot-loader/react-dom": "^16.13.0",
"@nivo/line": "^0.49.1",
"axios": "^0.19.2",
"classnames": "^2.2.6",
@@ -19,7 +21,8 @@
"i18next": "^19.4.4",
"i18next-browser-languagedetector": "^4.2.0",
"ipaddr.js": "^1.9.1",
"lodash": "^4.17.15",
"js-yaml": "^3.14.0",
"lodash": "^4.17.19",
"nanoid": "^3.1.9",
"prop-types": "^15.7.2",
"query-string": "^6.13.1",
@@ -73,6 +76,7 @@
"path": "^0.12.7",
"postcss-flexbugs-fixes": "4.2.1",
"postcss-loader": "^3.0.0",
"react-hot-loader": "^4.12.21",
"style-loader": "^1.2.1",
"stylelint": "^13.5.0",
"stylelint-webpack-plugin": "2.0.0",

View File

@@ -236,5 +236,6 @@
"reset_settings": "Изтрий всички настройки",
"update_announcement": "Има нова AdGuard Home {{version}}! <0>Цъкни тук</0> за повече информация.",
"disable_ipv6": "Изключете IPv6 протокола",
"show_blocked_responses": "Блокирано"
"show_blocked_responses": "Блокирано",
"port_53_faq_link": "Порт 53 често е зает от \"DNSStubListener\" или \"systemd-resolved\" услуги. Моля, прочетете <0>тази инструкция</0> как да решите това."
}

View File

@@ -562,5 +562,6 @@
"filter_category_regional_desc": "Seznamy, které jsou zaměřené na regionální reklamy a sledovací servery",
"filter_category_other_desc": "Další seznamy zakázaných",
"original_response": "Původní odezva",
"click_to_view_queries": "Klikněte pro zobrazení dotazů"
"click_to_view_queries": "Klikněte pro zobrazení dotazů",
"port_53_faq_link": "Port 53 je často obsazen službami \"DNSStubListener\" nebo \"systemd-resolved\". Přečtěte si <0>tento návod</0> o tom, jak to vyřešit."
}

View File

@@ -562,5 +562,6 @@
"filter_category_regional_desc": "Lister, der fokuserer på regionale annoncer og tracking-servere",
"filter_category_other_desc": "Andre blokeringslister",
"original_response": "Oprindeligt svar",
"click_to_view_queries": "Klik for at se forespørgsler"
"click_to_view_queries": "Klik for at se forespørgsler",
"port_53_faq_link": "Port 53 optages ofte af \"DNSStubListener\" eller \"systemd-resolved\" tjenester. Læs <0>denne instruktion</0> om, hvordan du løser dette."
}

View File

@@ -562,5 +562,6 @@
"filter_category_regional_desc": "Listen, die sich auf regionale Werbeanzeigen und Tracking-Server konzentrieren",
"filter_category_other_desc": "Weitere Sperrlisten",
"original_response": "Ursprüngliche Antwort",
"click_to_view_queries": "Anklicken, um Abfragen anzuzeigen"
"click_to_view_queries": "Anklicken, um Abfragen anzuzeigen",
"port_53_faq_link": "Port 53 wird oft von Diensten wie „DNSStubListener” oder „systemresolved” belegt. Bitte lesen Sie <0>diese Anweisung</0>, wie dies behoben werden kann."
}

View File

@@ -542,7 +542,6 @@
"safe_search": "Safe search",
"blocklist": "Blocklist",
"milliseconds_abbreviation": "ms",
"dnssec_enable_desc": "Set DNSSEC flag in the outcoming DNS queries and check the result (DNSSEC-enabled resolver is required)",
"cache_size": "Cache size",
"cache_size_desc": "DNS cache size (in bytes)",
"cache_ttl_min_override": "Override minimum TTL",
@@ -565,4 +564,4 @@
"original_response": "Original response",
"click_to_view_queries": "Click to view queries",
"port_53_faq_link": "Port 53 is often occupied by \"DNSStubListener\" or \"systemd-resolved\" services. Please read <0>this instruction</0> on how to resolve this."
}
}

View File

@@ -354,8 +354,8 @@
"fix": "Corregir",
"dns_providers": "Aquí hay una <0>lista de proveedores DNS</0> conocidos para elegir.",
"update_now": "Actualizar ahora",
"update_failed": "Error en la actualización automática. Por favor <a href='https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started#update'>siga los pasos</a> para actualizar manualmente.",
"processing_update": "Por favor espere, AdGuard Home se está actualizando",
"update_failed": "Error en la actualización automática. Por favor <a href='https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started#update'>sigue los pasos</a> para actualizar manualmente.",
"processing_update": "Por favor espera, AdGuard Home se está actualizando",
"clients_title": "Clientes",
"clients_desc": "Configurar dispositivos conectados con AdGuard Home",
"settings_global": "Global",
@@ -562,5 +562,6 @@
"filter_category_regional_desc": "Listas que se centran en anuncios regionales y servidores de rastreo",
"filter_category_other_desc": "Otras listas de bloqueo",
"original_response": "Respuesta original",
"click_to_view_queries": "Clic para ver las consultas"
"click_to_view_queries": "Clic para ver las consultas",
"port_53_faq_link": "El puerto 53 suele estar ocupado por los servicios \"DNSStubListener\" o \"systemd-resolved\". Por favor lee <0>esta instrucción</0> sobre cómo resolver esto."
}

View File

@@ -559,5 +559,6 @@
"filter_category_security_desc": "Listes spécialisées dans le blocage de logiciels malveillants, dhameçonnage ou de domaines frauduleux",
"filter_category_regional_desc": "Listes axées sur les annonces régionales et les serveurs de pistage",
"filter_category_other_desc": "Autres listes noires",
"click_to_view_queries": "Cliquez pour voir les requêtes"
"click_to_view_queries": "Cliquez pour voir les requêtes",
"port_53_faq_link": "Le port 53 est souvent occupé par les services « DNSStubListener » ou « systemd-resolved ». Veuillez lire <0>cette instruction</0> pour savoir comment résoudre ce problème."
}

View File

@@ -84,7 +84,7 @@
"disable_protection": "Onemogući zaštitu",
"disabled_protection": "Onemogućena zaštita",
"refresh_statics": "Osvježi statistiku",
"dns_query": "DNS Upiti",
"dns_query": "DNS upiti",
"blocked_by": "<0>Blokirano filtrima</0>",
"stats_malware_phishing": "Blokiran zločudni program/krađe identiteta",
"stats_adult": "Blokirane web stranice za odrasle",
@@ -111,7 +111,7 @@
"block_domain_use_filters_and_hosts": "Blokiraj domene koristeći filtre ili hosts datoteke",
"filters_block_toggle_hint": "Pravila blokiranja možete postaviti u postavkama <a href='#filters'>filtara</a>.",
"use_adguard_browsing_sec": "Koristi AdGuard uslugu zaštite pregledavanja",
"use_adguard_browsing_sec_hint": "AdGuard Home će provjeriti nalazi li se domena na popisu neželjenih domena od usluge zaštite pregledavanja. Za provjeru će se koristiti API za provjeru koji poštuje vašu privatnost. Samo mali dio SHA256 hash-a odnaziva domene se šalje poslužitelju.",
"use_adguard_browsing_sec_hint": "AdGuard Home će provjeriti nalazi li se domena na popisu neželjenih domena od usluge zaštite pregledavanja. Za provjeru će se koristiti API za provjeru koji poštuje vašu privatnost. Samo mali dio SHA256 hash-a od naziva domene se šalje poslužitelju.",
"use_adguard_parental": "Koristi web uslugu AdGuard roditeljske zaštite",
"use_adguard_parental_hint": "AdGuard Home provjeriti će sadrži li domena sadržaj za odrasle. Koristi isti API za zaštitu privatnosti kao i naša usluga zaštite pregledavanja.",
"enforce_safe_search": "Omogući sigurno pretraživanje",
@@ -562,5 +562,6 @@
"filter_category_regional_desc": "Popisi koji se fokusiraju na regionalne oglase i poslužitelje za praćenje",
"filter_category_other_desc": "Ostali popisi blokiranih",
"original_response": "Originalni odgovor",
"click_to_view_queries": "Kliknite za pregled upita"
"click_to_view_queries": "Kliknite za pregled upita",
"port_53_faq_link": "Port 53 često zauzimaju usluge \"DNSStubListener\" ili \"systemd-resolved\". Molimo pročitajte <0>ove upute</0> o tome kako to riješiti."
}

View File

@@ -3,6 +3,8 @@
"example_upstream_reserved": "Anda dapat menetapkan DNS upstream <0>untuk domain spesifik</0>",
"upstream_parallel": "Gunakan kueri paralel untuk mempercepat resoluasi dengan menanyakan semua server upstream secara bersamaan",
"parallel_requests": "Permintaan paralel",
"load_balancing": "Penyeimbang beban",
"load_balancing_desc": "Permintaan satu server pada satu waktu. AdGuard Home akan menggunakan algoritma acak tertimbang untuk memilih server sehingga server tercepat akan lebih sering digunakan.",
"bootstrap_dns": "Server DNS bootstrap",
"bootstrap_dns_desc": "Server Bootstrap DNS dapat digunakan untuk meresolve alamat IP pada DoH/DoT resolvers yang Anda tentukan sebagai upstreams.",
"check_dhcp_servers": "Cek untuk server DHCP",
@@ -51,9 +53,11 @@
"dhcp_add_static_lease": "Tambah static lease",
"dhcp_reset": "Apakah anda yakin ingin mengatur ulang konfigurasi DHCP anda?",
"country": "Negara",
"city": "Kota",
"delete_confirm": "Apakah anda yakin ingin menghapus \"{{key}}\"?",
"form_enter_hostname": "Masukkan hostname",
"error_details": "Detail kesalahan",
"response_details": "Detail respon",
"request_details": "Detai permintaan",
"client_details": "Detail klien",
"details": "Detail",
@@ -63,6 +67,8 @@
"filters": "Penyaring",
"filter": "Filter",
"query_log": "Catatan Kueri",
"compact": "Rapat",
"nothing_found": "Tidak ditemukan",
"faq": "Tanya Jawab",
"version": "versi",
"address": "Alamat",
@@ -114,12 +120,16 @@
"general_settings": "Pengaturan umum",
"dns_settings": "Pengaturan DNS",
"dns_blocklists": "Daftar blokir DNS",
"dns_allowlists": "Daftar putih DNS",
"dns_blocklists_desc": "AdGuard Home akan memblokir domain yang cocok dengan daftar hitam.",
"dns_allowlists_desc": "Domain dari daftar putih DNS akan diizinkan bahkan jika mereka ada juga di daftar hitam.",
"custom_filtering_rules": "Aturan penyaringan khusus",
"encryption_settings": "Pengaturan enkripsi",
"dhcp_settings": "Pengaturan DHCP",
"upstream_dns": "Server DNS hulu",
"upstream_dns_hint": "Jika Anda mengosongkan kolom ini, AdGuard Home akan menggunakan <a href='https://1.1.1.1/' target='_blank'>Cloudflare DNS</a> sebagai hulu. Gunakan tls:// untuk server DNS over TLS.",
"test_upstream_btn": "Uji hulu",
"upstreams": "Upstream",
"apply_btn": "Terapkan",
"disabled_filtering_toast": "Penyaringan nonaktif",
"enabled_filtering_toast": "Penyaringan aktif",
@@ -131,6 +141,7 @@
"enabled_save_search_toast": "Pencarian aman diaktifkan",
"enabled_table_header": "Diaktifkan",
"name_table_header": "Nama",
"list_url_table_header": "Daftar URL",
"rules_count_table_header": "Jumlah Aturan",
"last_time_updated_table_header": "Terakhir diperbaharui",
"actions_table_header": "Aksi",
@@ -139,10 +150,23 @@
"delete_table_action": "Hapus",
"elapsed": "Berlalu",
"filters_and_hosts_hint": "AdGuard Home memahami aturan dasar adblock dan sintak file hosts.",
"no_blocklist_added": "Tiada daftar hitam ditambahkan",
"no_whitelist_added": "Tiada daftar putih ditambahkan",
"add_blocklist": "Tambah daftar hitam",
"add_allowlist": "Tambah daftar putih",
"cancel_btn": "Batal",
"enter_name_hint": "Masukkan nama",
"enter_url_or_path_hint": "Masukan sebuah URL atau jalur absolut dari daftar",
"check_updates_btn": "Cek pembaruan",
"new_blocklist": "Daftar hitam baru",
"new_allowlist": "Daftar putih baru",
"edit_blocklist": "Edit daftar hitam",
"edit_allowlist": "Edit daftar putih",
"choose_blocklist": "Pilih daftar hitam",
"choose_allowlist": "Pilih daftar putih",
"enter_valid_blocklist": "Masukkan valid URL ke daftar hitam.",
"enter_valid_allowlist": "Masukkan valid URL ke daftar putih.",
"form_error_url_format": "Format URL tidak valid",
"form_error_url_or_path_format": "URL atau jalur absolut dari daftar tidak valid",
"custom_filter_rules": "Aturan penyaringan khusus",
"custom_filter_rules_hint": "Masukkan satu aturan dalam sebuah baris. Anda dapat menggunakan baik aturan adblock maupun sintaks file hosts.",
@@ -159,6 +183,7 @@
"example_upstream_doh": "terenkripsi <0>DNS-over-HTTPS</0>",
"example_upstream_sdns": "anda bisa menggunakan <0>Stempel DNS</0> untuk <1>DNSCrypt</1> atau pengarah <2>DNS-over-HTTPS</2>",
"example_upstream_tcp": "DNS reguler (melalui TCP)",
"all_lists_up_to_date_toast": "Semua daftar sudah diperbarui",
"updated_upstream_dns_toast": "Server DNS hulu terbarui",
"dns_test_ok_toast": "Server DNS yang ditentukan bekerja dengan benar",
"dns_test_not_ok_toast": "Server \"{{key}}\": tidak dapat digunakan, mohon cek bahwa Anda telah menulisnya dengan benar",
@@ -170,6 +195,7 @@
"domain_or_client": "Domain atau klien",
"type_table_header": "Tipe",
"response_table_header": "Respon",
"response_code": "Kode respon",
"client_table_header": "Klien",
"empty_response_status": "Kosong",
"show_all_filter_type": "Tampilkan semua",
@@ -188,6 +214,7 @@
"query_log_filtered": "Difilter oleh {{filter}}",
"query_log_confirm_clear": "Apakah Anda yakin ingin menghapus seluruh kueri log?",
"query_log_cleared": "Kueri log telah berhasil dihapus",
"query_log_updated": "Log permintaan telah berhasil diperbarui",
"query_log_clear": "Hapus kueri log",
"query_log_retention": "Retensi kueri log",
"query_log_enable": "Aktifkan log",
@@ -195,19 +222,39 @@
"query_log_disabled": "Kueri log dinonaktifkan dan dapat dikonfigurasi di <0>pengaturan</0>",
"query_log_strict_search": "Gunakan tanda kutip ganda untuk pencarian ketat",
"query_log_retention_confirm": "Apakah Anda yakin ingin mengubah retensi kueri log? Jika Anda menurunkan nilai interval, beberapa data akan hilang",
"anonymize_client_ip": "Anonim IP klien",
"anonymize_client_ip_desc": "Jangan simpan alamat lengkap IP klien dalam log dan statistik",
"dns_config": "Konfigurasi server DNS",
"dns_cache_config": "Konfigurasi cache DNS",
"dns_cache_config_desc": "Disini Anda bisa mengonfigurasi cache DNS",
"blocking_mode": "Mode blokir",
"default": "Standar",
"nxdomain": "NXDOMAIN",
"null_ip": "Null IP",
"custom_ip": "Custom IP",
"blocking_ipv4": "Blokiran IPv4",
"blocking_ipv6": "Blokiran IPv6",
"dns_over_https": "DNS-over-HTTPS",
"dns_over_tls": "DNS-over-TLS",
"plain_dns": "Plain DNS",
"form_enter_rate_limit": "Masukkan batas nilai",
"rate_limit": "Batas nilai",
"edns_enable": "Aktifkan EDNS Klien Subnet",
"edns_cs_desc": "Apabila dinyalakan, AdGuard Home akan mengirim subnet klien ke server-server DNS.",
"rate_limit_desc": "Jumlah permintaan per detik yang diperbolehkan untuk satu klien (0: tidak terbatas)",
"blocking_ipv4_desc": "Alamat IP akan dikembalikan untuk permintaan A yang diblokir",
"blocking_ipv6_desc": "Alamat IP akan dipulihkan untuk permintaan AAAA yang diblokir",
"blocking_mode_default": "Standar: Respon pakai NXDOMAIN saat diblokir oleh aturan gaya Adblock; membalas dengan alamat IP yang ditentukan dalam aturan ketika diblokir oleh /et /aturan hosts-style",
"blocking_mode_nxdomain": "NXDOMAIN: Respon pakai kode NXDOMAIN",
"blocking_mode_null_ip": "Null IP: Respon pakai alamat IP kosong (0.0.0.0 untuk A; :: untuk AAAA)",
"blocking_mode_custom_ip": "IP kustom: respon dengan alamat IP yang diset secara manual",
"upstream_dns_client_desc": "Jika Anda biarkan bidang ini kosong, AdGuard Home akan memakai server yang dikonfigurasi di<0>Pengaturan DNS</0>.",
"tracker_source": "Sumber pelacak",
"source_label": "Sumber",
"found_in_known_domain_db": "Ditemukan di database domain dikenal",
"category_label": "Kategori",
"rule_label": "Aturan",
"list_label": "Daftar",
"unknown_filter": "Penyaringan {{filterId}} tidak dikenal",
"known_tracker": "Pelacak yang dikenal",
"install_welcome_title": "Selamat datang di AdGuard Home!",
@@ -324,6 +371,8 @@
"client_identifier_desc": "Klien dapat diidentifikasi dengan alamat IP atau alamat MAC. Harap dicatat bahwa menggunakan MAC sebagai pengidentifikasi hanya dimungkinkan jika AdGuard Home juga merupakan <0>server DHCP</0>",
"form_enter_ip": "Masukkan IP",
"form_enter_mac": "Masukkan MAC",
"form_enter_id": "Masukkan pengenal",
"form_add_id": "Tambah pengenal",
"form_client_name": "Masukkan nama klien",
"name": "Nama",
"client_global_settings": "Gunakan pengaturan global",
@@ -332,6 +381,7 @@
"client_updated": "Klien \"{{key}}\" berhasil diperbarui",
"clients_not_found": "Tidak ada klien ditemukan",
"client_confirm_delete": "Apakah anda yakin ingin menghapus klien \"{{key}}\"?",
"list_confirm_delete": "Anda yakin ingin menghapus daftar ini?",
"auto_clients_title": "Klien (waktu berjalan)",
"auto_clients_desc": "Data pada klien yang menggunakan AdGuard Home, tetapi tidak disimpan dalam konfigurasi",
"access_title": "Pengaturan akses",
@@ -399,6 +449,8 @@
"domain": "Domain",
"answer": "Jawab",
"filter_added_successfully": "Filter telah berhasil ditambahkan",
"filter_removed_successfully": "Daftar ini telah sukses dihapus",
"filter_updated": "Daftar telah sukses diperbarui",
"statistics_configuration": "Konfigurasi statistik",
"statistics_retention": "Statistik disimpan",
"statistics_retention_desc": "Jika Anda menurunkan nilai interval, beberapa data akan hilang",
@@ -428,13 +480,33 @@
"whois": "Whois",
"filtering_rules_learn_more": "<0>Pelajari lebih lanjut</0> tentang membuat daftar hitam host Anda sendiri.",
"blocked_by_response": "Diblokir oleh CNAME atau IP sebagai respon",
"blocked_by_cname_or_ip": "Diblokir oleh CNAME atau IP",
"try_again": "Coba lagi",
"domain_desc": "Masukkan nama domain atau wildcard yang ingin Anda tulis ulang.",
"example_rewrite_domain": "tulis ulang respon hanya untuk domain ini saja.",
"example_rewrite_wildcard": "tulis ulang respon untuk semua subdomain <0>contoh.org</0>.",
"rewrite_ip_address": "Alamat IP: pakai IP ini dalam respons A atau AAAA",
"rewrite_domain_name": "Nama domain: tambah ke rekaman CNAME",
"rewrite_A": "<0>A</0>: nilai khusus, biarkan <0>A</0> merekam dari upstream",
"rewrite_AAAA": "<0>AAAA</0>: nilai khusus, biarkan <0>AAAA</0> merekam dari upstream",
"disable_ipv6": "Matikan IPv6",
"disable_ipv6_desc": "Apabila fitur ini dinyalakan, semua permintaan DNS untuk alamat-alamat IPv6 (tipe AAAA) akan diputus.",
"fastest_addr": "Alamat IP tercepat",
"fastest_addr_desc": "Permintaan semua server DNS dan kembalinya alamat IP tercepat di antara semua respons",
"autofix_warning_text": "Apabila anda menekan \"Perbaiki\", AdGuardHome akan mengatur sistem anda untuk menggunakan server DNS AdGuardHome.",
"autofix_warning_list": "Ini akan melakukan tugas berikut: <0>Nonaktifkan sistem DNSStubListener</0> <0> Atur alamat server DNS ke 127.0.0.1</0> <0>Ganti target tautan simbolis /etc/resolv.conf pakai /run/systemd/resolve/resolv.conf</0> <0>Hentikan DNSStubListener (muat ulang layanan sistemd-resolve service)</0>",
"autofix_warning_result": "Hasilnya, semua permintaan DNS dari sistem anda akan diproses oleh AdGuardHome secara standar.",
"tags_title": "Tag",
"tags_desc": "Anda dapat memilih tag sesuai dengan klien. Tag dapat dimasukkan dalam aturan pemfilteran dan memungkinkan Anda untuk menerapkannya lebih akurat. <0>Pelajari lebih</0>",
"form_select_tags": "Pilih tag klien",
"check_title": "Periksa penyaringan",
"check_desc": "Periksa apakah nama host telah tersaring",
"check": "Periksa",
"form_enter_host": "Masukkan nama host",
"filtered_custom_rules": "Tersaring oleh aturan penyaring Buatan",
"choose_from_list": "Pilih dari daftar",
"add_custom_list": "Tambah daftar buatan",
"host_whitelisted": "Host didaftar putihkan",
"check_ip": "Alamat IP: {{ip}}",
"check_cname": "CNAME: {{cname}}",
"check_reason": "Alasan: {{reason}}",
@@ -446,11 +518,50 @@
"client_blocked": "Klien \"{{ip}}\" sukses di blokir",
"client_unblocked": "Klien \"{{ip}}\" sukses di unblock",
"static_ip": "Alamat IP statis",
"static_ip_desc": "AdGuard Home adalah server jadi perlu alamat IP statis agar berfungsi dengan benar. Jika tidak, pada titik tertentu, router Anda dapat menetapkan alamat IP yang berbeda untuk perangkat ini.",
"set_static_ip": "Atur alamat IP statik",
"install_static_ok": "Kabar baik! Alamat IP statis sudah dikonfigurasi",
"install_static_error": "AdGuard Home tidak dapat mengonfigurasinya secara otomatis untuk antarmuka jaringan ini. Silakan cari instruksi tentang cara melakukan ini secara manual.",
"install_static_configure": "Kami mendeteksi alamat IP dinamis digunakan - <0>{{ip}}</0>. Anda ingin menggunakannya sebagai alamat statis Anda?",
"confirm_static_ip": "AdGuard Home akan mengonfigurasi {{ip}} menjadi alamat IP statis Anda. Anda ingin melanjutkan?",
"list_updated": "{{count}} daftar terbarui",
"list_updated_plural": "{{count}} daftar terbarui",
"dnssec_enable": "Aktifkan DNSSEC",
"dnssec_enable_desc": "Atur bendera DNSSEC di permintaan keluar DNS dan periksa hasilnya (resolver berkemampuan DNSSEC diperlukan)",
"validated_with_dnssec": "Tervalidasi dengan DNSSEC",
"all_queries": "Semua permintaan",
"show_blocked_responses": "Diblokir",
"show_whitelisted_responses": "Dalam Daftar Putih",
"show_processed_responses": "Terproses",
"blocked_safebrowsing": "Terblokir oleh Safebrowsing",
"blocked_adult_websites": "Situs Dewasa Terblokir",
"blocked_threats": "Blokir Ancaman",
"allowed": "Dibolehkan",
"filtered": "Tersaring",
"rewritten": "Tulis ulang",
"safe_search": "Pencarian aman",
"blocklist": "Daftar blokir",
"milliseconds_abbreviation": "ms"
"milliseconds_abbreviation": "ms",
"cache_size": "Ukuran cache",
"cache_size_desc": "Ukuran DNS cache (dalam bit)",
"cache_ttl_min_override": "Tumpuk TTL minimum",
"cache_ttl_max_override": "Tumpuk TTL maksimum",
"enter_cache_size": "Masukkan ukuran cache",
"enter_cache_ttl_min_override": "Masukkan TTL minimum",
"enter_cache_ttl_max_override": "Masukkan TTL maksimum",
"cache_ttl_min_override_desc": "Ganti nilai TTL (minimum) yang diterima dari server upstream. Nilai ini tidak boleh lebih dari 3600 (1 jam)",
"cache_ttl_max_override_desc": "Ganti nilai TTL (maksimum) yang diterima dari server upstream",
"min_exceeds_max_value": "Nilai minimum melebihi nilai maksimum",
"value_not_larger_than": "Nilai tidak bisa lebih dari {{maximum}}",
"filter_category_general": "Umum",
"filter_category_security": "Keamanan",
"filter_category_regional": "Wilayah",
"filter_category_other": "Lainnya",
"filter_category_general_desc": "Daftar yang memblokir pelacakan dan iklan di sebagian besar perangkat",
"filter_category_security_desc": "Daftar yang khusus pada pemblokiran malware, phishing, atau domain penipuan",
"filter_category_regional_desc": "Daftar yang berfokus pada iklan regional dan server pelacakan",
"filter_category_other_desc": "Daftar hitam lain",
"original_response": "Respon asli",
"click_to_view_queries": "Klik untuk lihat permintaan",
"port_53_faq_link": "Port 53 sering ditempati oleh layanan \"DNSStubListener\" atau \"systemd-resolved\". Silakan baca <0>instruksi ini</0> tentang cara menyelesaikan ini."
}

View File

@@ -562,5 +562,6 @@
"filter_category_regional_desc": "それぞれの地域の広告と追跡サーバをターゲットするリストです。",
"filter_category_other_desc": "その他のブロックリストです。",
"original_response": "当初の応答",
"click_to_view_queries": "クエリを表示するにはクリックしてください"
"click_to_view_queries": "クエリを表示するにはクリックしてください",
"port_53_faq_link": "多くの場合、ポート53は \"DNSStubListener\" または \"systemd-resolved\" サービスによって利用されています。これを解決する方法については、<0>この手順</0>をお読みください。"
}

View File

@@ -562,5 +562,6 @@
"filter_category_regional_desc": "Lijsten die focussen op regionale ads en tracking servers",
"filter_category_other_desc": "Overige blokkeerlijsten",
"original_response": "Oorspronkelijke reactie",
"click_to_view_queries": "Klik om queries te bekijken"
"click_to_view_queries": "Klik om queries te bekijken",
"port_53_faq_link": "Poort 53 wordt vaak gebruikt door services als DNSStubListener- of de systeem DNS-resolver. Lees a.u.b. <0>deze instructie</0> hoe dit is op te lossen."
}

View File

@@ -108,7 +108,7 @@
"number_of_dns_query_to_safe_search": "Liczba żądań DNS do wyszukiwarek, dla których zastosowano wymuszenie bezpiecznego wyszukiwania",
"average_processing_time": "Średni czas przetwarzania",
"average_processing_time_hint": "Średni czas przetwarzania żądania DNS liczony w milisekundach",
"block_domain_use_filters_and_hosts": "Blokuj domeny za pomocą filtrów i plików host",
"block_domain_use_filters_and_hosts": "Zablokuj domeny za pomocą filtrów i plików host",
"filters_block_toggle_hint": "Możesz skonfigurować reguły blokowania w ustawieniach <a href='#filters'>Filtry</a> ",
"use_adguard_browsing_sec": "Użyj usługi sieciowej Bezpieczne Przeglądanie AdGuard",
"use_adguard_browsing_sec_hint": "AdGuard Home sprawdzi, czy domena jest na czarnej liście przez serwis internetowy Bezpieczne Przeglądanie. Będzie korzystać z interfejsu API przyjaznego dla prywatności w celu przeprowadzenia kontroli: na serwer wysyłany jest tylko krótki prefiks nazwy domeny SHA256.",
@@ -177,7 +177,7 @@
"example_comment": "! Tutaj jest komentarz",
"example_comment_meaning": "komentarz",
"example_comment_hash": "# Również komentarz",
"example_regex_meaning": "blokuj dostęp do domen pasujących do określonego wyrażenia regularnego",
"example_regex_meaning": "zablokuj dostęp do domen pasujących do określonego wyrażenia regularnego",
"example_upstream_regular": "normalny DNS (przez UDP)",
"example_upstream_dot": "zaszyfrowany <0>DNS-over-TLS</0>",
"example_upstream_doh": "zaszyfrowany <0>DNS-over-HTTPS</0>",
@@ -427,7 +427,7 @@
"form_error_answer_format": "Nieprawidłowy format odpowiedzi",
"configure": "Skonfiguruj",
"main_settings": "Ustawienia główne",
"block_services": "Blokuj określone usługi",
"block_services": "Zablokuj określone usługi",
"blocked_services": "Zablokowane usługi",
"blocked_services_desc": "Pozwala szybko zablokować popularne witryny i usługi.",
"blocked_services_saved": "Zablokowane usługi zostały pomyślnie zapisane",
@@ -444,7 +444,7 @@
"stats_params": "Konfiguracja statystyk",
"config_successfully_saved": "Konfiguracja została pomyślnie zapisana",
"interval_24_hour": "24 godziny",
"interval_days": "{{count}} dzień",
"interval_days": "{{count}} dni",
"interval_days_plural": "{{count}} dni",
"domain": "Domena",
"answer": "Odpowiedź",
@@ -562,5 +562,6 @@
"filter_category_regional_desc": "Listy, które koncentrują się na reklamach regionalnych i serwerach ze skryptami śledzącymi",
"filter_category_other_desc": "Inne listy zablokowanych",
"original_response": "Oryginalna odpowiedź",
"click_to_view_queries": "Kliknij, aby wyświetlić zapytania"
"click_to_view_queries": "Kliknij, aby wyświetlić zapytania",
"port_53_faq_link": "Port 53 jest często zajęty przez usługi \"DNSStubListener\" lub \"systemd-resolved\". Przeczytaj <0>tę instrukcję</0> jak to rozwiązać."
}

View File

@@ -562,5 +562,6 @@
"filter_category_regional_desc": "Liste focalizate pe reclame regionale și servere de urmărire",
"filter_category_other_desc": "Alte liste de blocări",
"original_response": "Răspuns original",
"click_to_view_queries": "Clicați pentru a vizualiza interogări"
"click_to_view_queries": "Clicați pentru a vizualiza interogări",
"port_53_faq_link": "Portul 53 este adesea ocupat de serviciile \"DNSStubListener\" sau \"systemd-resolved\". Vă rugăm să citiți <0>această instrucțiune</0> despre cum să rezolvați aceasta."
}

View File

@@ -530,7 +530,7 @@
"dnssec_enable_desc": "Установите флаг DNSSEC в исходящих DNS-запросах и проверьте результат (требуется резолвер с поддержкой DNSSEC)",
"validated_with_dnssec": "Подтверждено с помощью DNSSEC",
"all_queries": "Все запросы",
"show_blocked_responses": "Blocked",
"show_blocked_responses": "Заблокировано",
"show_whitelisted_responses": "В белом списке",
"show_processed_responses": "Обработан",
"blocked_safebrowsing": "Заблокировано согласно базе данных Safebrowsing",
@@ -562,5 +562,6 @@
"filter_category_regional_desc": "Списки, которые фокусируются на региональной рекламе и серверах отслеживания",
"filter_category_other_desc": "Другие списки блокировки",
"original_response": "Первоначальный ответ",
"click_to_view_queries": "Нажмите, чтобы просмотреть запросы"
"click_to_view_queries": "Нажмите, чтобы просмотреть запросы",
"port_53_faq_link": "Порт 53 часто занят службами \"DNSStubListener\" или \"systemd-resolved\". Ознакомьтесь с <0>инструкцией</0> о том, как это разрешить."
}

View File

@@ -562,5 +562,6 @@
"filter_category_regional_desc": "Zoznamy zamerané na regionálne reklamy a sledovacie servery",
"filter_category_other_desc": "Iné blokovacie zoznamy",
"original_response": "Pôvodná odozva",
"click_to_view_queries": "Kliknite pre zobrazenie dopytov"
"click_to_view_queries": "Kliknite pre zobrazenie dopytov",
"port_53_faq_link": "Port 53 je často obsadený službami \"DNSStubListener\" alebo \"systemd-resolved\". Prečítajte si <0>tento návod</0> o tom, ako to vyriešiť."
}

View File

@@ -562,5 +562,6 @@
"filter_category_regional_desc": "Seznami, ki so osredotočeni na področne oglase in strežnike za sledenje",
"filter_category_other_desc": "Drugi seznami za zaviranje",
"original_response": "Izviren odgovor",
"click_to_view_queries": "Kliknite za prikaz poizvedb"
"click_to_view_queries": "Kliknite za prikaz poizvedb",
"port_53_faq_link": "Vrata 53 pogosto zasedajo storitve 'DNSStubListener' ali 'Sistemsko razrešene storitve'. Preberite <0>to navodilo</0> o tem, kako to rešiti."
}

View File

@@ -562,5 +562,6 @@
"filter_category_regional_desc": "Lista koja se usredsređuje na regionalne reklame i servere praćenja",
"filter_category_other_desc": "Ostale liste blokiranja",
"original_response": "Izvorni odgovor",
"click_to_view_queries": "Kliknite da pogledate zahteve"
"click_to_view_queries": "Kliknite da pogledate zahteve",
"port_53_faq_link": "Port 53 je najčešće zauzet od \"DNSStubListener\" ili \"systemd-resolved\" usluga. Pročitajte <0>ovo uputstvo</0> kako da to rešite."
}

View File

@@ -239,7 +239,7 @@
"rule_label": "Kural",
"list_label": "Liste",
"unknown_filter": "Bilinmeyen filtre {{filterId}}",
"install_welcome_title": "AdGuard Home'a hoşgeldiniz!",
"install_welcome_title": "AdGuard Home'a hoş geldiniz!",
"install_welcome_desc": "AdGuard Home, ağ genelinde reklam ve izleyicileri engelleyen bir DNS sunucusudur. Tüm ağınızı ve tüm cihazlarınızı kontrol etmenize yarayan bir araçtır, istemci tarafında bir program kullanmanıza gerek duymaz.",
"install_settings_title": "Yönetici Web Arayüzü",
"install_settings_listen": "Dinleme arayüzü",
@@ -505,5 +505,6 @@
"blocked_adult_websites": "Yetişkin içerikli site engellendi",
"blocked_threats": "Engellenen Tehditler",
"allowed": "İzin verildi",
"blocklist": "Engellenen listesi"
"blocklist": "Engellenen listesi",
"port_53_faq_link": "Port 53 genellikle \"DNSStubListener\" veya \"sistemd-resolved\" hizmetler tarafından kullanılır. Lütfen problemin nasıl çözüleceğine ilişkin <0>bu talimatı</0> okuyun."
}

View File

@@ -562,5 +562,6 @@
"filter_category_regional_desc": "专注于区域广告和跟踪服务器的列表",
"filter_category_other_desc": "其他阻止列表",
"original_response": "原始响应",
"click_to_view_queries": "点击查看查询"
"click_to_view_queries": "点击查看查询",
"port_53_faq_link": "53端口常被DNSStubListener或systemdn解析的服务占用。请阅读<0>这份关于如何解决这一问题的说明</0>"
}

View File

@@ -544,8 +544,8 @@
"milliseconds_abbreviation": "ms",
"cache_size": "快取大小",
"cache_size_desc": "DNS 快取大小(以位元組)",
"cache_ttl_min_override": "覆寫最小的存活時間TTL",
"cache_ttl_max_override": "覆寫最大的存活時間TTL",
"cache_ttl_min_override": "覆寫最小的存活時間TTL(以秒數)",
"cache_ttl_max_override": "覆寫最大的存活時間TTL(以秒數)",
"enter_cache_size": "輸入快取大小",
"enter_cache_ttl_min_override": "輸入最小的存活時間TTL",
"enter_cache_ttl_max_override": "輸入最大的存活時間TTL",
@@ -562,5 +562,6 @@
"filter_category_regional_desc": "專注於區域性的廣告和追蹤伺服器之清單",
"filter_category_other_desc": "其它的封鎖清單",
"original_response": "原始的回應",
"click_to_view_queries": "點擊以檢視查詢"
"click_to_view_queries": "點擊以檢視查詢",
"port_53_faq_link": "連接埠 53 常被 \"DNSStubListener\" 或 \"systemd-resolved\" 服務佔用。請閱讀有關如何解決這個的<0>用法說明</0>。"
}

View File

@@ -1,4 +1,4 @@
import { getIpMatchListStatus } from '../helpers/helpers';
import { getIpMatchListStatus, sortIp } from '../helpers/helpers';
import { IP_MATCH_LIST_STATUS } from '../helpers/constants';
describe('getIpMatchListStatus', () => {
@@ -129,3 +129,356 @@ describe('getIpMatchListStatus', () => {
});
});
});
describe('sortIp', () => {
describe('ipv4', () => {
test('one octet differ', () => {
const arr = [
'127.0.2.0',
'127.0.3.0',
'127.0.1.0',
];
const sortedArr = [
'127.0.1.0',
'127.0.2.0',
'127.0.3.0',
];
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
});
test('few octets differ', () => {
const arr = [
'192.168.11.10',
'192.168.10.0',
'192.168.11.11',
'192.168.10.10',
'192.168.1.10',
'192.168.0.1',
'192.168.1.0',
'192.168.1.1',
'192.168.11.0',
'192.168.0.10',
'192.168.10.11',
'192.168.0.11',
'192.168.1.11',
'192.168.0.0',
'192.168.10.1',
'192.168.11.1',
];
const sortedArr = [
'192.168.0.0',
'192.168.0.1',
'192.168.0.10',
'192.168.0.11',
'192.168.1.0',
'192.168.1.1',
'192.168.1.10',
'192.168.1.11',
'192.168.10.0',
'192.168.10.1',
'192.168.10.10',
'192.168.10.11',
'192.168.11.0',
'192.168.11.1',
'192.168.11.10',
'192.168.11.11',
];
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
// Example from issue https://github.com/AdguardTeam/AdGuardHome/issues/1778#issuecomment-640937599
const arr2 = [
'192.168.2.11',
'192.168.3.1',
'192.168.2.100',
'192.168.2.2',
'192.168.2.1',
'192.168.2.10',
'192.168.2.99',
'192.168.2.200',
'192.168.2.199',
];
const sortedArr2 = [
'192.168.2.1',
'192.168.2.2',
'192.168.2.10',
'192.168.2.11',
'192.168.2.99',
'192.168.2.100',
'192.168.2.199',
'192.168.2.200',
'192.168.3.1',
];
expect(arr2.sort(sortIp)).toStrictEqual(sortedArr2);
});
});
describe('ipv6', () => {
test('only long form', () => {
const arr = [
'2001:db8:11a3:9d7:0:0:0:2',
'2001:db8:11a3:9d7:0:0:0:3',
'2001:db8:11a3:9d7:0:0:0:1',
];
const sortedArr = [
'2001:db8:11a3:9d7:0:0:0:1',
'2001:db8:11a3:9d7:0:0:0:2',
'2001:db8:11a3:9d7:0:0:0:3',
];
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
});
test('only short form', () => {
const arr = [
'2001:db8::',
'2001:db7::',
'2001:db9::',
];
const sortedArr = [
'2001:db7::',
'2001:db8::',
'2001:db9::',
];
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
});
test('long and short forms', () => {
const arr = [
'2001:db8::',
'2001:db7:11a3:9d7:0:0:0:2',
'2001:db6:11a3:9d7:0:0:0:1',
'2001:db6::',
'2001:db7:11a3:9d7:0:0:0:1',
'2001:db7::',
];
const sortedArr = [
'2001:db6::',
'2001:db6:11a3:9d7:0:0:0:1',
'2001:db7::',
'2001:db7:11a3:9d7:0:0:0:1',
'2001:db7:11a3:9d7:0:0:0:2',
'2001:db8::',
];
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
});
});
describe('ipv4 and ipv6', () => {
test('ipv6 long form', () => {
const arr = [
'127.0.0.3',
'2001:db8:11a3:9d7:0:0:0:1',
'2001:db8:11a3:9d7:0:0:0:3',
'127.0.0.1',
'2001:db8:11a3:9d7:0:0:0:2',
'127.0.0.2',
];
const sortedArr = [
'127.0.0.1',
'127.0.0.2',
'127.0.0.3',
'2001:db8:11a3:9d7:0:0:0:1',
'2001:db8:11a3:9d7:0:0:0:2',
'2001:db8:11a3:9d7:0:0:0:3',
];
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
});
test('ipv6 short form', () => {
const arr = [
'2001:db8:11a3:9d7::1',
'127.0.0.3',
'2001:db8:11a3:9d7::3',
'127.0.0.1',
'2001:db8:11a3:9d7::2',
'127.0.0.2',
];
const sortedArr = [
'127.0.0.1',
'127.0.0.2',
'127.0.0.3',
'2001:db8:11a3:9d7::1',
'2001:db8:11a3:9d7::2',
'2001:db8:11a3:9d7::3',
];
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
});
test('ipv6 long and short forms', () => {
const arr = [
'2001:db8:11a3:9d7::1',
'127.0.0.3',
'2001:db8:11a3:9d7:0:0:0:2',
'127.0.0.1',
'2001:db8:11a3:9d7::3',
'127.0.0.2',
];
const sortedArr = [
'127.0.0.1',
'127.0.0.2',
'127.0.0.3',
'2001:db8:11a3:9d7::1',
'2001:db8:11a3:9d7:0:0:0:2',
'2001:db8:11a3:9d7::3',
];
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
});
test('always put ipv4 before ipv6', () => {
const arr = [
'::1',
'0.0.0.2',
'127.0.0.1',
'::2',
'2001:db8:11a3:9d7:0:0:0:2',
'0.0.0.1',
'2001:db8:11a3:9d7::1',
];
const sortedArr = [
'0.0.0.1',
'0.0.0.2',
'127.0.0.1',
'::1',
'::2',
'2001:db8:11a3:9d7::1',
'2001:db8:11a3:9d7:0:0:0:2',
];
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
});
});
describe('cidr', () => {
test('only ipv4 cidr', () => {
const arr = [
'192.168.0.1/9',
'192.168.0.1/7',
'192.168.0.1/8',
];
const sortedArr = [
'192.168.0.1/7',
'192.168.0.1/8',
'192.168.0.1/9',
];
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
});
test('ipv4 and cidr ipv4', () => {
const arr = [
'192.168.0.1/9',
'192.168.0.1',
'192.168.0.1/32',
'192.168.0.1/7',
'192.168.0.1/8',
];
const sortedArr = [
'192.168.0.1/7',
'192.168.0.1/8',
'192.168.0.1/9',
'192.168.0.1/32',
'192.168.0.1',
];
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
});
test('only ipv6 cidr', () => {
const arr = [
'2001:db8:11a3:9d7::1/32',
'2001:db8:11a3:9d7::1/64',
'2001:db8:11a3:9d7::1/128',
'2001:db8:11a3:9d7::1/24',
];
const sortedArr = [
'2001:db8:11a3:9d7::1/24',
'2001:db8:11a3:9d7::1/32',
'2001:db8:11a3:9d7::1/64',
'2001:db8:11a3:9d7::1/128',
];
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
});
test('ipv6 and cidr ipv6', () => {
const arr = [
'2001:db8:11a3:9d7::1/32',
'2001:db8:11a3:9d7::1',
'2001:db8:11a3:9d7::1/64',
'2001:db8:11a3:9d7::1/128',
'2001:db8:11a3:9d7::1/24',
];
const sortedArr = [
'2001:db8:11a3:9d7::1/24',
'2001:db8:11a3:9d7::1/32',
'2001:db8:11a3:9d7::1/64',
'2001:db8:11a3:9d7::1/128',
'2001:db8:11a3:9d7::1',
];
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
});
});
describe('invalid input', () => {
const originalError = console.error;
beforeEach(() => {
console.error = jest.fn();
});
afterEach(() => {
expect(console.error).toHaveBeenCalled();
console.error = originalError;
});
test('invalid strings', () => {
const arr = ['invalid ip', 'invalid cidr'];
expect(arr.sort(sortIp)).toStrictEqual(arr);
});
test('invalid ip', () => {
const arr = ['127.0.0.2.', '.127.0.0.1.', '.2001:db8:11a3:9d7:0:0:0:0'];
expect(arr.sort(sortIp)).toStrictEqual(arr);
});
test('invalid cidr', () => {
const arr = ['127.0.0.2/33', '2001:db8:11a3:9d7:0:0:0:0/129'];
expect(arr.sort(sortIp)).toStrictEqual(arr);
});
test('valid and invalid ip', () => {
const arr = ['127.0.0.4.', '127.0.0.1', '.127.0.0.3', '127.0.0.2'];
expect(arr.sort(sortIp)).toStrictEqual(arr);
});
});
describe('mixed', () => {
test('ipv4, ipv6 in short and long forms and cidr', () => {
const arr = [
'2001:db8:11a3:9d7:0:0:0:1/32',
'192.168.1.2',
'127.0.0.2',
'2001:db8:11a3:9d7::1/128',
'2001:db8:11a3:9d7:0:0:0:1',
'127.0.0.1/12',
'192.168.1.1',
'2001:db8::/32',
'2001:db8:11a3:9d7::1/24',
'192.168.1.2/12',
'2001:db7::/32',
'127.0.0.1',
'2001:db8:11a3:9d7:0:0:0:2',
'192.168.1.1/24',
'2001:db7::/64',
'2001:db7::',
'2001:db8::',
'2001:db8:11a3:9d7:0:0:0:1/128',
'192.168.1.1/12',
'127.0.0.1/32',
'::1',
];
const sortedArr = [
'127.0.0.1/12',
'127.0.0.1/32',
'127.0.0.1',
'127.0.0.2',
'192.168.1.1/12',
'192.168.1.1/24',
'192.168.1.1',
'192.168.1.2/12',
'192.168.1.2',
'::1',
'2001:db7::/32',
'2001:db7::/64',
'2001:db7::',
'2001:db8::/32',
'2001:db8::',
'2001:db8:11a3:9d7::1/24',
'2001:db8:11a3:9d7:0:0:0:1/32',
'2001:db8:11a3:9d7::1/128',
'2001:db8:11a3:9d7:0:0:0:1/128',
'2001:db8:11a3:9d7:0:0:0:1',
'2001:db8:11a3:9d7:0:0:0:2',
];
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
});
});
});

View File

@@ -2,8 +2,9 @@ import { createAction } from 'redux-actions';
import i18next from 'i18next';
import axios from 'axios';
import { isVersionGreater, splitByNewLine, sortClients } from '../helpers/helpers';
import { splitByNewLine, sortClients } from '../helpers/helpers';
import { CHECK_TIMEOUT, SETTINGS_NAMES } from '../helpers/constants';
import { areEqualVersions } from '../helpers/version';
import { getTlsStatus } from './encryption';
import apiClient from '../api/Api';
import { addErrorToast, addNoticeToast, addSuccessToast } from './toasts';
@@ -121,7 +122,7 @@ export const getVersion = (recheck = false) => async (dispatch, getState) => {
const { dnsVersion } = getState().dashboard;
const currentVersion = dnsVersion === 'undefined' ? 0 : dnsVersion;
if (data && isVersionGreater(currentVersion, data.new_version)) {
if (data && !areEqualVersions(currentVersion, data.new_version)) {
dispatch(addSuccessToast('updates_checked'));
} else {
dispatch(addSuccessToast('updates_version_equal'));
@@ -147,7 +148,7 @@ const checkStatus = async (handleRequestSuccess, handleRequestError, attempts =
const rmTimeout = (t) => t && clearTimeout(t);
try {
const response = await axios.get('control/status');
const response = await axios.get(`${apiClient.baseUrl}/status`);
rmTimeout(timeout);
if (response?.status === 200) {
handleRequestSuccess(response);

View File

@@ -2,9 +2,10 @@ import axios from 'axios';
import { getPathWithQueryString } from '../helpers/helpers';
import { R_PATH_LAST_PART } from '../helpers/constants';
import { BASE_URL } from '../../constants';
class Api {
baseUrl = 'control';
baseUrl = BASE_URL;
async makeRequest(path, method = 'POST', config) {
try {
@@ -26,18 +27,30 @@ class Api {
throw new Error(`${errorPath} | ${error.response.data} | ${error.response.status}`);
}
throw new Error(`${errorPath} | ${error.message ? error.message : error}`);
throw new Error(`${errorPath} | ${error.message || error}`);
}
}
// Global methods
GLOBAL_STATUS = { path: 'status', method: 'GET' };
GLOBAL_STATUS = {
path: 'status',
method: 'GET',
};
GLOBAL_TEST_UPSTREAM_DNS = { path: 'test_upstream_dns', method: 'POST' };
GLOBAL_TEST_UPSTREAM_DNS = {
path: 'test_upstream_dns',
method: 'POST',
};
GLOBAL_VERSION = { path: 'version.json', method: 'POST' };
GLOBAL_VERSION = {
path: 'version.json',
method: 'POST',
};
GLOBAL_UPDATE = { path: 'update', method: 'POST' };
GLOBAL_UPDATE = {
path: 'update',
method: 'POST',
};
getGlobalStatus() {
const { path, method } = this.GLOBAL_STATUS;
@@ -68,21 +81,45 @@ class Api {
}
// Filtering
FILTERING_STATUS = { path: 'filtering/status', method: 'GET' };
FILTERING_STATUS = {
path: 'filtering/status',
method: 'GET',
};
FILTERING_ADD_FILTER = { path: 'filtering/add_url', method: 'POST' };
FILTERING_ADD_FILTER = {
path: 'filtering/add_url',
method: 'POST',
};
FILTERING_REMOVE_FILTER = { path: 'filtering/remove_url', method: 'POST' };
FILTERING_REMOVE_FILTER = {
path: 'filtering/remove_url',
method: 'POST',
};
FILTERING_SET_RULES = { path: 'filtering/set_rules', method: 'POST' };
FILTERING_SET_RULES = {
path: 'filtering/set_rules',
method: 'POST',
};
FILTERING_REFRESH = { path: 'filtering/refresh', method: 'POST' };
FILTERING_REFRESH = {
path: 'filtering/refresh',
method: 'POST',
};
FILTERING_SET_URL = { path: 'filtering/set_url', method: 'POST' };
FILTERING_SET_URL = {
path: 'filtering/set_url',
method: 'POST',
};
FILTERING_CONFIG = { path: 'filtering/config', method: 'POST' };
FILTERING_CONFIG = {
path: 'filtering/config',
method: 'POST',
};
FILTERING_CHECK_HOST = { path: 'filtering/check_host', method: 'GET' };
FILTERING_CHECK_HOST = {
path: 'filtering/check_host',
method: 'GET',
};
getFilteringStatus() {
const { path, method } = this.FILTERING_STATUS;
@@ -153,11 +190,20 @@ class Api {
}
// Parental
PARENTAL_STATUS = { path: 'parental/status', method: 'GET' };
PARENTAL_STATUS = {
path: 'parental/status',
method: 'GET',
};
PARENTAL_ENABLE = { path: 'parental/enable', method: 'POST' };
PARENTAL_ENABLE = {
path: 'parental/enable',
method: 'POST',
};
PARENTAL_DISABLE = { path: 'parental/disable', method: 'POST' };
PARENTAL_DISABLE = {
path: 'parental/disable',
method: 'POST',
};
getParentalStatus() {
const { path, method } = this.PARENTAL_STATUS;
@@ -180,11 +226,20 @@ class Api {
}
// Safebrowsing
SAFEBROWSING_STATUS = { path: 'safebrowsing/status', method: 'GET' };
SAFEBROWSING_STATUS = {
path: 'safebrowsing/status',
method: 'GET',
};
SAFEBROWSING_ENABLE = { path: 'safebrowsing/enable', method: 'POST' };
SAFEBROWSING_ENABLE = {
path: 'safebrowsing/enable',
method: 'POST',
};
SAFEBROWSING_DISABLE = { path: 'safebrowsing/disable', method: 'POST' };
SAFEBROWSING_DISABLE = {
path: 'safebrowsing/disable',
method: 'POST',
};
getSafebrowsingStatus() {
const { path, method } = this.SAFEBROWSING_STATUS;
@@ -202,11 +257,20 @@ class Api {
}
// Safesearch
SAFESEARCH_STATUS = { path: 'safesearch/status', method: 'GET' };
SAFESEARCH_STATUS = {
path: 'safesearch/status',
method: 'GET',
};
SAFESEARCH_ENABLE = { path: 'safesearch/enable', method: 'POST' };
SAFESEARCH_ENABLE = {
path: 'safesearch/enable',
method: 'POST',
};
SAFESEARCH_DISABLE = { path: 'safesearch/disable', method: 'POST' };
SAFESEARCH_DISABLE = {
path: 'safesearch/disable',
method: 'POST',
};
getSafesearchStatus() {
const { path, method } = this.SAFESEARCH_STATUS;
@@ -224,9 +288,15 @@ class Api {
}
// Language
CURRENT_LANGUAGE = { path: 'i18n/current_language', method: 'GET' };
CURRENT_LANGUAGE = {
path: 'i18n/current_language',
method: 'GET',
};
CHANGE_LANGUAGE = { path: 'i18n/change_language', method: 'POST' };
CHANGE_LANGUAGE = {
path: 'i18n/change_language',
method: 'POST',
};
getCurrentLanguage() {
const { path, method } = this.CURRENT_LANGUAGE;
@@ -243,19 +313,40 @@ class Api {
}
// DHCP
DHCP_STATUS = { path: 'dhcp/status', method: 'GET' };
DHCP_STATUS = {
path: 'dhcp/status',
method: 'GET',
};
DHCP_SET_CONFIG = { path: 'dhcp/set_config', method: 'POST' };
DHCP_SET_CONFIG = {
path: 'dhcp/set_config',
method: 'POST',
};
DHCP_FIND_ACTIVE = { path: 'dhcp/find_active_dhcp', method: 'POST' };
DHCP_FIND_ACTIVE = {
path: 'dhcp/find_active_dhcp',
method: 'POST',
};
DHCP_INTERFACES = { path: 'dhcp/interfaces', method: 'GET' };
DHCP_INTERFACES = {
path: 'dhcp/interfaces',
method: 'GET',
};
DHCP_ADD_STATIC_LEASE = { path: 'dhcp/add_static_lease', method: 'POST' };
DHCP_ADD_STATIC_LEASE = {
path: 'dhcp/add_static_lease',
method: 'POST',
};
DHCP_REMOVE_STATIC_LEASE = { path: 'dhcp/remove_static_lease', method: 'POST' };
DHCP_REMOVE_STATIC_LEASE = {
path: 'dhcp/remove_static_lease',
method: 'POST',
};
DHCP_RESET = { path: 'dhcp/reset', method: 'POST' };
DHCP_RESET = {
path: 'dhcp/reset',
method: 'POST',
};
getDhcpStatus() {
const { path, method } = this.DHCP_STATUS;
@@ -309,11 +400,20 @@ class Api {
}
// Installation
INSTALL_GET_ADDRESSES = { path: 'install/get_addresses', method: 'GET' };
INSTALL_GET_ADDRESSES = {
path: 'install/get_addresses',
method: 'GET',
};
INSTALL_CONFIGURE = { path: 'install/configure', method: 'POST' };
INSTALL_CONFIGURE = {
path: 'install/configure',
method: 'POST',
};
INSTALL_CHECK_CONFIG = { path: 'install/check_config', method: 'POST' };
INSTALL_CHECK_CONFIG = {
path: 'install/check_config',
method: 'POST',
};
getDefaultAddresses() {
const { path, method } = this.INSTALL_GET_ADDRESSES;
@@ -339,11 +439,20 @@ class Api {
}
// DNS-over-HTTPS and DNS-over-TLS
TLS_STATUS = { path: 'tls/status', method: 'GET' };
TLS_STATUS = {
path: 'tls/status',
method: 'GET',
};
TLS_CONFIG = { path: 'tls/configure', method: 'POST' };
TLS_CONFIG = {
path: 'tls/configure',
method: 'POST',
};
TLS_VALIDATE = { path: 'tls/validate', method: 'POST' };
TLS_VALIDATE = {
path: 'tls/validate',
method: 'POST',
};
getTlsStatus() {
const { path, method } = this.TLS_STATUS;
@@ -369,15 +478,30 @@ class Api {
}
// Per-client settings
GET_CLIENTS = { path: 'clients', method: 'GET' };
GET_CLIENTS = {
path: 'clients',
method: 'GET',
};
FIND_CLIENTS = { path: 'clients/find', method: 'GET' };
FIND_CLIENTS = {
path: 'clients/find',
method: 'GET',
};
ADD_CLIENT = { path: 'clients/add', method: 'POST' };
ADD_CLIENT = {
path: 'clients/add',
method: 'POST',
};
DELETE_CLIENT = { path: 'clients/delete', method: 'POST' };
DELETE_CLIENT = {
path: 'clients/delete',
method: 'POST',
};
UPDATE_CLIENT = { path: 'clients/update', method: 'POST' };
UPDATE_CLIENT = {
path: 'clients/update',
method: 'POST',
};
getClients() {
const { path, method } = this.GET_CLIENTS;
@@ -418,9 +542,15 @@ class Api {
}
// DNS access settings
ACCESS_LIST = { path: 'access/list', method: 'GET' };
ACCESS_LIST = {
path: 'access/list',
method: 'GET',
};
ACCESS_SET = { path: 'access/set', method: 'POST' };
ACCESS_SET = {
path: 'access/set',
method: 'POST',
};
getAccessList() {
const { path, method } = this.ACCESS_LIST;
@@ -437,11 +567,20 @@ class Api {
}
// DNS rewrites
REWRITES_LIST = { path: 'rewrite/list', method: 'GET' };
REWRITES_LIST = {
path: 'rewrite/list',
method: 'GET',
};
REWRITE_ADD = { path: 'rewrite/add', method: 'POST' };
REWRITE_ADD = {
path: 'rewrite/add',
method: 'POST',
};
REWRITE_DELETE = { path: 'rewrite/delete', method: 'POST' };
REWRITE_DELETE = {
path: 'rewrite/delete',
method: 'POST',
};
getRewritesList() {
const { path, method } = this.REWRITES_LIST;
@@ -467,9 +606,15 @@ class Api {
}
// Blocked services
BLOCKED_SERVICES_LIST = { path: 'blocked_services/list', method: 'GET' };
BLOCKED_SERVICES_LIST = {
path: 'blocked_services/list',
method: 'GET',
};
BLOCKED_SERVICES_SET = { path: 'blocked_services/set', method: 'POST' };
BLOCKED_SERVICES_SET = {
path: 'blocked_services/set',
method: 'POST',
};
getBlockedServices() {
const { path, method } = this.BLOCKED_SERVICES_LIST;
@@ -486,13 +631,25 @@ class Api {
}
// Settings for statistics
GET_STATS = { path: 'stats', method: 'GET' };
GET_STATS = {
path: 'stats',
method: 'GET',
};
STATS_INFO = { path: 'stats_info', method: 'GET' };
STATS_INFO = {
path: 'stats_info',
method: 'GET',
};
STATS_CONFIG = { path: 'stats_config', method: 'POST' };
STATS_CONFIG = {
path: 'stats_config',
method: 'POST',
};
STATS_RESET = { path: 'stats_reset', method: 'POST' };
STATS_RESET = {
path: 'stats_reset',
method: 'POST',
};
getStats() {
const { path, method } = this.GET_STATS;
@@ -519,13 +676,25 @@ class Api {
}
// Query log
GET_QUERY_LOG = { path: 'querylog', method: 'GET' };
GET_QUERY_LOG = {
path: 'querylog',
method: 'GET',
};
QUERY_LOG_CONFIG = { path: 'querylog_config', method: 'POST' };
QUERY_LOG_CONFIG = {
path: 'querylog_config',
method: 'POST',
};
QUERY_LOG_INFO = { path: 'querylog_info', method: 'GET' };
QUERY_LOG_INFO = {
path: 'querylog_info',
method: 'GET',
};
QUERY_LOG_CLEAR = { path: 'querylog_clear', method: 'POST' };
QUERY_LOG_CLEAR = {
path: 'querylog_clear',
method: 'POST',
};
getQueryLog(params) {
const { path, method } = this.GET_QUERY_LOG;
@@ -553,7 +722,10 @@ class Api {
}
// Login
LOGIN = { path: 'login', method: 'POST' };
LOGIN = {
path: 'login',
method: 'POST',
};
login(data) {
const { path, method } = this.LOGIN;
@@ -565,7 +737,10 @@ class Api {
}
// Profile
GET_PROFILE = { path: 'profile', method: 'GET' };
GET_PROFILE = {
path: 'profile',
method: 'GET',
};
getProfile() {
const { path, method } = this.GET_PROFILE;
@@ -573,9 +748,15 @@ class Api {
}
// DNS config
GET_DNS_CONFIG = { path: 'dns_info', method: 'GET' };
GET_DNS_CONFIG = {
path: 'dns_info',
method: 'GET',
};
SET_DNS_CONFIG = { path: 'dns_config', method: 'POST' };
SET_DNS_CONFIG = {
path: 'dns_config',
method: 'POST',
};
getDnsConfig() {
const { path, method } = this.GET_DNS_CONFIG;

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

@@ -1,30 +1,16 @@
import React, { Component, Fragment } from 'react';
import React, { useEffect } from 'react';
import { HashRouter, Route } from 'react-router-dom';
import PropTypes from 'prop-types';
import { withTranslation } from 'react-i18next';
import LoadingBar from 'react-redux-loading-bar';
import { hot } from 'react-hot-loader/root';
import 'react-table/react-table.css';
import '../ui/Tabler.css';
import '../ui/ReactTable.css';
import './index.css';
import Header from '../../containers/Header';
import Dashboard from '../../containers/Dashboard';
import Settings from '../../containers/Settings';
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import CustomRules from '../../containers/CustomRules';
import DnsBlocklist from '../../containers/DnsBlocklist';
import DnsAllowlist from '../../containers/DnsAllowlist';
import DnsRewrites from '../../containers/DnsRewrites';
import Dns from '../../containers/Dns';
import Encryption from '../../containers/Encryption';
import Dhcp from '../../containers/Dhcp';
import Clients from '../../containers/Clients';
import Logs from '../../containers/Logs';
import SetupGuide from '../../containers/SetupGuide';
import propTypes from 'prop-types';
import Toasts from '../Toasts';
import Footer from '../ui/Footer';
import Status from '../ui/Status';
@@ -35,31 +21,107 @@ import Icons from '../ui/Icons';
import i18n from '../../i18n';
import Loading from '../ui/Loading';
import { FILTERS_URLS, MENU_URLS, SETTINGS_URLS } from '../../helpers/constants';
import Services from '../Filters/Services';
import { getLogsUrlParams, setHtmlLangAttr } from '../../helpers/helpers';
import Header from '../Header';
import { changeLanguage, getDnsStatus } from '../../actions';
class App extends Component {
componentDidMount() {
this.props.getDnsStatus();
}
import Dashboard from '../../containers/Dashboard';
import Logs from '../../containers/Logs';
import SetupGuide from '../../containers/SetupGuide';
import Settings from '../../containers/Settings';
import Dns from '../../containers/Dns';
import Encryption from '../../containers/Encryption';
import Dhcp from '../../containers/Dhcp';
import Clients from '../../containers/Clients';
import DnsBlocklist from '../../containers/DnsBlocklist';
import DnsAllowlist from '../../containers/DnsAllowlist';
import DnsRewrites from '../../containers/DnsRewrites';
import CustomRules from '../../containers/CustomRules';
import Services from '../Filters/Services';
componentDidUpdate(prevProps) {
if (this.props.dashboard.language !== prevProps.dashboard.language) {
this.setLanguage();
}
}
const ROUTES = [
{
path: MENU_URLS.root,
component: Dashboard,
exact: true,
},
{
path: [`${MENU_URLS.logs}${getLogsUrlParams(':search?', ':response_status?')}`, MENU_URLS.logs],
component: Logs,
},
{
path: MENU_URLS.guide,
component: SetupGuide,
},
{
path: SETTINGS_URLS.settings,
component: Settings,
},
{
path: SETTINGS_URLS.dns,
component: Dns,
},
{
path: SETTINGS_URLS.encryption,
component: Encryption,
},
{
path: SETTINGS_URLS.dhcp,
component: Dhcp,
},
{
path: SETTINGS_URLS.clients,
component: Clients,
},
{
path: FILTERS_URLS.dns_blocklists,
component: DnsBlocklist,
},
{
path: FILTERS_URLS.dns_allowlists,
component: DnsAllowlist,
},
{
path: FILTERS_URLS.dns_rewrites,
component: DnsRewrites,
},
{
path: FILTERS_URLS.custom_rules,
component: CustomRules,
},
{
path: FILTERS_URLS.blocked_services,
component: Services,
},
];
reloadPage = () => {
window.location.reload();
};
const renderRoute = ({ path, component, exact }, idx) => <Route
key={idx}
exact={exact}
path={path}
component={component}
/>;
handleUpdate = () => {
this.props.getUpdate();
};
const App = () => {
const dispatch = useDispatch();
const {
language,
isCoreRunning,
isUpdateAvailable,
processing,
} = useSelector((state) => state.dashboard, shallowEqual);
setLanguage = () => {
const { processing, language } = this.props.dashboard;
const { processing: processingEncryption } = useSelector((
state,
) => state.encryption, shallowEqual);
const updateAvailable = isCoreRunning && isUpdateAvailable;
useEffect(() => {
dispatch(getDnsStatus());
}, []);
const setLanguage = () => {
if (!processing) {
if (language) {
i18n.changeLanguage(language);
@@ -68,93 +130,52 @@ class App extends Component {
}
i18n.on('languageChanged', (lang) => {
this.props.changeLanguage(lang);
dispatch(changeLanguage(lang));
});
};
render() {
const { dashboard, encryption, getVersion } = this.props;
const updateAvailable = dashboard.isCoreRunning && dashboard.isUpdateAvailable;
useEffect(() => {
setLanguage();
}, [language]);
return (
<HashRouter hashType="noslash">
<Fragment>
{updateAvailable && (
<Fragment>
<UpdateTopline
url={dashboard.announcementUrl}
version={dashboard.newVersion}
canAutoUpdate={dashboard.canAutoUpdate}
getUpdate={this.handleUpdate}
processingUpdate={dashboard.processingUpdate}
/>
<UpdateOverlay processingUpdate={dashboard.processingUpdate} />
</Fragment>
)}
{!encryption.processing && (
<EncryptionTopline notAfter={encryption.not_after} />
)}
<LoadingBar className="loading-bar" updateTime={1000} />
<Route component={Header} />
<div className="container container--wrap pb-5">
{dashboard.processing && <Loading />}
{!dashboard.isCoreRunning && (
<div className="row row-cards">
<div className="col-lg-12">
<Status reloadPage={this.reloadPage}
message="dns_start"
/>
<Loading />
</div>
const reloadPage = () => {
window.location.reload();
};
return (
<HashRouter hashType="noslash">
<>
{updateAvailable && <>
<UpdateTopline />
<UpdateOverlay />
</>}
{!processingEncryption && <EncryptionTopline />}
<LoadingBar className="loading-bar" updateTime={1000} />
<Header />
<div className="container container--wrap pb-5">
{processing && <Loading />}
{!isCoreRunning && (
<div className="row row-cards">
<div className="col-lg-12">
<Status reloadPage={reloadPage} message="dns_start" />
<Loading />
</div>
)}
{!dashboard.processing && dashboard.isCoreRunning && (
<>
<Route path={MENU_URLS.root} exact component={Dashboard} />
<Route
path={[`${MENU_URLS.logs}${getLogsUrlParams(':search?', ':response_status?')}`, MENU_URLS.logs]}
component={Logs} />
<Route path={MENU_URLS.guide} component={SetupGuide} />
<Route path={SETTINGS_URLS.settings} component={Settings} />
<Route path={SETTINGS_URLS.dns} component={Dns} />
<Route path={SETTINGS_URLS.encryption} component={Encryption} />
<Route path={SETTINGS_URLS.dhcp} component={Dhcp} />
<Route path={SETTINGS_URLS.clients} component={Clients} />
<Route path={FILTERS_URLS.dns_blocklists}
component={DnsBlocklist} />
<Route path={FILTERS_URLS.dns_allowlists}
component={DnsAllowlist} />
<Route path={FILTERS_URLS.dns_rewrites} component={DnsRewrites} />
<Route path={FILTERS_URLS.custom_rules} component={CustomRules} />
<Route path={FILTERS_URLS.blocked_services} component={Services} />
</>
)}
</div>
<Footer
dnsVersion={dashboard.dnsVersion}
dnsPort={dashboard.dnsPort}
processingVersion={dashboard.processingVersion}
getVersion={getVersion}
checkUpdateFlag={dashboard.checkUpdateFlag}
/>
<Toasts />
<Icons />
</Fragment>
</HashRouter>
);
}
}
App.propTypes = {
getDnsStatus: PropTypes.func,
getUpdate: PropTypes.func,
enableDns: PropTypes.func,
dashboard: PropTypes.object,
isCoreRunning: PropTypes.bool,
error: PropTypes.string,
changeLanguage: PropTypes.func,
encryption: PropTypes.object,
getVersion: PropTypes.func,
</div>
)}
{!processing && isCoreRunning && ROUTES.map(renderRoute)}
</div>
<Footer />
<Toasts />
<Icons />
</>
</HashRouter>
);
};
export default withTranslation()(App);
renderRoute.propTypes = {
path: propTypes.oneOfType([propTypes.string, propTypes.arrayOf(propTypes.string)]).isRequired,
component: propTypes.element.isRequired,
exact: propTypes.bool,
};
export default hot(App);

View File

@@ -6,7 +6,7 @@ import { Trans, withTranslation } from 'react-i18next';
import Card from '../ui/Card';
import Cell from '../ui/Cell';
import { getPercent, getIpMatchListStatus } from '../../helpers/helpers';
import { getPercent, getIpMatchListStatus, sortIp } from '../../helpers/helpers';
import { IP_MATCH_LIST_STATUS, STATUS_COLORS } from '../../helpers/constants';
import { formatClientCell } from '../../helpers/formatClientCell';
@@ -62,7 +62,7 @@ const clientCell = (t, toggleClientStatus, processing, disallowedClients) => fun
return (
<>
<div className="logs__row logs__row--overflow logs__row--column">
{formatClientCell(row, true)}
{formatClientCell(row, true, false)}
</div>
{ipMatchListStatus !== IP_MATCH_LIST_STATUS.CIDR
&& renderBlockingButton(ipMatchListStatus, value, toggleClientStatus, processing)}
@@ -99,7 +99,7 @@ const Clients = ({
{
Header: 'IP',
accessor: 'ip',
sortMethod: (a, b) => parseInt(a.replace(/\./g, ''), 10) - parseInt(b.replace(/\./g, ''), 10),
sortMethod: sortIp,
Cell: clientCell(t, toggleClientStatus, processingAccessSet, disallowedClients),
},
{

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

@@ -59,6 +59,4 @@ const Services = () => {
);
};
Services.propTypes = {};
export default Services;

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

@@ -90,9 +90,8 @@ class Menu extends Component {
};
getActiveClassForDropdown = (URLS) => {
const { pathname } = this.props.location;
const isActivePage = Object.values(URLS)
.some((item) => item === pathname);
.some((item) => item === this.props.pathname);
return isActivePage ? 'active' : '';
};
@@ -180,9 +179,9 @@ class Menu extends Component {
}
Menu.propTypes = {
isMenuOpen: PropTypes.bool,
closeMenu: PropTypes.func,
location: PropTypes.object,
isMenuOpen: PropTypes.bool.isRequired,
closeMenu: PropTypes.func.isRequired,
pathname: PropTypes.string.isRequired,
t: PropTypes.func,
};

View File

@@ -1,83 +1,77 @@
import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import PropTypes from 'prop-types';
import React, { useState } from 'react';
import { Link, useLocation } from 'react-router-dom';
import { shallowEqual, useSelector } from 'react-redux';
import { Trans } from 'react-i18next';
import classnames from 'classnames';
import { Trans, withTranslation } from 'react-i18next';
import Menu from './Menu';
import logo from '../ui/svg/logo.svg';
import './Header.css';
class Header extends Component {
state = {
isMenuOpen: false,
const Header = () => {
const [isMenuOpen, setIsMenuOpen] = useState(false);
const {
protectionEnabled,
processing,
isCoreRunning,
processingProfile,
name,
} = useSelector((state) => state.dashboard, shallowEqual);
const { pathname } = useLocation();
const toggleMenuOpen = () => {
setIsMenuOpen((isMenuOpen) => !isMenuOpen);
};
toggleMenuOpen = () => {
this.setState((prevState) => ({ isMenuOpen: !prevState.isMenuOpen }));
const closeMenu = () => {
setIsMenuOpen(false);
};
closeMenu = () => {
this.setState({ isMenuOpen: false });
};
const badgeClass = classnames('badge dns-status', {
'badge-success': protectionEnabled,
'badge-danger': !protectionEnabled,
});
render() {
const { dashboard, location } = this.props;
const { isMenuOpen } = this.state;
const badgeClass = classnames({
'badge dns-status': true,
'badge-success': dashboard.protectionEnabled,
'badge-danger': !dashboard.protectionEnabled,
});
return (
<div className="header">
<div className="header__container">
<div className="header__row">
<div
className="header-toggler d-lg-none ml-lg-0 collapsed"
onClick={this.toggleMenuOpen}
>
<span className="header-toggler-icon" />
</div>
<div className="header__column">
<div className="d-flex align-items-center">
<Link to="/" className="nav-link pl-0 pr-1">
<img src={logo} alt="" className="header-brand-img" />
</Link>
{!dashboard.processing && dashboard.isCoreRunning && (
<span className={badgeClass}>
<Trans>{dashboard.protectionEnabled ? 'on' : 'off'}</Trans>
return (
<div className="header">
<div className="header__container">
<div className="header__row">
<div
className="header-toggler d-lg-none ml-lg-0 collapsed"
onClick={toggleMenuOpen}
>
<span className="header-toggler-icon" />
</div>
<div className="header__column">
<div className="d-flex align-items-center">
<Link to="/" className="nav-link pl-0 pr-1">
<img src={logo} alt="" className="header-brand-img" />
</Link>
{!processing && isCoreRunning && (
<span className={badgeClass}>
<Trans>{protectionEnabled ? 'on' : 'off'}</Trans>
</span>
)}
</div>
)}
</div>
<Menu
location={location}
isMenuOpen={isMenuOpen}
closeMenu={this.closeMenu}
/>
<div className="header__column">
<div className="header__right">
{!dashboard.processingProfile && dashboard.name
&& <a href="control/logout" className="btn btn-sm btn-outline-secondary">
<Trans>sign_out</Trans>
</a>
}
</div>
</div>
<Menu
pathname={pathname}
isMenuOpen={isMenuOpen}
closeMenu={closeMenu}
/>
<div className="header__column">
<div className="header__right">
{!processingProfile && name
&& <a href="control/logout" className="btn btn-sm btn-outline-secondary">
<Trans>sign_out</Trans>
</a>}
</div>
</div>
</div>
</div>
);
}
}
Header.propTypes = {
dashboard: PropTypes.object.isRequired,
location: PropTypes.object.isRequired,
getVersion: PropTypes.func.isRequired,
t: PropTypes.func.isRequired,
</div>
);
};
export default withTranslation()(Header);
export default Header;

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

@@ -9,7 +9,7 @@ const Filters = ({ filter, refreshLogs, setIsLoading }) => (
<Trans>query_log</Trans>
<button
type="button"
className="btn btn-icon--green ml-3 bg-transparent"
className="btn btn-icon--green logs__refresh"
onClick={refreshLogs}
>
<svg className="icons icon--24">

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: var(--white) url('../ui/svg/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;
}
}
@@ -536,3 +539,16 @@
.loading__text {
transform: translateY(3rem);
}
.logs__refresh {
position: relative;
top: 3px;
display: inline-flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
padding: 0;
margin-left: 15px;
background-color: transparent;
}

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

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#9aa0ac" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-chevron-down"><polyline points="6 9 12 15 18 9"></polyline></svg>

Before

Width:  |  Height:  |  Size: 264 B

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

@@ -8,6 +8,7 @@ import CellWrap from '../../ui/CellWrap';
import whoisCell from './whoisCell';
import LogsSearchLink from '../../ui/LogsSearchLink';
import { sortIp } from '../../../helpers/helpers';
const COLUMN_MIN_WIDTH = 200;
@@ -18,6 +19,7 @@ class AutoClients extends Component {
accessor: 'ip',
minWidth: COLUMN_MIN_WIDTH,
Cell: CellWrap,
sortMethod: sortIp,
},
{
Header: this.props.t('table_name'),
@@ -85,23 +87,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

@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import ReactTable from 'react-table';
import { Trans, withTranslation } from 'react-i18next';
import { LEASES_TABLE_DEFAULT_PAGE_SIZE } from '../../../helpers/constants';
import { sortIp } from '../../../helpers/helpers';
class Leases extends Component {
cellWrap = ({ value }) => (
@@ -27,6 +28,7 @@ class Leases extends Component {
Header: 'IP',
accessor: 'ip',
Cell: this.cellWrap,
sortMethod: sortIp,
}, {
Header: <Trans>dhcp_table_hostname</Trans>,
accessor: 'hostname',

View File

@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
import ReactTable from 'react-table';
import { Trans, withTranslation } from 'react-i18next';
import { LEASES_TABLE_DEFAULT_PAGE_SIZE } from '../../../../helpers/constants';
import { sortIp } from '../../../../helpers/helpers';
import Modal from './Modal';
class StaticLeases extends Component {
@@ -49,6 +49,7 @@ class StaticLeases extends Component {
{
Header: 'IP',
accessor: 'ip',
sortMethod: sortIp,
Cell: this.cellWrap,
},
{

View File

@@ -1,4 +1,4 @@
import React, { Component, Fragment } from 'react';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { Trans, withTranslation } from 'react-i18next';
@@ -34,13 +34,14 @@ class Dhcp extends Component {
} = this.props.dhcp;
const otherDhcpFound = check?.otherServer
&& check.otherServer.found === DHCP_STATUS_RESPONSE.YES;
const filledConfig = Object.keys(config).every((key) => {
if (key === 'enabled' || key === 'icmp_timeout_msec') {
return true;
}
const filledConfig = Object.keys(config)
.every((key) => {
if (key === 'enabled' || key === 'icmp_timeout_msec') {
return true;
}
return config[key];
});
return config[key];
});
if (config.enabled) {
return (
@@ -114,40 +115,35 @@ class Dhcp extends Component {
getStaticIpWarning = (t, check, interfaceName) => {
if (check.staticIP.static === DHCP_STATUS_RESPONSE.ERROR) {
return (
<Fragment>
<div className="text-danger mb-2">
<Trans>dhcp_static_ip_error</Trans>
<div className="mt-2 mb-2">
<Accordion label={t('error_details')}>
<span>{check.staticIP.error}</span>
</Accordion>
</div>
return <>
<div className="text-danger mb-2">
<Trans>dhcp_static_ip_error</Trans>
<div className="mt-2 mb-2">
<Accordion label={t('error_details')}>
<span>{check.staticIP.error}</span>
</Accordion>
</div>
<hr className="mt-4 mb-4" />
</Fragment>
);
} if (
check.staticIP.static === DHCP_STATUS_RESPONSE.NO
</div>
<hr className="mt-4 mb-4" />
</>;
}
if (check.staticIP.static === DHCP_STATUS_RESPONSE.NO
&& check.staticIP.ip
&& interfaceName
) {
return (
<Fragment>
<div className="text-secondary mb-2">
<Trans
components={[<strong key="0">example</strong>]}
values={{
interfaceName,
ipAddress: check.staticIP.ip,
}}
>
dhcp_dynamic_ip_found
</Trans>
</div>
<hr className="mt-4 mb-4" />
</Fragment>
);
&& interfaceName) {
return <>
<div className="text-secondary mb-2">
<Trans
components={[<strong key="0">example</strong>]}
values={{
interfaceName,
ipAddress: check.staticIP.ip,
}}
>
dhcp_dynamic_ip_found
</Trans>
</div>
<hr className="mt-4 mb-4" />
</>;
}
return '';
@@ -163,104 +159,101 @@ class Dhcp extends Component {
removeStaticLease,
toggleLeaseModal,
} = this.props;
const statusButtonClass = classnames({
'btn btn-primary btn-standard': true,
'btn btn-primary btn-standard btn-loading': dhcp.processingStatus,
});
const { enabled, interface_name, ...values } = dhcp.config;
return (
<Fragment>
<PageTitle title={t('dhcp_settings')} />
{(dhcp.processing || dhcp.processingInterfaces) && <Loading />}
{!dhcp.processing && !dhcp.processingInterfaces && (
<Fragment>
<Card
title={t('dhcp_title')}
subtitle={t('dhcp_description')}
bodyType="card-body box-body--settings"
>
<div className="dhcp">
<Fragment>
<Form
onSubmit={this.handleFormSubmit}
initialValues={{
interface_name,
...values,
}}
interfaces={dhcp.interfaces}
processingConfig={dhcp.processingConfig}
processingInterfaces={dhcp.processingInterfaces}
enabled={enabled}
resetDhcp={resetDhcp}
/>
<hr />
<div className="card-actions mb-3">
{this.getToggleDhcpButton()}
<button
type="button"
className={statusButtonClass}
onClick={() => findActiveDhcp(interface_name)}
disabled={
enabled || !interface_name || dhcp.processingConfig
}
>
<Trans>check_dhcp_servers</Trans>
</button>
</div>
{!enabled && dhcp.check && (
<Fragment>
{this.getStaticIpWarning(t, dhcp.check, interface_name)}
{this.getActiveDhcpMessage(t, dhcp.check)}
{this.getDhcpWarning(dhcp.check)}
</Fragment>
)}
</Fragment>
return <>
<PageTitle title={t('dhcp_settings')} />
{(dhcp.processing || dhcp.processingInterfaces) && <Loading />}
{!dhcp.processing && !dhcp.processingInterfaces && <>
<Card
title={t('dhcp_title')}
subtitle={t('dhcp_description')}
bodyType="card-body box-body--settings"
>
<div className="dhcp">
<>
<Form
onSubmit={this.handleFormSubmit}
initialValues={{
interface_name,
...values,
}}
interfaces={dhcp.interfaces}
processingConfig={dhcp.processingConfig}
processingInterfaces={dhcp.processingInterfaces}
enabled={enabled}
resetDhcp={resetDhcp}
/>
<hr />
<div className="card-actions mb-3">
{this.getToggleDhcpButton()}
<button
type="button"
className={statusButtonClass}
onClick={() => findActiveDhcp(interface_name)}
disabled={
enabled || !interface_name || dhcp.processingConfig
}
>
<Trans>check_dhcp_servers</Trans>
</button>
</div>
</Card>
{dhcp.config.enabled && (
<Card
title={t('dhcp_leases')}
bodyType="card-body box-body--settings"
>
<div className="row">
<div className="col">
<Leases leases={dhcp.leases} />
</div>
</div>
</Card>
)}
<Card
title={t('dhcp_static_leases')}
bodyType="card-body box-body--settings"
>
<div className="row">
<div className="col-12">
<StaticLeases
staticLeases={dhcp.staticLeases}
isModalOpen={dhcp.isModalOpen}
addStaticLease={addStaticLease}
removeStaticLease={removeStaticLease}
toggleLeaseModal={toggleLeaseModal}
processingAdding={dhcp.processingAdding}
processingDeleting={dhcp.processingDeleting}
/>
</div>
<div className="col-12">
<button
type="button"
className="btn btn-success btn-standard mt-3"
onClick={() => toggleLeaseModal()}
>
<Trans>dhcp_add_static_lease</Trans>
</button>
</div>
{!enabled && dhcp.check && (
<>
{this.getStaticIpWarning(t, dhcp.check, interface_name)}
{this.getActiveDhcpMessage(t, dhcp.check)}
{this.getDhcpWarning(dhcp.check)}
</>
)}
</>
</div>
</Card>
{dhcp.config.enabled && (
<Card
title={t('dhcp_leases')}
bodyType="card-body box-body--settings"
>
<div className="row">
<div className="col">
<Leases leases={dhcp.leases} />
</div>
</Card>
</Fragment>
</div>
</Card>
)}
</Fragment>
);
<Card
title={t('dhcp_static_leases')}
bodyType="card-body box-body--settings"
>
<div className="row">
<div className="col-12">
<StaticLeases
staticLeases={dhcp.staticLeases}
isModalOpen={dhcp.isModalOpen}
addStaticLease={addStaticLease}
removeStaticLease={removeStaticLease}
toggleLeaseModal={toggleLeaseModal}
processingAdding={dhcp.processingAdding}
processingDeleting={dhcp.processingDeleting}
/>
</div>
<div className="col-12">
<button
type="button"
className="btn btn-success btn-standard mt-3"
onClick={() => toggleLeaseModal()}
>
<Trans>dhcp_add_static_lease</Trans>
</button>
</div>
</div>
</Card>
</>}
</>;
}
}

View File

@@ -1,40 +1,36 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { withTranslation } from 'react-i18next';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import Form from './Form';
import Card from '../../../ui/Card';
import { setAccessList } from '../../../../actions/access';
class Access extends Component {
handleFormSubmit = (values) => {
this.props.setAccessList(values);
const Access = () => {
const { t } = useTranslation();
const dispatch = useDispatch();
const {
processing,
processingSet,
...values
} = useSelector((state) => state.access, shallowEqual);
const handleFormSubmit = (values) => {
dispatch(setAccessList(values));
};
render() {
const { t, access } = this.props;
const { processing, processingSet, ...values } = access;
return (
<Card
title={t('access_title')}
subtitle={t('access_desc')}
bodyType="card-body box-body--settings"
>
<Form
initialValues={values}
onSubmit={this.handleFormSubmit}
processingSet={processingSet}
/>
</Card>
);
}
}
Access.propTypes = {
access: PropTypes.object.isRequired,
setAccessList: PropTypes.func.isRequired,
t: PropTypes.func.isRequired,
return (
<Card
title={t('access_title')}
subtitle={t('access_desc')}
bodyType="card-body box-body--settings"
>
<Form
initialValues={values}
onSubmit={handleFormSubmit}
processingSet={processingSet}
/>
</Card>
);
};
export default withTranslation()(Access);
export default Access;

View File

@@ -53,7 +53,7 @@ const Form = ({
{INPUTS_FIELDS.map(({
name, title, description, placeholder, validate, max,
}) => <div className="col-12" key={name}>
<div className="col-7 p-0">
<div className="col-12 col-md-7 p-0">
<div className="form__group form__group--settings">
<label htmlFor={name}
className="form__label form__label--with-desc">{t(title)}</label>

View File

@@ -1,9 +1,8 @@
import React, { Fragment } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { Field, reduxForm, formValueSelector } from 'redux-form';
import { Trans, withTranslation } from 'react-i18next';
import flow from 'lodash/flow';
import { shallowEqual, useSelector } from 'react-redux';
import { Field, reduxForm } from 'redux-form';
import { Trans, useTranslation } from 'react-i18next';
import {
renderInputField,
renderRadioField,
@@ -18,32 +17,36 @@ import {
} from '../../../../helpers/validators';
import { BLOCKING_MODES, FORM_NAME } from '../../../../helpers/constants';
const checkboxes = [{
name: 'edns_cs_enabled',
placeholder: 'edns_enable',
subtitle: 'edns_cs_desc',
},
{
name: 'dnssec_enabled',
placeholder: 'dnssec_enable',
subtitle: 'dnssec_enable_desc',
},
{
name: 'disable_ipv6',
placeholder: 'disable_ipv6',
subtitle: 'disable_ipv6_desc',
}];
const checkboxes = [
{
name: 'edns_cs_enabled',
placeholder: 'edns_enable',
subtitle: 'edns_cs_desc',
},
{
name: 'dnssec_enabled',
placeholder: 'dnssec_enable',
subtitle: 'dnssec_enable_desc',
},
{
name: 'disable_ipv6',
placeholder: 'disable_ipv6',
subtitle: 'disable_ipv6_desc',
},
];
const customIps = [{
description: 'blocking_ipv4_desc',
name: 'blocking_ipv4',
validateIp: validateIpv4,
},
{
description: 'blocking_ipv6_desc',
name: 'blocking_ipv6',
validateIp: validateIpv6,
}];
const customIps = [
{
description: 'blocking_ipv4_desc',
name: 'blocking_ipv4',
validateIp: validateIpv4,
},
{
description: 'blocking_ipv6_desc',
name: 'blocking_ipv6',
validateIp: validateIpv6,
},
];
const getFields = (processing, t) => Object.values(BLOCKING_MODES)
.map((mode) => (
@@ -58,114 +61,107 @@ const getFields = (processing, t) => Object.values(BLOCKING_MODES)
/>
));
let Form = ({
handleSubmit, submitting, invalid, processing, blockingMode, t,
}) => <form onSubmit={handleSubmit}>
<div className="row">
<div className="col-12 col-sm-6">
<div className="form__group form__group--settings">
<label htmlFor="ratelimit"
className="form__label form__label--with-desc">
<Trans>rate_limit</Trans>
</label>
<div className="form__desc form__desc--top">
<Trans>rate_limit_desc</Trans>
</div>
<Field
name="ratelimit"
type="number"
component={renderInputField}
className="form-control"
placeholder={t('form_enter_rate_limit')}
normalize={toNumber}
validate={[validateRequiredValue, validateBiggerOrEqualZeroValue]}
/>
</div>
</div>
{checkboxes.map(({ name, placeholder, subtitle }) => <div className="col-12" key={name}>
<div className="form__group form__group--settings">
<Field
name={name}
type="checkbox"
component={renderSelectField}
placeholder={t(placeholder)}
disabled={processing}
subtitle={t(subtitle)}
/>
</div>
</div>)}
<div className="col-12">
<div className="form__group form__group--settings mb-4">
<label className="form__label form__label--with-desc">
<Trans>blocking_mode</Trans>
</label>
<div className="form__desc form__desc--top">
{Object.values(BLOCKING_MODES)
.map((mode) => (
<li key={mode}>
<Trans>{`blocking_mode_${mode}`}</Trans>
</li>
))}
</div>
<div className="custom-controls-stacked">
{getFields(processing, t)}
</div>
</div>
</div>
{blockingMode === BLOCKING_MODES.custom_ip && (
<Fragment>
{customIps.map(({
description,
name,
validateIp,
}) => <div className="col-12 col-sm-6" key={name}>
<div className="form__group form__group--settings">
<label className="form__label form__label--with-desc"
htmlFor={name}><Trans>{name}</Trans>
</label>
<div className="form__desc form__desc--top">
<Trans>{description}</Trans>
</div>
<Field
name={name}
component={renderInputField}
className="form-control"
placeholder={t('form_enter_ip')}
validate={[validateIp, validateRequiredValue]}
/>
const Form = ({
handleSubmit, submitting, invalid, processing,
}) => {
const { t } = useTranslation();
const {
blocking_mode,
} = useSelector((state) => state.form[FORM_NAME.BLOCKING_MODE].values ?? {}, shallowEqual);
return <form onSubmit={handleSubmit}>
<div className="row">
<div className="col-12 col-sm-6">
<div className="form__group form__group--settings">
<label htmlFor="ratelimit"
className="form__label form__label--with-desc">
<Trans>rate_limit</Trans>
</label>
<div className="form__desc form__desc--top">
<Trans>rate_limit_desc</Trans>
</div>
</div>)}
</Fragment>
)}
</div>
<button
type="submit"
className="btn btn-success btn-standard btn-large"
disabled={submitting || invalid || processing}
>
<Trans>save_btn</Trans>
</button>
</form>;
<Field
name="ratelimit"
type="number"
component={renderInputField}
className="form-control"
placeholder={t('form_enter_rate_limit')}
normalize={toNumber}
validate={[validateRequiredValue, validateBiggerOrEqualZeroValue]}
/>
</div>
</div>
{checkboxes.map(({ name, placeholder, subtitle }) => <div className="col-12" key={name}>
<div className="form__group form__group--settings">
<Field
name={name}
type="checkbox"
component={renderSelectField}
placeholder={t(placeholder)}
disabled={processing}
subtitle={t(subtitle)}
/>
</div>
</div>)}
<div className="col-12">
<div className="form__group form__group--settings mb-4">
<label className="form__label form__label--with-desc">
<Trans>blocking_mode</Trans>
</label>
<div className="form__desc form__desc--top">
{Object.values(BLOCKING_MODES)
.map((mode) => (
<li key={mode}>
<Trans>{`blocking_mode_${mode}`}</Trans>
</li>
))}
</div>
<div className="custom-controls-stacked">
{getFields(processing, t)}
</div>
</div>
</div>
{blocking_mode === BLOCKING_MODES.custom_ip && (
<>
{customIps.map(({
description,
name,
validateIp,
}) => <div className="col-12 col-sm-6" key={name}>
<div className="form__group form__group--settings">
<label className="form__label form__label--with-desc"
htmlFor={name}><Trans>{name}</Trans>
</label>
<div className="form__desc form__desc--top">
<Trans>{description}</Trans>
</div>
<Field
name={name}
component={renderInputField}
className="form-control"
placeholder={t('form_enter_ip')}
validate={[validateIp, validateRequiredValue]}
/>
</div>
</div>)}
</>
)}
</div>
<button
type="submit"
className="btn btn-success btn-standard btn-large"
disabled={submitting || invalid || processing}
>
<Trans>save_btn</Trans>
</button>
</form>;
};
Form.propTypes = {
blockingMode: PropTypes.string.isRequired,
handleSubmit: PropTypes.func.isRequired,
submitting: PropTypes.bool.isRequired,
invalid: PropTypes.bool.isRequired,
processing: PropTypes.bool.isRequired,
t: PropTypes.func.isRequired,
};
const selector = formValueSelector(FORM_NAME.BLOCKING_MODE);
Form = connect((state) => {
const blockingMode = selector(state, 'blocking_mode');
return {
blockingMode,
};
})(Form);
export default flow([
withTranslation(),
reduxForm({ form: FORM_NAME.BLOCKING_MODE }),
])(Form);
export default reduxForm({ form: FORM_NAME.BLOCKING_MODE })(Form);

View File

@@ -1,15 +1,13 @@
import React from 'react';
import PropTypes from 'prop-types';
import { withTranslation } from 'react-i18next';
import { useTranslation } from 'react-i18next';
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import Card from '../../../ui/Card';
import Form from './Form';
import { setDnsConfig } from '../../../../actions/dnsConfig';
const Config = ({ t, dnsConfig, setDnsConfig }) => {
const handleFormSubmit = (values) => {
setDnsConfig(values);
};
const Config = () => {
const { t } = useTranslation();
const dispatch = useDispatch();
const {
blocking_mode,
ratelimit,
@@ -19,7 +17,11 @@ const Config = ({ t, dnsConfig, setDnsConfig }) => {
dnssec_enabled,
disable_ipv6,
processingSetConfig,
} = dnsConfig;
} = useSelector((state) => state.dnsConfig, shallowEqual);
const handleFormSubmit = (values) => {
dispatch(setDnsConfig(values));
};
return (
<Card
@@ -46,10 +48,4 @@ const Config = ({ t, dnsConfig, setDnsConfig }) => {
);
};
Config.propTypes = {
dnsConfig: PropTypes.object.isRequired,
setDnsConfig: PropTypes.func.isRequired,
t: PropTypes.func.isRequired,
};
export default withTranslation()(Config);
export default Config;

View File

@@ -52,10 +52,11 @@ const Form = ({
submitting, invalid, processingSetConfig, processingTestUpstream, handleSubmit,
}) => {
const dispatch = useDispatch();
const [t] = useTranslation();
const { t } = useTranslation();
const upstream_dns = useSelector((store) => store.form[FORM_NAME.UPSTREAM].values.upstream_dns);
const bootstrap_dns = useSelector((store) => store.form[FORM_NAME.UPSTREAM]
.values.bootstrap_dns);
const bootstrap_dns = useSelector(
(store) => store.form[FORM_NAME.UPSTREAM].values.bootstrap_dns,
);
const handleUpstreamTest = () => dispatch(testUpstream({
upstream_dns,

View File

@@ -1,56 +1,46 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import Form from './Form';
import Card from '../../../ui/Card';
import { setDnsConfig } from '../../../../actions/dnsConfig';
const Upstream = (props) => {
const [t] = useTranslation();
const Upstream = () => {
const { t } = useTranslation();
const dispatch = useDispatch();
const {
upstream_dns,
bootstrap_dns,
upstream_mode,
processingSetConfig,
} = useSelector((state) => state.dnsConfig, shallowEqual);
const { processingTestUpstream } = useSelector((state) => state.settings, shallowEqual);
const handleSubmit = (values) => {
dispatch(setDnsConfig(values));
};
const {
processingTestUpstream,
dnsConfig: {
upstream_dns,
bootstrap_dns,
processingSetConfig,
upstream_mode,
},
} = props;
return (
<Card
title={t('upstream_dns')}
subtitle={t('upstream_dns_hint')}
bodyType="card-body box-body--settings"
>
<div className="row">
<div className="col">
<Form
initialValues={{
upstream_dns,
bootstrap_dns,
upstream_mode,
}}
onSubmit={handleSubmit}
processingTestUpstream={processingTestUpstream}
processingSetConfig={processingSetConfig}
/>
</div>
return <Card
title={t('upstream_dns')}
subtitle={t('upstream_dns_hint')}
bodyType="card-body box-body--settings"
>
<div className="row">
<div className="col">
<Form
initialValues={{
upstream_dns,
bootstrap_dns,
upstream_mode,
}}
onSubmit={handleSubmit}
processingTestUpstream={processingTestUpstream}
processingSetConfig={processingSetConfig}
/>
</div>
</Card>
);
};
Upstream.propTypes = {
processingTestUpstream: PropTypes.bool.isRequired,
dnsConfig: PropTypes.object.isRequired,
</div>
</Card>;
};
export default Upstream;

View File

@@ -1,67 +1,40 @@
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import Upstream from './Upstream';
import Access from './Access';
import Config from './Config';
import PageTitle from '../../ui/PageTitle';
import Loading from '../../ui/Loading';
import CacheConfig from './Cache';
import { getDnsConfig } from '../../../actions/dnsConfig';
import { getAccessList } from '../../../actions/access';
const Dns = (props) => {
const Dns = () => {
const { t } = useTranslation();
const dispatch = useDispatch();
const processing = useSelector((state) => state.access.processing);
const processingGetConfig = useSelector((state) => state.dnsConfig.processingGetConfig);
const isDataLoading = processing || processingGetConfig;
useEffect(() => {
props.getAccessList();
props.getDnsConfig();
dispatch(getAccessList());
dispatch(getDnsConfig());
}, []);
const {
settings,
access,
setAccessList,
dnsConfig,
setDnsConfig,
} = props;
const isDataLoading = access.processing || dnsConfig.processingGetConfig;
return (
<>
<PageTitle title={t('dns_settings')} />
{isDataLoading
? <Loading />
: <>
<Upstream
processingTestUpstream={settings.processingTestUpstream}
dnsConfig={dnsConfig}
/>
<Config
dnsConfig={dnsConfig}
setDnsConfig={setDnsConfig}
/>
<CacheConfig
dnsConfig={dnsConfig}
setDnsConfig={setDnsConfig}
/>
<Access
access={access}
setAccessList={setAccessList}
/>
</>}
</>
);
};
Dns.propTypes = {
settings: PropTypes.object.isRequired,
getAccessList: PropTypes.func.isRequired,
setAccessList: PropTypes.func.isRequired,
access: PropTypes.object.isRequired,
dnsConfig: PropTypes.object.isRequired,
setDnsConfig: PropTypes.func.isRequired,
getDnsConfig: PropTypes.func.isRequired,
return <>
<PageTitle title={t('dns_settings')} />
{isDataLoading
? <Loading />
: <>
<Upstream />
<Config />
<CacheConfig />
<Access />
</>}
</>;
};
export default Dns;

View File

@@ -10,53 +10,50 @@ import Checkbox from '../ui/Checkbox';
import Loading from '../ui/Loading';
import PageTitle from '../ui/PageTitle';
import Card from '../ui/Card';
import { getObjectKeysSorted } from '../../helpers/helpers';
import './Settings.css';
class Settings extends Component {
settings = {
safebrowsing: {
enabled: false,
title: 'use_adguard_browsing_sec',
subtitle: 'use_adguard_browsing_sec_hint',
},
parental: {
enabled: false,
title: 'use_adguard_parental',
subtitle: 'use_adguard_parental_hint',
},
safesearch: {
enabled: false,
title: 'enforce_safe_search',
subtitle: 'enforce_save_search_hint',
},
};
const ORDER_KEY = 'order';
const SETTINGS = {
safebrowsing: {
enabled: false,
title: 'use_adguard_browsing_sec',
subtitle: 'use_adguard_browsing_sec_hint',
[ORDER_KEY]: 0,
},
parental: {
enabled: false,
title: 'use_adguard_parental',
subtitle: 'use_adguard_parental_hint',
[ORDER_KEY]: 1,
},
safesearch: {
enabled: false,
title: 'enforce_safe_search',
subtitle: 'enforce_save_search_hint',
[ORDER_KEY]: 2,
},
};
class Settings extends Component {
componentDidMount() {
this.props.initSettings(this.settings);
this.props.initSettings(SETTINGS);
this.props.getStatsConfig();
this.props.getLogsConfig();
this.props.getFilteringStatus();
}
renderSettings = (settings) => {
const settingsKeys = Object.keys(settings);
if (settingsKeys.length > 0) {
return settingsKeys.map((key) => {
const setting = settings[key];
const { enabled } = setting;
return (
<Checkbox
{...settings[key]}
key={key}
handleChange={() => this.props.toggleSetting(key, enabled)}
/>
);
});
}
return '';
};
renderSettings = (settings) => getObjectKeysSorted(settings, ORDER_KEY)
.map((key) => {
const setting = settings[key];
const { enabled } = setting;
return <Checkbox
{...setting}
key={key}
handleChange={() => this.props.toggleSetting(key, enabled)}
/>;
});
render() {
const {

View File

@@ -13,3 +13,17 @@
margin-bottom: 20px;
font-size: 15px;
}
.guide__address {
display: block;
margin-bottom: 7px;
font-size: 13px;
font-weight: 700;
}
@media screen and (min-width: 768px) {
.guide__address {
display: list-item;
font-size: 15px;
}
}

View File

@@ -24,8 +24,8 @@ const SetupGuide = ({
<div className="mt-1">
<Trans>install_devices_address</Trans>:
</div>
<div className="mt-2 font-weight-bold">
{dnsAddresses.map((ip) => <li key={ip}>{ip}</li>)}
<div className="mt-3">
{dnsAddresses.map((ip) => <li key={ip} className="guide__address">{ip}</li>)}
</div>
</div>
<Guide dnsAddresses={dnsAddresses} />

View File

@@ -1,19 +1,20 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Trans, withTranslation } from 'react-i18next';
import { Trans } from 'react-i18next';
import isAfter from 'date-fns/is_after';
import addDays from 'date-fns/add_days';
import { useSelector } from 'react-redux';
import Topline from './Topline';
import { EMPTY_DATE } from '../../helpers/constants';
const EncryptionTopline = (props) => {
if (props.notAfter === EMPTY_DATE) {
return false;
const EncryptionTopline = () => {
const not_after = useSelector((state) => state.encryption.not_after);
if (not_after === EMPTY_DATE) {
return null;
}
const isAboutExpire = isAfter(addDays(Date.now(), 30), props.notAfter);
const isExpired = isAfter(Date.now(), props.notAfter);
const isAboutExpire = isAfter(addDays(Date.now(), 30), not_after);
const isExpired = isAfter(Date.now(), not_after);
if (isExpired) {
return (
@@ -23,7 +24,9 @@ const EncryptionTopline = (props) => {
</Trans>
</Topline>
);
} if (isAboutExpire) {
}
if (isAboutExpire) {
return (
<Topline type="warning">
<Trans components={[<a href="#encryption" key="0">link</a>]}>
@@ -36,8 +39,4 @@ const EncryptionTopline = (props) => {
return false;
};
EncryptionTopline.propTypes = {
notAfter: PropTypes.string.isRequired,
};
export default withTranslation()(EncryptionTopline);
export default EncryptionTopline;

View File

@@ -1,5 +1,4 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useTranslation } from 'react-i18next';
import classNames from 'classnames';
@@ -28,7 +27,7 @@ const linksData = [
},
];
const Footer = (props) => {
const Footer = () => {
const { t } = useTranslation();
const getYear = () => {
@@ -59,11 +58,6 @@ const Footer = (props) => {
{t(name)}
</a>);
const {
dnsVersion, processingVersion, getVersion, checkUpdateFlag,
} = props;
return (
<>
<footer className="footer">
@@ -94,12 +88,7 @@ const Footer = (props) => {
<div className="footer__row">
{renderCopyright()}
<div className="footer__column footer__column--language">
<Version
dnsVersion={dnsVersion}
processingVersion={processingVersion}
getVersion={getVersion}
checkUpdateFlag={checkUpdateFlag}
/>
<Version />
</div>
</div>
</div>
@@ -108,11 +97,4 @@ const Footer = (props) => {
);
};
Footer.propTypes = {
dnsVersion: PropTypes.string,
processingVersion: PropTypes.bool,
getVersion: PropTypes.func,
checkUpdateFlag: PropTypes.bool,
};
export default Footer;

View File

@@ -1,9 +1,14 @@
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import './Loading.css';
const Loading = () => (
<div className="loading" />
const Loading = ({ className }) => (
<div className={classNames('loading', className)} />
);
Loading.propTypes = {
className: PropTypes.string,
};
export default Loading;

View File

@@ -6,18 +6,24 @@
.page-header--logs {
flex-direction: row;
align-items: flex-end;
margin: 2rem 0 3rem;
margin: 2rem 0 2.8rem;
}
.page-header--logs .page-title {
display: inline-flex;
align-items: center;
}
@media (max-width: 991px) {
.page-header--logs {
flex-direction: column;
align-items: center;
margin-bottom: 1.5rem;
margin: 1.1rem 0;
}
.page-header--logs .page-title {
padding-bottom: 2.5rem;;
margin-bottom: 1.1rem;
font-size: 1.8rem;
}
}

View File

@@ -2,8 +2,16 @@
display: flex;
justify-content: space-between;
margin-bottom: 15px;
padding: 15px 0;
padding: 10px 0;
border-bottom: 1px solid #e8e8e8;
overflow: auto;
}
@media screen and (min-width: 768px) {
.tabs__controls {
padding: 15px 0;
overflow: initial;
}
}
.tabs__controls--form {
@@ -26,11 +34,18 @@
align-items: center;
min-width: 70px;
font-size: 13px;
white-space: nowrap;
color: #555555;
cursor: pointer;
opacity: 0.6;
}
@media screen and (min-width: 768px) {
.tab__control {
white-space: normal;
}
}
.tab__control:hover,
.tab__control:focus {
opacity: 1;

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,
MEDIUM_SCREEN_SIZE,
SHOW_TOOLTIP_DELAY,
} from '../../helpers/constants';
import 'react-popper-tooltip/dist/styles.css';
import './Tooltip.css';
@@ -13,30 +18,51 @@ const Tooltip = ({
className = 'tooltip-container',
placement = 'bottom',
trigger = 'hover',
delayShow = SHOW_TOOLTIP_DELAY,
delayHide = HIDE_TOOLTIP_DELAY,
}) => {
const { t } = useTranslation();
const touchEventsAvailable = 'ontouchstart' in window;
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>;
let triggerValue = trigger;
let delayHideValue = delayHide;
let delayShowValue = delayShow;
if (window.matchMedia(`(max-width: ${MEDIUM_SCREEN_SIZE}px)`).matches || touchEventsAvailable) {
triggerValue = 'click';
delayHideValue = 0;
delayShowValue = 0;
}
return (
<TooltipTrigger
placement={placement}
trigger={triggerValue}
delayHide={delayHideValue}
delayShow={delayShowValue}
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 = {
@@ -51,6 +77,7 @@ Tooltip.propTypes = {
placement: propTypes.string,
trigger: propTypes.string,
delayHide: propTypes.string,
delayShow: propTypes.string,
className: propTypes.string,
triggerClass: propTypes.string,
};

View File

@@ -1,14 +1,13 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Trans, withTranslation } from 'react-i18next';
import { Trans } from 'react-i18next';
import classnames from 'classnames';
import { useSelector } from 'react-redux';
import './Overlay.css';
const UpdateOverlay = (props) => {
const overlayClass = classnames({
overlay: true,
'overlay--visible': props.processingUpdate,
const UpdateOverlay = () => {
const processingUpdate = useSelector((state) => state.dashboard.processingUpdate);
const overlayClass = classnames('overlay', {
'overlay--visible': processingUpdate,
});
return (
@@ -19,8 +18,4 @@ const UpdateOverlay = (props) => {
);
};
UpdateOverlay.propTypes = {
processingUpdate: PropTypes.bool,
};
export default withTranslation()(UpdateOverlay);
export default UpdateOverlay;

View File

@@ -1,42 +1,46 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { Trans, withTranslation } from 'react-i18next';
import React from 'react';
import { Trans } from 'react-i18next';
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import Topline from './Topline';
import { getUpdate } from '../../actions';
const UpdateTopline = (props) => (
<Topline type="info">
<Fragment>
const UpdateTopline = () => {
const {
announcementUrl,
newVersion,
canAutoUpdate,
processingUpdate,
} = useSelector((state) => state.dashboard, shallowEqual);
const dispatch = useDispatch();
const handleUpdate = () => {
dispatch(getUpdate());
};
return <Topline type="info">
<>
<Trans
values={{ version: props.version }}
values={{ version: newVersion }}
components={[
<a href={props.url} target="_blank" rel="noopener noreferrer" key="0">
<a href={announcementUrl} target="_blank" rel="noopener noreferrer" key="0">
Click here
</a>,
]}
>
update_announcement
</Trans>
{props.canAutoUpdate
&& <button
type="button"
className="btn btn-sm btn-primary ml-3"
onClick={props.getUpdate}
disabled={props.processingUpdate}
>
<Trans>update_now</Trans>
</button>
{canAutoUpdate
&& <button
type="button"
className="btn btn-sm btn-primary ml-3"
onClick={handleUpdate}
disabled={processingUpdate}
>
<Trans>update_now</Trans>
</button>
}
</Fragment>
</Topline>
);
UpdateTopline.propTypes = {
version: PropTypes.string,
url: PropTypes.string.isRequired,
canAutoUpdate: PropTypes.bool,
getUpdate: PropTypes.func,
processingUpdate: PropTypes.bool,
</>
</Topline>;
};
export default withTranslation()(UpdateTopline);
export default UpdateTopline;

View File

@@ -1,13 +1,21 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Trans, withTranslation } from 'react-i18next';
import { Trans, useTranslation } from 'react-i18next';
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import { getVersion } from '../../actions';
import './Version.css';
const Version = (props) => {
const Version = () => {
const dispatch = useDispatch();
const { t } = useTranslation();
const {
dnsVersion, processingVersion, t, checkUpdateFlag,
} = props;
dnsVersion,
processingVersion,
checkUpdateFlag,
} = useSelector((state) => state?.dashboard ?? {}, shallowEqual);
const onClick = () => {
dispatch(getVersion(true));
};
return (
<div className="version">
@@ -20,7 +28,7 @@ const Version = (props) => {
{checkUpdateFlag && <button
type="button"
className="btn btn-icon btn-icon-sm btn-outline-primary btn-sm ml-2"
onClick={() => props.getVersion(true)}
onClick={onClick}
disabled={processingVersion}
title={t('check_updates_now')}
>
@@ -33,12 +41,4 @@ const Version = (props) => {
);
};
Version.propTypes = {
dnsVersion: PropTypes.string,
getVersion: PropTypes.func,
processingVersion: PropTypes.bool,
checkUpdateFlag: PropTypes.bool,
t: PropTypes.func.isRequired,
};
export default withTranslation()(Version);
export default Version;

View File

@@ -1 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#9aa0ac" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-chevron-down"><polyline points="6 9 12 15 18 9"></polyline></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#9aa0ac"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-chevron-down">
<polyline points="6 9 12 15 18 9"></polyline>
</svg>

Before

Width:  |  Height:  |  Size: 264 B

After

Width:  |  Height:  |  Size: 276 B

View File

@@ -1 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#9aa0ac" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-globe"><circle cx="12" cy="12" r="10"/><path d="M2 12h20M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#9aa0ac"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-globe">
<circle cx="12" cy="12" r="10"/>
<path d="M2 12h20M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/>
</svg>

Before

Width:  |  Height:  |  Size: 354 B

After

Width:  |  Height:  |  Size: 371 B

View File

@@ -1 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#9aa0ac" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-help-circle"><circle cx="12" cy="12" r="10"></circle><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path><line x1="12" y1="17" x2="12" y2="17"></line></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#9aa0ac"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-help-circle">
<circle cx="12" cy="12" r="10"></circle>
<path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path>
<line x1="12" y1="17" x2="12" y2="17"></line>
</svg>

Before

Width:  |  Height:  |  Size: 357 B

After

Width:  |  Height:  |  Size: 379 B

View File

@@ -1 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" width="164" height="41" viewBox="0 0 164 41"><g fill-rule="evenodd"><path d="M129.984 22l-1.162-2.945h-5.792L121.931 22H118l6.277-15h3.509L134 22h-4.016zm-4.016-10.996l-1.902 5.149h3.762l-1.86-5.149zM117 16.1c0 .88-.153 1.682-.46 2.404a5.223 5.223 0 0 1-1.318 1.857c-.57.516-1.26.918-2.066 1.207-.807.289-1.703.433-2.688.433-1 0-1.9-.144-2.699-.433-.8-.29-1.477-.691-2.034-1.207a5.232 5.232 0 0 1-1.285-1.857c-.3-.722-.45-1.524-.45-2.404V7h3.64v8.81c0 .4.054.777.161 1.135.108.358.272.677.493.96.221.281.514.505.878.67.364.165.803.248 1.317.248.514 0 .953-.083 1.317-.248.365-.165.66-.389.89-.67.228-.283.392-.602.492-.96.1-.358.15-.736.15-1.135V7H117v9.099zm-16 4.673c-.733.362-1.59.658-2.57.886-.98.228-2.047.342-3.203.342-1.199 0-2.302-.181-3.31-.544-1.008-.362-1.875-.872-2.601-1.53a6.977 6.977 0 0 1-1.703-2.366c-.409-.92-.613-1.943-.613-3.07 0-1.141.208-2.175.624-3.1a6.903 6.903 0 0 1 1.723-2.367 7.71 7.71 0 0 1 2.58-1.5C92.914 7.174 93.98 7 95.121 7c1.184 0 2.284.171 3.299.513 1.015.343 1.84.802 2.474 1.38l-2.284 2.476c-.352-.39-.817-.708-1.395-.956-.579-.249-1.234-.373-1.967-.373-.635 0-1.22.111-1.756.332a4.23 4.23 0 0 0-1.395.927 4.178 4.178 0 0 0-.92 1.41 4.734 4.734 0 0 0-.328 1.78c0 .659.099 1.263.296 1.813.197.55.49 1.024.878 1.42.387.395.867.704 1.438.926.57.221 1.223.332 1.956.332.423 0 .825-.03 1.205-.09.381-.061.733-.158 1.058-.293V16h-2.855v-2.779H101v7.55zm63-6.314c0 1.313-.244 2.447-.73 3.4a6.855 6.855 0 0 1-1.928 2.352 8.035 8.035 0 0 1-2.7 1.356 10.94 10.94 0 0 1-3.05.434H150V7h5.422c1.06 0 2.104.124 3.135.37a7.866 7.866 0 0 1 2.753 1.23c.805.572 1.454 1.338 1.949 2.298.494.96.741 2.147.741 3.56zm-3.77 0c0-.848-.138-1.55-.413-2.108a3.549 3.549 0 0 0-1.101-1.335 4.405 4.405 0 0 0-1.568-.71 7.7 7.7 0 0 0-1.81-.212h-1.8v8.771h1.715c.65 0 1.274-.074 1.874-.222a4.43 4.43 0 0 0 1.589-.731 3.62 3.62 0 0 0 1.1-1.356c.276-.565.414-1.264.414-2.097zm-75.23 0c0 1.313-.244 2.447-.73 3.4a6.855 6.855 0 0 1-1.928 2.352 8.035 8.035 0 0 1-2.7 1.356 10.94 10.94 0 0 1-3.05.434H71V7h5.422c1.06 0 2.104.124 3.135.37A7.866 7.866 0 0 1 82.31 8.6c.805.572 1.454 1.338 1.949 2.298.494.96.741 2.147.741 3.56zm-3.77 0c0-.848-.138-1.55-.413-2.108a3.549 3.549 0 0 0-1.101-1.335 4.405 4.405 0 0 0-1.568-.71 7.7 7.7 0 0 0-1.81-.212h-1.8v8.771h1.715c.65 0 1.274-.074 1.874-.222a4.43 4.43 0 0 0 1.589-.731 3.62 3.62 0 0 0 1.1-1.356c.276-.565.414-1.264.414-2.097zM65.984 22l-1.162-2.945H59.03L57.931 22H54l6.277-15h3.509L70 22h-4.016zm-4.016-10.996l-1.902 5.149h3.762l-1.86-5.149zM143.855 22l-3.171-5.953h-1.202V22H136V7h5.596c.705 0 1.392.074 2.062.222.67.149 1.271.4 1.803.753a3.9 3.9 0 0 1 1.275 1.398c.318.579.476 1.3.476 2.16 0 1.018-.269 1.872-.808 2.564-.539.693-1.285 1.187-2.238 1.484L148 22h-4.145zm-.145-10.403c0-.353-.073-.639-.218-.858a1.502 1.502 0 0 0-.56-.508 2.393 2.393 0 0 0-.766-.244 5.535 5.535 0 0 0-.819-.063h-1.886v3.495h1.679c.29 0 .587-.024.891-.074.304-.05.58-.137.83-.264.248-.128.452-.311.61-.551.16-.24.239-.551.239-.933zM55 37.851v-8.702h.951v3.866h4.866V29.15h.952v8.702h-.952v-3.916h-4.866v3.916H55zM68.068 38c-2.565 0-4.288-2.076-4.288-4.5 0-2.4 1.747-4.5 4.312-4.5 2.565 0 4.288 2.076 4.288 4.5 0 2.4-1.747 4.5-4.312 4.5zm.024-.907c1.927 0 3.3-1.592 3.3-3.593 0-1.977-1.397-3.593-3.324-3.593-1.927 0-3.3 1.592-3.3 3.593 0 1.977 1.397 3.593 3.324 3.593zm6.3.758v-8.702h.963l3.07 4.749 3.072-4.749h.964v8.702h-.952v-7.049l-3.071 4.662h-.048l-3.071-4.65v7.037h-.928zm10.453 0v-8.702h6.095v.895h-5.143v2.971h4.6v.895h-4.6v3.046H91v.895h-6.155z"/><path fill-rule="nonzero" d="M2.831 14.045c.775 4.287 2.266 8.333 4.685 12.143 2.958 4.659 7.21 8.797 12.984 12.319 5.774-3.522 10.026-7.66 12.984-12.319 2.42-3.81 3.91-7.856 4.685-12.143.489-2.706.644-4.844.672-8.003C33.368 3.522 26.636 2.14 20.5 2.14c-6.137 0-12.869 1.381-18.341 3.9.028 3.16.183 5.298.672 8.004zM20.5 0C26.908 0 34.637 1.47 41 4.706c0 6.988.087 24.398-20.5 36.294C-.088 29.104 0 11.694 0 4.706 6.363 1.47 14.092 0 20.5 0z"/><path d="M20.234 27L33 11.344c-.935-.682-1.756-.2-2.208.172l-.016.001-10.644 10.076-4.01-4.392c-1.913-2.011-4.514-.477-5.122-.072L20.234 27"/></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="164" height="41" viewBox="0 0 164 41">
<g fill-rule="evenodd">
<path d="M129.984 22l-1.162-2.945h-5.792L121.931 22H118l6.277-15h3.509L134 22h-4.016zm-4.016-10.996l-1.902 5.149h3.762l-1.86-5.149zM117 16.1c0 .88-.153 1.682-.46 2.404a5.223 5.223 0 0 1-1.318 1.857c-.57.516-1.26.918-2.066 1.207-.807.289-1.703.433-2.688.433-1 0-1.9-.144-2.699-.433-.8-.29-1.477-.691-2.034-1.207a5.232 5.232 0 0 1-1.285-1.857c-.3-.722-.45-1.524-.45-2.404V7h3.64v8.81c0 .4.054.777.161 1.135.108.358.272.677.493.96.221.281.514.505.878.67.364.165.803.248 1.317.248.514 0 .953-.083 1.317-.248.365-.165.66-.389.89-.67.228-.283.392-.602.492-.96.1-.358.15-.736.15-1.135V7H117v9.099zm-16 4.673c-.733.362-1.59.658-2.57.886-.98.228-2.047.342-3.203.342-1.199 0-2.302-.181-3.31-.544-1.008-.362-1.875-.872-2.601-1.53a6.977 6.977 0 0 1-1.703-2.366c-.409-.92-.613-1.943-.613-3.07 0-1.141.208-2.175.624-3.1a6.903 6.903 0 0 1 1.723-2.367 7.71 7.71 0 0 1 2.58-1.5C92.914 7.174 93.98 7 95.121 7c1.184 0 2.284.171 3.299.513 1.015.343 1.84.802 2.474 1.38l-2.284 2.476c-.352-.39-.817-.708-1.395-.956-.579-.249-1.234-.373-1.967-.373-.635 0-1.22.111-1.756.332a4.23 4.23 0 0 0-1.395.927 4.178 4.178 0 0 0-.92 1.41 4.734 4.734 0 0 0-.328 1.78c0 .659.099 1.263.296 1.813.197.55.49 1.024.878 1.42.387.395.867.704 1.438.926.57.221 1.223.332 1.956.332.423 0 .825-.03 1.205-.09.381-.061.733-.158 1.058-.293V16h-2.855v-2.779H101v7.55zm63-6.314c0 1.313-.244 2.447-.73 3.4a6.855 6.855 0 0 1-1.928 2.352 8.035 8.035 0 0 1-2.7 1.356 10.94 10.94 0 0 1-3.05.434H150V7h5.422c1.06 0 2.104.124 3.135.37a7.866 7.866 0 0 1 2.753 1.23c.805.572 1.454 1.338 1.949 2.298.494.96.741 2.147.741 3.56zm-3.77 0c0-.848-.138-1.55-.413-2.108a3.549 3.549 0 0 0-1.101-1.335 4.405 4.405 0 0 0-1.568-.71 7.7 7.7 0 0 0-1.81-.212h-1.8v8.771h1.715c.65 0 1.274-.074 1.874-.222a4.43 4.43 0 0 0 1.589-.731 3.62 3.62 0 0 0 1.1-1.356c.276-.565.414-1.264.414-2.097zm-75.23 0c0 1.313-.244 2.447-.73 3.4a6.855 6.855 0 0 1-1.928 2.352 8.035 8.035 0 0 1-2.7 1.356 10.94 10.94 0 0 1-3.05.434H71V7h5.422c1.06 0 2.104.124 3.135.37A7.866 7.866 0 0 1 82.31 8.6c.805.572 1.454 1.338 1.949 2.298.494.96.741 2.147.741 3.56zm-3.77 0c0-.848-.138-1.55-.413-2.108a3.549 3.549 0 0 0-1.101-1.335 4.405 4.405 0 0 0-1.568-.71 7.7 7.7 0 0 0-1.81-.212h-1.8v8.771h1.715c.65 0 1.274-.074 1.874-.222a4.43 4.43 0 0 0 1.589-.731 3.62 3.62 0 0 0 1.1-1.356c.276-.565.414-1.264.414-2.097zM65.984 22l-1.162-2.945H59.03L57.931 22H54l6.277-15h3.509L70 22h-4.016zm-4.016-10.996l-1.902 5.149h3.762l-1.86-5.149zM143.855 22l-3.171-5.953h-1.202V22H136V7h5.596c.705 0 1.392.074 2.062.222.67.149 1.271.4 1.803.753a3.9 3.9 0 0 1 1.275 1.398c.318.579.476 1.3.476 2.16 0 1.018-.269 1.872-.808 2.564-.539.693-1.285 1.187-2.238 1.484L148 22h-4.145zm-.145-10.403c0-.353-.073-.639-.218-.858a1.502 1.502 0 0 0-.56-.508 2.393 2.393 0 0 0-.766-.244 5.535 5.535 0 0 0-.819-.063h-1.886v3.495h1.679c.29 0 .587-.024.891-.074.304-.05.58-.137.83-.264.248-.128.452-.311.61-.551.16-.24.239-.551.239-.933zM55 37.851v-8.702h.951v3.866h4.866V29.15h.952v8.702h-.952v-3.916h-4.866v3.916H55zM68.068 38c-2.565 0-4.288-2.076-4.288-4.5 0-2.4 1.747-4.5 4.312-4.5 2.565 0 4.288 2.076 4.288 4.5 0 2.4-1.747 4.5-4.312 4.5zm.024-.907c1.927 0 3.3-1.592 3.3-3.593 0-1.977-1.397-3.593-3.324-3.593-1.927 0-3.3 1.592-3.3 3.593 0 1.977 1.397 3.593 3.324 3.593zm6.3.758v-8.702h.963l3.07 4.749 3.072-4.749h.964v8.702h-.952v-7.049l-3.071 4.662h-.048l-3.071-4.65v7.037h-.928zm10.453 0v-8.702h6.095v.895h-5.143v2.971h4.6v.895h-4.6v3.046H91v.895h-6.155z"/>
<path fill-rule="nonzero"
d="M2.831 14.045c.775 4.287 2.266 8.333 4.685 12.143 2.958 4.659 7.21 8.797 12.984 12.319 5.774-3.522 10.026-7.66 12.984-12.319 2.42-3.81 3.91-7.856 4.685-12.143.489-2.706.644-4.844.672-8.003C33.368 3.522 26.636 2.14 20.5 2.14c-6.137 0-12.869 1.381-18.341 3.9.028 3.16.183 5.298.672 8.004zM20.5 0C26.908 0 34.637 1.47 41 4.706c0 6.988.087 24.398-20.5 36.294C-.088 29.104 0 11.694 0 4.706 6.363 1.47 14.092 0 20.5 0z"/>
<path d="M20.234 27L33 11.344c-.935-.682-1.756-.2-2.208.172l-.016.001-10.644 10.076-4.01-4.392c-1.913-2.011-4.514-.477-5.122-.072L20.234 27"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@@ -1 +1,7 @@
<svg stroke="#84868c" fill="none" height="24" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m3 6h2 16"/><path d="m19 6v14a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2-2v-14m3 0v-2a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><path d="m10 11v6"/><path d="m14 11v6"/></svg>
<svg stroke="#84868c" fill="none" height="24" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="m3 6h2 16"/>
<path d="m19 6v14a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2-2v-14m3 0v-2a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
<path d="m10 11v6"/>
<path d="m14 11v6"/>
</svg>

Before

Width:  |  Height:  |  Size: 340 B

After

Width:  |  Height:  |  Size: 367 B

View File

@@ -1 +1,5 @@
<svg stroke="#84868c" fill="none" height="24" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m18 6-12 12"/><path d="m6 6 12 12"/></svg>
<svg stroke="#84868c" fill="none" height="24" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
<path d="m18 6-12 12"/>
<path d="m6 6 12 12"/>
</svg>

Before

Width:  |  Height:  |  Size: 227 B

After

Width:  |  Height:  |  Size: 244 B

View File

@@ -1,14 +0,0 @@
import { connect } from 'react-redux';
import * as actionCreators from '../actions';
import App from '../components/App';
const mapStateToProps = (state) => {
const { dashboard, encryption } = state;
const props = { dashboard, encryption };
return props;
};
export default connect(
mapStateToProps,
actionCreators,
)(App);

View File

@@ -1,18 +0,0 @@
import { connect } from 'react-redux';
import { getVersion } from '../actions';
import Header from '../components/Header';
const mapStateToProps = (state) => {
const { dashboard } = state;
const props = { dashboard };
return props;
};
const mapDispatchToProps = {
getVersion,
};
export default connect(
mapStateToProps,
mapDispatchToProps,
)(Header);

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 SHOW_TOOLTIP_DELAY = 200;
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;
@@ -370,11 +337,6 @@ export const RESPONSE_FILTER = {
query: 'processed',
label: 'show_processed_responses',
},
SPACE: {
query: 'all',
label: '',
disabled: true,
},
BLOCKED: {
query: 'blocked',
label: 'show_blocked_responses',
@@ -401,10 +363,11 @@ export const RESPONSE_FILTER = {
},
};
export const RESPONSE_FILTER_QUERIES = Object.values(RESPONSE_FILTER).reduce((acc, { query }) => {
acc[query] = query;
return acc;
}, {});
export const RESPONSE_FILTER_QUERIES = Object.values(RESPONSE_FILTER)
.reduce((acc, { query }) => {
acc[query] = query;
return acc;
}, {});
export const FILTERED_STATUS_TO_META_MAP = {
[FILTERED_STATUS.NOT_FILTERED_WHITE_LIST]: {
@@ -516,6 +479,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

@@ -75,7 +75,7 @@
"the-big-list-of-hacked-malware-web-sites": {
"name": "The Big List of Hacked Malware Web Sites",
"categoryId": "security",
"homepage": "https://github.com/hoshsadiq/adblock-nocoin-list/",
"homepage": "https://github.com/mitchellkrogza/The-Big-List-of-Hacked-Malware-Web-Sites",
"source": "https://raw.githubusercontent.com/mitchellkrogza/The-Big-List-of-Hacked-Malware-Web-Sites/master/hacked-domains.list"
},
"scam-blocklist-by-durable-napkin": {
@@ -147,8 +147,8 @@
"CHN-anti-ad" : {
"name": "CHN: anti-AD",
"categoryId": "regional",
"homepage": "https://github.com/privacy-protection-tools/anti-AD",
"source": "https://gitee.com/privacy-protection-tools/anti-ad/raw/master/easylist.txt"
"homepage": "https://anti-ad.net/",
"source": "https://anti-ad.net/easylist.txt"
},
"BarbBlock": {
"name": "BarbBlock",

View File

@@ -24,25 +24,38 @@ const getFormattedWhois = (whois) => {
);
};
export const formatClientCell = (row, isDetailed = false) => {
export const formatClientCell = (row, isDetailed = false, isLogs = true) => {
const { value, original: { info } } = row;
let whoisContainer = '';
let nameContainer = value;
if (info) {
const { name, whois_info } = info;
const whoisAvailable = whois_info && Object.keys(whois_info).length > 0;
if (name) {
nameContainer = !whois_info && isDetailed
? <small title={value}>{value}</small>
: <div className="logs__text logs__text--nowrap" title={`${name} (${value})`}>
{name}
{' '}
<small>{`(${value})`}</small>
</div>;
if (isLogs) {
nameContainer = !whoisAvailable && isDetailed
? (
<small title={value}>{value}</small>
) : (
<div className="logs__text logs__text--nowrap" title={`${name} (${value})`}>
{name}&nbsp;<small>{`(${value})`}</small>
</div>
);
} else {
nameContainer = (
<div
className="logs__text logs__text--nowrap"
title={`${name} (${value})`}
>
{name}&nbsp;<small>{`(${value})`}</small>
</div>
);
}
}
if (whois_info && isDetailed) {
if (whoisAvailable && isDetailed) {
whoisContainer = (
<div className="logs__text logs__text--wrap logs__text--whois">
{getFormattedWhois(whois_info)}

View File

@@ -5,14 +5,12 @@ 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';
import uniqBy from 'lodash/uniqBy';
import ipaddr from 'ipaddr.js';
import queryString from 'query-string';
import versionCompare from './versionCompare';
import { getTrackerData } from './trackers/trackers';
import {
@@ -21,7 +19,6 @@ import {
DEFAULT_LANGUAGE,
DEFAULT_TIME_FORMAT,
DETAILED_DATE_FORMAT_OPTIONS,
DNS_RECORD_TYPES,
FILTERED,
FILTERED_STATUS,
IP_MATCH_LIST_STATUS,
@@ -32,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) => {
@@ -61,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,
@@ -352,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();
@@ -418,10 +377,6 @@ export const secondsToMilliseconds = (seconds) => {
export const normalizeRulesTextarea = (text) => text?.replace(/^\n/g, '')
.replace(/\n\s*\n/g, '\n');
export const isVersionGreater = (currentVersion, previousVersion) => (
versionCompare(currentVersion, previousVersion) === -1
);
export const normalizeWhois = (whois) => {
if (Object.keys(whois).length > 0) {
const {
@@ -448,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);
@@ -547,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();
@@ -561,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}
@@ -578,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;
}
};
@@ -638,5 +669,80 @@ export const getLogsUrlParams = (search, response_status) => `?${queryString.str
})}`;
export const processContent = (content) => (Array.isArray(content)
? content.filter(([, value]) => value)
.flat() : content);
? content.filter(([, value]) => value).reduce((acc, val) => acc.concat(val), [])
: content
);
/**
* @param object {object}
* @param sortKey {string}
* @returns {string[]}
*/
export const getObjectKeysSorted = (object, sortKey) => Object.entries(object)
.sort(([, { [sortKey]: order1 }], [, { [sortKey]: order2 }]) => order1 - order2)
.map(([key]) => key);
/**
* @param ip
* @returns {[IPv4|IPv6, 33|129]}
*/
const getParsedIpWithPrefixLength = (ip) => {
const MAX_PREFIX_LENGTH_V4 = 32;
const MAX_PREFIX_LENGTH_V6 = 128;
const parsedIp = ipaddr.parse(ip);
const prefixLength = parsedIp.kind() === 'ipv4' ? MAX_PREFIX_LENGTH_V4 : MAX_PREFIX_LENGTH_V6;
// Increment prefix length to always put IP after CIDR, e.g. 127.0.0.1/32, 127.0.0.1
return [parsedIp, prefixLength + 1];
};
/**
* Helper function for IP and CIDR comparison (supports both v4 and v6)
* @param item - ip or cidr
* @returns {number[]}
*/
const getAddressesComparisonBytes = (item) => {
// Sort ipv4 before ipv6
const IP_V4_COMPARISON_CODE = 0;
const IP_V6_COMPARISON_CODE = 1;
const [parsedIp, cidr] = ipaddr.isValid(item)
? getParsedIpWithPrefixLength(item)
: ipaddr.parseCIDR(item);
const [normalizedBytes, ipVersionComparisonCode] = parsedIp.kind() === 'ipv4'
? [parsedIp.toIPv4MappedAddress().parts, IP_V4_COMPARISON_CODE]
: [parsedIp.parts, IP_V6_COMPARISON_CODE];
return [ipVersionComparisonCode, ...normalizedBytes, cidr];
};
/**
* Compare function for IP and CIDR sort in ascending order (supports both v4 and v6)
* @param a
* @param b
* @returns {number} -1 | 0 | 1
*/
export const sortIp = (a, b) => {
try {
const comparisonBytesA = getAddressesComparisonBytes(a);
const comparisonBytesB = getAddressesComparisonBytes(b);
for (let i = 0; i < comparisonBytesA.length; i += 1) {
const byteA = comparisonBytesA[i];
const byteB = comparisonBytesB[i];
if (byteA === byteB) {
// eslint-disable-next-line no-continue
continue;
}
return byteA > byteB ? 1 : -1;
}
return 0;
} catch (e) {
console.error(e);
return 0;
}
};

View File

@@ -0,0 +1,17 @@
/**
* Checks if versions are equal.
* Please note, that this method strips the "v" prefix.
*
* @param left {string} - left version
* @param right {string} - right version
* @return {boolean} true if versions are equal
*/
export const areEqualVersions = (left, right) => {
if (!left || !right) {
return false;
}
const leftVersion = left.replace(/^v/, '');
const rightVersion = right.replace(/^v/, '');
return leftVersion === rightVersion;
};

View File

@@ -1,63 +0,0 @@
/*
* Project: tiny-version-compare https://github.com/bfred-it/tiny-version-compare
* License (MIT) https://github.com/bfred-it/tiny-version-compare/blob/master/LICENSE
*/
const split = (v) => String(v).replace(/^[vr]/, '') // Drop initial 'v' or 'r'
.replace(/([a-z]+)/gi, '.$1.') // Sort each word separately
.replace(/[-.]+/g, '.') // Consider dashes as separators (+ trim multiple separators)
.split('.');
// Development versions are considered "negative",
// but localeCompare doesn't handle negative numbers.
// This offset is applied to reset the lowest development version to 0
const offset = (part) => {
// Not numeric, return as is
if (Number.isNaN(part)) {
return part;
}
return 5 + Number(part);
};
const parsePart = (part) => {
// Missing, consider it zero
if (typeof part === 'undefined') {
return 0;
}
// Sort development versions
switch (part.toLowerCase()) {
case 'dev':
return -5;
case 'alpha':
return -4;
case 'beta':
return -3;
case 'rc':
return -2;
case 'pre':
return -1;
default:
}
// Return as is, its either a plain number or text that will be sorted alphabetically
return part;
};
const versionCompare = (prev, next) => {
const a = split(prev);
const b = split(next);
for (let i = 0; i < a.length || i < b.length; i += 1) {
const ai = offset(parsePart(a[i]));
const bi = offset(parsePart(b[i]));
const sort = String(ai).localeCompare(bi, 'en', {
numeric: true,
});
// Once the difference is found,
// stop comparing the rest of the parts
if (sort !== 0) {
return sort;
}
}
// No difference found
return 0;
};
export default versionCompare;

View File

@@ -1,13 +1,14 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import './components/App/index.css';
import App from './containers/App';
import configureStore from './configureStore';
import reducers from './reducers';
import App from './components/App';
import './components/App/index.css';
import './i18n';
const store = configureStore(reducers, {}); // set initial state
ReactDOM.render(
<Provider store={store}>
<App />

View File

@@ -2,7 +2,6 @@ import { combineReducers } from 'redux';
import { handleActions } from 'redux-actions';
import { loadingBarReducer } from 'react-redux-loading-bar';
import { reducer as formReducer } from 'redux-form';
import { isVersionGreater } from '../helpers/helpers';
import * as actions from '../actions';
import toasts from './toasts';
@@ -15,6 +14,7 @@ import stats from './stats';
import queryLogs from './queryLogs';
import dnsConfig from './dnsConfig';
import filtering from './filtering';
import { areEqualVersions } from '../helpers/version';
const settings = handleActions(
{
@@ -82,7 +82,7 @@ const dashboard = handleActions(
[actions.getVersionSuccess]: (state, { payload }) => {
const currentVersion = state.dnsVersion === 'undefined' ? 0 : state.dnsVersion;
if (!payload.disabled && isVersionGreater(currentVersion, payload.new_version)) {
if (!payload.disabled && !areEqualVersions(currentVersion, payload.new_version)) {
const {
announcement_url: announcementUrl,
new_version: newVersion,

View File

@@ -42,7 +42,7 @@ const queryLogs = handleActions(
const rowsStart = pageSize * page;
const rowsEnd = rowsStart + pageSize;
const logsSlice = logs.slice(rowsStart, rowsEnd);
const isFiltered = Object.keys(filter).some((key) => filter[key]);
const isFiltered = filter && Object.keys(filter).some((key) => filter[key]);
return {
...state,

View File

@@ -5,6 +5,7 @@ const flexBugsFixes = require('postcss-flexbugs-fixes');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
const CopyPlugin = require('copy-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const { BUILD_ENVS } = require('./constants');
const RESOURCES_PATH = path.resolve(__dirname);
const ENTRY_REACT = path.resolve(RESOURCES_PATH, 'src/index.js');
@@ -18,13 +19,10 @@ const ASSETS_PATH = path.resolve(RESOURCES_PATH, 'public/assets');
const PUBLIC_PATH = path.resolve(__dirname, '../build/static');
const PUBLIC_ASSETS_PATH = path.resolve(PUBLIC_PATH, 'assets');
const BUILD_ENVS = {
dev: 'development',
prod: 'production',
};
const BUILD_ENV = BUILD_ENVS[process.env.BUILD_ENV];
const isDev = BUILD_ENV === BUILD_ENVS.dev;
const config = {
mode: BUILD_ENV,
target: 'web',
@@ -36,22 +34,35 @@ const config = {
},
output: {
path: PUBLIC_PATH,
filename: '[name].[chunkhash].js',
filename: '[name].[hash].js',
},
resolve: {
modules: ['node_modules'],
alias: {
MainRoot: path.resolve(__dirname, '../'),
ClientRoot: path.resolve(__dirname, './src'),
// TODO: change to '@hot-loader/react-dom' when v16.13.1 is released
// https://stackoverflow.com/a/62671689/12942752
'react-dom': 'react-dom',
},
},
module: {
rules: [
{
test: /\.ya?ml$/,
type: 'json',
use: 'yaml-loader',
},
{
test: /\.css$/i,
use: [
'style-loader',
MiniCssExtractPlugin.loader,
{
loader: MiniCssExtractPlugin.loader,
options: {
hmr: isDev,
},
},
{
loader: 'css-loader',
options: {
@@ -122,7 +133,8 @@ const config = {
template: HTML_LOGIN_PATH,
}),
new MiniCssExtractPlugin({
filename: '[name].[contenthash].css',
filename: isDev ? '[name].css' : '[name].[hash].css',
chunkFilename: isDev ? '[id].css' : '[id].[hash].css',
}),
new CopyPlugin({
patterns: [

46
client/webpack.dev.js vendored
View File

@@ -1,5 +1,50 @@
const merge = require('webpack-merge');
const yaml = require('js-yaml');
const fs = require('fs');
const common = require('./webpack.common.js');
const { BASE_URL } = require('./constants');
const ZERO_HOST = '0.0.0.0';
const LOCALHOST = '127.0.0.1';
const DEFAULT_PORT = 80;
/**
* Get document, or throw exception on error
* @returns {{bind_host: string, bind_port: number}}
*/
const importConfig = () => {
try {
const doc = yaml.safeLoad(fs.readFileSync('../AdguardHome.yaml', 'utf8'));
const { bind_host, bind_port } = doc;
return {
bind_host,
bind_port,
};
} catch (e) {
console.error(e);
return {
bind_host: ZERO_HOST,
bind_port: DEFAULT_PORT,
};
}
};
const getDevServerConfig = (proxyUrl = BASE_URL) => {
const { bind_host: host, bind_port: port } = importConfig();
const { DEV_SERVER_PORT } = process.env;
const devServerHost = host === ZERO_HOST ? LOCALHOST : host;
const devServerPort = DEV_SERVER_PORT || port + 8000;
return {
hot: true,
host: devServerHost,
port: devServerPort,
proxy: {
[proxyUrl]: `http://${devServerHost}:${port}`,
},
};
};
module.exports = merge(common, {
devtool: 'eval-source-map',
@@ -16,4 +61,5 @@ module.exports = merge(common, {
},
],
},
devServer: process.env.WEBPACK_DEV_SERVER ? getDevServerConfig(BASE_URL) : undefined,
});

View File

@@ -2,18 +2,19 @@ const StyleLintPlugin = require('stylelint-webpack-plugin');
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
module: {
rules: [{
test: /\.js$/,
exclude: /node_modules/,
loader: 'eslint-loader',
options: {
failOnError: true,
configFile: 'prod.eslintrc',
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'eslint-loader',
options: {
failOnError: true,
configFile: 'prod.eslintrc',
},
},
}],
],
},
plugins: [
new StyleLintPlugin({

View File

@@ -47,9 +47,9 @@ func NewForTest(c *Config, filters []Filter) *Dnsfilter {
setts = RequestFilteringSettings{}
setts.FilteringEnabled = true
if c != nil {
c.SafeBrowsingCacheSize = 1000
c.SafeBrowsingCacheSize = 10000
c.ParentalCacheSize = 10000
c.SafeSearchCacheSize = 1000
c.ParentalCacheSize = 1000
c.CacheTime = 30
setts.SafeSearchEnabled = c.SafeSearchEnabled
setts.SafeBrowsingEnabled = c.SafeBrowsingEnabled
@@ -146,12 +146,6 @@ func TestEtcHostsMatching(t *testing.T) {
// SAFE BROWSING
func TestSafeBrowsingHash(t *testing.T) {
q, hashes := hostnameToHashParam("1.2.3.4.5.6")
assert.Equal(t, "0132d0fa.b5413b4e.5fa067c1.e7f6c011.", q)
assert.Equal(t, 4, len(hashes))
}
func TestSafeBrowsing(t *testing.T) {
d := NewForTest(&Config{SafeBrowsingEnabled: true}, nil)
defer d.Close()

149
dnsfilter/safe_search.go Normal file
View File

@@ -0,0 +1,149 @@
package dnsfilter
import (
"bytes"
"encoding/binary"
"encoding/gob"
"encoding/json"
"fmt"
"net"
"net/http"
"time"
"github.com/AdguardTeam/golibs/cache"
"github.com/AdguardTeam/golibs/log"
)
/*
expire byte[4]
res Result
*/
func (d *Dnsfilter) setCacheResult(cache cache.Cache, host string, res Result) int {
var buf bytes.Buffer
expire := uint(time.Now().Unix()) + d.Config.CacheTime*60
var exp []byte
exp = make([]byte, 4)
binary.BigEndian.PutUint32(exp, uint32(expire))
_, _ = buf.Write(exp)
enc := gob.NewEncoder(&buf)
err := enc.Encode(res)
if err != nil {
log.Error("gob.Encode(): %s", err)
return 0
}
val := buf.Bytes()
_ = cache.Set([]byte(host), val)
return len(val)
}
func getCachedResult(cache cache.Cache, host string) (Result, bool) {
data := cache.Get([]byte(host))
if data == nil {
return Result{}, false
}
exp := int(binary.BigEndian.Uint32(data[:4]))
if exp <= int(time.Now().Unix()) {
cache.Del([]byte(host))
return Result{}, false
}
var buf bytes.Buffer
buf.Write(data[4:])
dec := gob.NewDecoder(&buf)
r := Result{}
err := dec.Decode(&r)
if err != nil {
log.Debug("gob.Decode(): %s", err)
return Result{}, false
}
return r, true
}
// SafeSearchDomain returns replacement address for search engine
func (d *Dnsfilter) SafeSearchDomain(host string) (string, bool) {
val, ok := safeSearchDomains[host]
return val, ok
}
func (d *Dnsfilter) checkSafeSearch(host string) (Result, error) {
if log.GetLevel() >= log.DEBUG {
timer := log.StartTimer()
defer timer.LogElapsed("SafeSearch: lookup for %s", host)
}
// Check cache. Return cached result if it was found
cachedValue, isFound := getCachedResult(gctx.safeSearchCache, host)
if isFound {
// atomic.AddUint64(&gctx.stats.Safesearch.CacheHits, 1)
log.Tracef("SafeSearch: found in cache: %s", host)
return cachedValue, nil
}
safeHost, ok := d.SafeSearchDomain(host)
if !ok {
return Result{}, nil
}
res := Result{IsFiltered: true, Reason: FilteredSafeSearch}
if ip := net.ParseIP(safeHost); ip != nil {
res.IP = ip
valLen := d.setCacheResult(gctx.safeSearchCache, host, res)
log.Debug("SafeSearch: stored in cache: %s (%d bytes)", host, valLen)
return res, nil
}
// TODO this address should be resolved with upstream that was configured in dnsforward
addrs, err := net.LookupIP(safeHost)
if err != nil {
log.Tracef("SafeSearchDomain for %s was found but failed to lookup for %s cause %s", host, safeHost, err)
return Result{}, err
}
for _, i := range addrs {
if ipv4 := i.To4(); ipv4 != nil {
res.IP = ipv4
break
}
}
if len(res.IP) == 0 {
return Result{}, fmt.Errorf("no ipv4 addresses in safe search response for %s", safeHost)
}
// Cache result
valLen := d.setCacheResult(gctx.safeSearchCache, host, res)
log.Debug("SafeSearch: stored in cache: %s (%d bytes)", host, valLen)
return res, nil
}
func (d *Dnsfilter) handleSafeSearchEnable(w http.ResponseWriter, r *http.Request) {
d.Config.SafeSearchEnabled = true
d.Config.ConfigModified()
}
func (d *Dnsfilter) handleSafeSearchDisable(w http.ResponseWriter, r *http.Request) {
d.Config.SafeSearchEnabled = false
d.Config.ConfigModified()
}
func (d *Dnsfilter) handleSafeSearchStatus(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{
"enabled": d.Config.SafeSearchEnabled,
}
jsonVal, err := json.Marshal(data)
if err != nil {
httpError(r, w, http.StatusInternalServerError, "Unable to marshal status json: %s", err)
return
}
w.Header().Set("Content-Type", "application/json")
_, err = w.Write(jsonVal)
if err != nil {
httpError(r, w, http.StatusInternalServerError, "Unable to write response json: %s", err)
return
}
}

View File

@@ -1,4 +1,4 @@
// Parental Control, Safe Browsing, Safe Search
// Safe Browsing, Parental Control
package dnsfilter
@@ -6,12 +6,12 @@ import (
"bytes"
"crypto/sha256"
"encoding/binary"
"encoding/gob"
"encoding/hex"
"encoding/json"
"fmt"
"net"
"net/http"
"sort"
"strings"
"time"
@@ -22,9 +22,6 @@ import (
"golang.org/x/net/publicsuffix"
)
// Servers to use for resolution of SB/PC server name
var bootstrapServers = []string{"176.103.130.130", "176.103.130.131"}
const dnsTimeout = 3 * time.Second
const defaultSafebrowsingServer = "https://dns-family.adguard.com/dns-query"
const defaultParentalServer = "https://dns-family.adguard.com/dns-query"
@@ -35,7 +32,15 @@ func (d *Dnsfilter) initSecurityServices() error {
var err error
d.safeBrowsingServer = defaultSafebrowsingServer
d.parentalServer = defaultParentalServer
opts := upstream.Options{Timeout: dnsTimeout, Bootstrap: bootstrapServers}
opts := upstream.Options{
Timeout: dnsTimeout,
ServerIPAddrs: []net.IP{
net.ParseIP("176.103.130.132"),
net.ParseIP("176.103.130.134"),
net.ParseIP("2a00:5a60::bad1:ff"),
net.ParseIP("2a00:5a60::bad2:ff"),
},
}
d.parentalUpstream, err = upstream.AddressToUpstream(d.parentalServer, opts)
if err != nil {
@@ -52,115 +57,65 @@ func (d *Dnsfilter) initSecurityServices() error {
/*
expire byte[4]
res Result
hash byte[32]
...
*/
func (d *Dnsfilter) setCacheResult(cache cache.Cache, host string, res Result) int {
var buf bytes.Buffer
expire := uint(time.Now().Unix()) + d.Config.CacheTime*60
var exp []byte
exp = make([]byte, 4)
binary.BigEndian.PutUint32(exp, uint32(expire))
_, _ = buf.Write(exp)
enc := gob.NewEncoder(&buf)
err := enc.Encode(res)
if err != nil {
log.Error("gob.Encode(): %s", err)
return 0
}
val := buf.Bytes()
_ = cache.Set([]byte(host), val)
return len(val)
func (c *sbCtx) setCache(prefix []byte, hashes []byte) {
d := make([]byte, 4+len(hashes))
expire := uint(time.Now().Unix()) + c.cacheTime*60
binary.BigEndian.PutUint32(d[:4], uint32(expire))
copy(d[4:], hashes)
c.cache.Set(prefix, d)
log.Debug("%s: stored in cache: %v", c.svc, prefix)
}
func getCachedResult(cache cache.Cache, host string) (Result, bool) {
data := cache.Get([]byte(host))
if data == nil {
return Result{}, false
}
exp := int(binary.BigEndian.Uint32(data[:4]))
if exp <= int(time.Now().Unix()) {
cache.Del([]byte(host))
return Result{}, false
}
var buf bytes.Buffer
buf.Write(data[4:])
dec := gob.NewDecoder(&buf)
r := Result{}
err := dec.Decode(&r)
if err != nil {
log.Debug("gob.Decode(): %s", err)
return Result{}, false
}
return r, true
}
// SafeSearchDomain returns replacement address for search engine
func (d *Dnsfilter) SafeSearchDomain(host string) (string, bool) {
val, ok := safeSearchDomains[host]
return val, ok
}
func (d *Dnsfilter) checkSafeSearch(host string) (Result, error) {
if log.GetLevel() >= log.DEBUG {
timer := log.StartTimer()
defer timer.LogElapsed("SafeSearch: lookup for %s", host)
}
// Check cache. Return cached result if it was found
cachedValue, isFound := getCachedResult(gctx.safeSearchCache, host)
if isFound {
// atomic.AddUint64(&gctx.stats.Safesearch.CacheHits, 1)
log.Tracef("SafeSearch: found in cache: %s", host)
return cachedValue, nil
}
safeHost, ok := d.SafeSearchDomain(host)
if !ok {
return Result{}, nil
}
res := Result{IsFiltered: true, Reason: FilteredSafeSearch}
if ip := net.ParseIP(safeHost); ip != nil {
res.IP = ip
valLen := d.setCacheResult(gctx.safeSearchCache, host, res)
log.Debug("SafeSearch: stored in cache: %s (%d bytes)", host, valLen)
return res, nil
}
// TODO this address should be resolved with upstream that was configured in dnsforward
addrs, err := net.LookupIP(safeHost)
if err != nil {
log.Tracef("SafeSearchDomain for %s was found but failed to lookup for %s cause %s", host, safeHost, err)
return Result{}, err
}
for _, i := range addrs {
if ipv4 := i.To4(); ipv4 != nil {
res.IP = ipv4
break
func (c *sbCtx) getCached() int {
now := time.Now().Unix()
hashesToRequest := map[[32]byte]string{}
for k, v := range c.hashToHost {
key := k[0:2]
val := c.cache.Get(key)
if val != nil {
expire := binary.BigEndian.Uint32(val)
if now >= int64(expire) {
val = nil
} else {
for i := 4; i < len(val); i += 32 {
hash := val[i : i+32]
var hash32 [32]byte
copy(hash32[:], hash[0:32])
_, found := c.hashToHost[hash32]
if found {
log.Debug("%s: found in cache: %s: blocked by %v", c.svc, c.host, hash32)
return 1
}
}
}
}
if val == nil {
hashesToRequest[k] = v
}
}
if len(res.IP) == 0 {
return Result{}, fmt.Errorf("no ipv4 addresses in safe search response for %s", safeHost)
if len(hashesToRequest) == 0 {
log.Debug("%s: found in cache: %s: not blocked", c.svc, c.host)
return -1
}
// Cache result
valLen := d.setCacheResult(gctx.safeSearchCache, host, res)
log.Debug("SafeSearch: stored in cache: %s (%d bytes)", host, valLen)
return res, nil
c.hashToHost = hashesToRequest
return 0
}
// for each dot, hash it and add it to string
// The maximum is 4 components: "a.b.c.d"
func hostnameToHashParam(host string) (string, map[string]bool) {
var hashparam bytes.Buffer
hashes := map[string]bool{}
type sbCtx struct {
host string
svc string
hashToHost map[[32]byte]string
cache cache.Cache
cacheTime uint
}
func hostnameToHashes(host string) map[[32]byte]string {
hashes := map[[32]byte]string{}
tld, icann := publicsuffix.PublicSuffix(host)
if !icann {
// private suffixes like cloudfront.net
@@ -190,8 +145,7 @@ func hostnameToHashParam(host string) (string, map[string]bool) {
}
sum := sha256.Sum256([]byte(curhost))
hashes[hex.EncodeToString(sum[:])] = true
hashparam.WriteString(fmt.Sprintf("%s.", hex.EncodeToString(sum[0:4])))
hashes[sum] = curhost
pos := strings.IndexByte(curhost, byte('.'))
if pos < 0 {
@@ -199,26 +153,91 @@ func hostnameToHashParam(host string) (string, map[string]bool) {
}
curhost = curhost[pos+1:]
}
return hashparam.String(), hashes
return hashes
}
// convert hash array to string
func (c *sbCtx) getQuestion() string {
q := ""
for hash := range c.hashToHost {
q += fmt.Sprintf("%s.", hex.EncodeToString(hash[0:2]))
}
if c.svc == "SafeBrowsing" {
q += sbTXTSuffix
} else {
q += pcTXTSuffix
}
return q
}
// Find the target hash in TXT response
func (d *Dnsfilter) processTXT(svc, host string, resp *dns.Msg, hashes map[string]bool) bool {
func (c *sbCtx) processTXT(resp *dns.Msg) (bool, [][]byte) {
matched := false
hashes := [][]byte{}
for _, a := range resp.Answer {
txt, ok := a.(*dns.TXT)
if !ok {
continue
}
log.Tracef("%s: hashes for %s: %v", svc, host, txt.Txt)
log.Debug("%s: received hashes for %s: %v", c.svc, c.host, txt.Txt)
for _, t := range txt.Txt {
_, ok := hashes[t]
if ok {
log.Tracef("%s: matched %s by %s", svc, host, t)
return true
if len(t) != 32*2 {
continue
}
hash, err := hex.DecodeString(t)
if err != nil {
continue
}
hashes = append(hashes, hash)
if !matched {
var hash32 [32]byte
copy(hash32[:], hash)
hashHost, ok := c.hashToHost[hash32]
if ok {
log.Debug("%s: matched %s by %s/%s", c.svc, c.host, hashHost, t)
matched = true
}
}
}
}
return false
return matched, hashes
}
func (c *sbCtx) storeCache(hashes [][]byte) {
sort.Slice(hashes, func(a, b int) bool {
return bytes.Compare(hashes[a], hashes[b]) < 0
})
var curData []byte
var prevPrefix []byte
for i, hash := range hashes {
prefix := hash[0:2]
if !bytes.Equal(prefix, prevPrefix) {
if i != 0 {
c.setCache(prevPrefix, curData)
curData = nil
}
prevPrefix = hashes[i][0:2]
}
curData = append(curData, hash...)
}
if len(prevPrefix) != 0 {
c.setCache(prevPrefix, curData)
}
for hash := range c.hashToHost {
prefix := hash[0:2]
val := c.cache.Get(prefix)
if val == nil {
c.setCache(prefix, nil)
}
}
}
// Disabling "dupl": the algorithm of SB/PC is similar, but it uses different data
@@ -229,18 +248,29 @@ func (d *Dnsfilter) checkSafeBrowsing(host string) (Result, error) {
defer timer.LogElapsed("SafeBrowsing lookup for %s", host)
}
// check cache
cachedValue, isFound := getCachedResult(gctx.safebrowsingCache, host)
if isFound {
// atomic.AddUint64(&gctx.stats.Safebrowsing.CacheHits, 1)
log.Tracef("SafeBrowsing: found in cache: %s", host)
return cachedValue, nil
result := Result{}
hashes := hostnameToHashes(host)
c := &sbCtx{
host: host,
svc: "SafeBrowsing",
hashToHost: hashes,
cache: gctx.safebrowsingCache,
cacheTime: d.Config.CacheTime,
}
result := Result{}
question, hashes := hostnameToHashParam(host)
question = question + sbTXTSuffix
// check cache
match := c.getCached()
if match < 0 {
return result, nil
} else if match > 0 {
result.IsFiltered = true
result.Reason = FilteredSafeBrowsing
result.Rule = "adguard-malware-shavar"
return result, nil
}
question := c.getQuestion()
log.Tracef("SafeBrowsing: checking %s: %s", host, question)
req := dns.Msg{}
@@ -250,14 +280,14 @@ func (d *Dnsfilter) checkSafeBrowsing(host string) (Result, error) {
return result, err
}
if d.processTXT("SafeBrowsing", host, resp, hashes) {
matched, receivedHashes := c.processTXT(resp)
if matched {
result.IsFiltered = true
result.Reason = FilteredSafeBrowsing
result.Rule = "adguard-malware-shavar"
}
c.storeCache(receivedHashes)
valLen := d.setCacheResult(gctx.safebrowsingCache, host, result)
log.Debug("SafeBrowsing: stored in cache: %s (%d bytes)", host, valLen)
return result, nil
}
@@ -269,18 +299,29 @@ func (d *Dnsfilter) checkParental(host string) (Result, error) {
defer timer.LogElapsed("Parental lookup for %s", host)
}
// check cache
cachedValue, isFound := getCachedResult(gctx.parentalCache, host)
if isFound {
// atomic.AddUint64(&gctx.stats.Parental.CacheHits, 1)
log.Tracef("Parental: found in cache: %s", host)
return cachedValue, nil
result := Result{}
hashes := hostnameToHashes(host)
c := &sbCtx{
host: host,
svc: "Parental",
hashToHost: hashes,
cache: gctx.parentalCache,
cacheTime: d.Config.CacheTime,
}
result := Result{}
question, hashes := hostnameToHashParam(host)
question = question + pcTXTSuffix
// check cache
match := c.getCached()
if match < 0 {
return result, nil
} else if match > 0 {
result.IsFiltered = true
result.Reason = FilteredParental
result.Rule = "parental CATEGORY_BLACKLISTED"
return result, nil
}
question := c.getQuestion()
log.Tracef("Parental: checking %s: %s", host, question)
req := dns.Msg{}
@@ -290,14 +331,14 @@ func (d *Dnsfilter) checkParental(host string) (Result, error) {
return result, err
}
if d.processTXT("Parental", host, resp, hashes) {
matched, receivedHashes := c.processTXT(resp)
if matched {
result.IsFiltered = true
result.Reason = FilteredParental
result.Rule = "parental CATEGORY_BLACKLISTED"
}
c.storeCache(receivedHashes)
valLen := d.setCacheResult(gctx.parentalCache, host, result)
log.Debug("Parental: stored in cache: %s (%d bytes)", host, valLen)
return result, err
}
@@ -362,34 +403,6 @@ func (d *Dnsfilter) handleParentalStatus(w http.ResponseWriter, r *http.Request)
}
}
func (d *Dnsfilter) handleSafeSearchEnable(w http.ResponseWriter, r *http.Request) {
d.Config.SafeSearchEnabled = true
d.Config.ConfigModified()
}
func (d *Dnsfilter) handleSafeSearchDisable(w http.ResponseWriter, r *http.Request) {
d.Config.SafeSearchEnabled = false
d.Config.ConfigModified()
}
func (d *Dnsfilter) handleSafeSearchStatus(w http.ResponseWriter, r *http.Request) {
data := map[string]interface{}{
"enabled": d.Config.SafeSearchEnabled,
}
jsonVal, err := json.Marshal(data)
if err != nil {
httpError(r, w, http.StatusInternalServerError, "Unable to marshal status json: %s", err)
return
}
w.Header().Set("Content-Type", "application/json")
_, err = w.Write(jsonVal)
if err != nil {
httpError(r, w, http.StatusInternalServerError, "Unable to write response json: %s", err)
return
}
}
func (d *Dnsfilter) registerSecurityHandlers() {
d.Config.HTTPRegister("POST", "/control/safebrowsing/enable", d.handleSafeBrowsingEnable)
d.Config.HTTPRegister("POST", "/control/safebrowsing/disable", d.handleSafeBrowsingDisable)

91
dnsfilter/sb_pc_test.go Normal file
View File

@@ -0,0 +1,91 @@
package dnsfilter
import (
"crypto/sha256"
"strings"
"testing"
"github.com/AdguardTeam/golibs/cache"
"github.com/stretchr/testify/assert"
)
func TestSafeBrowsingHash(t *testing.T) {
// test hostnameToHashes()
hashes := hostnameToHashes("1.2.3.sub.host.com")
assert.Equal(t, 3, len(hashes))
_, ok := hashes[sha256.Sum256([]byte("3.sub.host.com"))]
assert.True(t, ok)
_, ok = hashes[sha256.Sum256([]byte("sub.host.com"))]
assert.True(t, ok)
_, ok = hashes[sha256.Sum256([]byte("host.com"))]
assert.True(t, ok)
_, ok = hashes[sha256.Sum256([]byte("com"))]
assert.False(t, ok)
c := &sbCtx{
svc: "SafeBrowsing",
}
// test getQuestion()
c.hashToHost = hashes
q := c.getQuestion()
assert.True(t, strings.Index(q, "7a1b.") >= 0)
assert.True(t, strings.Index(q, "af5a.") >= 0)
assert.True(t, strings.Index(q, "eb11.") >= 0)
assert.True(t, strings.Index(q, "sb.dns.adguard.com.") > 0)
}
func TestSafeBrowsingCache(t *testing.T) {
c := &sbCtx{
svc: "SafeBrowsing",
cacheTime: 100,
}
conf := cache.Config{}
c.cache = cache.New(conf)
// store in cache hashes for "3.sub.host.com" and "host.com"
// and empty data for hash-prefix for "sub.host.com"
hash := sha256.Sum256([]byte("sub.host.com"))
c.hashToHost = make(map[[32]byte]string)
c.hashToHost[hash] = "sub.host.com"
var hashesArray [][]byte
hash4 := sha256.Sum256([]byte("3.sub.host.com"))
hashesArray = append(hashesArray, hash4[:])
hash2 := sha256.Sum256([]byte("host.com"))
hashesArray = append(hashesArray, hash2[:])
c.storeCache(hashesArray)
// match "3.sub.host.com" or "host.com" from cache
c.hashToHost = make(map[[32]byte]string)
hash = sha256.Sum256([]byte("3.sub.host.com"))
c.hashToHost[hash] = "3.sub.host.com"
hash = sha256.Sum256([]byte("sub.host.com"))
c.hashToHost[hash] = "sub.host.com"
hash = sha256.Sum256([]byte("host.com"))
c.hashToHost[hash] = "host.com"
assert.Equal(t, 1, c.getCached())
// match "sub.host.com" from cache
c.hashToHost = make(map[[32]byte]string)
hash = sha256.Sum256([]byte("sub.host.com"))
c.hashToHost[hash] = "sub.host.com"
assert.Equal(t, -1, c.getCached())
// match "sub.host.com" from cache,
// but another hash for "nonexisting.com" is not in cache
// which means that we must get data from server for it
c.hashToHost = make(map[[32]byte]string)
hash = sha256.Sum256([]byte("sub.host.com"))
c.hashToHost[hash] = "sub.host.com"
hash = sha256.Sum256([]byte("nonexisting.com"))
c.hashToHost[hash] = "nonexisting.com"
assert.Equal(t, 0, c.getCached())
hash = sha256.Sum256([]byte("sub.host.com"))
_, ok := c.hashToHost[hash]
assert.False(t, ok)
hash = sha256.Sum256([]byte("nonexisting.com"))
_, ok = c.hashToHost[hash]
assert.True(t, ok)
}

View File

@@ -81,6 +81,7 @@ type FilteringConfig struct {
AAAADisabled bool `yaml:"aaaa_disabled"` // Respond with an empty answer to all AAAA requests
EnableDNSSEC bool `yaml:"enable_dnssec"` // Set DNSSEC flag in outcoming DNS request
EnableEDNSClientSubnet bool `yaml:"edns_client_subnet"` // Enable EDNS Client Subnet option
MaxGoroutines uint32 `yaml:"max_goroutines"` // Max. number of parallel goroutines for processing incoming requests
}
// TLSConfig is the TLS configuration for HTTPS, DNS-over-HTTPS, and DNS-over-TLS
@@ -133,8 +134,8 @@ var defaultValues = ServerConfig{
// createProxyConfig creates and validates configuration for the main proxy
func (s *Server) createProxyConfig() (proxy.Config, error) {
proxyConfig := proxy.Config{
UDPListenAddr: s.conf.UDPListenAddr,
TCPListenAddr: s.conf.TCPListenAddr,
UDPListenAddr: []*net.UDPAddr{s.conf.UDPListenAddr},
TCPListenAddr: []*net.TCPAddr{s.conf.TCPListenAddr},
Ratelimit: int(s.conf.Ratelimit),
RatelimitWhitelist: s.conf.RatelimitWhitelist,
RefuseAny: s.conf.RefuseAny,
@@ -144,6 +145,7 @@ func (s *Server) createProxyConfig() (proxy.Config, error) {
BeforeRequestHandler: s.beforeRequestHandler,
RequestHandler: s.handleDNSRequest,
EnableEDNSClientSubnet: s.conf.EnableEDNSClientSubnet,
MaxGoroutines: int(s.conf.MaxGoroutines),
}
if s.conf.CacheSize != 0 {
@@ -229,7 +231,7 @@ func (s *Server) prepareIntlProxy() {
// prepareTLS - prepares TLS configuration for the DNS proxy
func (s *Server) prepareTLS(proxyConfig *proxy.Config) error {
if s.conf.TLSListenAddr != nil && len(s.conf.CertificateChainData) != 0 && len(s.conf.PrivateKeyData) != 0 {
proxyConfig.TLSListenAddr = s.conf.TLSListenAddr
proxyConfig.TLSListenAddr = []*net.TCPAddr{s.conf.TLSListenAddr}
var err error
s.conf.cert, err = tls.X509KeyPair(s.conf.CertificateChainData, s.conf.PrivateKeyData)
if err != nil {

View File

@@ -51,6 +51,9 @@ type Server struct {
stats stats.Stats
access *accessCtx
tableHostToIP map[string]net.IP // "hostname -> IP" table for internal addresses (DHCP)
tableHostToIPLock sync.Mutex
tablePTR map[string]string // "IP -> hostname" table for reverse lookup
tablePTRLock sync.Mutex
@@ -176,6 +179,9 @@ func (s *Server) Prepare(config *ServerConfig) error {
return fmt.Errorf("DNS: invalid custom blocking IP address specified")
}
}
if s.conf.MaxGoroutines == 0 {
s.conf.MaxGoroutines = 50
}
}
// 2. Set default values in the case if nothing is configured

Some files were not shown because too many files have changed in this diff Show More