Compare commits

...

499 Commits
v0.9 ... v0.93

Author SHA1 Message Date
Andrey Meshkov
aa30728cda Fix docker build 2019-02-25 19:15:27 +03:00
Andrey Meshkov
71ab95f12f Bump version to v0.93 2019-02-25 19:04:41 +03:00
Andrey Meshkov
c71d6ed433 Fix race in safesearch tests 2019-02-25 18:56:51 +03:00
Andrey Meshkov
77348e746f Merge pull request #151 in DNS/adguard-dns from fix/576 to master
* commit '86279f19b0b5bce37a83c05c88511fb82441c730':
  Add TODO
  Fix merge issues
  Add stats assertions
  Add safesearch test for dnsforward
  Use go tools
  Fix #576 - Fix safesearch
2019-02-25 18:27:17 +03:00
Andrey Meshkov
27c33b2fa9 Merge pull request #152 in DNS/adguard-dns from feature/update_readme to master
* commit '4bbc5037098636d9c68ae41391315da3737404a5':
  Added guides to readme
  update readme
2019-02-25 17:23:22 +03:00
Aleksey Dmitrevskiy
86279f19b0 Add TODO 2019-02-25 17:15:50 +03:00
Aleksey Dmitrevskiy
3d901a82ad Fix merge issues 2019-02-25 17:07:26 +03:00
Aleksey Dmitrevskiy
d351ed82c1 Merge branch 'master' into fix/576 2019-02-25 17:07:02 +03:00
Aleksey Dmitrevskiy
8e13f22aa5 Add stats assertions 2019-02-25 17:01:57 +03:00
Aleksey Dmitrevskiy
d0f4f22e0d Add safesearch test for dnsforward 2019-02-25 14:58:54 +03:00
Andrey Meshkov
4bbc503709 Added guides to readme 2019-02-24 20:13:58 +03:00
Andrey Meshkov
1b305d94a7 update readme 2019-02-24 18:42:44 +03:00
Andrey Meshkov
a7478255a1 Merge pull request #148 in DNS/adguard-dns from feature/285 to master
* commit '84604e292b7c80980dc51a4692715350e93590c9': (88 commits)
  Update dnsproxy to v0.11.1
  Fix printing HTTPS address
  Fix tests
  Update dnsproxy to 0.11.0
  Added install methods to openapi.yaml Print all net interfaces when bind_host is 0.0.0.0
  Added DOH url
  Added DNS-over-TLS unit-test and a test looking for race-conditions
  Fixed port validation
  Fix data races found by race detector.
  /tls/configure -- don't close https connection mid-request when configuration removes ports and certificates
  Fixed checkRedirect helper
  Added new config fields to readme
  Added openapi description
  Fixed EncryptionTopline check
  Fixed stylelint errors
  Added UpdateTopline component
  Remove unused package
  Fixed npm audit vulnerabilities
  Fix empty values on validate
  Disable save button if key or certificate is empty
  ...
2019-02-22 19:48:10 +03:00
Andrey Meshkov
84604e292b Update dnsproxy to v0.11.1 2019-02-22 19:45:43 +03:00
Andrey Meshkov
a04923a4f3 Fix printing HTTPS address 2019-02-22 18:47:54 +03:00
Andrey Meshkov
1da954fa97 Fix tests 2019-02-22 18:41:59 +03:00
Andrey Meshkov
ad4b58472f Update dnsproxy to 0.11.0 2019-02-22 18:16:47 +03:00
Andrey Meshkov
4e1c1618cb Added install methods to openapi.yaml
Print all net interfaces when bind_host is 0.0.0.0
2019-02-22 17:59:42 +03:00
Aleksey Dmitrevskiy
3916f1073d Use go tools 2019-02-22 16:41:30 +03:00
Aleksey Dmitrevskiy
623c3bba09 Fix #576 - Fix safesearch 2019-02-22 16:34:36 +03:00
Andrey Meshkov
e8898811fe Added DOH url 2019-02-22 15:52:12 +03:00
Andrey Meshkov
71df659dc9 Added DNS-over-TLS unit-test and a test looking for race-conditions 2019-02-22 15:23:39 +03:00
Ildar Kamalov
158f2f6100 Fixed port validation 2019-02-21 19:16:09 +03:00
Eugene Bujak
8e993cd788 Fix data races found by race detector. 2019-02-21 19:07:12 +03:00
Eugene Bujak
12f8590228 /tls/configure -- don't close https connection mid-request when configuration removes ports and certificates 2019-02-21 19:01:20 +03:00
Ildar Kamalov
2814c393ad Fixed checkRedirect helper 2019-02-21 18:28:23 +03:00
Andrey Meshkov
37431735fd Added new config fields to readme 2019-02-21 17:48:18 +03:00
Andrey Meshkov
251beb24d3 Added openapi description 2019-02-21 17:33:46 +03:00
Ildar Kamalov
37a1a98c49 Fixed EncryptionTopline check 2019-02-21 15:39:15 +03:00
Ildar Kamalov
5ac775aa4a Fixed stylelint errors 2019-02-21 15:05:54 +03:00
Ildar Kamalov
c53a132072 Added UpdateTopline component 2019-02-20 16:54:14 +03:00
Ildar Kamalov
8e7ceec1a1 Remove unused package 2019-02-20 16:10:32 +03:00
Ildar Kamalov
89446fccd5 Fixed npm audit vulnerabilities 2019-02-20 15:39:36 +03:00
Ildar Kamalov
4f45f2c3e3 Fix empty values on validate 2019-02-20 14:26:56 +03:00
Ildar Kamalov
9c8e4c64ea Disable save button if key or certificate is empty 2019-02-20 13:33:42 +03:00
Ildar Kamalov
2c2295c161 Fixed http port and reset with save 2019-02-20 12:46:34 +03:00
Eugene Bujak
a2dd7c32d5 /tls/ -- move certificate logging to verbose 2019-02-20 12:32:10 +03:00
Eugene Bujak
b3f33b4b0b /status -- add http_port 2019-02-20 12:25:13 +03:00
Ildar Kamalov
de08b53ae1 Fix list styles 2019-02-20 12:02:46 +03:00
Ildar Kamalov
a60eeb55f1 Check if redirect is available before enable 2019-02-20 11:36:24 +03:00
Eugene Bujak
9d4b829fb6 Add Access-Control-Allow-Origin: * header to postinstall wrapper 2019-02-20 10:40:18 +03:00
Eugene Bujak
1515c353f8 implement redirecting to https if configured and https server is running 2019-02-19 21:19:27 +03:00
Ildar Kamalov
f0536b6347 Check response 2019-02-19 19:42:59 +03:00
Ildar Kamalov
340a4fb58e Check if redirect available 2019-02-19 19:19:40 +03:00
Eugene Bujak
e873149bee Fix inability to start https server if it wasn't running 2019-02-19 19:11:39 +03:00
Ildar Kamalov
77793e5f21 Check is safe port 2019-02-19 18:56:13 +03:00
Ildar Kamalov
24154f0033 Redirect on port change 2019-02-19 18:04:23 +03:00
Eugene Bujak
8c406427af /tls/configure -- accept empty certificates for saving 2019-02-19 17:52:27 +03:00
Eugene Bujak
885e4e16c8 /tls/ -- prevent encryption errors when changing certificates mid-request 2019-02-19 17:52:19 +03:00
Ildar Kamalov
0b7f0396de Fixed processing config 2019-02-19 15:46:29 +03:00
Ildar Kamalov
cca6998efe Added https redirect 2019-02-19 15:43:36 +03:00
Eugene Bujak
3c374b5940 /tls/ -- add internal usable flag to simplify logic when https needs to be booted up 2019-02-19 15:21:38 +03:00
Eugene Bujak
ba103f9825 /tls/ -- add ValidCert, without it being true https is not usable 2019-02-19 15:21:19 +03:00
Eugene Bujak
2748d4c889 /tls/configure -- check if https port is usable before accepting the new config 2019-02-19 15:19:11 +03:00
Ildar Kamalov
b8c0ed9335 Reset fields on click 2019-02-19 13:05:16 +03:00
Ildar Kamalov
ff012cf0a3 Fix error message 2019-02-19 11:05:30 +03:00
Ildar Kamalov
2b0addd505 Fix copy symbol
Closes #588
2019-02-19 11:04:43 +03:00
Eugene Bujak
2de0f82bbc release.sh -- don't require directory of this repo to be named specifically 2019-02-18 21:13:58 +03:00
Ildar Kamalov
1fc5f15aaa Check if error has response 2019-02-18 19:36:24 +03:00
Ildar Kamalov
954d923975 Remove valid_chain and warning_validation from button disable 2019-02-18 19:20:17 +03:00
Ildar Kamalov
05cce8b107 Added validation on change and enable encryption checkbox 2019-02-18 16:06:27 +03:00
Eugene Bujak
d44f68e844 /tls/configure and /tls/validate -- make validation failures non-fatal 2019-02-15 17:07:45 +03:00
Eugene Bujak
cb97c221fd /tls/validate and /tls/configure -- do checks on private key, add more fields to certificate status, do keypair check last. 2019-02-15 16:28:28 +03:00
Eugene Bujak
81bb4aea78 /tls/configure and /tls/status -- now there's an explicit 'enabled' bool. 2019-02-15 16:28:28 +03:00
Eugene Bujak
8da90a7f4a Fix panic when https server is not running 2019-02-15 16:28:28 +03:00
Eugene Bujak
b4b800565c Fixup for "validate certificates". 2019-02-15 16:28:28 +03:00
Eugene Bujak
e8280c60d8 /tls/status — Add not_after field with a valid certificate expiration date. 2019-02-15 16:28:28 +03:00
Eugene Bujak
571be68733 Validate certificates and update certificate statuses on launch as well. 2019-02-15 16:28:28 +03:00
Eugene Bujak
bdec98f18e Properly calculate if certificate expires in 30 minutes or not. 2019-02-15 16:28:28 +03:00
Eugene Bujak
28df187012 /tls/configure -- restart HTTPS server if settings changed
Fixes not using new HTTPS certificate after submitting it.
2019-02-15 16:28:28 +03:00
Eugene Bujak
f0569af367 Remove redundant printf 2019-02-15 16:28:28 +03:00
Eugene Bujak
e2956cae82 release.sh -- Place the targz into dist subdir 2019-02-15 16:28:28 +03:00
Eugene Bujak
110434c2d5 Fix broken tar.gz not having a subdirectory inside. 2019-02-15 16:28:28 +03:00
Eugene Bujak
f417f6257f release.sh -- there is no need to run make clean 2019-02-15 16:28:28 +03:00
Eugene Bujak
1d2958f4aa add temporary packr output to gitignore 2019-02-15 16:28:28 +03:00
Eugene Bujak
3e67c8d79a Older npm rewrote the package-lock.json again 2019-02-15 16:28:28 +03:00
Eugene Bujak
57a33654f7 Certificate that doesn't go through the chain is not fatal, just send the warning over json. 2019-02-15 16:28:28 +03:00
Eugene Bujak
30050bf278 Spin up an HTTPS server when certificates, port and private key are configured. 2019-02-15 16:28:28 +03:00
Eugene Bujak
5cbaeb82a8 Introduce /tls/validate and validateCertificates() that will also be used by /tls/configure 2019-02-15 16:28:28 +03:00
Eugene Bujak
876bec5a65 /tls/configure -- introduce unmarshalTLS() that transparently base64-decodes the certificate 2019-02-15 16:28:28 +03:00
Eugene Bujak
4b4faad9e8 Fix status for certificates not updating. 2019-02-15 16:28:28 +03:00
Eugene Bujak
c061bec6d8 Lower down logging noise when idle. 2019-02-15 16:28:28 +03:00
Eugene Bujak
229ef78085 Activate DNS-over-TLS server when certificates, keys and ports are configured. 2019-02-15 16:28:28 +03:00
Eugene Bujak
0aeca6bbf5 Don't keep certificates and keys encoded with base64 in yaml config 2019-02-15 16:28:28 +03:00
Ildar Kamalov
35b5f4b48b Fixed json and updated zh_tw 2019-02-15 16:28:28 +03:00
Eugene Bujak
0d3aa00956 Default values for DoH and DoT ports 2019-02-15 16:28:28 +03:00
Ildar Kamalov
cb9ffe4de9 Send 0 on empty port value 2019-02-15 16:28:28 +03:00
Ildar Kamalov
351673c060 Initial port values 2019-02-15 16:28:28 +03:00
Eugene Bujak
4a14c199d8 /tls/configure -- allow submitting empty certificates and keys to clear them out from config 2019-02-15 16:28:28 +03:00
Ildar Kamalov
1dd548c36c Added button to reset encryption settings 2019-02-15 16:28:28 +03:00
Eugene Bujak
d42718465d /tls/configure -- certificates/keys are now transferred encoded with base64 2019-02-15 16:28:28 +03:00
Ildar Kamalov
93847bd309 Convert certificate and key to base64 2019-02-15 16:28:28 +03:00
Eugene Bujak
4da55dc2aa Fixup of previous commit -- fix build failure 2019-02-15 16:28:27 +03:00
Eugene Bujak
3d3e0784ea tls/configure -- Backend implementation of parsing user certs 2019-02-15 16:28:27 +03:00
Ildar Kamalov
3898309778 Request tls status after save 2019-02-15 16:28:27 +03:00
Eugene Bujak
c19416bf8e Move up tls block in config, don't send json with zero values 2019-02-15 16:28:27 +03:00
Ildar Kamalov
c025c845d2 Show random status and warning 2019-02-15 16:28:27 +03:00
Eugene Bujak
c5b1105fc1 /tls/status -- Expand random stubs for separate statuses of certificate and key 2019-02-15 16:28:27 +03:00
Eugene Bujak
38869b22a6 tls/status -- make stubs add warning and status randomly 2019-02-15 16:28:27 +03:00
Ildar Kamalov
ab11c912db Added topline component and fixed string interpolation 2019-02-15 16:28:27 +03:00
Ildar Kamalov
7451eb1346 Initial components for encryption settings 2019-02-15 16:28:27 +03:00
Eugene Bujak
8725c1df7a Add stub OpenAPI methods 2019-02-15 16:28:26 +03:00
Eugene Bujak
0820983d81 go.mod -- update dnsproxy to v0.9.11 and it's dependencies 2019-02-15 16:28:26 +03:00
Eugene Bujak
a5b61459cc Merge pull request #150 in DNS/adguard-dns from fix/582 to master
* commit 'dd3621bcf65df76fa866866edb8410f5aea46e2a':
  Fix #582
2019-02-12 15:02:03 +03:00
Andrey Meshkov
dd3621bcf6 Fix #582 2019-02-12 14:46:44 +03:00
Eugene Bujak
571370ab16 Merge pull request #149 in DNS/adguard-dns from docker-expose to master
* commit 'e33c8a3cde35ac06a34099ecd94b29b4d9721744':
  Add exposed ports
2019-02-12 13:22:22 +03:00
Eugene Zbiranik
e33c8a3cde Add exposed ports 2019-02-12 11:54:40 +03:00
Eugene Bujak
0d5f24927c Merge pull request #147 in DNS/adguard-dns from docker-versions to master
* commit '27ea739cfdc782daeca07ae8af6bb8f6ef6d65b3':
  fix
  doc
  Fix to go along with new concept
  Build latest from tag branch, edge from master
2019-02-11 19:44:24 +03:00
Eugene Zbiranik
27ea739cfd fix 2019-02-11 19:38:45 +03:00
Eugene Zbiranik
899b26725e doc 2019-02-11 16:56:11 +03:00
Eugene Bujak
26f2207b5c Merge pull request #146 in DNS/adguard-dns from fix/579 to master
* commit 'a40ddb094b4af768ee4b78b09a4a50112eae3b2f':
  Fix review comments
  go mod tidy
  Add workdir to readme
  Do not store last_updated in the config file anymore
  Fix #579
2019-02-11 15:34:15 +03:00
Eugene Zbiranik
6d7d10ec38 Fix to go along with new concept 2019-02-11 15:17:49 +03:00
Eugene Zbiranik
c1f6da2b52 Build latest from tag branch, edge from master 2019-02-11 15:10:36 +03:00
Andrey Meshkov
a40ddb094b Fix review comments 2019-02-11 14:22:36 +03:00
Andrey Meshkov
b477b67428 go mod tidy 2019-02-10 21:52:29 +03:00
Andrey Meshkov
cd9db6440b Add workdir to readme 2019-02-10 21:50:55 +03:00
Andrey Meshkov
9ff420bb52 Do not store last_updated in the config file anymore 2019-02-10 21:44:16 +03:00
Andrey Meshkov
9a03190a62 Fix #579
1. Added --workdir command-line argument that lets configure the working dir.
2. Made "dnsforward" use this workdir parameter when saving/reading querylog.
3. Reworked "dnsforward" -- moved http handlers out of there to control.go
2019-02-10 20:47:43 +03:00
Eugene Bujak
6b6eacaa2b Merge pull request #145 in DNS/adguard-dns from feature/425 to master
* commit '5ca33e44d897d08117e10b248fe9dbe25d3b31f8': (45 commits)
  Fix object spread
  Demote some log.printf into log.tracef
  Makefile -- no need for go get -d . anymore
  npm 6.7.0 unconditionally modifies package-lock.json. Commit those changes.
  /install/configure -- Don't fail if HTTP listen host and port don't change
  /install/get_addresses -- don't send link-local addresses
  Increase button width
  Disable button on submit
  Common reducer for toasts
  Check if ip_addresses exist in the interface
  Fix data race found by tests -- https://travis-ci.org/AdguardTeam/AdGuardHome/jobs/489674061#L970
  Minor cleanup, added strings, added more information to response when error occurs
  ingnore Shutdown by golangci
  Fixed custom select arrow
  Remove unused icons
  get rid of go-spew and cleanup go.mod from unused packages
  Hide 80 web port
  Default listening to 0.0.0.0 for first-time setup
  Move installation of /install handlers into a separate optional function
  /install/configure -- Rebind HTTP server when we get new host and port
  ...
2019-02-08 13:52:04 +03:00
Ildar Kamalov
5ca33e44d8 Fix object spread 2019-02-07 18:51:21 +03:00
Eugene Bujak
68c8a4d484 Demote some log.printf into log.tracef 2019-02-07 18:24:43 +03:00
Eugene Bujak
6e5731ab02 Makefile -- no need for go get -d . anymore 2019-02-07 18:24:42 +03:00
Eugene Bujak
548f539566 npm 6.7.0 unconditionally modifies package-lock.json. Commit those changes. 2019-02-07 18:24:42 +03:00
Eugene Bujak
853582dade /install/configure -- Don't fail if HTTP listen host and port don't change 2019-02-07 18:24:42 +03:00
Eugene Bujak
3a94080491 /install/get_addresses -- don't send link-local addresses 2019-02-07 18:24:42 +03:00
Ildar Kamalov
ba161e9a6f Increase button width 2019-02-07 16:21:17 +03:00
Ildar Kamalov
91eaf72051 Disable button on submit 2019-02-07 16:09:12 +03:00
Ildar Kamalov
826529e73e Common reducer for toasts 2019-02-07 15:40:26 +03:00
Ildar Kamalov
c466f8cc73 Check if ip_addresses exist in the interface 2019-02-07 15:17:27 +03:00
Eugene Bujak
f9d1948f6a Fix data race found by tests -- https://travis-ci.org/AdguardTeam/AdGuardHome/jobs/489674061#L970 2019-02-07 14:45:46 +03:00
Andrey Meshkov
bb8d7c37bb Minor cleanup, added strings, added more information to response when error occurs 2019-02-07 14:22:08 +03:00
Andrey Meshkov
f2d7f8161b ingnore Shutdown by golangci 2019-02-07 13:52:14 +03:00
Ildar Kamalov
39b2e345c3 Fixed custom select arrow 2019-02-07 11:24:23 +03:00
Ildar Kamalov
a7a38413fe Remove unused icons 2019-02-07 10:58:02 +03:00
Eugene Bujak
fe671152c2 get rid of go-spew and cleanup go.mod from unused packages 2019-02-06 20:50:17 +03:00
Ildar Kamalov
ba678ffa82 Hide 80 web port 2019-02-06 17:32:32 +03:00
Eugene Bujak
672ff33879 Default listening to 0.0.0.0 for first-time setup 2019-02-06 17:28:09 +03:00
Eugene Bujak
398312cd80 Move installation of /install handlers into a separate optional function 2019-02-06 17:28:08 +03:00
Eugene Bujak
06a28a461d /install/configure -- Rebind HTTP server when we get new host and port 2019-02-06 17:25:18 +03:00
Ildar Kamalov
31b855f9ab Show list of addresses 2019-02-06 17:22:46 +03:00
Ildar Kamalov
f379d34813 Added select for listen interfaces 2019-02-06 17:22:46 +03:00
Eugene Bujak
5abe5af707 /install/configure -- Start DNS server explicitly 2019-02-06 17:22:46 +03:00
Eugene Bujak
daae040f9c Check if IP:port combinations are possible before returning OK on /install/configure 2019-02-06 17:22:46 +03:00
Eugene Bujak
f2b3c3a14c /install/get_addresses -- made IP address omitempty 2019-02-06 17:21:23 +03:00
Eugene Bujak
d3e81c47f6 rename /install/ path names to be more fitting 2019-02-06 17:21:23 +03:00
Eugene Bujak
c14aff3dba /install/get_default_addresses -- Remove subnet suffix from addresses 2019-02-06 17:21:23 +03:00
Eugene Bujak
d97c426646 Fill out port 80 if it's available, otherwise port 3000 2019-02-06 17:21:23 +03:00
Eugene Bujak
34e14930de /install/get_default_addresses -- now it gives out list of interfaces 2019-02-06 17:21:23 +03:00
Ildar Kamalov
924afea22b Show port in interface and dns address 2019-02-06 17:21:23 +03:00
Eugene Bujak
302c3a767a Initial implementation of welcome/firstrun/installer page in go backend 2019-02-06 17:21:23 +03:00
Ildar Kamalov
c494e17df5 Update packages 2019-02-06 17:17:38 +03:00
Ildar Kamalov
7c25c0febe Added DHCP warning 2019-02-06 17:17:38 +03:00
Ildar Kamalov
5f7fc0f041 Remove 'enabled' from initial values 2019-02-06 17:17:38 +03:00
Ildar Kamalov
beb94741cf Fixed initial values and string interpolation 2019-02-06 17:17:38 +03:00
Ildar Kamalov
24be7ce4ed Fixed page reload on settings change 2019-02-06 17:17:38 +03:00
Ildar Kamalov
6e41897323 Disable form submit on error 2019-02-06 17:17:38 +03:00
Ildar Kamalov
b5e7237169 Update components on language change 2019-02-06 17:17:38 +03:00
Ildar Kamalov
7e95ce9136 Add device configuration instruction 2019-02-06 17:17:38 +03:00
Ildar Kamalov
a7416f9c34 Fixed validation and added toasts 2019-02-06 17:17:38 +03:00
Ildar Kamalov
2bd4840ba5 Fix styles 2019-02-06 17:17:38 +03:00
Ildar Kamalov
5349ec76fd Added components for web setup 2019-02-06 17:17:38 +03:00
Ildar Kamalov
71259c5f19 Added web setup entry point 2019-02-06 17:17:38 +03:00
Eugene Bujak
f21aebd1cf /install/get_default_addresses -- make fields lowercase 2019-02-06 17:17:38 +03:00
Eugene Bujak
c36a7895ad Add install page API stubs 2019-02-06 17:17:38 +03:00
Andrey Meshkov
5fed5c0718 We'd better keep -h for host 2019-02-05 23:29:11 +03:00
Andrey Meshkov
f437d53c1c Fix Dockerfile run cmd 2019-02-05 23:17:17 +03:00
Eugene Bujak
bfe25ba014 Make build_docker.sh executable. 2019-02-05 22:28:24 +03:00
Andrey Meshkov
a8cdc5b01c Merge pull request #144 in DNS/adguard-dns from feature/490 to master
* commit '0fbfa057b1e2e709357fda08caea20fcbc61e9f4':
  Get rid of hardcoded binary name
  service properties to constants
  Added logging description to README
  Fixed review comments Fixed running as a windows service Added logging to windows evenlog
  Added github.com/kardianos/service to README
  AdGuard Home as a system service
2019-02-05 22:02:01 +03:00
Andrey Meshkov
0fbfa057b1 Get rid of hardcoded binary name 2019-02-05 20:35:48 +03:00
Andrey Meshkov
93ea27077f service properties to constants 2019-02-05 14:21:07 +03:00
Andrey Meshkov
aab8da4c7c Added logging description to README 2019-02-05 14:15:22 +03:00
Andrey Meshkov
448a6caeb8 Fixed review comments
Fixed running as a windows service
Added logging to windows evenlog
2019-02-05 14:09:05 +03:00
Andrey Meshkov
a4dc4c61d8 Added github.com/kardianos/service to README 2019-02-04 13:57:35 +03:00
Andrey Meshkov
277415124e AdGuard Home as a system service
1. Reworked working with command-line arguments
2. Added service control actions: install/uninstall/start/stop/status
3. Added log settings to the configuration file
4. Updated the README file
2019-02-04 13:54:53 +03:00
Eugene Bujak
b216475c20 Merge pull request #142 in DNS/adguard-dns from feature/562 to master
* commit '09b49d01450f059ecc4649433a1510f31d2205ac':
  Add Docker Hub image builds
2019-01-30 15:07:23 +03:00
Eugene Bujak
c776ad21b7 Merge pull request #143 in DNS/adguard-dns from fix/564 to master
* commit 'b56dcc9de16db455aa5e3cf775f75c00beca9525':
  Fix version
  Add minimum supported node version
2019-01-30 15:06:43 +03:00
Ildar Kamalov
b56dcc9de1 Fix version 2019-01-30 15:05:19 +03:00
Ildar Kamalov
05cab6fde0 Add minimum supported node version
Closes #564
2019-01-30 14:59:22 +03:00
Eugene Zbiranik
09b49d0145 Merge master 2019-01-29 15:18:36 +03:00
Eugene Zbiranik
98bfb82787 Add Docker Hub image builds 2019-01-29 15:11:11 +03:00
Eugene Bujak
d238e1feb3 Merge pull request #140 in DNS/adguard-dns from fix/557_travis to master
* commit '911250cfbec46c79181bf09fd2f0f397cc7eca35':
  MORE BADGES FOR THE GOD OF BADGES!
  Closes #557: Travis deploy
2019-01-26 02:13:18 +03:00
Andrey Meshkov
911250cfbe MORE BADGES FOR THE GOD OF BADGES! 2019-01-25 22:23:09 +03:00
Andrey Meshkov
4b4cb99b30 Closes #557: Travis deploy 2019-01-25 22:12:48 +03:00
Eugene Bujak
0161509b5f Merge pull request #137 in DNS/adguard-dns from fix/557 to master
* commit 'ec6b1f7c42c8d2fc413d29fba55430b89fcbce2d':
  Added golangci-lint configuration and prepared for the integrattion
  Added codecov, goreport
  Use EnableAll in gometalinter config
  gometalinter
2019-01-25 20:14:03 +03:00
Andrey Meshkov
ec6b1f7c42 Added golangci-lint configuration and prepared for the integrattion 2019-01-25 20:13:57 +03:00
Andrey Meshkov
69a75fbcaa Added codecov, goreport 2019-01-25 20:13:57 +03:00
Andrey Meshkov
a0157e39c6 Use EnableAll in gometalinter config 2019-01-25 20:13:57 +03:00
Andrey Meshkov
d078851246 gometalinter 2019-01-25 20:13:57 +03:00
Andrey Meshkov
c9d627ea71 Merge pull request #139 in DNS/adguard-dns from fix/559 to master
* commit '297a1c7fa53910e38c041ed2c430ff27b0509213':
  Added regex example to the "Custom filtering rules" settings
2019-01-25 18:43:47 +03:00
Ildar Kamalov
297a1c7fa5 Added regex example to the "Custom filtering rules" settings
Closes #559
2019-01-25 18:40:42 +03:00
rpassmore
f1c3fecfb2 Allow configuring IP address the DNS server binds to (#552)
Closes #550.
2019-01-19 04:41:43 +03:00
Eugene Bujak
79eff5f260 Merge pull request #133 in DNS/adguard-dns from fix/544 to master
* commit '8d5d37c28161a4c4720fa82311c7061095385acf':
  Removed extra key
  Only allow single click on buttons
2019-01-16 17:17:17 +03:00
Ildar Kamalov
8d5d37c281 Removed extra key 2019-01-16 15:16:34 +03:00
Ildar Kamalov
e1bb428a6a Only allow single click on buttons
Closes #544
2019-01-16 14:51:17 +03:00
Eugene Bujak
f1b6da93cf Merge pull request #132 in DNS/adguard-dns from fix/536 to master
* commit '61f4b6f1aee8581e3c659569e0629f1e7280e73e':
  Update ru translation
  Update translations
2019-01-15 16:45:16 +03:00
Ildar Kamalov
61f4b6f1ae Update ru translation
Closes #536
2019-01-15 15:36:55 +03:00
Ildar Kamalov
f678eaf9c0 Update translations
Closes #540
2019-01-15 15:35:29 +03:00
Hoàng Rio
607089cd25 Fix missing translate key when added new filter url 2019-01-15 14:22:05 +03:00
Hoàng Rio
df94d76a8b Add translate to Filter ReactTable 2019-01-15 14:22:05 +03:00
Eugene Bujak
8294bb1c7c Bump version to v0.92-hotfix2 2019-01-11 15:28:16 +03:00
Eugene Bujak
ec157ac4ea Merge pull request #131 in DNS/adguard-dns from fix/521 to master
* commit 'c4ba2849643b27a0b454fe83a4a87f7c46138038':
  fix tests
  Added TCPListenAddr
2019-01-09 12:50:55 +03:00
Andrey Meshkov
c4ba284964 fix tests 2019-01-05 22:24:07 +03:00
Andrey Meshkov
f3a97ed7ab Added TCPListenAddr 2019-01-05 22:15:20 +03:00
Eugene Bujak
d90da5d540 Bump version to v0.92-hotfix1 2019-01-04 22:18:53 +03:00
Eugene Bujak
6cd93139fd Add release.sh script that I've been using to build release binaries. 2019-01-04 22:12:43 +03:00
Andrey Meshkov
246f726115 Fix #502 2019-01-04 21:22:22 +03:00
Andrey Meshkov
5a6dc34ec0 Fix #519 2019-01-04 21:22:22 +03:00
Andrey Meshkov
ddcfe7c4bf Upgrade dnsproxy to 0.9.10
Fix #520 #505 #518
2019-01-04 21:22:22 +03:00
Ildar Kamalov
eb71f3ed8f Added Traditional Chinese
Closes #510
2019-01-04 21:22:22 +03:00
Mordy Ovits
fd629be6e6 Fix misspelling 2018-12-31 22:19:13 +03:00
Mordy Ovits
ce1aaea4ca Clarify install language 2018-12-31 22:19:13 +03:00
Mordy Ovits
fa8c038bc1 Add note re setcap installation 2018-12-31 22:19:13 +03:00
Mordy Ovits
9fdf946fc0 Update README with instructions for setcap non-root use
On Linux you can run it listening on port 53 without root privs.  This is the best option: clients still send on port 53 (no wonky configs) and AdGuard doesn't run as root (!).
2018-12-31 22:19:13 +03:00
Eugene Bujak
fd8860a389 Bump version to 0.92 2018-12-29 20:21:36 +03:00
Eugene Bujak
cbe83e2053 Merge pull request #126 in DNS/adguard-dns from feature/423 to master
* commit 'b0c4d88d5454f8dd5a92a73615cce3a31450f56b': (45 commits)
  Indicate that DHCP is experimental
  Update dnsproxy and dnscrypt, and run go mod tidy.
  Fix race conditions found by -race
  move log wrapper library outside into hmage/golibs/log
  Added check for active DHCP before enable
  Use new log wrapper and add more functions to it.
  Implement a log wrapper
  /dhcp/status -- give out hostname for UI
  dhcpd -- Remember hostname, for UI.
  Update comment why filter_conn.go is needed.
  Fixup of previous commit.
  /dhcp/find_active_dhcp -- use interface name from request body
  Don't try to start DHCP server if it's not enabled.
  Get rid of logrus, it's TTY output is not friendly or human parseable if we will want users to send us logs.
  Flag parser -- support options without values, move code for help and verbose into table.
  verbose output parameter
  Pretty-format leases so it shows human readable MAC address.
  Start DHCP on launch if it's enabled in config.
  Update makefile to detect changes in dhcpd/*.go
  DHCPD — don't forget to make Lease fields public.
  ...
2018-12-29 20:07:14 +03:00
Andrey Meshkov
b0c4d88d54 Indicate that DHCP is experimental 2018-12-29 20:00:20 +03:00
Eugene Bujak
ec0b8c687a Update dnsproxy and dnscrypt, and run go mod tidy. 2018-12-29 19:28:46 +03:00
Eugene Bujak
4d3f1b83a6 Fix race conditions found by -race 2018-12-29 19:13:00 +03:00
Eugene Bujak
368e2d1ebd move log wrapper library outside into hmage/golibs/log 2018-12-29 19:12:45 +03:00
Ildar Kamalov
568784b992 Added check for active DHCP before enable 2018-12-29 18:43:17 +03:00
Eugene Bujak
243603e04c Fix panic when DNS query doesn't have questions.
Closes #491.
2018-12-29 17:47:50 +03:00
Eugene Bujak
d8802a9709 Use new log wrapper and add more functions to it. 2018-12-29 17:37:18 +03:00
Eugene Bujak
7463e54258 Implement a log wrapper 2018-12-29 17:23:08 +03:00
Eugene Bujak
7acb107cbf /dhcp/status -- give out hostname for UI 2018-12-29 17:04:40 +03:00
Eugene Bujak
86d79ae232 dhcpd -- Remember hostname, for UI. 2018-12-29 16:44:07 +03:00
Eugene Bujak
fedfc3a1fd Update comment why filter_conn.go is needed. 2018-12-29 16:40:38 +03:00
Eugene Bujak
bf15a40248 Fixup of previous commit. 2018-12-29 16:40:29 +03:00
Eugene Bujak
4efa30edc4 /dhcp/find_active_dhcp -- use interface name from request body 2018-12-29 16:39:29 +03:00
Eugene Bujak
7ab03e9335 Don't try to start DHCP server if it's not enabled. 2018-12-29 15:04:14 +03:00
Eugene Bujak
55a7ff7447 Get rid of logrus, it's TTY output is not friendly or human parseable if we will want users to send us logs. 2018-12-29 14:55:35 +03:00
Eugene Bujak
a7e0f66492 Flag parser -- support options without values, move code for help and verbose into table. 2018-12-29 14:55:03 +03:00
Andrey Meshkov
f312575da4 verbose output parameter 2018-12-29 00:49:39 +03:00
Eugene Bujak
8fc5aebf12 Pretty-format leases so it shows human readable MAC address. 2018-12-28 21:01:31 +03:00
Eugene Bujak
03effab345 Start DHCP on launch if it's enabled in config. 2018-12-28 21:01:16 +03:00
Eugene Bujak
f868fdbf7a Update makefile to detect changes in dhcpd/*.go 2018-12-28 21:00:41 +03:00
Eugene Bujak
1b7db49062 DHCPD — don't forget to make Lease fields public. 2018-12-28 20:50:24 +03:00
Eugene Bujak
f5e7eed447 /dhcp/find_active_dhcp API — Don't return 'found' key when there's an error. And return error string. 2018-12-28 20:50:00 +03:00
Eugene Bujak
6fd9af3c60 /dhcp/set_config API — don't forget to save changed config to YAML 2018-12-28 20:49:27 +03:00
Ildar Kamalov
4aea91a70c Refresh status button 2018-12-28 19:48:02 +03:00
Eugene Bujak
8b4a1ca713 First implementation of DHCP server, compiles but not tested yet. 2018-12-28 18:28:46 +03:00
Eugene Bujak
73f71364b3 Add interface name to dhcp config 2018-12-28 18:26:57 +03:00
Ildar Kamalov
712493aafd UI for DHCP interfaces 2018-12-28 18:26:56 +03:00
Eugene Bujak
1270bbad1a fixup of previous commit -- Remove commented out code 2018-12-28 18:26:56 +03:00
Eugene Bujak
c073f9db7b fixup of previous commit -- make json fields lowercase_underscored 2018-12-28 18:26:56 +03:00
Eugene Bujak
87b3c92f71 Add /dhcp/interfaces API call to list available network interfaces. 2018-12-28 18:26:56 +03:00
Ildar Kamalov
9fa85a5c48 Added DHCP form strings to translations 2018-12-28 18:26:56 +03:00
Ildar Kamalov
52b81a27fb Send lease duration as number 2018-12-28 18:26:56 +03:00
Eugene Bujak
39bc55e430 Fixup of previous commit. 2018-12-28 18:26:56 +03:00
Eugene Bujak
59adad4d53 DHCP -- Use uint64 for lease duration 2018-12-28 18:26:56 +03:00
Ildar Kamalov
a74c2248fb Send dhcp/find_active_dhcp as POST request 2018-12-28 18:26:56 +03:00
Ildar Kamalov
d46b65f982 Add enable/disable for DHCP server 2018-12-28 18:26:56 +03:00
Eugene Bujak
96fbf7f134 Fix yaml marshalling panic. 2018-12-28 18:26:56 +03:00
Eugene Bujak
9294c9ecb2 Add DHCP API stubs for JS development. 2018-12-28 18:26:56 +03:00
Ildar Kamalov
dd21f497e3 Added initial layout for DHCP server config 2018-12-28 18:26:56 +03:00
Eugene Bujak
390883126c Change openapi doc port from 3000 to 4000 to avoid clashing with adguardhome. 2018-12-28 18:26:14 +03:00
Andrey Meshkov
fb24447915 Added version.json 2018-12-28 18:26:14 +03:00
Andrey Meshkov
fcf7b2185e Finished reworking openapi, added DHCP methods there 2018-12-28 18:26:14 +03:00
Andrey Meshkov
b91c829f4c Added more openapi definitions 2018-12-28 18:26:14 +03:00
Andrey Meshkov
7106a8eb35 Added more definitions 2018-12-28 18:26:14 +03:00
Andrey Meshkov
09702c724e Added swagger UI scripts 2018-12-28 18:26:14 +03:00
Andrey Meshkov
4623817894 Add dhcp methods to the openapi.yaml 2018-12-28 18:26:14 +03:00
Eugene Bujak
413bc75320 Merge pull request #124 in DNS/adguard-dns from fix/469 to master
* commit '1b84a9233d0056a53cc2fbe48a58e5c39547c178':
  Fix link
  Added filters link to the blocked_by translation
2018-12-26 14:45:15 +03:00
Ildar Kamalov
1b84a9233d Fix link 2018-12-26 11:42:55 +03:00
Ildar Kamalov
aed87ce741 Added filters link to the blocked_by translation
Closes #469
2018-12-26 11:22:15 +03:00
Eugene Bujak
2652ed34b1 Merge pull request #123 in DNS/adguard-dns from feature/dnsproxy_0.9.2 to master
* commit 'cc96593ebf2bf65c6b14761efbca060fc1f2e85a':
  upd to 0.9.3, removed jedist1/xsecretbox from dependencies
  upgraded dnsproxy to 0.9.2
2018-12-25 18:53:34 +03:00
Andrey Meshkov
cc96593ebf upd to 0.9.3, removed jedist1/xsecretbox from dependencies 2018-12-25 01:59:38 +03:00
Andrey Meshkov
3ade62301b upgraded dnsproxy to 0.9.2 2018-12-25 00:08:51 +03:00
Andrey Meshkov
62606db1af fix client IP address 2018-12-24 23:06:36 +03:00
Andrey Meshkov
8227970d39 Merge pull request #122 in DNS/adguard-dns from feature/dnsproxy to master
* commit '374a0dc2e5b8a93ada7e69242a909607756074c8':
  Fixing review comments
  fix imports
  changed to logrus
  Start using dnsproxy
2018-12-24 22:51:02 +03:00
Andrey Meshkov
374a0dc2e5 Fixing review comments 2018-12-24 18:47:33 +03:00
Andrey Meshkov
2bc1d737cc fix imports 2018-12-24 16:58:48 +03:00
Andrey Meshkov
bac2c39107 Merge pull request #121 in DNS/adguard-dns from fix/484 to master
* commit '9fe9baf7f4a68bc95adf8cfcb40f8e3346e46547':
  Added pagination to the Filters table
2018-12-24 16:09:27 +03:00
Andrey Meshkov
0a977fee87 changed to logrus 2018-12-24 15:27:14 +03:00
Andrey Meshkov
e711f6e5fe Start using dnsproxy 2018-12-24 15:19:52 +03:00
Ildar Kamalov
9fe9baf7f4 Added pagination to the Filters table
Closes #484
2018-12-24 12:08:39 +03:00
Andrey Meshkov
b195080012 Merge pull request #120 in DNS/adguard-dns from feature/284 to master
* commit '3d179079661e3c97041a8448ec3aecea40199032':
  upgrade dnscrypt client to v1.0.0
  Handle cert expiration or rotation
  Fix #284
2018-12-19 16:28:56 +03:00
Andrey Meshkov
3d17907966 upgrade dnscrypt client to v1.0.0 2018-12-18 13:24:15 +03:00
Andrey Meshkov
45626b139d Handle cert expiration or rotation 2018-12-18 01:45:19 +03:00
Andrey Meshkov
b30b6b1d66 Fix #284
Added DNSCrypt upstreams support
Added DNS Stamps support
2018-12-18 01:20:38 +03:00
Andrey Meshkov
6e6c321871 Merge pull request #119 in DNS/adguard-dns from fix/479 to master
* commit '6addc04b97bb46299fb7c76e2b11bb9f469c238c':
  Update locales
  Fixed logs page crush on filter removing
2018-12-17 12:13:54 +03:00
Ildar Kamalov
6addc04b97 Update locales 2018-12-17 11:30:52 +03:00
Ildar Kamalov
717a58a872 Fixed logs page crush on filter removing
Closes #479
2018-12-17 11:28:44 +03:00
Eugene Bujak
1c89e1df32 Resolve into a stub page when blocked by parental or safebrowsing.
Closes #475.
2018-12-11 15:09:07 +03:00
Eugene Bujak
5c4ec62d96 Check if protection is enabled before running the host through dnsfilter.
Closes #476.
2018-12-11 14:20:14 +03:00
Eugene Bujak
69a387547d Merge pull request #118 in DNS/adguard-dns from bugfix/472 to master
* commit '8411de88879e13acbe6f6a42d8659af91a511467':
  Don't log ANY requests if refuseAny is enabled.
2018-12-07 14:34:17 +03:00
Eugene Bujak
8411de8887 Don't log ANY requests if refuseAny is enabled.
Closes #472.
2018-12-07 14:12:26 +03:00
Andrey Meshkov
b5121c5754 Merge pull request #117 in DNS/adguard-dns from no_coredns to master
* commit '253d8a4016d66863ecee426b8f7d74841c4ed4de': (58 commits)
  Pointer for dnsfilter.Result in querylog didn't make things simpler, revert that change and all related changes.
  Fixup of previous commit -- remove unused import.
  Remove unused code.
  Use filter deduplication function.
  Small code review update -- use CamelCase
  readme -- Update config field descriptions and clarify about coredns.
  dnsforward -- fix panic on ANY request
  dnsfilter -- fix broken tests
  config -- Avoid deleting existing dns section if someone removes schema_version from yaml file.
  Rename coredns.go to dns.go
  Add support for bootstrapping upstream DNS servers by hostname.
  dnsforward -- support tcp:// schema
  dnsforward -- add upstream tests.
  Don't omit empty user rules in configfile -- otherwise users might not be able to find that it's customizable in configfile.
  Get rid of mentions of CoreDNS in code except for upgrading and in readme. Add config upgrade.
  dnsforward -- add a simple test that launches a server and queries well-known value through it
  Remove old entries from .gitignore
  Remove unused code. Goodbye CoreDNS.
  Use dnsforward for checking if upstream DNS server is working.
  dnsforward -- implement ratelimit and refuseany
  ...
2018-12-06 17:29:36 +03:00
Eugene Bujak
253d8a4016 Pointer for dnsfilter.Result in querylog didn't make things simpler, revert that change and all related changes. 2018-12-06 17:27:38 +03:00
Eugene Bujak
2ba5cb48b2 Fixup of previous commit -- remove unused import. 2018-12-06 17:19:57 +03:00
Eugene Bujak
e056fb2eb9 Remove unused code. 2018-12-06 17:19:04 +03:00
Eugene Bujak
8fb6f92753 Use filter deduplication function. 2018-12-06 17:19:04 +03:00
Eugene Bujak
e5c1211e17 Small code review update -- use CamelCase 2018-12-06 17:18:16 +03:00
Eugene Bujak
217124cb3b readme -- Update config field descriptions and clarify about coredns. 2018-12-06 17:17:50 +03:00
Eugene Bujak
15f3c82238 dnsforward -- fix panic on ANY request 2018-12-06 16:55:05 +03:00
Eugene Bujak
c82a5ac0cb dnsfilter -- fix broken tests 2018-12-06 16:54:48 +03:00
Eugene Bujak
250cc0ec0f config -- Avoid deleting existing dns section if someone removes schema_version from yaml file. 2018-12-06 00:29:38 +03:00
Eugene Bujak
3ad4b2864d Rename coredns.go to dns.go 2018-12-06 00:23:03 +03:00
Eugene Bujak
0f5dd661f5 Add support for bootstrapping upstream DNS servers by hostname. 2018-12-06 00:22:20 +03:00
Eugene Bujak
ff1c19cac5 dnsforward -- support tcp:// schema 2018-12-05 21:33:32 +03:00
Eugene Bujak
2a1059107a dnsforward -- add upstream tests. 2018-12-05 21:33:07 +03:00
Eugene Bujak
609523a59c Don't omit empty user rules in configfile -- otherwise users might not be able to find that it's customizable in configfile. 2018-12-05 21:09:37 +03:00
Eugene Bujak
e31905864b Get rid of mentions of CoreDNS in code except for upgrading and in readme. Add config upgrade. 2018-12-05 21:08:43 +03:00
Eugene Bujak
bb6c596b22 dnsforward -- add a simple test that launches a server and queries well-known value through it 2018-12-05 20:13:35 +03:00
Eugene Bujak
2745223dbf Remove old entries from .gitignore 2018-12-05 19:26:54 +03:00
Eugene Bujak
b847866310 Remove unused code. Goodbye CoreDNS. 2018-12-05 19:18:58 +03:00
Eugene Bujak
f6942213c8 Use dnsforward for checking if upstream DNS server is working. 2018-12-05 19:17:17 +03:00
Eugene Bujak
478ce03386 dnsforward -- implement ratelimit and refuseany 2018-12-05 18:49:19 +03:00
Eugene Bujak
15f0dee719 readme -- Cleanup, mention that coredns was removed. 2018-12-05 16:57:21 +03:00
Eugene Bujak
7ddc71006b stop DNS server properly when interrupted with ctrl+c, SIGTERM, SIGHUP or SIGQUIT 2018-12-05 16:57:21 +03:00
Eugene Bujak
b0149972cc dnsforward -- give only ip address to querylog, without port 2018-12-05 16:57:21 +03:00
Eugene Bujak
9b43e07d7f dnsforward -- flush querylog to file on server stop 2018-12-05 16:57:21 +03:00
Eugene Bujak
e357620740 Plug correct stats handler functions. 2018-12-05 16:57:21 +03:00
Eugene Bujak
052f975762 dnsforward -- Move querylog from coredns plugin, a more complex migration with proper API took too long so a simple move was used instead to save time. 2018-12-05 16:57:21 +03:00
Eugene Bujak
e5d2f883ac dnsforward -- Make Upstream interface give access to Address field. 2018-12-05 16:57:21 +03:00
Eugene Bujak
8396dc2fdb Update docs for formatting in godoc. 2018-12-05 16:57:21 +03:00
Eugene Bujak
09fb539875 Simplify two lines into one line. 2018-12-05 16:57:21 +03:00
Eugene Bujak
be4b65fdca dnsforward -- use dnsfilter before cache -- changed settings or filters would require cache invalidation otherwise 2018-12-05 16:57:21 +03:00
Eugene Bujak
0a4627f4f0 Fix engrish 2018-12-05 16:57:21 +03:00
Eugene Bujak
0502ef6cc7 dnsforward -- initialize all dnsfilter settings at start and reconfigure 2018-12-05 16:57:21 +03:00
Eugene Bujak
2281b60ebb dnsfilter -- add trace() 2018-12-05 16:57:21 +03:00
Eugene Bujak
7d2e39ed52 dnsfilter -- Add a convinience function to add all rules from all filters. 2018-12-05 16:57:21 +03:00
Eugene Bujak
e26837d9e8 dnsfilter -- Add parameter to New() to supply optional initial config. 2018-12-05 16:57:21 +03:00
Eugene Bujak
3ecc0ee24b Makefile -- don't dictate to go what binary to build, so it can append .exe if building for windows. 2018-12-05 16:57:21 +03:00
Eugene Bujak
057db71f3b Get rid of duplicate variable definitions 2018-12-05 16:57:21 +03:00
Eugene Bujak
ce615e1855 dnsfilter -- Get rid of accessors. 2018-12-05 16:57:21 +03:00
Eugene Bujak
87c54ebd4c Move Filter definition from dnsforward to dnsfilter, it belongs there. 2018-12-05 16:57:21 +03:00
Eugene Bujak
a6e0a17454 dnsforward -- trim dot in the end of hostname, dnsfilter does not expect it there 2018-12-05 16:56:11 +03:00
Eugene Bujak
9089122b56 Compress the packed static js and css to save some space. 2018-12-05 16:56:11 +03:00
Eugene Bujak
e0286ee85d Don't forget to give user filter to dns forwarding server 2018-12-05 16:56:11 +03:00
Eugene Bujak
31f77af534 Move user filter saving into writeAllConfigs() 2018-12-05 16:56:11 +03:00
Eugene Bujak
0d1478b635 Remove unused struct field 2018-12-05 16:56:11 +03:00
Eugene Bujak
d27fd0488d Move filter-related variables, types and methods to filter.go 2018-12-05 16:56:11 +03:00
Eugene Bujak
9c4b791621 coredns reload -> dnsServer.Reconfigure() 2018-12-05 16:56:11 +03:00
Eugene Bujak
9d87ae95e6 dnsforward -- if given addresses without ports, assign default ports 2018-12-05 16:56:11 +03:00
Eugene Bujak
8316d39b42 Move filtering setting fields from main app to dnsforward. 2018-12-05 16:56:11 +03:00
Eugene Bujak
7120f551c8 dnsforward -- rename BlockedTTL to BlockedResponseTTL to be in line with app's config variable. 2018-12-05 16:56:11 +03:00
Eugene Bujak
e4a3564706 Fix a logical race that wasn't detectable by -race -- we were closing a connection that was already reestablished. 2018-12-05 16:56:11 +03:00
Eugene Bujak
4eb122e973 Avoid duplication of fields in filter struct. 2018-12-05 16:56:11 +03:00
Eugene Bujak
feabc21864 Unplug coreDNS and plug dnsforward library. 2018-12-05 16:54:56 +03:00
Eugene Bujak
a904f85e61 dnsforward library -- default to plain DNS for high-performance testing. 2018-12-05 16:54:56 +03:00
Eugene Bujak
584f441141 dnsforward library -- introduce IsRunning() 2018-12-05 16:54:56 +03:00
Eugene Bujak
7944f23d95 dnsforward library -- consistently nullify and close listening socket when we're done with it. 2018-12-05 16:54:56 +03:00
Eugene Bujak
639b34c7d1 dnsforward library -- Fix race conditions found by -race 2018-12-05 16:54:56 +03:00
Eugene Bujak
ea1353422f User rules -- hold them as a slice of strings, which is how dns forwarding server will expect them. 2018-12-05 16:54:56 +03:00
Eugene Bujak
5a548be16c Add dns forwarding server library 2018-12-05 16:54:56 +03:00
Eugene Bujak
39eccc62b1 Fix that filter ID is uppercase while js expects it to be lowercase. 2018-12-05 16:50:06 +03:00
Eugene Bujak
ea25510a08 Travis -- separate js build time from go build time. 2018-11-29 15:05:29 +03:00
Eugene Bujak
45ae984f3b Fix incorrect cherry-pick in previous commit. 2018-11-29 14:58:25 +03:00
Eugene Bujak
2012e707d0 Fix race condition of trying to write YAML config simultaneously and failing. 2018-11-29 13:31:50 +03:00
Eugene Bujak
942cde79bd Merge pull request #116 in DNS/adguard-dns from fix/466 to master
* commit 'c37c3e0459eb71ffed40e2ff3a4239a1c258e02b':
  Fix #466
2018-11-29 13:19:21 +03:00
Andrey Meshkov
c37c3e0459 Fix #466 2018-11-29 11:24:27 +03:00
Eugene Bujak
cab73c0d68 Fix travis tests -- since we don't use modules there's no need to do git checkout 2018-11-28 14:34:28 +03:00
Eugene Bujak
58129543de Fix panic in upstream test if upstream returns with i/o timeout 2018-11-28 14:34:04 +03:00
Eugene Bujak
504aaddc32 Update README -- translations section had wrong level of heading 2018-11-28 13:39:48 +03:00
Eugene Bujak
6257ff123f Fix gometalinter warnings 2018-11-28 13:38:19 +03:00
Eugene Bujak
aa3f3e2c43 Make some traces into a log, remove others 2018-11-28 13:38:19 +03:00
Eugene Bujak
70c5afd6a5 Restore Engrish function names to normal English. 2018-11-28 13:38:19 +03:00
Eugene Bujak
701fd10c1c Protect against users deleting the filter ID's in the config file.
Incidentally, it also simplifies upgrade schema from 0 to 1.
2018-11-28 13:38:19 +03:00
Eugene Bujak
6cb991fe7f Clean up some code -- reorganize some structs and unexport some consts. 2018-11-28 13:38:19 +03:00
Eugene Bujak
ec7efcc9d6 Move config upgrade to separate upgrade.go 2018-11-28 13:38:19 +03:00
Ildar Kamalov
489c29b472 Merge pull request #114 in DNS/adguard-dns from fix/463 to master
* commit '5609e47c28d9481d75bf73d153bf369bca987137':
  Fixed filters table update on language change
  Add client translations
2018-11-28 11:51:03 +03:00
Ildar Kamalov
5609e47c28 Fixed filters table update on language change 2018-11-28 10:25:06 +03:00
Ildar Kamalov
8796a52c09 Add client translations 2018-11-28 10:23:50 +03:00
Eugene Bujak
12a8011fb3 Get rid of unnecessary duplicate type coreDnsFilter. 2018-11-27 16:48:57 +03:00
Eugene Bujak
47e2a1004d Remove IDE-specific noise from source code. 2018-11-27 16:05:43 +03:00
Eugene Bujak
89753c4efb Bump version to 0.91 2018-11-27 12:44:37 +03:00
Andrey Meshkov
8e57243275 Merge pull request #112 in DNS/adguard-dns from fix_block_button to master
* commit 'e08c5efd999895c1f782aca01b45b669e9c850e0':
  Fix ru translation
  Fix block button
2018-11-26 18:38:44 +03:00
Ildar Kamalov
e08c5efd99 Fix ru translation 2018-11-26 18:33:49 +03:00
Ildar Kamalov
c17c282901 Fix block button 2018-11-26 18:17:34 +03:00
Eugene Bujak
8966383ca3 Merge pull request #111 in DNS/adguard-dns from fix/config_ratelimit to master
* commit '82da886df5428335719f9e861cf59672f962e5e8':
  Add ratelimit and refuse_any to config file
2018-11-26 17:01:21 +03:00
Andrey Meshkov
82da886df5 Add ratelimit and refuse_any to config file 2018-11-26 17:00:46 +03:00
Andrey Meshkov
afe234759f Merge pull request #108 in DNS/adguard-dns from add_languages to master
* commit 'd1f5f781c9690129514a3f2629b1729b911dfdcf':
  Update locales
  Add new languages
2018-11-26 16:22:25 +03:00
Ildar Kamalov
d1f5f781c9 Merge branch 'master' into add_languages 2018-11-26 15:44:07 +03:00
Andrey Meshkov
f95bea325b Merge pull request #109 in DNS/adguard-dns from fix/453 to master
* commit '69cc597b87a23443d57cf35551f03a3846b64797':
  Fix #453: Added upstream examples
  Added upstream examples
2018-11-26 15:40:15 +03:00
Ildar Kamalov
d8c97cbabe Update locales 2018-11-26 15:26:38 +03:00
Eugene Bujak
c995726f78 Merge pull request #110 in DNS/adguard-dns from bugfix/424 to master
* commit 'd2a0d0333241d8f31718e4e8765f57d9ec68f7df':
  Allow specifying absolute paths in -c command line option.
2018-11-26 15:20:38 +03:00
Eugene Bujak
d2a0d03332 Allow specifying absolute paths in -c command line option.
Closes #424.
2018-11-26 15:18:56 +03:00
Andrey Meshkov
69cc597b87 Fix #453: Added upstream examples 2018-11-26 15:12:04 +03:00
Ildar Kamalov
15f8cfce64 Add new languages 2018-11-26 15:00:56 +03:00
Andrey Meshkov
939c902fb0 Added upstream examples 2018-11-26 15:00:17 +03:00
Eugene Bujak
d9a65631b9 Merge pull request #107 in DNS/adguard-dns from fix/460 to master
* commit 'c500345d16586e5a7c917611d52261beac2ac501':
  Fix #460
2018-11-26 14:47:05 +03:00
Eugene Bujak
093bd164d6 Merge pull request #106 in DNS/adguard-dns from fix/426_refuseany to master
* commit 'a0482fc201a735d6868b301d5b55f47c9f6dbfaf':
  Fix #426 Added refuseany (enabled by default) Added ratelimit and refuseany to the config file (for manual editing only)
2018-11-26 14:47:00 +03:00
Andrey Meshkov
c500345d16 Fix #460 2018-11-26 14:09:43 +03:00
Andrey Meshkov
a0482fc201 Fix #426
Added refuseany (enabled by default)
Added ratelimit and refuseany to the config file (for manual editing only)
2018-11-26 13:38:17 +03:00
Andrey Meshkov
a6c9210461 Merge pull request #103 in DNS/adguard-dns from feature/426 to master
* commit 'f94c63ed5b4fe028a74ce074b66afcbad7b426f4':
  Set default ratelimit to 30/sec
  Fix #426: add ratelimit to directives
  Added ratelimit plugin
2018-11-26 12:38:04 +03:00
Ildar Kamalov
4ae91f0c1b Merge pull request #104 in DNS/adguard-dns from fix/442 to master
* commit '903c1da993c24ab4a338a3cee22c947a8fcc2e19':
  Remove change_language call on loading app
  Pass two-letter language code
2018-11-26 12:26:45 +03:00
Ildar Kamalov
903c1da993 Remove change_language call on loading app 2018-11-26 11:48:06 +03:00
Eugene Bujak
dcbf083d5b Merge pull request #105 in DNS/adguard-dns from fix/265_restrictmoderate to master
* commit '1fa250bb35e9e44b091be36a9f08bb12705cc518':
  Use restrictmoderate.youtube.com #265
2018-11-26 11:11:49 +03:00
Andrey Meshkov
1fa250bb35 Use restrictmoderate.youtube.com #265 2018-11-26 11:05:39 +03:00
Ildar Kamalov
18f210eef5 Pass two-letter language code 2018-11-26 10:55:42 +03:00
Andrey Meshkov
f94c63ed5b Set default ratelimit to 30/sec 2018-11-25 23:32:28 +03:00
Andrey Meshkov
e4998651fe Fix #426: add ratelimit to directives 2018-11-25 23:17:49 +03:00
Andrey Meshkov
668dcebf13 Added ratelimit plugin 2018-11-25 23:11:36 +03:00
Andrey Meshkov
cdd2e8ecb4 Merge pull request #99 in DNS/adguard-dns from feature/442 to master
* commit '63f20bc3979c61f71afcad1e9c7bbcdbe783a98e': (27 commits)
  Request language from server
  i18n -— fiz broken change_language APO method
  Allow querying and changeing i18n language via API.
  Update readme and translations
  Add ru locale
  Move translations script to scripts folder
  Fix footer layout
  Add oneskyapp scripts for download and upload translations
  Fix minor mistake
  Translate Copyright text on footer
  Fix wrong translate key
  Fix wrong check update button text
  Convert i18n resource to key type
  Translate missing toast message when toggle query log feature
  create locales/en.js and change to key type. Next may be use auto load translate resource
  Tranlsate missing toast message
  add missing translate
  Update missing trasnlate
  Fix eslint
  Add translate PopoverFilter
  ...
2018-11-22 18:46:21 +03:00
Ildar Kamalov
63f20bc397 Request language from server 2018-11-22 17:56:57 +03:00
Eugene Bujak
83544ab0f6 i18n -— fiz broken change_language APO method 2018-11-22 13:15:38 +03:00
Eugene Bujak
2139bb9c79 Allow querying and changeing i18n language via API. 2018-11-21 20:44:20 +03:00
Ildar Kamalov
0530f5dff2 Update readme and translations 2018-11-21 11:43:55 +03:00
Andrey Meshkov
4e27ad0c8e Merge pull request #102 in DNS/adguard-dns from fix/improve_ratelimit to master
* commit '166bc72ff34f839d205166b3d560e97e8d6cf208':
  Fix tests
  Fix binary search in the whitelist
  Added ratelimit whitelist and tests
2018-11-21 00:24:34 +03:00
Andrey Meshkov
166bc72ff3 Fix tests 2018-11-21 00:23:09 +03:00
Andrey Meshkov
25f20bd5a7 Fix binary search in the whitelist 2018-11-21 00:18:13 +03:00
Andrey Meshkov
345e4dc89a Added ratelimit whitelist and tests 2018-11-20 23:51:18 +03:00
Ildar Kamalov
1ae6af44d1 Add ru locale 2018-11-19 19:23:57 +03:00
Ildar Kamalov
3779407291 Move translations script to scripts folder 2018-11-19 09:57:45 +03:00
Ildar Kamalov
ced5499083 Fix footer layout 2018-11-16 12:46:18 +03:00
Ildar Kamalov
5bf38041c5 Add oneskyapp scripts for download and upload translations 2018-11-16 12:45:41 +03:00
Hoàng Rio
25f469efd7 Fix minor mistake 2018-11-12 08:32:49 +07:00
hoangrio
3d3e8e7dbc Translate Copyright text on footer 2018-11-10 21:52:28 +07:00
hoangrio
346fa6e921 Fix wrong translate key 2018-11-10 14:11:10 +07:00
Eugene Bujak
54ee16634c Merge pull request #96 in DNS/adguard-dns from gomodules to master
* commit '3c427ba2956a0ceae80ed27976d1a3615494a80a':
  Drop support for go 1.10 or lower.
  Introduce go module support
2018-11-09 19:33:13 +03:00
Eugene Bujak
3c427ba295 Drop support for go 1.10 or lower. 2018-11-09 19:31:38 +03:00
Eugene Bujak
a6e4c48567 Introduce go module support 2018-11-09 18:19:55 +03:00
hoangrio
628323761a Merge remote-tracking branch 'upstream/master' into i18n 2018-11-09 21:40:23 +07:00
hoangrio
e1276d089b Fix wrong check update button text 2018-11-09 21:12:46 +07:00
Hoàng Rio
d47a23269d Convert i18n resource to key type 2018-11-09 13:51:28 +07:00
Hoàng Rio
beab9a1be0 Merge branch 'i18n' into i18n_wip 2018-11-09 10:03:06 +07:00
Hoàng Rio
82bc5965f4 Translate missing toast message when toggle query log feature 2018-11-09 10:02:27 +07:00
Hoàng Rio
8d209773b3 Merge branch 'i18n' into i18n_wip 2018-11-08 13:29:47 +07:00
Hoàng Rio
67c8abcb8e Merge branch 'master' into i18n 2018-11-08 13:29:15 +07:00
Eugene Bujak
bd39509458 Merge pull request #95 in DNS/adguard-dns from feature/265 to master
* commit 'fc7d93b920b196e654502e1a2b720d56bae05711':
  Force safe search on Youtube
2018-11-06 15:11:06 +03:00
Andrey Meshkov
fc7d93b920 Force safe search on Youtube 2018-11-06 15:03:42 +03:00
Andrey Meshkov
4a357f1345 Merge pull request #93 in DNS/adguard-dns from fix/414 to master
* commit '914eb612cd0da015b98c151b7ac603fb4126a2c3':
  Add bootstrap DNS to readme
  Fix review comments
  Close test upstream
  Added bootstrap DNS to the config file DNS healthcheck now uses the upstream package methods
  goimports files
  Added CoreDNS plugin setup and replaced forward
  Added factory method for creating DNS upstreams
  Added health-check method
  Added persistent connections cache
  Upstream plugin prototype
2018-11-06 12:27:08 +03:00
Hoàng Rio
3693047270 create locales/en.js and change to key type. Next may be use auto load translate resource 2018-11-06 14:55:26 +07:00
Hoàng Rio
92fbbc8cc5 Tranlsate missing toast message 2018-11-06 09:51:00 +07:00
Andrey Meshkov
914eb612cd Add bootstrap DNS to readme 2018-11-06 01:20:53 +03:00
Andrey Meshkov
cc40826299 Fix review comments 2018-11-06 01:14:28 +03:00
Andrey Meshkov
2e879896ff Close test upstream 2018-11-06 00:52:27 +03:00
Andrey Meshkov
451922b858 Added bootstrap DNS to the config file
DNS healthcheck now uses the upstream package methods
2018-11-06 00:47:59 +03:00
Andrey Meshkov
7f018234f6 goimports files 2018-11-05 23:52:11 +03:00
Andrey Meshkov
efdd1c1ff2 Added CoreDNS plugin setup and replaced forward 2018-11-05 23:49:31 +03:00
Andrey Meshkov
9bc4bf66ed Added factory method for creating DNS upstreams 2018-11-05 22:11:13 +03:00
Andrey Meshkov
a6022fc198 Added health-check method 2018-11-05 21:19:01 +03:00
Andrey Meshkov
d6f560ecaf Added persistent connections cache 2018-11-05 20:40:10 +03:00
Hoàng Rio
839c2ebdd4 add missing translate 2018-11-05 11:46:18 +07:00
Hoàng Rio
9cd7a37646 Update missing trasnlate 2018-11-05 11:26:32 +07:00
Hoàng Rio
cd75c406c1 Merge remote-tracking branch 'upstream/master' into i18n 2018-11-05 11:23:16 +07:00
Eugene Bujak
2449075bca Revert "Cache DNS lookups when resolving safebrowsing or parental servers, also cache replacement hostnames as well."
This reverts commit a5d1053520.

This cache had unintended side effects.
2018-11-02 12:15:30 +03:00
Hoàng Rio
4c9a84dda0 Fix eslint 2018-11-02 13:34:38 +07:00
Hoàng Rio
262e9acc03 Add translate PopoverFilter 2018-11-02 13:09:08 +07:00
Andrey Meshkov
484c0ceaff Upstream plugin prototype 2018-11-01 14:45:32 +03:00
Hoàng Rio
e399a5fe37 Merge branch 'master' into i18n 2018-10-31 08:19:43 +07:00
Andrey Meshkov
19e30dbccc Merge pull request #92 in DNS/adguard-dns from feature/371 to master
* commit '49ff0d2b9ae4982d4352b668b8d7085ed72f2d4e':
  Added green background for whitelist rows
  Fix rule text in the Reason
  Added filter name to popover
  Fix review comments: NextFilterId collisions
  Added more logging to the plugin
  Added filterId to the querylog Updated the openapi.yaml accordingly Some minor refactoring/renaming Fix other review comments
  Fix review comments Fixed coredns plugin tests Check that user filter is not empty
  Fix #371 #421
2018-10-30 18:20:34 +03:00
Ildar Kamalov
49ff0d2b9a Added green background for whitelist rows 2018-10-30 18:10:05 +03:00
Andrey Meshkov
800002f83d Merge branch 'feature/371' of ssh://bit.adguard.com:7999/dns/adguard-dns into feature/371 2018-10-30 17:41:12 +03:00
Andrey Meshkov
73e20d1dd0 Fix rule text in the Reason 2018-10-30 17:37:11 +03:00
Ildar Kamalov
9bb788ecb5 Merge branch 'feature/371' of ssh://bit.adguard.com:7999/dns/adguard-dns into feature/371 2018-10-30 17:28:08 +03:00
Ildar Kamalov
f3fa497af3 Added filter name to popover 2018-10-30 17:27:47 +03:00
Andrey Meshkov
54bdacdde2 Fix review comments: NextFilterId collisions 2018-10-30 17:16:20 +03:00
Andrey Meshkov
0e065a2e61 Added more logging to the plugin 2018-10-30 12:57:16 +03:00
Andrey Meshkov
591065aa3a Added filterId to the querylog
Updated the openapi.yaml accordingly
Some minor refactoring/renaming
Fix other review comments
2018-10-30 12:24:59 +03:00
Andrey Meshkov
760e3596b6 Fix review comments
Fixed coredns plugin tests
Check that user filter is not empty
2018-10-30 11:01:09 +03:00
Hoàng Rio
21b8b233f8 Merge branch 'master' into i18n 2018-10-30 13:07:54 +07:00
Andrey Meshkov
32d4e80c93 Fix #371 #421
Filters are now saved to a file
Also, they're loaded from the file on startup
Filter ID is not passed to the CoreDNS plugin config (server-side AG DNS must be changed accordingly)
Some minor refactoring, unused functions removed
2018-10-30 02:17:24 +03:00
Andrey Meshkov
30f3eb446c Merge pull request #443 from AdguardTeam/fix/sync_with_bitbucket
Fix/sync with bitbucket
2018-10-29 13:50:41 +00:00
Andrey Meshkov
f711d6558f Merge pull request #90 in DNS/adguard-dns from fix/437 to master
* commit 'c39831abbcc3942bc7af9f8fd6c816b96770ee3e':
  Fix sorting method
  Move tiny-version-compare module to helpers
  Fix tooltips over-lapping
  Fix IP address sorting
2018-10-29 16:34:02 +03:00
Andrey Meshkov
abd1d306dc Merge pull request #91 in DNS/adguard-dns from fix/383 to master
* commit '1e1ce606c552d83daa4272a301d373561182820b':
  gofmt instead of goland's format
  Add ErrAlreadyExists
  Moved hosts-syntax matching to DnsFilter
  Fix gitignore
2018-10-29 16:33:53 +03:00
Andrey Meshkov
1e1ce606c5 gofmt instead of goland's format 2018-10-29 16:29:30 +03:00
Andrey Meshkov
abb51ddb8a Add ErrAlreadyExists 2018-10-29 16:17:18 +03:00
Andrey Meshkov
2b2a797cf7 Moved hosts-syntax matching to DnsFilter 2018-10-29 15:46:58 +03:00
Ildar Kamalov
c39831abbc Fix sorting method 2018-10-29 15:24:35 +03:00
Ildar Kamalov
9173b0ee7a Move tiny-version-compare module to helpers 2018-10-29 14:38:01 +03:00
Ildar Kamalov
c427034e27 Fix tooltips over-lapping
Closes #439
2018-10-29 13:13:32 +03:00
Ildar Kamalov
3cd3b93511 Fix IP address sorting
Closes #437
2018-10-29 13:12:04 +03:00
Ildar Kamalov
41c9a89516 Merge pull request #89 in DNS/adguard-dns from fix/433 to master
* commit '90ed48e9fbc140ef3ab09f4ae006a4b4d433e518':
  Fix graph corruption on Edge browser
2018-10-29 10:05:59 +03:00
Hoàng Rio
9863c1f1ac Fix i18n in setting page not update when click change language button at footer 2018-10-29 11:58:25 +07:00
Hoàng Rio
79468ab1bc Merge branch 'i18n' of https://github.com/abpvn/AdGuardHome into i18n 2018-10-29 10:26:41 +07:00
Hoàng Rio
4b821f0bd7 Add switch language button on footer 2018-10-29 10:26:19 +07:00
hoangrio
54b0f073e8 Merge remote-tracking branch 'upstream/master' into i18n 2018-10-28 13:05:52 +07:00
Ildar Kamalov
90ed48e9fb Fix graph corruption on Edge browser
Closes #433
2018-10-27 21:17:48 +03:00
Ildar Kamalov
7a68c3dfc6 Merge pull request #431 from abpvn/master
Allow click footer link when loading
2018-10-26 14:58:50 +03:00
Andrey Meshkov
234ab23557 Fix gitignore 2018-10-26 14:13:31 +03:00
Hoàng Rio
234e29697f Add translate for Popover 2018-10-26 10:58:18 +07:00
Hoàng Rio
4590564fea Complete translate client to Vietnamese 2018-10-26 09:44:23 +07:00
hoangrio
bfb7a252ad Fix i18n config for use : character 2018-10-25 23:02:04 +07:00
hoangrio
1d12e35dac Merge branch 'master' into i18n 2018-10-25 22:22:10 +07:00
hoangnd
3854a7acf9 Add translate Setting page 2018-10-25 17:33:44 +07:00
hoangnd
3be7366ae1 Fix toast z-index after allow click link on footer on loading 2018-10-25 16:44:23 +07:00
hoangnd
e1069f6bd1 Translate dashboard to Vietnamese with i18n 2018-10-25 14:28:52 +07:00
hoangnd
f8ee8a7907 Allow click footer link when loading 2018-10-25 12:55:35 +07:00
Andrey Meshkov
b6bc613c87 Merge pull request #411 from mustakimali/master
Formatted 'Rules count' number with thousands separator
2018-10-23 18:30:40 +01:00
Mo Mustakim Ali
e466a09e20 Update index.js 2018-10-23 15:58:07 +01:00
Mo Mustakim Ali
98bf5322a3 Using culture-specific approach instead of regex 2018-10-23 13:48:51 +01:00
A.J. Ruckman
b3ae247520 use keyed fields 2018-10-22 22:51:51 +03:00
A.J. Ruckman
b3840b5790 minor tweaks 2018-10-22 22:51:51 +03:00
Mustakim Ali
0c4646201f Fixed linter issue 2018-10-20 17:03:36 +01:00
Mustakim Ali
66b83a5fb5 Formatted 'Rules count' number with ',' separator
147500 => 147,500
2018-10-20 16:50:25 +01:00
Mustakim Ali
12706d4a97 Added missing JSDoc 2018-10-20 16:49:02 +01:00
Eugene Bujak
50d2c0a8d3 v0.9-hotfix1 2018-10-18 16:00:51 +03:00
Erik Rogers
4ad29ee65d Add Dockerfiles 2018-10-18 15:34:29 +03:00
Eugene Bujak
b2998d77f0 Merge pull request #88 in DNS/adguard-dns from bugfix/381 to master
* commit 'a528ed9f947d42f4324cd4f2263a015d34d7341f':
  Stop requiring current working directory to be the location of AdGuardHome.
2018-10-17 20:46:40 +03:00
Eugene Bujak
a528ed9f94 Stop requiring current working directory to be the location of AdGuardHome.
Fixes #381.
2018-10-17 20:43:26 +03:00
Eugene Bujak
a1bc008190 Merge pull request #87 in DNS/adguard-dns from feature/378 to master
* commit 'd3a6a8625406bfc452c545ed2f5eff6340bb86c5':
  Makefile -- add support for providing different GOPATH in command line.
  Do not use port 8618, it's a leftover from a time when we had two binaries.
2018-10-17 19:01:50 +03:00
Eugene Bujak
d3a6a86254 Makefile -- add support for providing different GOPATH in command line. 2018-10-17 18:57:47 +03:00
Eugene Bujak
5437a9d3a6 Do not use port 8618, it's a leftover from a time when we had two binaries.
Should fix 378 but needs testing from users having the problem since couldn't reproduce it here yet.
2018-10-17 18:55:27 +03:00
Ildar Kamalov
bdfb141d36 Fix logo 2018-10-17 14:44:42 +03:00
Andrey Meshkov
550dc3b129 fix gh language 2018-10-17 13:14:45 +03:00
Eugene Bujak
bacc465ebd README -- fix broken links, part deux. 2018-10-17 02:33:01 +03:00
Eugene Bujak
e606d63525 README -- fix broken links 2018-10-17 00:01:22 +03:00
Eugene Bujak
dbde07eea2 version.json -- point to actual files 2018-10-17 00:00:07 +03:00
169 changed files with 18358 additions and 8676 deletions

8
.codecov.yml Normal file
View File

@@ -0,0 +1,8 @@
coverage:
status:
project:
default:
target: 40%
threshold: null
patch: false
changes: false

1
.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
client/* linguist-vendored

20
.gitignore vendored
View File

@@ -1,12 +1,20 @@
.DS_Store
.vscode
debug
/.vscode
/.idea
/AdGuardHome
/AdGuardHome.exe
/AdGuardHome.yaml
/AdGuardHome.log
/data/
/build/
/dist/
/client/node_modules/
/coredns
/Corefile
/dnsfilter.txt
/querylog.json
/querylog.json.1
/querylog.json.1
/scripts/translations/node_modules
/scripts/translations/oneskyapp.json
/a_main-packr.go
# Test output
dnsfilter/tests/top-1m.csv
dnsfilter/tests/dnsfilter.TestLotsOfRules*.pprof

56
.golangci.yml Normal file
View File

@@ -0,0 +1,56 @@
# options for analysis running
run:
# default concurrency is a available CPU number
concurrency: 4
# timeout for analysis, e.g. 30s, 5m, default is 1m
deadline: 2m
# which files to skip: they will be analyzed, but issues from them
# won't be reported. Default value is empty list, but there is
# no need to include all autogenerated files, we confidently recognize
# autogenerated files. If it's not please let us know.
skip-files:
- ".*generated.*"
- dnsfilter/rule_to_regexp.go
# all available settings of specific linters
linters-settings:
errcheck:
# [deprecated] comma-separated list of pairs of the form pkg:regex
# the regex is used to ignore names within pkg. (default "fmt:.*").
# see https://github.com/kisielk/errcheck#the-deprecated-method for details
ignore: fmt:.*,net:SetReadDeadline,net/http:^Write
gocyclo:
min-complexity: 20
lll:
line-length: 200
linters:
enable-all: true
disable:
- interfacer
- gocritic
- scopelint
- gochecknoglobals
- gochecknoinits
- prealloc
- maligned
- goconst # disabled until it's possible to configure
fast: true
issues:
# List of regexps of issue texts to exclude, empty list by default.
# But independently from this option we use default exclude patterns,
# it can be disabled by `exclude-use-default: false`. To list all
# excluded by default patterns execute `golangci-lint run --help`
exclude:
# structcheck cannot detect usages while they're there
- .parentalServer. is unused
- .safeBrowsingServer. is unused
# errcheck
- Error return value of .s.closeConn. is not checked
- Error return value of ..*.Shutdown.
# goconst
- string .forcesafesearch.google.com. has 3 occurrences

29
.gometalinter.json Normal file
View File

@@ -0,0 +1,29 @@
{
"Vendor": true,
"Test": true,
"Deadline": "2m",
"Sort": ["linter", "severity", "path", "line"],
"Exclude": [
".*generated.*",
"dnsfilter/rule_to_regexp.go"
],
"EnableGC": true,
"Linters": {
"nakedret": {
"Command": "nakedret",
"Pattern": "^(?P<path>.*?\\.go):(?P<line>\\d+)\\s*(?P<message>.*)$"
}
},
"WarnUnmatchedDirective": true,
"EnableAll": true,
"DisableAll": false,
"Disable": [
"maligned",
"goconst",
"vetshadow"
],
"Cyclo": 20,
"LineLength": 200
}

View File

@@ -1,20 +1,81 @@
language: go
sudo: false
go:
- 1.10.x
- 1.11.x
- 1.x
os:
- linux
- osx
before_install:
- nvm install node
- npm install -g npm
install:
- go get -v -d -t ./...
- npm --prefix client install
cache:
directories:
- $HOME/.cache/go-build
- $HOME/gopath/pkg/mod
- $HOME/Library/Caches/go-build
script:
- (cd `go env GOPATH`/src/github.com/prometheus/client_golang && git checkout -q v0.8.0)
- go test ./...
- node -v
- npm -v
# Run tests
- go test -race -v -bench=. -coverprofile=coverage.txt -covermode=atomic ./...
# Make
- make build/static/index.html
- make
after_success:
- bash <(curl -s https://codecov.io/bash)
matrix:
include:
# Release build configuration
- name: release
go:
- 1.11.x
os:
- linux
script:
- node -v
- npm -v
# Run tests just in case
- go test -race -v -bench=. ./...
# Prepare releases
- ./release.sh
- ls -l dist
deploy:
provider: releases
api_key: $GITHUB_TOKEN
file:
- dist/AdGuardHome_*
on:
repo: AdguardTeam/AdGuardHome
tags: true
draft: true
file_glob: true
skip_cleanup: true
- name: docker
if: type != pull_request AND (branch = master OR tag IS present)
go:
- 1.11.x
os:
- linux
services:
- docker
before_script:
- nvm install node
- npm install -g npm
script:
- docker login -u="$DOCKER_USER" -p="$DOCKER_PASSWORD"
- ./build_docker.sh
after_script:
- docker images

24
Dockerfile Normal file
View File

@@ -0,0 +1,24 @@
FROM golang:alpine AS build
RUN apk add --update git make build-base npm && \
rm -rf /var/cache/apk/*
WORKDIR /src/AdGuardHome
COPY . /src/AdGuardHome
RUN make
FROM alpine:latest
LABEL maintainer="AdGuard Team <devteam@adguard.com>"
# Update CA certs
RUN apk --no-cache --update add ca-certificates && \
rm -rf /var/cache/apk/* && mkdir -p /opt/adguardhome
COPY --from=build /src/AdGuardHome/AdGuardHome /opt/adguardhome/AdGuardHome
EXPOSE 53/tcp 53/udp 67/tcp 67/udp 68/tcp 68/udp 80/tcp 443/tcp 853/tcp 853/udp 3000/tcp
VOLUME ["/opt/adguardhome/conf", "/opt/adguardhome/work"]
ENTRYPOINT ["/opt/adguardhome/AdGuardHome"]
CMD ["-h", "0.0.0.0", "-c", "/opt/adguardhome/conf/AdGuardHome.yaml", "-w", "/opt/adguardhome/work"]

16
Dockerfile.travis Normal file
View File

@@ -0,0 +1,16 @@
FROM alpine:latest
LABEL maintainer="AdGuard Team <devteam@adguard.com>"
# Update CA certs
RUN apk --no-cache --update add ca-certificates && \
rm -rf /var/cache/apk/* && mkdir -p /opt/adguardhome
COPY ./AdGuardHome /opt/adguardhome/AdGuardHome
EXPOSE 53/tcp 53/udp 67/tcp 67/udp 68/tcp 68/udp 80/tcp 443/tcp 853/tcp 853/udp 3000/tcp
VOLUME ["/opt/adguardhome/conf", "/opt/adguardhome/work"]
ENTRYPOINT ["/opt/adguardhome/AdGuardHome"]
CMD ["-h", "0.0.0.0", "-c", "/opt/adguardhome/conf/AdGuardHome.yaml", "-w", "/opt/adguardhome/work"]

View File

@@ -1,9 +1,7 @@
GIT_VERSION := $(shell git describe --abbrev=4 --dirty --always --tags)
NATIVE_GOOS = $(shell unset GOOS; go env GOOS)
NATIVE_GOARCH = $(shell unset GOARCH; go env GOARCH)
mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST)))
mkfile_dir := $(patsubst %/,%,$(dir $(mkfile_path)))
GOPATH := $(mkfile_dir)/build/gopath
GOPATH := $(shell go env GOPATH)
JSFILES = $(shell find client -path client/node_modules -prune -o -type f -name '*.js')
STATIC = build/static/index.html
@@ -21,16 +19,11 @@ client/node_modules: client/package.json client/package-lock.json
$(STATIC): $(JSFILES) client/node_modules
npm --prefix client run build-prod
$(TARGET): $(STATIC) *.go coredns_plugin/*.go dnsfilter/*.go
mkdir -p $(GOPATH)/src/github.com/AdguardTeam
if [ ! -h $(GOPATH)/src/github.com/AdguardTeam/AdGuardHome ]; then rm -rf $(GOPATH)/src/github.com/AdguardTeam/AdGuardHome && ln -fs ../../../../.. $(GOPATH)/src/github.com/AdguardTeam/AdGuardHome; fi
GOPATH=$(GOPATH) go get -v -d .
GOPATH=$(GOPATH) GOOS=$(NATIVE_GOOS) GOARCH=$(NATIVE_GOARCH) go get -v github.com/gobuffalo/packr/...
mkdir -p $(GOPATH)/src/github.com/AdguardTeam/AdGuardHome/build/static ## work around packr bug
cd $(GOPATH)/src/github.com/prometheus/client_golang && git reset --hard v0.8.0
perl -0777 -p -i.bak -e 's/pprofOnce.Do\(func\(\) {(.*)}\)/\1/ms' $(GOPATH)/src/github.com/coredns/coredns/plugin/pprof/setup.go
perl -0777 -p -i.bak -e 's/c.OnShutdown/c.OnRestart/' $(GOPATH)/src/github.com/coredns/coredns/plugin/pprof/setup.go
GOPATH=$(GOPATH) PATH=$(GOPATH)/bin:$(PATH) packr build -ldflags="-X main.VersionString=$(GIT_VERSION)" -o $(TARGET)
$(TARGET): $(STATIC) *.go dhcpd/*.go dnsfilter/*.go dnsforward/*.go
GOOS=$(NATIVE_GOOS) GOARCH=$(NATIVE_GOARCH) GO111MODULE=off go get -v github.com/gobuffalo/packr/...
PATH=$(GOPATH)/bin:$(PATH) packr -z
CGO_ENABLED=0 go build -ldflags="-s -w -X main.VersionString=$(GIT_VERSION)" -asmflags="-trimpath=$(PWD)" -gcflags="-trimpath=$(PWD)"
PATH=$(GOPATH)/bin:$(PATH) packr clean
clean:
$(MAKE) cleanfast

150
README.md
View File

@@ -11,11 +11,21 @@
<a href="https://adguard.com/">AdGuard.com</a> |
<a href="https://github.com/AdguardTeam/AdGuardHome/wiki">Wiki</a> |
<a href="https://reddit.com/r/Adguard">Reddit</a> |
<a href="https://twitter.com/AdGuard">Twitter</a>
<a href="https://twitter.com/AdGuard">Twitter</a> |
<a href="https://t.me/adguard_en">Telegram</a>
<br /><br />
<a href="https://travis-ci.org/AdguardTeam/AdGuardHome">
<img src="https://travis-ci.org/AdguardTeam/AdGuardHome.svg" alt="Build status" />
</a>
<a href="https://codecov.io/github/AdguardTeam/AdGuardHome?branch=master">
<img src="https://img.shields.io/codecov/c/github/AdguardTeam/AdGuardHome/master.svg" alt="Code Coverage" />
</a>
<a href="https://goreportcard.com/report/AdguardTeam/AdGuardHome">
<img src="https://goreportcard.com/badge/github.com/AdguardTeam/AdGuardHome" alt="Go Report Card" />
</a>
<a href="https://golangci.com/r/github.com/AdguardTeam/AdGuardHome">
<img src="https://golangci.com/badges/github.com/AdguardTeam/AdGuardHome.svg" alt="GolangCI" />
</a>
<a href="https://github.com/AdguardTeam/AdGuardHome/releases">
<img src="https://img.shields.io/github/release/AdguardTeam/AdGuardHome/all.svg" alt="Latest release" />
</a>
@@ -29,99 +39,39 @@
<hr />
# AdGuard Home
AdGuard Home is a network-wide software for blocking ads & tracking. After you set it up, it'll cover ALL your home devices, and you don't need any client-side software for that.
## How does AdGuard Home work?
It operates as a DNS server that re-routes tracking domains to a "black hole," thus preventing your devices from connecting to those servers. It's based on software we use for our public [AdGuard DNS](https://adguard.com/en/adguard-dns/overview.html) servers -- both share a lot of common code.
AdGuard Home operates as a DNS server that re-routes tracking domains to a "black hole," thus preventing your devices from connecting to those servers. It's based on software we use for our public [AdGuard DNS](https://adguard.com/en/adguard-dns/overview.html) servers -- both share a lot of common code.
* [Getting Started](#getting-started)
* [How to build from source](#how-to-build)
* [Contributing](#contributing)
* [Reporting issues](#reporting-issues)
* [Acknowledgments](#acknowledgments)
## How is this different from public AdGuard DNS servers?
<a id="getting-started"></a>
## Getting Started
Running your own AdGuard Home server allows you to do much more than using a public DNS server.
Please read the [Getting Started](https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started) article on our Wiki to learn how to install AdGuard Home, and how to configure your devices to use it.
* Choose what exactly will the server block or not block;
* Monitor your network activity;
* Add your own custom filtering rules;
Alternatively, you can use our [official Docker image](https://hub.docker.com/r/adguard/adguardhome).
In the future, AdGuard Home is supposed to become more than just a DNS server.
### Guides
## Installation
### Mac
Download this file: [AdguardDNS_0.9_MacOS.zip](https://github.com/AdguardTeam/AdGuardHome/releases/download/v0.9/AdguardDNS_0.9_MacOS.zip), then unpack it and follow ["How to run"](#how-to-run) instructions below.
### Linux 64-bit Intel
Download this file: [AdguardDNS_0.9_linux_amd64.tar.gz](https://github.com/AdguardTeam/AdGuardHome/releases/download/v0.9/AdguardDNS_0.9_linux_amd64.tar.gz), then unpack it and follow ["How to run"](#how-to-run) instructions below.
### Linux 32-bit Intel
Download this file: [AdguardDNS_0.9_linux_386.tar.gz](https://github.com/AdguardTeam/AdGuardHome/releases/download/v0.9/AdguardDNS_0.9_linux_386.tar.gz), then unpack it and follow ["How to run"](#how-to-run) instructions below.
### Raspberry Pi (32-bit ARM)
Download this file: [AdguardDNS_0.9_linux_arm.tar.gz](https://github.com/AdguardTeam/AdGuardHome/releases/download/v0.9/AdguardDNS_0.9_linux_arm.tar.gz), then unpack it and follow ["How to run"](#how-to-run) instructions below.
## How to run
DNS works on port 53, which requires superuser privileges. Therefore, you need to run it with `sudo` in terminal:
```bash
sudo ./AdGuardHome
```
Now open the browser and navigate to http://localhost:3000/ to control your AdGuard Home service.
### Running without superuser
You can run AdGuard Home without superuser privileges, but you need to instruct it to use a different port rather than 53. You can do that by editing `AdGuardHome.yaml` and finding these two lines:
```yaml
coredns:
port: 53
```
You can change port 53 to anything above 1024 to avoid requiring superuser privileges.
If the file does not exist, create it in the same folder, type these two lines down and save.
### Additional configuration
Upon the first execution, a file named `AdGuardHome.yaml` will be created, with default values written in it. You can modify the file while your AdGuard Home service is not running. Otherwise, any changes to the file will be lost because the running program will overwrite them.
Settings are stored in [YAML format](https://en.wikipedia.org/wiki/YAML), possible parameters that you can configure are listed below:
* `bind_host` — Web interface IP address to listen on
* `bind_port` — Web interface IP port to listen on
* `auth_name` — Web interface optional authorization username
* `auth_pass` — Web interface optional authorization password
* `coredns` — CoreDNS configuration section
* `port` — DNS server port to listen on
* `filtering_enabled` — Filtering of DNS requests based on filter lists
* `safebrowsing_enabled` — Filtering of DNS requests based on safebrowsing
* `safesearch_enabled` — Enforcing "Safe search" option for search engines, when possible
* `parental_enabled` — Parental control-based DNS requests filtering
* `parental_sensitivity` — Age group for parental control-based filtering, must be either 3, 10, 13 or 17
* `querylog_enabled` — Query logging (also used to calculate top 50 clients, blocked domains and requested domains for statistic purposes)
* `upstream_dns` — List of upstream DNS servers
* `filters` — List of filters, each filter has the following values:
* `url` — URL pointing to the filter contents (filtering rules)
* `enabled` — Current filter's status (enabled/disabled)
* `user_rules` — User-specified filtering rules
Removing an entry from settings file will reset it to the default value. Deleting the file will reset all settings to the default values.
* [Configuration](https://github.com/AdguardTeam/AdGuardHome/wiki/Configuration)
* [AdGuard Home as a DNS-over-HTTPS or DNS-over-TLS server](https://github.com/AdguardTeam/AdGuardHome/wiki/Encryption)
* [How to install and run AdGuard Home on Raspberry Pi](https://github.com/AdguardTeam/AdGuardHome/wiki/Raspberry-Pi)
* [How to install and run AdGuard Home on a Virtual Private Server](https://github.com/AdguardTeam/AdGuardHome/wiki/VPS)
<a id="how-to-build"></a>
## How to build from source
### Prerequisites
You will need:
* [go](https://golang.org/dl/)
* [node.js](https://nodejs.org/en/download/)
* [go](https://golang.org/dl/) v1.11 or later.
* [node.js](https://nodejs.org/en/download/) v10 or later.
You can either install it via the provided links or use [brew.sh](https://brew.sh/) if you're on Mac:
@@ -139,28 +89,68 @@ cd AdGuardHome
make
```
<a id="contributing"></a>
## Contributing
You are welcome to fork this repository, make your changes and submit a pull request — https://github.com/AdguardTeam/AdGuardHome/pulls
### How to update translations
If you want to help with AdGuard Home translations, please learn more about translating AdGuard products here: https://kb.adguard.com/en/general/adguard-translations
Here is a direct link to AdGuard Home project: http://translate.adguard.com/collaboration/project?id=153384
Before updating translations you need to install dependencies:
```
cd scripts/translations
npm install
```
Create file `oneskyapp.json` in `scripts/translations` folder.
Example of `oneskyapp.json`
```
{
"url": "https://platform.api.onesky.io/1/projects/",
"projectId": <PROJECT ID>,
"apiKey": <API KEY>,
"secretKey": <SECRET KEY>
}
```
#### Upload translations
```
node upload.js
```
#### Download translations
```
node download.js
```
<a id="reporting-issues"></a>
## Reporting issues
If you run into any problem or have a suggestion, head to [this page](https://github.com/AdguardTeam/AdGuardHome/issues) and click on the `New issue` button.
<a id="acknowledgments"></a>
## Acknowledgments
This software wouldn't have been possible without:
* [Go](https://golang.org/dl/) and it's libraries:
* [CoreDNS](https://coredns.io)
* [packr](https://github.com/gobuffalo/packr)
* [gcache](https://github.com/bluele/gcache)
* [miekg's dns](https://github.com/miekg/dns)
* [go-yaml](https://github.com/go-yaml/yaml)
* [service](https://godoc.org/github.com/kardianos/service)
* [dnsproxy](https://github.com/AdguardTeam/dnsproxy)
* [Node.js](https://nodejs.org/) and it's libraries:
* [React.js](https://reactjs.org)
* [Tabler](https://github.com/tabler/tabler)
* And many more node.js packages.
* [whotracks.me data](https://github.com/cliqz-oss/whotracks.me)
You might have seen that [CoreDNS](https://coredns.io) was mentioned here before — we've stopped using it in AdGuardHome. While we still use it on our servers for [AdGuard DNS](https://adguard.com/adguard-dns/overview.html) service, it seemed like an overkill for Home as it impeded with Home features that we plan to implement.
For a full list of all node.js packages in use, please take a look at [client/package.json](https://github.com/AdguardTeam/AdGuardHome/blob/master/client/package.json) file.

597
app.go
View File

@@ -1,268 +1,427 @@
package main
import (
"bufio"
"crypto/tls"
"fmt"
"log"
stdlog "log"
"net"
"net/http"
"os"
"os/signal"
"path/filepath"
"runtime"
"strconv"
"sync"
"syscall"
"time"
"github.com/gobuffalo/packr"
"golang.org/x/crypto/ssh/terminal"
"github.com/hmage/golibs/log"
)
// VersionString will be set through ldflags, contains current version
var VersionString = "undefined"
var httpServer *http.Server
var httpsServer struct {
server *http.Server
cond *sync.Cond // reacts to config.TLS.Enabled, PortHTTPS, CertificateChain and PrivateKey
sync.Mutex // protects config.TLS
}
const (
// Used in config to indicate that syslog or eventlog (win) should be used for logger output
configSyslog = "syslog"
)
// main is the entry point
func main() {
log.Printf("AdGuard Home web interface backend, version %s\n", VersionString)
box := packr.NewBox("build/static")
{
executable, err := os.Executable()
if err != nil {
panic(err)
}
config.ourBinaryDir = filepath.Dir(executable)
}
doConfigRename := true
// config can be specified, which reads options from there, but other command line flags have to override config values
// therefore, we must do it manually instead of using a lib
{
var configFilename *string
var bindHost *string
var bindPort *int
var opts = []struct {
longName string
shortName string
description string
callback func(value string)
}{
{"config", "c", "path to config file", func(value string) { configFilename = &value }},
{"host", "h", "host address to bind HTTP server on", func(value string) { bindHost = &value }},
{"port", "p", "port to serve HTTP pages on", func(value string) {
v, err := strconv.Atoi(value)
if err != nil {
panic("Got port that is not a number")
}
bindPort = &v
}},
{"help", "h", "print this help", nil},
}
printHelp := func() {
fmt.Printf("Usage:\n\n")
fmt.Printf("%s [options]\n\n", os.Args[0])
fmt.Printf("Options:\n")
for _, opt := range opts {
fmt.Printf(" -%s, %-30s %s\n", opt.shortName, "--"+opt.longName, opt.description)
}
}
for i := 1; i < len(os.Args); i++ {
v := os.Args[i]
// short-circuit for help
if v == "--help" || v == "-h" {
printHelp()
os.Exit(64)
}
knownParam := false
for _, opt := range opts {
if v == "--"+opt.longName {
if i+1 > len(os.Args) {
log.Printf("ERROR: Got %s without argument\n", v)
os.Exit(64)
}
i++
opt.callback(os.Args[i])
knownParam = true
break
}
if v == "-"+opt.shortName {
if i+1 > len(os.Args) {
log.Printf("ERROR: Got %s without argument\n", v)
os.Exit(64)
}
i++
opt.callback(os.Args[i])
knownParam = true
break
}
}
if !knownParam {
log.Printf("ERROR: unknown option %v\n", v)
printHelp()
os.Exit(64)
}
}
if configFilename != nil {
// config was manually specified, don't do anything
doConfigRename = false
config.ourConfigFilename = *configFilename
}
args := loadOptions()
if doConfigRename {
err := renameOldConfigIfNeccessary()
if err != nil {
panic(err)
}
}
err := askUsernamePasswordIfPossible()
if err != nil {
log.Fatal(err)
}
// parse from config file
err = parseConfig()
if err != nil {
log.Fatal(err)
}
if bindHost != nil {
config.BindHost = *bindHost
}
if bindPort != nil {
config.BindPort = *bindPort
}
if args.serviceControlAction != "" {
handleServiceControlAction(args.serviceControlAction)
return
}
// eat all args so that coredns can start happily
if len(os.Args) > 1 {
os.Args = os.Args[:1]
signalChannel := make(chan os.Signal)
signal.Notify(signalChannel, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT)
go func() {
<-signalChannel
cleanup()
os.Exit(0)
}()
// run the protection
run(args)
}
// run initializes configuration and runs the AdGuard Home
// run is a blocking method and it won't exit until the service is stopped!
func run(args options) {
// config file path can be overridden by command-line arguments:
if args.configFilename != "" {
config.ourConfigFilename = args.configFilename
}
err := writeConfig()
// configure working dir and config path
initWorkingDir(args)
// configure log level and output
configureLogger(args)
// print the first message after logger is configured
log.Printf("AdGuard Home, version %s\n", VersionString)
log.Tracef("Current working directory is %s", config.ourWorkingDir)
if args.runningAsService {
log.Printf("AdGuard Home is running as a service")
}
config.firstRun = detectFirstRun()
// Do the upgrade if necessary
err := upgradeConfig()
if err != nil {
log.Fatal(err)
}
address := net.JoinHostPort(config.BindHost, strconv.Itoa(config.BindPort))
// parse from config file
err = parseConfig()
if err != nil {
log.Fatal(err)
}
runFilterRefreshers()
// override bind host/port from the console
if args.bindHost != "" {
config.BindHost = args.bindHost
}
if args.bindPort != 0 {
config.BindPort = args.bindPort
}
http.Handle("/", optionalAuthHandler(http.FileServer(box)))
// Load filters from the disk
// And if any filter has zero ID, assign a new one
for i := range config.Filters {
filter := &config.Filters[i] // otherwise we're operating on a copy
if filter.ID == 0 {
filter.ID = assignUniqueFilterID()
}
err = filter.load()
if err != nil {
// This is okay for the first start, the filter will be loaded later
log.Printf("Couldn't load filter %d contents due to %s", filter.ID, err)
// clear LastUpdated so it gets fetched right away
}
if len(filter.Rules) == 0 {
filter.LastUpdated = time.Time{}
}
}
// Save the updated config
err = config.write()
if err != nil {
log.Fatal(err)
}
// Init the DNS server instance before registering HTTP handlers
dnsBaseDir := filepath.Join(config.ourWorkingDir, dataDir)
initDNSServer(dnsBaseDir)
if !config.firstRun {
err = startDNSServer()
if err != nil {
log.Fatal(err)
}
err = startDHCPServer()
if err != nil {
log.Fatal(err)
}
}
// Update filters we've just loaded right away, don't wait for periodic update timer
go func() {
refreshFiltersIfNecessary(false)
// Save the updated config
err := config.write()
if err != nil {
log.Fatal(err)
}
}()
// Schedule automatic filters updates
go periodicallyRefreshFilters()
// Initialize and run the admin Web interface
box := packr.NewBox("build/static")
// if not configured, redirect / to /install.html, otherwise redirect /install.html to /
http.Handle("/", postInstallHandler(optionalAuthHandler(http.FileServer(box))))
registerControlHandlers()
err = startDNSServer()
if err != nil {
log.Fatal(err)
// add handlers for /install paths, we only need them when we're not configured yet
if config.firstRun {
log.Printf("This is the first launch of AdGuard Home, redirecting everything to /install.html ")
http.Handle("/install.html", preInstallHandler(http.FileServer(box)))
registerInstallHandlers()
}
URL := fmt.Sprintf("http://%s", address)
log.Println("Go to " + URL)
log.Fatal(http.ListenAndServe(address, nil))
}
httpsServer.cond = sync.NewCond(&httpsServer.Mutex)
func getInput() (string, error) {
scanner := bufio.NewScanner(os.Stdin)
scanner.Scan()
text := scanner.Text()
err := scanner.Err()
return text, err
}
// for https, we have a separate goroutine loop
go func() {
for { // this is an endless loop
httpsServer.cond.L.Lock()
// this mechanism doesn't let us through until all conditions are ment
for config.TLS.Enabled == false || config.TLS.PortHTTPS == 0 || config.TLS.PrivateKey == "" || config.TLS.CertificateChain == "" { // sleep until necessary data is supplied
httpsServer.cond.Wait()
}
address := net.JoinHostPort(config.BindHost, strconv.Itoa(config.TLS.PortHTTPS))
// validate current TLS config and update warnings (it could have been loaded from file)
data := validateCertificates(config.TLS)
if !data.usable {
log.Fatal(data.WarningValidation)
os.Exit(1)
}
config.Lock()
config.TLS = data // update warnings
config.Unlock()
func promptAndGet(prompt string) (string, error) {
// prepare certs for HTTPS server
// important -- they have to be copies, otherwise changing the contents in config.TLS will break encryption for in-flight requests
certchain := make([]byte, len(config.TLS.CertificateChain))
copy(certchain, []byte(config.TLS.CertificateChain))
privatekey := make([]byte, len(config.TLS.PrivateKey))
copy(privatekey, []byte(config.TLS.PrivateKey))
cert, err := tls.X509KeyPair(certchain, privatekey)
if err != nil {
log.Fatal(err)
os.Exit(1)
}
httpsServer.cond.L.Unlock()
// prepare HTTPS server
httpsServer.server = &http.Server{
Addr: address,
TLSConfig: &tls.Config{
Certificates: []tls.Certificate{cert},
},
}
printHTTPAddresses("https")
err = httpsServer.server.ListenAndServeTLS("", "")
if err != http.ErrServerClosed {
log.Fatal(err)
os.Exit(1)
}
}
}()
// this loop is used as an ability to change listening host and/or port
for {
fmt.Printf(prompt)
input, err := getInput()
printHTTPAddresses("http")
// we need to have new instance, because after Shutdown() the Server is not usable
address := net.JoinHostPort(config.BindHost, strconv.Itoa(config.BindPort))
httpServer = &http.Server{
Addr: address,
}
err := httpServer.ListenAndServe()
if err != http.ErrServerClosed {
log.Fatal(err)
os.Exit(1)
}
// We use ErrServerClosed as a sign that we need to rebind on new address, so go back to the start of the loop
}
}
// initWorkingDir initializes the ourWorkingDir
// if no command-line arguments specified, we use the directory where our binary file is located
func initWorkingDir(args options) {
exec, err := os.Executable()
if err != nil {
panic(err)
}
if args.workDir != "" {
// If there is a custom config file, use it's directory as our working dir
config.ourWorkingDir = args.workDir
} else {
config.ourWorkingDir = filepath.Dir(exec)
}
}
// configureLogger configures logger level and output
func configureLogger(args options) {
ls := getLogSettings()
// command-line arguments can override config settings
if args.verbose {
ls.Verbose = true
}
if args.logFile != "" {
ls.LogFile = args.logFile
}
log.Verbose = ls.Verbose
if args.runningAsService && ls.LogFile == "" && runtime.GOOS == "windows" {
// When running as a Windows service, use eventlog by default if nothing else is configured
// Otherwise, we'll simply loose the log output
ls.LogFile = configSyslog
}
if ls.LogFile == "" {
return
}
if ls.LogFile == configSyslog {
// Use syslog where it is possible and eventlog on Windows
err := configureSyslog()
if err != nil {
log.Printf("Failed to get input, aborting: %s", err)
return "", err
log.Fatalf("cannot initialize syslog: %s", err)
}
if len(input) != 0 {
return input, nil
}
// try again
}
return "", nil
}
func promptAndGetPassword(prompt string) (string, error) {
for {
fmt.Printf(prompt)
password, err := terminal.ReadPassword(int(os.Stdin.Fd()))
fmt.Printf("\n")
} else {
logFilePath := filepath.Join(config.ourWorkingDir, ls.LogFile)
file, err := os.OpenFile(logFilePath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0755)
if err != nil {
log.Printf("Failed to get input, aborting: %s", err)
return "", err
log.Fatalf("cannot create a log file: %s", err)
}
if len(password) != 0 {
return string(password), nil
stdlog.SetOutput(file)
}
}
func cleanup() {
log.Printf("Stopping AdGuard Home")
err := stopDNSServer()
if err != nil {
log.Printf("Couldn't stop DNS server: %s", err)
}
err = stopDHCPServer()
if err != nil {
log.Printf("Couldn't stop DHCP server: %s", err)
}
}
// command-line arguments
type options struct {
verbose bool // is verbose logging enabled
configFilename string // path to the config file
workDir string // path to the working directory where we will store the filters data and the querylog
bindHost string // host address to bind HTTP server on
bindPort int // port to serve HTTP pages on
logFile string // Path to the log file. If empty, write to stdout. If "syslog", writes to syslog
// service control action (see service.ControlAction array + "status" command)
serviceControlAction string
// runningAsService flag is set to true when options are passed from the service runner
runningAsService bool
}
// loadOptions reads command line arguments and initializes configuration
func loadOptions() options {
o := options{}
var printHelp func()
var opts = []struct {
longName string
shortName string
description string
callbackWithValue func(value string)
callbackNoValue func()
}{
{"config", "c", "path to the config file", func(value string) { o.configFilename = value }, nil},
{"work-dir", "w", "path to the working directory", func(value string) { o.workDir = value }, nil},
{"host", "h", "host address to bind HTTP server on", func(value string) { o.bindHost = value }, nil},
{"port", "p", "port to serve HTTP pages on", func(value string) {
v, err := strconv.Atoi(value)
if err != nil {
panic("Got port that is not a number")
}
o.bindPort = v
}, nil},
{"service", "s", "service control action: status, install, uninstall, start, stop, restart", func(value string) {
o.serviceControlAction = value
}, nil},
{"logfile", "l", "path to the log file. If empty, writes to stdout, if 'syslog' -- system log", func(value string) {
o.logFile = value
}, nil},
{"verbose", "v", "enable verbose output", nil, func() { o.verbose = true }},
{"help", "", "print this help", nil, func() {
printHelp()
os.Exit(64)
}},
}
printHelp = func() {
fmt.Printf("Usage:\n\n")
fmt.Printf("%s [options]\n\n", os.Args[0])
fmt.Printf("Options:\n")
for _, opt := range opts {
if opt.shortName != "" {
fmt.Printf(" -%s, %-30s %s\n", opt.shortName, "--"+opt.longName, opt.description)
} else {
fmt.Printf(" %-34s %s\n", "--"+opt.longName, opt.description)
}
}
// try again
}
for i := 1; i < len(os.Args); i++ {
v := os.Args[i]
knownParam := false
for _, opt := range opts {
if v == "--"+opt.longName || (opt.shortName != "" && v == "-"+opt.shortName) {
if opt.callbackWithValue != nil {
if i+1 >= len(os.Args) {
log.Printf("ERROR: Got %s without argument\n", v)
os.Exit(64)
}
i++
opt.callbackWithValue(os.Args[i])
} else if opt.callbackNoValue != nil {
opt.callbackNoValue()
}
knownParam = true
break
}
}
if !knownParam {
log.Printf("ERROR: unknown option %v\n", v)
printHelp()
os.Exit(64)
}
}
return o
}
func askUsernamePasswordIfPossible() error {
configfile := filepath.Join(config.ourBinaryDir, config.ourConfigFilename)
_, err := os.Stat(configfile)
if !os.IsNotExist(err) {
// do nothing, file exists
trace("File %s exists, won't ask for password", configfile)
return nil
}
if !terminal.IsTerminal(int(os.Stdin.Fd())) {
return nil // do nothing
}
if !terminal.IsTerminal(int(os.Stdout.Fd())) {
return nil // do nothing
}
fmt.Printf("Would you like to set user/password for the web interface authentication (yes/no)?\n")
yesno, err := promptAndGet("Please type 'yes' or 'no': ")
if err != nil {
return err
}
if yesno[0] != 'y' && yesno[0] != 'Y' {
return nil
}
username, err := promptAndGet("Please enter the username: ")
if err != nil {
return err
}
// prints IP addresses which user can use to open the admin interface
// proto is either "http" or "https"
func printHTTPAddresses(proto string) {
var address string
password, err := promptAndGetPassword("Please enter the password: ")
if err != nil {
return err
}
if proto == "https" && config.TLS.ServerName != "" {
if config.TLS.PortHTTPS == 443 {
log.Printf("Go to https://%s", config.TLS.ServerName)
} else {
log.Printf("Go to https://%s:%d", config.TLS.ServerName, config.TLS.PortHTTPS)
}
} else if config.BindHost == "0.0.0.0" {
log.Println("AdGuard Home is available on the following addresses:")
ifaces, err := getValidNetInterfacesForWeb()
if err != nil {
// That's weird, but we'll ignore it
address = net.JoinHostPort(config.BindHost, strconv.Itoa(config.BindPort))
log.Printf("Go to %s://%s", proto, address)
return
}
password2, err := promptAndGetPassword("Please enter password again: ")
if err != nil {
return err
for _, iface := range ifaces {
address = net.JoinHostPort(iface.Addresses[0], strconv.Itoa(config.BindPort))
log.Printf("Go to %s://%s", proto, address)
}
} else {
address = net.JoinHostPort(config.BindHost, strconv.Itoa(config.BindPort))
log.Printf("Go to %s://%s", proto, address)
}
if password2 != password {
fmt.Printf("Passwords do not match! Aborting\n")
os.Exit(1)
}
config.AuthName = username
config.AuthPass = password
return nil
}
func renameOldConfigIfNeccessary() error {
oldConfigFile := filepath.Join(config.ourBinaryDir, "AdguardDNS.yaml")
_, err := os.Stat(oldConfigFile)
if os.IsNotExist(err) {
// do nothing, file doesn't exist
trace("File %s doesn't exist, nothing to do", oldConfigFile)
return nil
}
newConfigFile := filepath.Join(config.ourBinaryDir, config.ourConfigFilename)
_, err = os.Stat(newConfigFile)
if !os.IsNotExist(err) {
// do nothing, file doesn't exist
trace("File %s already exists, will not overwrite", newConfigFile)
return nil
}
err = os.Rename(oldConfigFile, newConfigFile)
if err != nil {
log.Printf("Failed to rename %s to %s: %s", oldConfigFile, newConfigFile, err)
return err
}
return nil
}

74
build_docker.sh Executable file
View File

@@ -0,0 +1,74 @@
#!/usr/bin/env bash
set -eE
set -o pipefail
set -x
DOCKERFILE="Dockerfile.travis"
IMAGE_NAME="adguard/adguardhome"
if [[ "${TRAVIS_BRANCH}" == "master" ]]
then
VERSION="edge"
else
VERSION=`git describe --abbrev=4 --dirty --always --tags`
fi
build_image() {
from="$(awk '$1 == toupper("FROM") { print $2 }' ${DOCKERFILE})"
# See https://hub.docker.com/r/multiarch/alpine/tags
case "${GOARCH}" in
arm64)
alpineArch='arm64-edge'
imageArch='arm64'
;;
arm)
alpineArch='armhf-edge'
imageArch='armhf'
;;
386)
alpineArch='i386-edge'
imageArch='i386'
;;
amd64)
alpineArch='amd64-edge'
;;
*)
alpineArch='amd64-edge'
;;
esac
if [[ "${GOOS}" == "linux" ]] && [[ "${GOARCH}" == "amd64" ]]
then
image="${IMAGE_NAME}:${VERSION}"
else
image="${IMAGE_NAME}:${imageArch}-${VERSION}"
fi
make cleanfast; CGO_DISABLED=1 make
docker pull "multiarch/alpine:${alpineArch}"
docker tag "multiarch/alpine:${alpineArch}" "$from"
docker build -t "${image}" -f ${DOCKERFILE} .
docker push ${image}
if [[ "${VERSION}" != "edge" ]]
then
latest=${image/$VERSION/latest}
docker tag "${image}" "${latest}"
docker push ${latest}
docker rmi ${latest}
fi
docker rmi "$from"
}
# prepare qemu
docker run --rm --privileged multiarch/qemu-user-static:register --reset
make clean
# Prepare releases
GOOS=linux GOARCH=amd64 build_image
GOOS=linux GOARCH=386 build_image
GOOS=linux GOARCH=arm GOARM=6 build_image
GOOS=linux GOARCH=arm64 GOARM=6 build_image

4
client/.eslintrc vendored
View File

@@ -45,9 +45,7 @@
}],
"class-methods-use-this": "off",
"no-shadow": "off",
"camelcase": ["error", {
"properties": "never"
}],
"camelcase": "off",
"no-console": ["warn", { "allow": ["warn", "error"] }],
"import/no-extraneous-dependencies": ["error", { "devDependencies": true }],
"import/prefer-default-export": "off"

7238
client/package-lock.json generated vendored

File diff suppressed because it is too large Load Diff

16
client/package.json vendored
View File

@@ -14,12 +14,15 @@
"classnames": "^2.2.6",
"date-fns": "^1.29.0",
"file-saver": "^1.3.8",
"lodash": "^4.17.10",
"i18next": "^12.0.0",
"i18next-browser-languagedetector": "^2.2.3",
"lodash": "^4.17.11",
"nanoid": "^1.2.3",
"prop-types": "^15.6.1",
"react": "^16.4.0",
"react-click-outside": "^3.0.1",
"react-dom": "^16.4.0",
"react-i18next": "^8.2.0",
"react-modal": "^3.4.5",
"react-redux": "^5.0.7",
"react-redux-loading-bar": "^4.0.7",
@@ -28,16 +31,14 @@
"react-transition-group": "^2.4.0",
"redux": "^4.0.0",
"redux-actions": "^2.4.0",
"redux-form": "^7.4.2",
"redux-thunk": "^2.3.0",
"svg-url-loader": "^2.3.2",
"tiny-version-compare": "^0.9.1",
"whatwg-fetch": "2.0.3"
"svg-url-loader": "^2.3.2"
},
"devDependencies": {
"autoprefixer": "^8.6.3",
"babel-core": "6.26.0",
"babel-eslint": "^8.2.3",
"babel-jest": "20.0.3",
"babel-loader": "7.1.2",
"babel-plugin-transform-runtime": "^6.23.0",
"babel-preset-env": "^1.7.0",
@@ -57,7 +58,6 @@
"extract-text-webpack-plugin": "^3.0.2",
"file-loader": "1.1.5",
"html-webpack-plugin": "^3.2.0",
"jest": "20.0.4",
"postcss-flexbugs-fixes": "3.2.0",
"postcss-import": "^11.1.0",
"postcss-loader": "^2.1.5",
@@ -65,12 +65,12 @@
"postcss-preset-env": "^5.1.0",
"postcss-svg": "^2.4.0",
"style-loader": "^0.21.0",
"stylelint": "9.2.1",
"stylelint": "^9.10.1",
"stylelint-webpack-plugin": "0.10.4",
"uglifyjs-webpack-plugin": "^1.2.7",
"url-loader": "^1.0.1",
"webpack": "3.8.1",
"webpack-dev-server": "2.9.4",
"webpack-dev-server": "^3.1.14",
"webpack-merge": "^4.1.3"
}
}

View File

@@ -1,16 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<link rel="shortcut icon" href="https://adguard.com/img/favicons/favicon.ico">
<title>AdGuard Home</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
</body>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<link rel="shortcut icon" href="https://adguard.com/img/favicons/favicon.ico">
<title>AdGuard Home</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
</body>
</html>

View File

@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<link rel="shortcut icon" href="https://adguard.com/img/favicons/favicon.ico">
<title>Setup AdGuard Home</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
</body>
</html>

View File

@@ -0,0 +1,250 @@
{
"url_added_successfully": "Url added successfully",
"check_dhcp_servers": "Check for DHCP servers",
"save_config": "Save config",
"enabled_dhcp": "DHCP server enabled",
"disabled_dhcp": "DHCP server disabled",
"dhcp_title": "DHCP server (experimental!)",
"dhcp_description": "If your router does not provide DHCP settings, you can use AdGuard's own built-in DHCP server.",
"dhcp_enable": "Enable DHCP server",
"dhcp_disable": "Disable DHCP server",
"dhcp_not_found": "No active DHCP servers found on the network. It is safe to enable the built-in DHCP server.",
"dhcp_found": "Some active DHCP servers found on the network. It is not safe to enable the built-in DHCP server.",
"dhcp_leases": "DHCP leases",
"dhcp_leases_not_found": "No DHCP leases found",
"dhcp_config_saved": "Saved DHCP server config",
"form_error_required": "Required field",
"form_error_ip_format": "Invalid IPv4 format",
"form_error_positive": "Must be greater than 0",
"dhcp_form_gateway_input": "Gateway IP",
"dhcp_form_subnet_input": "Subnet mask",
"dhcp_form_range_title": "Range of IP addresses",
"dhcp_form_range_start": "Range start",
"dhcp_form_range_end": "Range end",
"dhcp_form_lease_title": "DHCP lease time (in seconds)",
"dhcp_form_lease_input": "Lease duration",
"dhcp_interface_select": "Select DHCP interface",
"dhcp_hardware_address": "Hardware address",
"dhcp_ip_addresses": "IP addresses",
"dhcp_table_hostname": "Hostname",
"dhcp_table_expires": "Expires",
"dhcp_warning": "If you want to enable the built-in DHCP server, make sure that there is no other active DHCP server. Otherwise, it can break the internet for connected devices!",
"back": "Back",
"dashboard": "Dashboard",
"settings": "Settings",
"filters": "Filters",
"query_log": "Query Log",
"faq": "FAQ",
"version": "version",
"address": "address",
"on": "ON",
"off": "OFF",
"copyright": "Copyright",
"homepage": "Homepage",
"report_an_issue": "Report an issue",
"enable_protection": "Enable protection",
"enabled_protection": "Enabled protection",
"disable_protection": "Disable protection",
"disabled_protection": "Disabled protection",
"refresh_statics": "Refresh statistics",
"dns_query": "DNS Queries",
"blocked_by": "Blocked by Filters",
"stats_malware_phishing": "Blocked malware\/phishing",
"stats_adult": "Blocked adult websites",
"stats_query_domain": "Top queried domains",
"for_last_24_hours": "for the last 24 hours",
"no_domains_found": "No domains found",
"requests_count": "Requests count",
"top_blocked_domains": "Top blocked domains",
"top_clients": "Top clients",
"no_clients_found": "No clients found",
"general_statistics": "General statistics",
"number_of_dns_query_24_hours": "A number of DNS quieries processed for the last 24 hours",
"number_of_dns_query_blocked_24_hours": "A number of DNS requests blocked by adblock filters and hosts blocklists",
"number_of_dns_query_blocked_24_hours_by_sec": "A number of DNS requests blocked by the AdGuard browsing security module",
"number_of_dns_query_blocked_24_hours_adult": "A number of adult websites blocked",
"enforced_save_search": "Enforced safe search",
"number_of_dns_query_to_safe_search": "A number of DNS requests to search engines for which Safe Search was enforced",
"average_processing_time": "Average processing time",
"average_processing_time_hint": "Average time in milliseconds on processing a DNS request",
"block_domain_use_filters_and_hosts": "Block domains using filters and hosts files",
"filters_block_toggle_hint": "You can setup blocking rules in the <a href='#filters'>Filters<\/a> settings.",
"use_adguard_browsing_sec": "Use AdGuard browsing security web service",
"use_adguard_browsing_sec_hint": "AdGuard Home will check if domain is blacklisted by the browsing security web service. It will use privacy-friendly lookup API to perform the check: only a short prefix of the domain name SHA256 hash is sent to the server.",
"use_adguard_parental": "Use AdGuard parental control web service",
"use_adguard_parental_hint": "AdGuard Home will check if domain contains adult materials. It uses the same privacy-friendly API as the browsing security web service.",
"enforce_safe_search": "Enforce safe search",
"enforce_save_search_hint": "AdGuard Home can enforce safe search in the following search engines: Google, Youtube, Bing, and Yandex.",
"no_servers_specified": "No servers specified",
"no_settings": "No settings",
"general_settings": "General settings",
"upstream_dns": "Upstream DNS servers",
"upstream_dns_hint": "If you keep this field empty, AdGuard Home will use <a href='https:\/\/1.1.1.1\/' target='_blank'>Cloudflare DNS<\/a> as an upstream. Use tls:\/\/ prefix for DNS over TLS servers.",
"test_upstream_btn": "Test upstreams",
"apply_btn": "Apply",
"disabled_filtering_toast": "Disabled filtering",
"enabled_filtering_toast": "Enabled filtering",
"disabled_safe_browsing_toast": "Disabled safebrowsing",
"enabled_safe_browsing_toast": "Enabled safebrowsing",
"disabled_parental_toast": "Disabled parental control",
"enabled_parental_toast": "Enabled parental control",
"disabled_safe_search_toast": "Disabled safe search",
"enabled_save_search_toast": "Enabled safe search",
"enabled_table_header": "Enabled",
"name_table_header": "Name",
"filter_url_table_header": "Filter URL",
"rules_count_table_header": "Rules count",
"last_time_updated_table_header": "Last time updated",
"actions_table_header": "Actions",
"delete_table_action": "Delete",
"filters_and_hosts": "Filters and hosts blocklists",
"filters_and_hosts_hint": "AdGuard Home understands basic adblock rules and hosts files syntax.",
"no_filters_added": "No filters added",
"add_filter_btn": "Add filter",
"cancel_btn": "Cancel",
"enter_name_hint": "Enter name",
"enter_url_hint": "Enter URL",
"check_updates_btn": "Check updates",
"new_filter_btn": "New filter subscription",
"enter_valid_filter_url": "Enter a valid URL to a filter subscription or a hosts file.",
"custom_filter_rules": "Custom filtering rules",
"custom_filter_rules_hint": "Enter one rule on a line. You can use either adblock rules or hosts files syntax.",
"examples_title": "Examples",
"example_meaning_filter_block": "block access to the example.org domain and all its subdomains",
"example_meaning_filter_whitelist": "unblock access to the example.org domain and all its subdomains",
"example_meaning_host_block": "AdGuard Home will now return 127.0.0.1 address for the example.org domain (but not its subdomains).",
"example_comment": "! Here goes a comment",
"example_comment_meaning": "just a comment",
"example_comment_hash": "# Also a comment",
"example_regex_meaning": "block access to the domains matching the specified regular expression",
"example_upstream_regular": "regular DNS (over UDP)",
"example_upstream_dot": "encrypted <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_TLS' target='_blank'>DNS-over-TLS<\/a>",
"example_upstream_doh": "encrypted <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>DNS-over-HTTPS<\/a>",
"example_upstream_sdns": "you can use <a href='https:\/\/dnscrypt.info\/stamps\/' target='_blank'>DNS Stamps<\/a> for <a href='https:\/\/dnscrypt.info\/' target='_blank'>DNSCrypt<\/a> or <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>DNS-over-HTTPS<\/a> resolvers",
"example_upstream_tcp": "regular DNS (over TCP)",
"all_filters_up_to_date_toast": "All filters are already up-to-date",
"updated_upstream_dns_toast": "Updated the upstream DNS servers",
"dns_test_ok_toast": "Specified DNS servers are working correctly",
"dns_test_not_ok_toast": "Server \"{{key}}\": could not be used, please check that you've written it correctly",
"unblock_btn": "Unblock",
"block_btn": "Block",
"time_table_header": "Time",
"domain_name_table_header": "Domain name",
"type_table_header": "Type",
"response_table_header": "Response",
"client_table_header": "Client",
"empty_response_status": "Empty",
"show_all_filter_type": "Show all",
"show_filtered_type": "Show filtered",
"no_logs_found": "No logs found",
"disabled_log_btn": "Disable log",
"download_log_file_btn": "Download log file",
"refresh_btn": "Refresh",
"enabled_log_btn": "Enable log",
"last_dns_queries": "Last 5000 DNS queries",
"previous_btn": "Previous",
"next_btn": "Next",
"loading_table_status": "Loading...",
"page_table_footer_text": "Page",
"of_table_footer_text": "of",
"rows_table_footer_text": "rows",
"updated_custom_filtering_toast": "Updated the custom filtering rules",
"rule_removed_from_custom_filtering_toast": "Rule removed from the custom filtering rules",
"rule_added_to_custom_filtering_toast": "Rule added to the custom filtering rules",
"query_log_disabled_toast": "Query log disabled",
"query_log_enabled_toast": "Query log enabled",
"source_label": "Source",
"found_in_known_domain_db": "Found in the known domains database.",
"category_label": "Category",
"rule_label": "Rule",
"filter_label": "Filter",
"unknown_filter": "Unknown filter {{filterId}}",
"install_welcome_title": "Welcome to AdGuard Home!",
"install_welcome_desc": "AdGuard Home is a network-wide ad-and-tracker blocking DNS server. Its purpose is to let you control your entire network and all your devices, and it does not require using a client-side program.",
"install_settings_title": "Admin Web Interface",
"install_settings_listen": "Listen interface",
"install_settings_port": "Port",
"install_settings_interface_link": "Your AdGuard Home admin web interface will be available on the following addresses:",
"form_error_port": "Enter valid port value",
"install_settings_dns": "DNS server",
"install_settings_dns_desc": "You will need to configure your devices or router to use the DNS server on the following addresses:",
"install_settings_all_interfaces": "All interfaces",
"install_auth_title": "Authentication",
"install_auth_desc": "It is highly recommended to configure password authentication to your AdGuard Home admin web interface. Even if it is accessible only in your local network, it is still important to have it protected from unrestricted access.",
"install_auth_username": "Username",
"install_auth_password": "Password",
"install_auth_confirm": "Confirm password",
"install_auth_username_enter": "Enter username",
"install_auth_password_enter": "Enter password",
"install_step": "Step",
"install_devices_title": "Configure your devices",
"install_devices_desc": "In order for AdGuard Home to start working, you need to configure your devices to use it.",
"install_submit_title": "Congratulations!",
"install_submit_desc": "The setup procedure is finished and you are ready to start using AdGuard Home.",
"install_devices_router": "Router",
"install_devices_router_desc": "This setup will automatically cover all the devices connected to your home router and you will not need to configure each of them manually.",
"install_devices_address": "AdGuard Home DNS server is listening to the following addresses",
"install_devices_router_list_1": "Open the preferences for your router. Usually, you can access it from your browser via a URL (like http:\/\/192.168.0.1\/ or http:\/\/192.168.1.1\/). You may be asked to enter the password. If you don't remember it, you can often reset the password by pressing a button on the router itself. Some routers require a specific application, which in that case should be already installed on your computer\/phone.",
"install_devices_router_list_2": "Find the DHCP\/DNS settings. Look for the DNS letters next to a field which allows two or three sets of numbers, each broken into four groups of one to three digits.",
"install_devices_router_list_3": "Enter your AdGuard Home server addresses there.",
"install_devices_windows_list_1": "Open Control Panel through Start menu or Windows search.",
"install_devices_windows_list_2": "Go to Network and Internet category and then to Network and Sharing Center.",
"install_devices_windows_list_3": "On the left side of the screen find Change adapter settings and click on it.",
"install_devices_windows_list_4": "Select your active connection, right-click on it and choose Properties.",
"install_devices_windows_list_5": "Find Internet Protocol Version 4 (TCP\/IP) in the list, select it and then click on Properties again.",
"install_devices_windows_list_6": "Choose Use the following DNS server addresses and enter your AdGuard Home server addresses.",
"install_devices_macos_list_1": "Click on Apple icon and go to System Preferences.",
"install_devices_macos_list_2": "Click on Network.",
"install_devices_macos_list_3": "Select the first connection in your list and click Advanced.",
"install_devices_macos_list_4": "Select the DNS tab and enter your AdGuard Home server addresses.",
"install_devices_android_list_1": "From the Android Menu home screen, tap Settings.",
"install_devices_android_list_2": "Tap Wi-Fi on the menu. The screen listing all of the available networks will be shown (it is impossible to set custom DNS for mobile connection).",
"install_devices_android_list_3": "Long press the network you're connected to, and tap Modify Network.",
"install_devices_android_list_4": "On some devices, you may need to check the box for Advanced to see further settings. To adjust your Android DNS settings, you will need to switch the IP settings from DHCP to Static.",
"install_devices_android_list_5": "Change set DNS 1 and DNS 2 values to your AdGuard Home server addresses.",
"install_devices_ios_list_1": "From the home screen, tap Settings.",
"install_devices_ios_list_2": "Choose Wi-Fi in the left menu (it is impossible to configure DNS for mobile networks).",
"install_devices_ios_list_3": "Tap on the name of the currently active network.",
"install_devices_ios_list_4": "In the DNS field enter your AdGuard Home server addresses.",
"get_started": "Get Started",
"next": "Next",
"open_dashboard": "Open Dashboard",
"install_saved": "Saved successfully",
"encryption_title": "Encryption",
"encryption_desc": "Encryption (HTTPS/TLS) support for both DNS and admin web interface",
"encryption_config_saved": "Encryption config saved",
"encryption_server": "Server name",
"encryption_server_enter": "Enter your domain name",
"encryption_server_desc": "In order to use HTTPS, you need yo enter the server name that matches your SSL certificate.",
"encryption_redirect": "Redirect to HTTPS automatically",
"encryption_redirect_desc": "If checked, AdGuard Home will automatically redirect you from HTTP to HTTPS addresses.",
"encryption_https": "HTTPS port",
"encryption_https_desc": "If HTTPS port is configured, AdGuard Home admin interface will be accessible via HTTPS, and it will also provide DNS-over-HTTPS on '\/dns-query' location.",
"encryption_dot": "DNS-over-TLS port",
"encryption_dot_desc": "If this port is configured, AdGuard Home will run a DNS-over-TLS server on this port.",
"encryption_certificates": "Certificates",
"encryption_certificates_desc": "In order to use encryption, you need to provide a valid SSL certificates chain for your domain. You can get a free certificate on <0>{{link}}</0> or you can buy it from one of the trusted Certificate Authorities.",
"encryption_certificates_input": "Copy/paste your PEM-encoded cerificates here.",
"encryption_status": "Status",
"encryption_expire": "Expires",
"encryption_key": "Private key",
"encryption_key_input": "Copy/paste your PEM-encoded private key for your cerficate here.",
"encryption_enable": "Enable Encryption (HTTPS, DNS-over-HTTPS, and DNS-over-TLS)",
"encryption_enable_desc": "If encryption is enabled, AdGuard Home admin interface will work over HTTPS, and the DNS server will listen for requests over DNS-over-HTTPS and DNS-over-TLS.",
"encryption_chain_valid": "Certificate chain is valid",
"encryption_chain_invalid": "Certificate chain is invalid",
"encryption_key_valid": "This is a valid {{type}} private key",
"encryption_key_invalid": "This is an invalid {{type}} private key",
"encryption_subject": "Subject",
"encryption_issuer": "Issuer",
"encryption_hostnames": "Hostnames",
"encryption_reset": "Are you sure you want to reset encryption settings?",
"topline_expiring_certificate": "Your SSL certificate is about to expire. Update <0>Encryption settings</0>.",
"topline_expired_certificate": "Your SSL certificate is expired. Update <0>Encryption settings</0>.",
"form_error_port_range": "Enter port value in the range of 80-65535",
"form_error_port_unsafe": "This is an unsafe port",
"form_error_equal": "Shouldn't be equal",
"form_error_password": "Password mismatched",
"reset_settings": "Reset settings",
"update_announcement": "AdGuard Home {{version}} is now available! <0>Click here</0> for more info."
}

View File

@@ -0,0 +1,159 @@
{
"check_dhcp_servers": "Compruebe si hay servidores DHCP",
"save_config": "Guardar config",
"enabled_dhcp": "Servidor DHCP habilitado",
"disabled_dhcp": "Servidor DHCP deshabilitado",
"dhcp_title": "Servidor DHCP (experimental)",
"dhcp_description": "Si su enrutador no proporciona la configuraci\u00f3n DHCP, puede utilizar el propio servidor DHCP incorporado de AdGuard.",
"dhcp_enable": "Habilitar servidor DHCP",
"dhcp_disable": "Deshabilitar el servidor DHCP",
"dhcp_not_found": "No se han encontrado servidores DHCP activos en la red. Es seguro habilitar el servidor DHCP incorporado.",
"dhcp_found": "Se encontraron servidores DHCP activos encontrados en la red. No es seguro habilitar el servidor DHCP incorporado.",
"dhcp_leases": "concesi\u00f3nes DHCP",
"dhcp_leases_not_found": "No se encontraron concesi\u00f3nes DHCP",
"dhcp_config_saved": "Configuraci\u00f3n del servidor DHCP guardada",
"form_error_required": "Campo obligatorio",
"form_error_ip_format": "Formato IPv4 no v\u00e1lido",
"form_error_positive": "Debe ser mayor que 0",
"dhcp_form_gateway_input": "IP de acceso",
"dhcp_form_subnet_input": "M\u00e1scara de subred",
"dhcp_form_range_title": "Rango de direcciones IP",
"dhcp_form_range_start": "Inicio de rango",
"dhcp_form_range_end": "Final de rango",
"dhcp_form_lease_title": "Tiempo de concesi\u00f3n DHCP (en segundos)",
"dhcp_form_lease_input": "duraci\u00f3n de la concesi\u00f3n",
"dhcp_interface_select": "Seleccione la interfaz DHCP",
"dhcp_hardware_address": "Direcci\u00f3n de hardware",
"dhcp_ip_addresses": "Direcciones IP",
"dhcp_table_hostname": "Hostname",
"dhcp_table_expires": "Expira",
"back": "Atr\u00e1s",
"dashboard": "Tablero de rendimiento",
"settings": "Ajustes",
"filters": "Filtros",
"query_log": "Log de consulta",
"faq": "FAQ",
"version": "versi\u00f3n",
"address": "direcci\u00f3n",
"on": "Activado",
"off": "Desactivado",
"copyright": "Derechos de autor",
"homepage": "P\u00e1gina de inicio",
"report_an_issue": "Reportar un error",
"enable_protection": "Activar la protecci\u00f3n",
"enabled_protection": "Protecci\u00f3n activada",
"disable_protection": "Desactivar protecci\u00f3n",
"disabled_protection": "Protecci\u00f3n desactivada",
"refresh_statics": "Restablecer estad\u00edsticas",
"dns_query": "Consultas DNS",
"blocked_by": "Bloqueado por filtros",
"stats_malware_phishing": "Malware\/phishing bloqueado",
"stats_adult": "Contenido para adultos bloqueado",
"stats_query_domain": "Dominios m\u00e1s solicitados",
"for_last_24_hours": "en las \u00faltimas 24 horas",
"no_domains_found": "Dominios no encontrados",
"requests_count": "N\u00famero de solicitudes",
"top_blocked_domains": "Dominios m\u00e1s bloqueados",
"top_clients": "Clientes m\u00e1s populares",
"no_clients_found": "No hay clientes",
"general_statistics": "Estad\u00edsticas generales",
"number_of_dns_query_24_hours": "Una serie de consultas DNS procesadas durante las \u00faltimas 24 horas",
"number_of_dns_query_blocked_24_hours": "El n\u00famero de solicitudes de DNS bloqueadas por los filtros de publicidad y listas de bloqueo de hosts",
"number_of_dns_query_blocked_24_hours_by_sec": "Un n\u00famero de solicitudes de DNS bloqueadas por el m\u00f3dulo de navegaci\u00f3n segura de AdGuard",
"number_of_dns_query_blocked_24_hours_adult": "Un n\u00famero de sitios para adultos bloqueados",
"enforced_save_search": "B\u00fasqueda segura forzada",
"number_of_dns_query_to_safe_search": "Una serie de solicitudes de DNS a los motores de b\u00fasqueda para los que se aplic\u00f3 la B\u00fasqueda Segura",
"average_processing_time": "Tiempo promedio de procesamiento",
"average_processing_time_hint": "Tiempo promedio en milisegundos al procesar una solicitud DNS",
"block_domain_use_filters_and_hosts": "Bloquear dominios usando filtros y archivos hosts",
"filters_block_toggle_hint": "Puede configurar las reglas de bloqueo en los ajustes <a href='#filters'>Filtros<\/a>.",
"use_adguard_browsing_sec": "Usar el servicio web de Seguridad de navegaci\u00f3n de AdGuard",
"use_adguard_browsing_sec_hint": "AdGuard Home comprobar\u00e1 si el dominio est\u00e1 en la lista negra del servicio web de seguridad de navegaci\u00f3n. Utilizar\u00e1 una API de b\u00fasqueda amigable con la privacidad para realizar la comprobaci\u00f3n: s\u00f3lo se env\u00eda al servidor un prefijo corto del hash del nombre de dominio SHA256.",
"use_adguard_parental": "Usar Control Parental de AdGuard ",
"use_adguard_parental_hint": "AdGuard Home comprobar\u00e1 si el dominio contiene materiales para adultos. Utiliza la misma API amigable con la privacidad que el servicio web de seguridad de navegaci\u00f3n.",
"enforce_safe_search": "Forzar b\u00fasqueda segura",
"enforce_save_search_hint": "AdGuard Home puede forzar la b\u00fasqueda segura en los siguientes motores de b\u00fasqueda: Google, Youtube, Bing y Yandex.",
"no_servers_specified": "No hay servidores especificados",
"no_settings": "No hay ajustes",
"general_settings": "Ajustes generales",
"upstream_dns": "Servidores DNS upstream",
"upstream_dns_hint": "Si mantiene este campo vac\u00edo, AdGuard Home utilizar\u00e1 <a href='https:\/\/1.1.1.1\/' target='_blank'>Cloudflare DNS<\/a> como upstream. Utilice el prefijo tls:\/\/ para DNS sobre servidores TLS.",
"test_upstream_btn": "Probar upstream",
"apply_btn": "Aplicar",
"disabled_filtering_toast": "Desactivar filtrado",
"enabled_filtering_toast": "Filtrado activado",
"disabled_safe_browsing_toast": "Navegaci\u00f3n segura desactivada",
"enabled_safe_browsing_toast": "Navegaci\u00f3n segura activada",
"disabled_parental_toast": "Control parental desactivado",
"enabled_parental_toast": "Control parental activado",
"disabled_safe_search_toast": "B\u00fasqueda segura desactivada",
"enabled_save_search_toast": "B\u00fasqueda segura activada",
"enabled_table_header": "Activado",
"name_table_header": "Nombre",
"filter_url_table_header": "Filtro URL",
"rules_count_table_header": "N\u00famero de reglas",
"last_time_updated_table_header": "\u00daltima actualizaci\u00f3n",
"actions_table_header": "Acciones",
"delete_table_action": "Eliminar",
"filters_and_hosts": "Filtros y listas de bloqueo de hosts",
"filters_and_hosts_hint": "AdGuard Home entiende reglas b\u00e1sicas de bloqueo y la sintaxis de los archivos de hosts.",
"no_filters_added": "No hay filtros agregados",
"add_filter_btn": "Agregar filtro",
"cancel_btn": "Cancelar",
"enter_name_hint": "Ingresar nombre",
"enter_url_hint": "Ingresar URL",
"check_updates_btn": "Revisar si hay actualizaciones",
"new_filter_btn": "Nueva suscripci\u00f3n de filtro",
"enter_valid_filter_url": "Ingrese una URL v\u00e1lida para suscribirse o un archivo de hosts.",
"custom_filter_rules": "Personalizar reglas del filtrado",
"custom_filter_rules_hint": "Introduzca una regla en una l\u00ednea. Puede utilizar reglas de bloqueo de anuncios o sintaxis de archivos de hosts.",
"examples_title": "Ejemplos",
"example_meaning_filter_block": "bloquear acceso al dominio ejemplo.org\ny a todos sus subdominios",
"example_meaning_filter_whitelist": "desbloquear el acceso al dominio ejemplo.org y a sus subdominios",
"example_meaning_host_block": "AdGuard Home regresar\u00e1 la direcci\u00f3n 127.0.0.1 para el dominio ejemplo.org (pero no para sus subdominios).",
"example_comment": "! Aqu\u00ed va un comentario",
"example_comment_meaning": "solo un comentario",
"example_comment_hash": "# Tambi\u00e9n un comentario",
"example_upstream_regular": "DNS regular (a trav\u00e9s de UDP)",
"example_upstream_dot": "encriptado <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_TLS' target='_blank'>DNS-a-trav\u00e9s-de-TLS<\/a>",
"example_upstream_doh": "encriptado <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>DNS-a-trav\u00e9s-de-TLS<\/a>",
"example_upstream_sdns": "puedes usar <a href='https:\/\/dnscrypt.info\/stamps\/' target='_blank'>DNS Stamps<\/a> para <a href='https:\/\/dnscrypt.info\/' target='_blank'>DNSCrypt<\/a> o <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>DNS-over-HTTPS<\/a> resolutores",
"example_upstream_tcp": "DNS regular (a trav\u00e9s de TCP)",
"all_filters_up_to_date_toast": "Todos los filtros son actualizados",
"updated_upstream_dns_toast": "Servidores DNS upstream actualizados",
"dns_test_ok_toast": "Servidores DNS especificados funcionan correctamente",
"dns_test_not_ok_toast": "Servidor \"{{key}}\": no puede ser usado, por favor, revise si lo ha escrito correctamente",
"unblock_btn": "Desbloquear",
"block_btn": "Bloquear",
"time_table_header": "Tiempo",
"domain_name_table_header": "Nombre de dominio",
"type_table_header": "Tipo",
"response_table_header": "Respuesta",
"client_table_header": "Cliente",
"empty_response_status": "Vac\u00edo",
"show_all_filter_type": "Mostrar todo",
"show_filtered_type": "Mostrar filtrados",
"no_logs_found": "No se han encontrado registros",
"disabled_log_btn": "Desactivar registro",
"download_log_file_btn": "Descargar el archivo de registro",
"refresh_btn": "Refrescar",
"enabled_log_btn": "Activar registro",
"last_dns_queries": "\u00daltimas 500 solicitudes de DNS",
"previous_btn": "Anterior",
"next_btn": "Siguiente",
"loading_table_status": "Cargando...",
"page_table_footer_text": "P\u00e1gina",
"of_table_footer_text": "de",
"rows_table_footer_text": "filas",
"updated_custom_filtering_toast": "Actualizadas las reglas de filtrado personalizadas",
"rule_removed_from_custom_filtering_toast": "Regla eliminada de las reglas de filtrado personalizadas",
"rule_added_to_custom_filtering_toast": "Regla a\u00f1adida a las reglas de filtrado personalizadas",
"query_log_disabled_toast": "Log de consulta desactivado",
"query_log_enabled_toast": "Log de consulta activado",
"source_label": "Fuente",
"found_in_known_domain_db": "Encontrado en la base de datos de dominios conocidos.",
"category_label": "Categor\u00eda",
"rule_label": "Regla",
"filter_label": "Filtro",
"unknown_filter": "Filtro desconocido {{filterId}}"
}

View File

@@ -0,0 +1,160 @@
{
"url_added_successfully": "Url ajout\u00e9e",
"check_dhcp_servers": "Rechercher les serveurs DHCP",
"save_config": "Sauvegarder la configuration",
"enabled_dhcp": "Serveur DHCP activ\u00e9",
"disabled_dhcp": "Serveur DHCP d\u00e9sactiv\u00e9",
"dhcp_title": "Serveur DHCP (experimental !)",
"dhcp_description": "Si votre routeur ne fonctionne pas avec les r\u00e9glages DHCP, vous pouvez utiliser le serveur DHCP par d\u00e9faut d'AdGuard.",
"dhcp_enable": "Activer le serveur DHCP",
"dhcp_disable": "D\u00e9sactiver le serveur DHCP",
"dhcp_not_found": "Aucun serveur DHCP actif trouv\u00e9 sur le r\u00e9seau. Vous pouvez activer le serveur DHCP int\u00e9gr\u00e9.",
"dhcp_found": "Il y a plusieurs serveurs DHCP actifs sur le r\u00e9seau. Ce n'est pas prudent d'activer le serveur DHCP int\u00e9gr\u00e9 en ce moment.",
"dhcp_leases": "Locations des serveurs DHCP",
"dhcp_leases_not_found": "Aucune location des serveurs DHCP trouv\u00e9e",
"dhcp_config_saved": "La configuration du serveur DHCP est sauvegard\u00e9e",
"form_error_required": "Champ requis",
"form_error_ip_format": "Format IPv4 invalide",
"form_error_positive": "Doit \u00eatre sup\u00e9rieur \u00e0 0\u001c",
"dhcp_form_gateway_input": "IP de la passerelle",
"dhcp_form_subnet_input": "Masque de sous-r\u00e9seau",
"dhcp_form_range_title": "Rang\u00e9e des adresses IP",
"dhcp_form_range_start": "D\u00e9but de la rang\u00e9e",
"dhcp_form_range_end": "Fin de la rang\u00e9e",
"dhcp_form_lease_title": "P\u00e9riode de location du serveur DHCP (secondes)",
"dhcp_form_lease_input": "Dur\u00e9e de la location",
"dhcp_interface_select": "S\u00e9lectionner l'interface du serveur DHCP",
"dhcp_hardware_address": "Adresse de la machine",
"dhcp_ip_addresses": "Adresses IP",
"dhcp_table_hostname": "Nom de machine",
"dhcp_table_expires": "Expire le",
"back": "Retour",
"dashboard": "Tableau de bord",
"settings": "Param\u00e8tres",
"filters": "Filtres",
"query_log": "Journal des requ\u00eates\u001c",
"faq": "FAQ",
"version": "version",
"address": "addresse",
"on": "Activ\u00e9",
"off": "\u00c9teint",
"copyright": "Copyright",
"homepage": "Page d'accueil",
"report_an_issue": "Signaler un probl\u00e8me",
"enable_protection": "Activer la protection",
"enabled_protection": "Protection activ\u00e9e",
"disable_protection": "D\u00e9sactiver la protection",
"disabled_protection": "Protection d\u00e9sactiv\u00e9e",
"refresh_statics": "Renouveler les statistiques",
"dns_query": "Requ\u00eates\u001c DNS",
"blocked_by": "Bloqu\u00e9 par Filtres",
"stats_malware_phishing": "Tentative de malware\/hamme\u00e7onnage bloqu\u00e9e",
"stats_adult": "Sites \u00e0 contenu adulte bloqu\u00e9s",
"stats_query_domain": "Domaines les plus recherch\u00e9s",
"for_last_24_hours": "pendant les derni\u00e8res 24 heures",
"no_domains_found": "Pas de domaines trouv\u00e9s",
"requests_count": "Nombre de requ\u00eates",
"top_blocked_domains": "Les domaines les plus fr\u00e9quemment bloqu\u00e9s",
"top_clients": "Meilleurs clients",
"no_clients_found": "Pas de clients trouv\u00e9s",
"general_statistics": "Statistiques g\u00e9n\u00e9rales",
"number_of_dns_query_24_hours": "Un nombre de requ\u00eates DNS quieries trait\u00e9es pendant les 24 heures derni\u00e8res",
"number_of_dns_query_blocked_24_hours": "Un nombre de requ\u00eates DNS bloqu\u00e9es par les filtres adblock et les listes de blocage des hosts",
"number_of_dns_query_blocked_24_hours_by_sec": "Un nombre de requ\u00eates DNS bloqu\u00e9es par le module S\u00e9curit\u00e9 de navigation d'AdGuard",
"number_of_dns_query_blocked_24_hours_adult": "Un nombre de sites \u00e0 contenu adulte bloqu\u00e9s",
"enforced_save_search": "Recherche s\u00e9curis\u00e9e renforc\u00e9e",
"number_of_dns_query_to_safe_search": "Un nombre de requ\u00eates DNS faites avec la Recherche securis\u00e9e",
"average_processing_time": "Temps moyen de traitement",
"average_processing_time_hint": "Temps moyen (en millisecondes) de traitement d'une requ\u00eate DNS",
"block_domain_use_filters_and_hosts": "Bloquez les domaines \u00e0 l'aide des filtres et fichiers hosts",
"filters_block_toggle_hint": "Vous pouvez configurer les r\u00e8gles de filtrage dans les param\u00e8tres des <a href='#filters'>Filtres<\/a>.",
"use_adguard_browsing_sec": "Utilisez le service S\u00e9curit\u00e9 de navigation d'AdGuard",
"use_adguard_browsing_sec_hint": "AdGuard Home va v\u00e9rifier si le domaine est dans la liste noire du service de s\u00e9curit\u00e9 de navigation. Pour cela il va utiliser un lookup API discret : le pr\u00e9fixe court du hash du nom de domaine SHA256 sera envoy\u00e9 au serveur.",
"use_adguard_parental": "Utiliser le contr\u00f4le parental d'AdGuard",
"use_adguard_parental_hint": "AdGuard Home va v\u00e9rifier s'il y a du contenu pour adultes sur le domaine. Ce sera fait par aide du m\u00eame API discret que celui utilis\u00e9 par le service de S\u00e9curit\u00e9 de navigation.",
"enforce_safe_search": "Renforcer la recherche s\u00e9curis\u00e9e",
"enforce_save_search_hint": "AdGuard Home peut renforcer la Recherche s\u00e9curis\u00e9e dans les moteurs de recherche suivants : Google, Youtube, Bing et Yandex.",
"no_servers_specified": "Pas de serveurs sp\u00e9cifi\u00e9s",
"no_settings": "Pas de param\u00e8tres",
"general_settings": "Param\u00e8tres g\u00e9n\u00e9raux",
"upstream_dns": "Serveurs DNS upstream",
"upstream_dns_hint": "Si vous laissez ce champ vide, AdGuard Home va utiliser <a href='https:\/\/1.1.1.1\/' target='_blank'>Cloudflare DNS<\/a> somme upstream. Utilisez le pr\u00e9fixe tls:\/\/ pour DNS via les serveurs TLS .",
"test_upstream_btn": "Tester les upstreams",
"apply_btn": "Appliquer",
"disabled_filtering_toast": "Filtrage d\u00e9sactiv\u00e9",
"enabled_filtering_toast": "Filtrage activ\u00e9",
"disabled_safe_browsing_toast": "Surfing s\u00e9curis\u00e9 d\u00e9sactiv\u00e9",
"enabled_safe_browsing_toast": "Surfing s\u00e9curis\u00e9 activ\u00e9",
"disabled_parental_toast": "Contr\u00f4le parental d\u00e9sactiv\u00e9",
"enabled_parental_toast": "Contr\u00f4le parental activ\u00e9",
"disabled_safe_search_toast": "Recherche s\u00e9curis\u00e9e d\u00e9sactiv\u00e9e",
"enabled_save_search_toast": "Recherche s\u00e9curis\u00e9e activ\u00e9e",
"enabled_table_header": "Activ\u00e9",
"name_table_header": "Nom",
"filter_url_table_header": "URL du filtre",
"rules_count_table_header": "Nombre des r\u00e8gles",
"last_time_updated_table_header": "Derni\u00e8re mise \u00e0 jour",
"actions_table_header": "Actions",
"delete_table_action": "Supprimer",
"filters_and_hosts": "Listes de blocage des filtres et hosts",
"filters_and_hosts_hint": "AdGuard Home comprend les r\u00e8gles basiques de blocage ainsi que la syntaxe des fichiers hosts.",
"no_filters_added": "Aucun filtre ajout\u00e9",
"add_filter_btn": "Ajouter filtre",
"cancel_btn": "Annuler",
"enter_name_hint": "Saisir nom",
"enter_url_hint": "Saisir URL",
"check_updates_btn": "V\u00e9rifier les mises \u00e0 jour",
"new_filter_btn": "Abonnement \u00e0 un nouveau filtre",
"enter_valid_filter_url": "Saisir un URL valide pour s'abonner au filtre ou \u00e0 un fichier host.",
"custom_filter_rules": "R\u00e8gles de filtrage d'utilisateur",
"custom_filter_rules_hint": "Saisissez la r\u00e8gle en une ligne. C'est possible d'utiliser les r\u00e8gles de blocage ou la syntaxe des fichiers hosts.",
"examples_title": "Exemples",
"example_meaning_filter_block": "bloquer l'acc\u00e9s au domaine exemple.org et \u00e0 tous ses sous-domaines",
"example_meaning_filter_whitelist": "d\u00e9bloquer l'acc\u00e9s au domaine exemple.org et \u00e0 tous ses sous-domaines",
"example_meaning_host_block": "AdGuard Home va retourner l'adresse 127.0.0.1 au domaine example.org (mais pas aux sous-domaines).",
"example_comment": "! Voici comment ajouter une d\u00e9scription",
"example_comment_meaning": "commentaire",
"example_comment_hash": "# Et comme \u00e7a aussi on peut laisser des commentaires",
"example_upstream_regular": "DNS classique (au-dessus de UDP)",
"example_upstream_dot": "<a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_TLS' target='_blank'>DNS-au-dessus-de-TLS<\/a> chiffr\u00e9",
"example_upstream_doh": "<a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>DNS-au-dessus-de-HTTPS<\/a> chiffr\u00e9",
"example_upstream_sdns": "vous pouvez utiliser <a href='https:\/\/dnscrypt.info\/stamps\/' target='_blank'>DNS Stamps<\/a> pour <a href='https:\/\/dnscrypt.info\/' target='_blank'>DNSCrypt<\/a> ou les resolveurs <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>DNS-au-dessus-de-HTTPS<\/a>",
"example_upstream_tcp": "DNS classique (au-dessus de TCP)",
"all_filters_up_to_date_toast": "Tous les filtres sont mis \u00e0 jour",
"updated_upstream_dns_toast": "Les serveurs DNS upstream sont mis \u00e0 jour",
"dns_test_ok_toast": "Les serveurs DNS sp\u00e9cifi\u00e9s fonctionnent de mani\u00e8re incorrecte",
"dns_test_not_ok_toast": "Impossible d'utiliser le serveur \"{{key}}\": veuillez v\u00e9rifier si le nom saisi est bien correct",
"unblock_btn": "D\u00e9bloquer",
"block_btn": "Bloquer",
"time_table_header": "Temps",
"domain_name_table_header": "Nom de domaine",
"type_table_header": "Type",
"response_table_header": "R\u00e9ponse",
"client_table_header": "Client",
"empty_response_status": "Vide",
"show_all_filter_type": "Montrer tout",
"show_filtered_type": "Montrer les sites filtr\u00e9s",
"no_logs_found": "Aucun journal trouv\u00e9",
"disabled_log_btn": "D\u00e9sactiver le journal",
"download_log_file_btn": "T\u00e9l\u00e9charger le fichier de journal",
"refresh_btn": "Actualiser",
"enabled_log_btn": "Activer le journal",
"last_dns_queries": "5000 derni\u00e8res requ\u00eates DNS",
"previous_btn": "Pr\u00e9c\u00e9dent",
"next_btn": "Suivant",
"loading_table_status": "Chargement en cours ...",
"page_table_footer_text": "Page",
"of_table_footer_text": "de",
"rows_table_footer_text": "lignes",
"updated_custom_filtering_toast": "R\u00e8gles de filtrage d'utilisateur mises \u00e0 jour",
"rule_removed_from_custom_filtering_toast": "R\u00e8gle retir\u00e9e des r\u00e8gles d'utilisateur",
"rule_added_to_custom_filtering_toast": "R\u00e8gle ajout\u00e9e aux r\u00e8gles d'utilisateur",
"query_log_disabled_toast": "Journal de requ\u00eates d\u00e9sactiv\u00e9",
"query_log_enabled_toast": "Journal de requ\u00eates activ\u00e9",
"source_label": "Source",
"found_in_known_domain_db": "Trouv\u00e9 dans la base de donn\u00e9es des domaines connus",
"category_label": "Cat\u00e9gorie",
"rule_label": "R\u00e8gle",
"filter_label": "Filtre",
"unknown_filter": "Filtre inconnu {{filterId}}"
}

View File

@@ -0,0 +1,159 @@
{
"check_dhcp_servers": "DHCP\u30b5\u30fc\u30d0\u3092\u30c1\u30a7\u30c3\u30af\u3059\u308b",
"save_config": "\u8a2d\u5b9a\u3092\u4fdd\u5b58\u3059\u308b",
"enabled_dhcp": "DHCP\u30b5\u30fc\u30d0\u3092\u6709\u52b9\u306b\u3057\u307e\u3057\u305f",
"disabled_dhcp": "DHCP\u30b5\u30fc\u30d0\u3092\u7121\u52b9\u306b\u3057\u307e\u3057\u305f",
"dhcp_title": "DHCP\u30b5\u30fc\u30d0\uff08\u5b9f\u9a13\u7684\uff01\uff09",
"dhcp_description": "\u3042\u306a\u305f\u306e\u30eb\u30fc\u30bf\u304cDHCP\u306e\u8a2d\u5b9a\u3092\u63d0\u4f9b\u3057\u3066\u3044\u306a\u3044\u306e\u306a\u3089\u3001AdGuard\u306b\u5185\u8535\u3055\u308c\u3066\u3044\u308bDHCP\u30b5\u30fc\u30d0\u3092\u5229\u7528\u3067\u304d\u307e\u3059\u3002",
"dhcp_enable": "DHCP\u30b5\u30fc\u30d0\u3092\u6709\u52b9\u306b\u3059\u308b",
"dhcp_disable": "DHCP\u30b5\u30fc\u30d0\u3092\u7121\u52b9\u306b\u3059\u308b",
"dhcp_not_found": "\u30cd\u30c3\u30c8\u30ef\u30fc\u30af\u5185\u306b\u52d5\u4f5c\u3057\u3066\u3044\u308bDHCP\u30b5\u30fc\u30d0\u306f\u3042\u308a\u307e\u305b\u3093\u3002\u5185\u8535\u3055\u308c\u3066\u3044\u308bDHCP\u30b5\u30fc\u30d0\u3092\u6709\u52b9\u306b\u3057\u3066\u3082\u5b89\u5168\u3067\u3059\u3002",
"dhcp_found": "\u30cd\u30c3\u30c8\u30ef\u30fc\u30af\u5185\u306b\u52d5\u4f5c\u3057\u3066\u3044\u308bDHCP\u30b5\u30fc\u30d0\u3092\u898b\u3064\u3051\u307e\u3057\u305f\u3002\u5185\u81d3\u3055\u308c\u3066\u3044\u308bDHCP\u30b5\u30fc\u30d0\u3092\u6709\u52b9\u306b\u3059\u308b\u306b\u306f\u5b89\u5168\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002",
"dhcp_leases": "DHCP\u5272\u5f53",
"dhcp_leases_not_found": "DHCP\u5272\u5f53\u306f\u3042\u308a\u307e\u305b\u3093",
"dhcp_config_saved": "DHCP\u30b5\u30fc\u30d0\u306e\u8a2d\u5b9a\u3092\u4fdd\u5b58\u3057\u307e\u3057\u305f",
"form_error_required": "\u5fc5\u9808\u9805\u76ee\u3067\u3059",
"form_error_ip_format": "IPv4\u30d5\u30a9\u30fc\u30de\u30c3\u30c8\u3067\u306f\u3042\u308a\u307e\u305b\u3093",
"form_error_positive": "0\u3088\u308a\u5927\u304d\u3044\u5fc5\u8981\u304c\u3042\u308a\u307e\u3059",
"dhcp_form_gateway_input": "\u30b2\u30fc\u30c8\u30a6\u30a7\u30a4IP",
"dhcp_form_subnet_input": "\u30b5\u30d6\u30cd\u30c3\u30c8\u30de\u30b9\u30af",
"dhcp_form_range_title": "IP\u30a2\u30c9\u30ec\u30b9\u306e\u7bc4\u56f2",
"dhcp_form_range_start": "\u7bc4\u56f2\u306e\u958b\u59cb",
"dhcp_form_range_end": "\u7bc4\u56f2\u306e\u7d42\u4e86",
"dhcp_form_lease_title": "DHCP\u5272\u5f53\u6642\u9593\uff08\u79d2\u5358\u4f4d\uff09",
"dhcp_form_lease_input": "\u5272\u5f53\u671f\u9593",
"dhcp_interface_select": "DHCP\u30a4\u30f3\u30bf\u30d5\u30a7\u30fc\u30b9\u306e\u9078\u629e",
"dhcp_hardware_address": "MAC\u30a2\u30c9\u30ec\u30b9",
"dhcp_ip_addresses": "IP\u30a2\u30c9\u30ec\u30b9",
"dhcp_table_hostname": "\u30db\u30b9\u30c8\u540d",
"dhcp_table_expires": "\u6709\u52b9\u671f\u9650",
"back": "\u623b\u308b",
"dashboard": "\u30c0\u30c3\u30b7\u30e5\u30dc\u30fc\u30c9",
"settings": "\u8a2d\u5b9a",
"filters": "\u30d5\u30a3\u30eb\u30bf",
"query_log": "\u30af\u30a8\u30ea\u30fb\u30ed\u30b0",
"faq": "\u3088\u304f\u3042\u308b\u8cea\u554f",
"version": "\u30d0\u30fc\u30b8\u30e7\u30f3",
"address": "\u30a2\u30c9\u30ec\u30b9",
"on": "\u30aa\u30f3",
"off": "\u30aa\u30d5",
"copyright": "\u8457\u4f5c\u6a29",
"homepage": "\u30db\u30fc\u30e0\u30da\u30fc\u30b8",
"report_an_issue": "\u554f\u984c\u3092\u5831\u544a\u3059\u308b",
"enable_protection": "\u4fdd\u8b77\u3092\u6709\u52b9\u306b\u3059\u308b",
"enabled_protection": "\u4fdd\u8b77\u3092\u6709\u52b9\u306b\u3057\u307e\u3057\u305f",
"disable_protection": "\u4fdd\u8b77\u3092\u7121\u52b9\u306b\u3059\u308b",
"disabled_protection": "\u4fdd\u8b77\u3092\u7121\u52b9\u306b\u3057\u307e\u3057\u305f",
"refresh_statics": "\u7d71\u8a08\u30c7\u30fc\u30bf\u3092\u6700\u65b0\u306b\u3059\u308b",
"dns_query": "DNS\u30af\u30a8\u30ea",
"blocked_by": "\u30d5\u30a3\u30eb\u30bf\u306b\u30d6\u30ed\u30c3\u30af\u3055\u308c\u305fDNS\u30af\u30a8\u30ea",
"stats_malware_phishing": "\u30d6\u30ed\u30c3\u30af\u3055\u308c\u305f\u30de\u30eb\u30a6\u30a7\u30a2\uff0f\u30d5\u30a3\u30c3\u30b7\u30f3\u30b0",
"stats_adult": "\u30d6\u30ed\u30c3\u30af\u3055\u308c\u305f\u30a2\u30c0\u30eb\u30c8\u30a6\u30a7\u30d6\u30b5\u30a4\u30c8",
"stats_query_domain": "\u6700\u3082\u554f\u5408\u305b\u3055\u308c\u305f\u30c9\u30e1\u30a4\u30f3",
"for_last_24_hours": "\u904e\u53bb24\u6642\u9593\u4ee5\u5185",
"no_domains_found": "\u30c9\u30e1\u30a4\u30f3\u60c5\u5831\u306f\u3042\u308a\u307e\u305b\u3093",
"requests_count": "\u30ea\u30af\u30a8\u30b9\u30c8\u6570",
"top_blocked_domains": "\u6700\u3082\u30d6\u30ed\u30c3\u30af\u3055\u308c\u305f\u30c9\u30e1\u30a4\u30f3",
"top_clients": "\u30c8\u30c3\u30d7\u30af\u30e9\u30a4\u30a2\u30f3\u30c8",
"no_clients_found": "\u30af\u30e9\u30a4\u30a2\u30f3\u30c8\u60c5\u5831\u306f\u3042\u308a\u307e\u305b\u3093",
"general_statistics": "\u5168\u822c\u7684\u306a\u7d71\u8a08",
"number_of_dns_query_24_hours": "\u904e\u53bb24\u6642\u9593\u306b\u51e6\u7406\u3055\u308c\u305fDNS\u30af\u30a8\u30ea\u306e\u6570",
"number_of_dns_query_blocked_24_hours": "\u5e83\u544a\u30d6\u30ed\u30c3\u30af\u30d5\u30a3\u30eb\u30bf\u3068hosts\u30d6\u30ed\u30c3\u30af\u30ea\u30b9\u30c8\u306b\u3088\u3063\u3066\u30d6\u30ed\u30c3\u30af\u3055\u308c\u305fDNS\u30ea\u30af\u30a8\u30b9\u30c8\u306e\u6570",
"number_of_dns_query_blocked_24_hours_by_sec": "AdGuard\u30d6\u30e9\u30a6\u30b8\u30f3\u30b0\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u30e2\u30b8\u30e5\u30fc\u30eb\u306b\u3088\u3063\u3066\u30d6\u30ed\u30c3\u30af\u3055\u308c\u305fDNS\u30ea\u30af\u30a8\u30b9\u30c8\u306e\u6570",
"number_of_dns_query_blocked_24_hours_adult": "\u30d6\u30ed\u30c3\u30af\u3055\u308c\u305f\u30a2\u30c0\u30eb\u30c8\u30a6\u30a7\u30d6\u30b5\u30a4\u30c8\u306e\u6570",
"enforced_save_search": "\u5f37\u5236\u3055\u308c\u305f\u30bb\u30fc\u30d5\u30b5\u30fc\u30c1",
"number_of_dns_query_to_safe_search": "\u30bb\u30fc\u30d5\u30b5\u30fc\u30c1\u304c\u5f37\u5236\u3055\u308c\u305f\u691c\u7d22\u30a8\u30f3\u30b8\u30f3\u306b\u5bfe\u3059\u308bDNS\u30ea\u30af\u30a8\u30b9\u30c8\u306e\u6570",
"average_processing_time": "\u5e73\u5747\u51e6\u7406\u6642\u9593",
"average_processing_time_hint": "DNS\u30ea\u30af\u30a8\u30b9\u30c8\u306e\u51e6\u7406\u306b\u304b\u304b\u308b\u5e73\u5747\u6642\u9593\uff08\u30df\u30ea\u79d2\u5358\u4f4d\uff09",
"block_domain_use_filters_and_hosts": "\u30d5\u30a3\u30eb\u30bf\u3068hosts\u30d5\u30a1\u30a4\u30eb\u3092\u4f7f\u7528\u3057\u3066\u30c9\u30e1\u30a4\u30f3\u3092\u30d6\u30ed\u30c3\u30af\u3059\u308b",
"filters_block_toggle_hint": "<a href='#filters'>\u30d5\u30a3\u30eb\u30bf<\/a>\u306e\u8a2d\u5b9a\u3067\u30d6\u30ed\u30c3\u30af\u3059\u308b\u30eb\u30fc\u30eb\u3092\u8a2d\u5b9a\u3059\u308b\u3053\u3068\u304c\u3067\u304d\u307e\u3059\u3002",
"use_adguard_browsing_sec": "AdGuard\u30d6\u30e9\u30a6\u30b8\u30f3\u30b0\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u30fb\u30a6\u30a7\u30d6\u30b5\u30fc\u30d3\u30b9\u3092\u4f7f\u7528\u3059\u308b",
"use_adguard_browsing_sec_hint": "AdGuard Home\u306f\u3001\u30d6\u30e9\u30a6\u30b8\u30f3\u30b0\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u30fb\u30a6\u30a7\u30d6\u30b5\u30fc\u30d3\u30b9\u306b\u3088\u3063\u3066\u30c9\u30e1\u30a4\u30f3\u304c\u30d6\u30e9\u30c3\u30af\u30ea\u30b9\u30c8\u306b\u767b\u9332\u3055\u308c\u3066\u3044\u308b\u304b\u3069\u3046\u304b\u3092\u78ba\u8a8d\u3057\u307e\u3059\u3002 \u3053\u308c\u306f\u30d7\u30e9\u30a4\u30d0\u30b7\u30fc\u3092\u8003\u616e\u3057\u305fAPI\u3092\u4f7f\u7528\u3057\u3066\u30c1\u30a7\u30c3\u30af\u3092\u5b9f\u884c\u3057\u307e\u3059\u3002\u30c9\u30e1\u30a4\u30f3\u540dSHA256\u30cf\u30c3\u30b7\u30e5\u306e\u77ed\u3044\u30d7\u30ec\u30d5\u30a3\u30c3\u30af\u30b9\u306e\u307f\u304c\u30b5\u30fc\u30d0\u306b\u9001\u4fe1\u3055\u308c\u307e\u3059\u3002",
"use_adguard_parental": "AdGuard\u30da\u30a2\u30ec\u30f3\u30bf\u30eb\u30b3\u30f3\u30c8\u30ed\u30fc\u30eb\u30fb\u30a6\u30a7\u30d6\u30b5\u30fc\u30d3\u30b9\u3092\u4f7f\u7528\u3059\u308b",
"use_adguard_parental_hint": "AdGuard Home\u306f\u3001\u30c9\u30e1\u30a4\u30f3\u306b\u30a2\u30c0\u30eb\u30c8\u30b3\u30f3\u30c6\u30f3\u30c4\u304c\u542b\u307e\u308c\u3066\u3044\u308b\u304b\u3069\u3046\u304b\u3092\u78ba\u8a8d\u3057\u307e\u3059\u3002 \u30d6\u30e9\u30a6\u30b8\u30f3\u30b0\u30bb\u30ad\u30e5\u30ea\u30c6\u30a3\u30fb\u30a6\u30a7\u30d6\u30b5\u30fc\u30d3\u30b9\u3068\u540c\u3058\u30d7\u30e9\u30a4\u30d0\u30b7\u30fc\u306b\u512a\u3057\u3044API\u3092\u4f7f\u7528\u3057\u307e\u3059\u3002",
"enforce_safe_search": "\u30bb\u30fc\u30d5\u30b5\u30fc\u30c1\u3092\u5f37\u5236\u3059\u308b",
"enforce_save_search_hint": "AdGuard Home\u306f\u3001Google\u3001Youtube\u3001Bing\u3001Yandex\u306e\u691c\u7d22\u30a8\u30f3\u30b8\u30f3\u3067\u30bb\u30fc\u30d5\u30b5\u30fc\u30c1\u3092\u5f37\u5236\u3067\u304d\u307e\u3059\u3002",
"no_servers_specified": "\u30b5\u30fc\u30d0\u304c\u6307\u5b9a\u3055\u308c\u3066\u3044\u307e\u305b\u3093",
"no_settings": "\u8a2d\u5b9a\u306a\u3057",
"general_settings": "\u4e00\u822c\u8a2d\u5b9a",
"upstream_dns": "\u4e0a\u6d41DNS\u30b5\u30fc\u30d0",
"upstream_dns_hint": "\u3053\u306e\u30d5\u30a3\u30fc\u30eb\u30c9\u3092\u672a\u5165\u529b\u306e\u307e\u307e\u306b\u3059\u308b\u3068\u3001AdGuard Home\u306f\u4e0a\u6d41\u3068\u3057\u3066<a href='https:\/\/1.1.1.1\/' target='_blank'>Cloudflare DNS<\/a>\u3092\u4f7f\u7528\u3057\u307e\u3059\u3002DNS over TLS\u30b5\u30fc\u30d0\u306b\u306f\u3001\uff62tls:\/\/\u300d\u30d7\u30ec\u30d5\u30a3\u30c3\u30af\u30b9\u3092\u4f7f\u7528\u3057\u3066\u304f\u3060\u3055\u3044\u3002",
"test_upstream_btn": "\u4e0a\u6d41\u30b5\u30fc\u30d0\u3092\u30c6\u30b9\u30c8\u3059\u308b",
"apply_btn": "\u9069\u7528\u3059\u308b",
"disabled_filtering_toast": "\u30d5\u30a3\u30eb\u30bf\u30ea\u30f3\u30b0\u3092\u7121\u52b9\u306b\u3057\u307e\u3057\u305f",
"enabled_filtering_toast": "\u30d5\u30a3\u30eb\u30bf\u30ea\u30f3\u30b0\u3092\u6709\u52b9\u306b\u3057\u307e\u3057\u305f",
"disabled_safe_browsing_toast": "\u30bb\u30fc\u30d5\u30d6\u30e9\u30a6\u30b8\u30f3\u30b0\u3092\u7121\u52b9\u306b\u3057\u307e\u3057\u305f",
"enabled_safe_browsing_toast": "\u30bb\u30fc\u30d5\u30d6\u30e9\u30a6\u30b8\u30f3\u30b0\u3092\u6709\u52b9\u306b\u3057\u307e\u3057\u305f",
"disabled_parental_toast": "\u30da\u30a2\u30ec\u30f3\u30bf\u30eb\u30b3\u30f3\u30c8\u30ed\u30fc\u30eb\u3092\u7121\u52b9\u306b\u3057\u307e\u3057\u305f",
"enabled_parental_toast": "\u30da\u30a2\u30ec\u30f3\u30bf\u30eb\u30b3\u30f3\u30c8\u30ed\u30fc\u30eb\u3092\u6709\u52b9\u306b\u3057\u307e\u3057\u305f",
"disabled_safe_search_toast": "\u30bb\u30fc\u30d5\u30b5\u30fc\u30c1\u3092\u7121\u52b9\u306b\u3057\u307e\u3057\u305f",
"enabled_save_search_toast": "\u30bb\u30fc\u30d5\u30b5\u30fc\u30c1\u3092\u6709\u52b9\u306b\u3057\u307e\u3057\u305f",
"enabled_table_header": "\u6709\u52b9",
"name_table_header": "\u540d\u79f0",
"filter_url_table_header": "\u30d5\u30a3\u30eb\u30bf\u306eURL",
"rules_count_table_header": "\u30eb\u30fc\u30eb\u6570",
"last_time_updated_table_header": "\u6700\u7d42\u66f4\u65b0\u6642\u523b",
"actions_table_header": "\u64cd\u4f5c",
"delete_table_action": "\u524a\u9664\u3059\u308b",
"filters_and_hosts": "\u30d5\u30a3\u30eb\u30bf\u3068hosts\u30d6\u30ed\u30c3\u30af\u30ea\u30b9\u30c8",
"filters_and_hosts_hint": "AdGuard Home\u306f\u3001\u57fa\u672c\u7684\u306a\u5e83\u544a\u30d6\u30ed\u30c3\u30af\u30eb\u30fc\u30eb\u3068hosts\u30d5\u30a1\u30a4\u30eb\u306e\u69cb\u6587\u3092\u7406\u89e3\u3057\u307e\u3059\u3002",
"no_filters_added": "\u30d5\u30a3\u30eb\u30bf\u306f\u8ffd\u52a0\u3055\u308c\u307e\u305b\u3093\u3067\u3057\u305f",
"add_filter_btn": "\u30d5\u30a3\u30eb\u30bf\u3092\u8ffd\u52a0\u3059\u308b",
"cancel_btn": "\u30ad\u30e3\u30f3\u30bb\u30eb",
"enter_name_hint": "\u540d\u79f0\u3092\u5165\u529b",
"enter_url_hint": "URL\u3092\u5165\u529b",
"check_updates_btn": "\u30a2\u30c3\u30d7\u30c7\u30fc\u30c8\u3092\u78ba\u8a8d\u3059\u308b",
"new_filter_btn": "\u65b0\u3057\u3044\u30d5\u30a3\u30eb\u30bf\u30fb\u30b5\u30d6\u30b9\u30af\u30ea\u30d7\u30b7\u30e7\u30f3",
"enter_valid_filter_url": "\u30d5\u30a3\u30eb\u30bf\u30fb\u30b5\u30d6\u30b9\u30af\u30ea\u30d7\u30b7\u30e7\u30f3\u3082\u3057\u304f\u306fhosts\u30d5\u30a1\u30a4\u30eb\u306e\u6709\u52b9\u306aURL\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\u3002",
"custom_filter_rules": "\u30ab\u30b9\u30bf\u30e0\u30fb\u30d5\u30a3\u30eb\u30bf\u30ea\u30f3\u30b0\u30eb\u30fc\u30eb",
"custom_filter_rules_hint": "1\u3064\u306e\u884c\u306b1\u3064\u306e\u30eb\u30fc\u30eb\u3092\u5165\u529b\u3057\u3066\u304f\u3060\u3055\u3044\u3002 \u5e83\u544a\u30d6\u30ed\u30c3\u30af\u30eb\u30fc\u30eb\u3084hosts\u30d5\u30a1\u30a4\u30eb\u69cb\u6587\u3092\u4f7f\u7528\u3067\u304d\u307e\u3059\u3002",
"examples_title": "\u4f8b",
"example_meaning_filter_block": "example.org\u30c9\u30e1\u30a4\u30f3\u3068\u305d\u306e\u3059\u3079\u3066\u306e\u30b5\u30d6\u30c9\u30e1\u30a4\u30f3\u3078\u306e\u30a2\u30af\u30bb\u30b9\u3092\u30d6\u30ed\u30c3\u30af\u3059\u308b",
"example_meaning_filter_whitelist": "example.org\u30c9\u30e1\u30a4\u30f3\u3068\u305d\u306e\u3059\u3079\u3066\u306e\u30b5\u30d6\u30c9\u30e1\u30a4\u30f3\u3078\u306e\u30a2\u30af\u30bb\u30b9\u306e\u30d6\u30ed\u30c3\u30af\u3092\u89e3\u9664\u3059\u308b",
"example_meaning_host_block": "AdGuard Home\u306f\u3001example.org\u30c9\u30e1\u30a4\u30f3\uff08\u30b5\u30d6\u30c9\u30e1\u30a4\u30f3\u3092\u9664\u304f\uff09\u306b\u5bfe\u3057\u3066127.0.0.1\u306e\u30a2\u30c9\u30ec\u30b9\u3092\u8fd4\u3059\u3088\u3046\u306b\u306a\u308a\u307e\u3059\u3002",
"example_comment": "! \u3053\u3053\u306b\u306f\u30b3\u30e1\u30f3\u30c8\u304c\u5165\u308a\u307e\u3059",
"example_comment_meaning": "\u305f\u3060\u306e\u30b3\u30e1\u30f3\u30c8\u3067\u3059",
"example_comment_hash": "# \u3053\u3053\u3082\u30b3\u30e1\u30f3\u30c8\u3067\u3059",
"example_upstream_regular": "\u901a\u5e38\u306eDNS\uff08UDP\u3067\u306e\u554f\u3044\u5408\u308f\u305b\uff09",
"example_upstream_dot": "\u6697\u53f7\u5316\u3055\u308c\u3066\u3044\u308b <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_TLS' target='_blank'>DNS-over-TLS<\/a>",
"example_upstream_doh": "\u6697\u53f7\u5316\u3055\u308c\u3066\u3044\u308b <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>DNS-over-HTTPS<\/a>",
"example_upstream_sdns": "<a href='https:\/\/dnscrypt.info\/' target='_blank'>DNSCrypt<\/a> \u307e\u305f\u306f <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>DNS-over-HTTPS<\/a> \u30ea\u30be\u30eb\u30d0\u306e\u305f\u3081\u306b <a href='https:\/\/dnscrypt.info\/stamps\/' target='_blank'>DNS Stamps<\/a> \u3092\u4f7f\u3048\u307e\u3059",
"example_upstream_tcp": "\u901a\u5e38\u306eDNS\uff08TCP\u3067\u306e\u554f\u3044\u5408\u308f\u305b\uff09",
"all_filters_up_to_date_toast": "\u3059\u3079\u3066\u306e\u30d5\u30a3\u30eb\u30bf\u306f\u65e2\u306b\u6700\u65b0\u3067\u3059",
"updated_upstream_dns_toast": "\u4e0a\u6d41DNS\u30b5\u30fc\u30d0\u3092\u66f4\u65b0\u3057\u307e\u3057\u305f",
"dns_test_ok_toast": "\u6307\u5b9a\u3055\u308c\u305fDNS\u30b5\u30fc\u30d0\u306f\u6b63\u3057\u304f\u52d5\u4f5c\u3057\u3066\u3044\u307e\u3059",
"dns_test_not_ok_toast": "\u30b5\u30fc\u30d0 \"{{key}}\": \u4f7f\u7528\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002\u6b63\u3057\u304f\u5165\u529b\u3055\u308c\u3066\u3044\u308b\u304b\u3069\u3046\u304b\u3092\u78ba\u8a8d\u3057\u3066\u304f\u3060\u3055\u3044",
"unblock_btn": "\u30d6\u30ed\u30c3\u30af\u89e3\u9664",
"block_btn": "\u30d6\u30ed\u30c3\u30af\u3059\u308b",
"time_table_header": "\u6642\u523b",
"domain_name_table_header": "\u30c9\u30e1\u30a4\u30f3\u540d",
"type_table_header": "\u7a2e\u985e",
"response_table_header": "\u5fdc\u7b54",
"client_table_header": "\u30af\u30e9\u30a4\u30a2\u30f3\u30c8",
"empty_response_status": "\u672a\u5b9a\u7fa9",
"show_all_filter_type": "\u3059\u3079\u3066\u8868\u793a",
"show_filtered_type": "\u30d5\u30a3\u30eb\u30bf\u3055\u308c\u305f\u30ed\u30b0\u3092\u8868\u793a",
"no_logs_found": "\u30ed\u30b0\u306f\u3042\u308a\u307e\u305b\u3093",
"disabled_log_btn": "\u30ed\u30b0\u3092\u7121\u52b9\u306b\u3059\u308b",
"download_log_file_btn": "\u30ed\u30b0\u30d5\u30a1\u30a4\u30eb\u3092\u30c0\u30a6\u30f3\u30ed\u30fc\u30c9\u3059\u308b",
"refresh_btn": "\u6700\u65b0\u306b\u3059\u308b",
"enabled_log_btn": "\u30ed\u30b0\u3092\u6709\u52b9\u306b\u3059\u308b",
"last_dns_queries": "\u6700\u65b05000\u4ef6\u5206\u306eDNS\u30af\u30a8\u30ea",
"previous_btn": "\u524d\u3078",
"next_btn": "\u6b21\u3078",
"loading_table_status": "\u8aad\u307f\u8fbc\u307f\u4e2d\u2026",
"page_table_footer_text": "\u30da\u30fc\u30b8",
"of_table_footer_text": "\uff0f",
"rows_table_footer_text": "\u884c",
"updated_custom_filtering_toast": "\u30ab\u30b9\u30bf\u30e0\u30fb\u30d5\u30a3\u30eb\u30bf\u30ea\u30f3\u30b0\u30eb\u30fc\u30eb\u3092\u66f4\u65b0\u3057\u307e\u3057\u305f",
"rule_removed_from_custom_filtering_toast": "\u30eb\u30fc\u30eb\u3092\u30ab\u30b9\u30bf\u30e0\u30fb\u30d5\u30a3\u30eb\u30bf\u30ea\u30f3\u30b0\u30eb\u30fc\u30eb\u304b\u3089\u9664\u53bb\u3057\u307e\u3057\u305f",
"rule_added_to_custom_filtering_toast": "\u30eb\u30fc\u30eb\u3092\u30ab\u30b9\u30bf\u30e0\u30fb\u30d5\u30a3\u30eb\u30bf\u30ea\u30f3\u30b0\u30eb\u30fc\u30eb\u306b\u8ffd\u52a0\u3057\u307e\u3057\u305f",
"query_log_disabled_toast": "\u30af\u30a8\u30ea\u30fb\u30ed\u30b0\u3092\u7121\u52b9\u306b\u3057\u307e\u3057\u305f",
"query_log_enabled_toast": "\u30af\u30a8\u30ea\u30fb\u30ed\u30b0\u3092\u6709\u52b9\u306b\u3057\u307e\u3057\u305f",
"source_label": "\u30bd\u30fc\u30b9",
"found_in_known_domain_db": "\u65e2\u77e5\u306e\u30c9\u30e1\u30a4\u30f3\u30c7\u30fc\u30bf\u30d9\u30fc\u30b9\u306b\u898b\u3064\u304b\u308a\u307e\u3057\u305f\u3002",
"category_label": "\u30ab\u30c6\u30b4\u30ea",
"rule_label": "\u30eb\u30fc\u30eb",
"filter_label": "\u30d5\u30a3\u30eb\u30bf",
"unknown_filter": "\u4e0d\u660e\u306a\u30d5\u30a3\u30eb\u30bf {{filterId}}"
}

View File

@@ -0,0 +1,160 @@
{
"url_added_successfully": "Url adicionada com sucesso",
"check_dhcp_servers": "Verifique se h\u00e1 servidores DHCP",
"save_config": "Salvar configura\u00e7\u00e3o",
"enabled_dhcp": "Servidor DHCP ativado",
"disabled_dhcp": "Servidor DHCP desativado",
"dhcp_title": "Servidor DHCP (experimental)",
"dhcp_description": "Se o seu roteador n\u00e3o fornecer configura\u00e7\u00f5es de DHCP, voc\u00ea poder\u00e1 usar o servidor DHCP integrado do AdGuard.",
"dhcp_enable": "Ativar servidor DHCP",
"dhcp_disable": "Desativar servidor DHCP",
"dhcp_not_found": "Nenhum servidor DHCP ativo foi encontrado na sua rede. \u00c9 seguro ativar o servidor DHCP integrado.",
"dhcp_found": "Foram encontrados servidores DHCP ativos na rede. N\u00e3o \u00e9 seguro ativar o servidor DHCP integrado.",
"dhcp_leases": "Concess\u00f5es DHCP",
"dhcp_leases_not_found": "Nenhuma concess\u00e3o DHCP encontrada",
"dhcp_config_saved": "Salvar configura\u00e7\u00f5es do servidor DHCP",
"form_error_required": "Campo obrigat\u00f3rio",
"form_error_ip_format": "formato de endere\u00e7o IPv4 inv\u00e1lido",
"form_error_positive": "Deve ser maior que 0",
"dhcp_form_gateway_input": "IP do gateway",
"dhcp_form_subnet_input": "M\u00e1scara de sub-rede",
"dhcp_form_range_title": "Faixa de endere\u00e7os IP",
"dhcp_form_range_start": "In\u00edcio da faixa",
"dhcp_form_range_end": "Final da faixa",
"dhcp_form_lease_title": "Tempo de concess\u00e3o do DHCP (em segundos)",
"dhcp_form_lease_input": "Dura\u00e7\u00e3o da concess\u00e3o",
"dhcp_interface_select": "Selecione a interface DHCP",
"dhcp_hardware_address": "Endere\u00e7o de hardware",
"dhcp_ip_addresses": "Endere\u00e7o de IP",
"dhcp_table_hostname": "Hostname",
"dhcp_table_expires": "Expira",
"back": "Voltar",
"dashboard": "Painel",
"settings": "Configura\u00e7\u00f5es",
"filters": "Filtros",
"query_log": "Registro de consultas",
"faq": "FAQ",
"version": "vers\u00e3o",
"address": "endere\u00e7o",
"on": "ON",
"off": "OFF",
"copyright": "Copyright",
"homepage": "P\u00e1gina inicial",
"report_an_issue": "Reportar um problema",
"enable_protection": "Ativar prote\u00e7\u00e3o",
"enabled_protection": "Prote\u00e7\u00e3o ativada",
"disable_protection": "Desativar prote\u00e7\u00e3o",
"disabled_protection": "Prote\u00e7\u00e3o desativada",
"refresh_statics": "Atualizar estat\u00edsticas",
"dns_query": "Consultas de DNS",
"blocked_by": "Bloqueador por filtros",
"stats_malware_phishing": "Bloqueado malware\/phishing",
"stats_adult": "Bloqueado sites adultos",
"stats_query_domain": "Principais dom\u00ednios consultados",
"for_last_24_hours": "nas \u00faltimas 24 horas",
"no_domains_found": "Nenhum dom\u00ednio encontrado",
"requests_count": "Contagem de solicita\u00e7\u00f5es",
"top_blocked_domains": "Principais dom\u00ednios bloqueados",
"top_clients": "Principais clientes",
"no_clients_found": "Nenhuma cliente encontrado",
"general_statistics": "Estat\u00edsticas gerais",
"number_of_dns_query_24_hours": "O n\u00famero de consultas DNS processadas nas \u00faltimas 24 horas",
"number_of_dns_query_blocked_24_hours": "V\u00e1rias solicita\u00e7\u00f5es DNS bloqueadas por filtros de bloqueio de an\u00fancios e listas de bloqueio de hosts",
"number_of_dns_query_blocked_24_hours_by_sec": "V\u00e1rias solicita\u00e7\u00f5es de DNS bloqueadas pelo m\u00f3dulo de seguran\u00e7a da navega\u00e7\u00e3o do AdGuard",
"number_of_dns_query_blocked_24_hours_adult": "V\u00e1rios sites adultos bloqueados",
"enforced_save_search": "For\u00e7ar pesquisa segura",
"number_of_dns_query_to_safe_search": "V\u00e1rias solicita\u00e7\u00f5es de DNS para motores de busca para os quais a pesquisa segura foi aplicada",
"average_processing_time": "Tempo m\u00e9dio de processamento",
"average_processing_time_hint": "Tempo m\u00e9dio em milissegundos no processamento de uma solicita\u00e7\u00e3o DNS",
"block_domain_use_filters_and_hosts": "Bloquear dom\u00ednios usando arquivos de filtros e hosts",
"filters_block_toggle_hint": "Voc\u00ea pode configurar as regras de bloqueio nas configura\u00e7\u00f5es de <a href='#filters'>Filtros<\/a>.",
"use_adguard_browsing_sec": "Usar o servi\u00e7o de seguran\u00e7a da navega\u00e7\u00e3o do AdGuard",
"use_adguard_browsing_sec_hint": "O AdGuard Home ir\u00e1 verificar se o dom\u00ednio est\u00e1 na lista negra do servi\u00e7o de seguran\u00e7a da navega\u00e7\u00e3o. Ele usar\u00e1 a API de pesquisa de privacidade para executar a verifica\u00e7\u00e3o: apenas um prefixo curto do hash do nome de dom\u00ednio SHA256 \u00e9 enviado para o servidor.",
"use_adguard_parental": "Usar o servi\u00e7o de controle parental do AdGuard",
"use_adguard_parental_hint": "O AdGuard Home ir\u00e1 verificar se o dom\u00ednio cont\u00e9m conte\u00fado adulto. Ele usa a mesma API amig\u00e1vel de privacidade que o servi\u00e7o de seguran\u00e7a da navega\u00e7\u00e3o.",
"enforce_safe_search": "For\u00e7ar pesquisa segura",
"enforce_save_search_hint": "O AdGuard Home pode for\u00e7ar a pesquisa segura nos seguintes motores de busca: Google, Youtube, Bing e Yandex.",
"no_servers_specified": "Nenhum servidor especificado",
"no_settings": "N\u00e3o configurado",
"general_settings": "Configura\u00e7\u00f5es gerais",
"upstream_dns": "Servidores DNS upstream",
"upstream_dns_hint": "Se voc\u00ea deixar este campo vazio, o AdGuard Home ir\u00e1 usar o<a href='https:\/\/1.1.1.1\/' target='_blank'>DNS da Cloudflare<\/a> como upstream. Use o prefixo tls:\/\/ para servidores DNS com TLS.",
"test_upstream_btn": "Testar upstreams",
"apply_btn": "Aplicar",
"disabled_filtering_toast": "Filtragem desativada",
"enabled_filtering_toast": "Filtragem ativada",
"disabled_safe_browsing_toast": "Navega\u00e7\u00e3o segura desativada",
"enabled_safe_browsing_toast": "Navega\u00e7\u00e3o segura ativada",
"disabled_parental_toast": "Controle parental desativado",
"enabled_parental_toast": "Controle parental ativado",
"disabled_safe_search_toast": "Pesquisa segura desativada",
"enabled_save_search_toast": "Pesquisa segura ativada",
"enabled_table_header": "Ativado",
"name_table_header": "Nome",
"filter_url_table_header": "URL do filtro",
"rules_count_table_header": "Quantidade de regras",
"last_time_updated_table_header": "\u00daltima atualiza\u00e7\u00e3o",
"actions_table_header": "A\u00e7\u00f5es",
"delete_table_action": "Excluir",
"filters_and_hosts": "Filtros e listas de bloqueio de hosts",
"filters_and_hosts_hint": "O AdGuard Home entende regras b\u00e1sicas de bloqueio de an\u00fancios e a sintaxe de arquivos de hosts.",
"no_filters_added": "Nenhum filtro adicionado",
"add_filter_btn": "Adicionar filtro",
"cancel_btn": "Cancelar",
"enter_name_hint": "Digite o nome",
"enter_url_hint": "Digite a URL",
"check_updates_btn": "Verificar atualiza\u00e7\u00f5es",
"new_filter_btn": "Nova inscri\u00e7\u00e3o de filtro",
"enter_valid_filter_url": "Digite a URL v\u00e1lida para efetuar a inscri\u00e7\u00e3o de filtro ou um arquivo de hosts.",
"custom_filter_rules": "Regras de filtragem personalizadas",
"custom_filter_rules_hint": "Digite uma regra por linha. Voc\u00ea pode usar regras de bloqueio de an\u00fancios ou a sintaxe de arquivos de hosts.",
"examples_title": "Exemplos",
"example_meaning_filter_block": "bloqueia o acesso ao dom\u00ednio exemplo.org e a todos os seus subdom\u00ednios",
"example_meaning_filter_whitelist": "desbloqueia o acesso ao dom\u00ednio exemplo.org e a todos os seus subdom\u00ednios",
"example_meaning_host_block": "O AdGuard Home ir\u00e1 retornar o endere\u00e7o 127.0.0.1 para o dom\u00ednio exemplo.org (exceto seus subdom\u00ednios).",
"example_comment": "! Aqui vai um coment\u00e1rio",
"example_comment_meaning": "apenas um coment\u00e1rio",
"example_comment_hash": "# Tamb\u00e9m um coment\u00e1rio",
"example_upstream_regular": "DNS regular (atrav\u00e9s do UDP)",
"example_upstream_dot": "DNS criptografado <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_TLS' target='_blank'>atrav\u00e9s do TLS<\/a>",
"example_upstream_doh": "DNS criptografado <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>atrav\u00e9s do HTTPS<\/a>",
"example_upstream_sdns": "Voc\u00ea pode usar <a href='https:\/\/dnscrypt.info\/stamps\/' target='_blank'>DNS Stamps<\/a>para o<a href='https:\/\/dnscrypt.info\/' target='_blank'>DNSCrypt<\/a>ou usar resolvedores<a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>DNS-sobre-HTTPS<\/a>",
"example_upstream_tcp": "DNS regular (atrav\u00e9s do TCP)",
"all_filters_up_to_date_toast": "Todos os filtros j\u00e1 est\u00e3o atualizados",
"updated_upstream_dns_toast": "Atualizado os servidores DNS upstream",
"dns_test_ok_toast": "Os servidores DNS especificados est\u00e3o funcionando corretamente",
"dns_test_not_ok_toast": "O servidor \"{{key}}\": n\u00e3o p\u00f4de ser utilizado. Por favor, verifique se voc\u00ea escreveu corretamente",
"unblock_btn": "Desbloquear",
"block_btn": "Bloquear",
"time_table_header": "Data",
"domain_name_table_header": "Nome de dom\u00ednio",
"type_table_header": "Tipo",
"response_table_header": "Resposta",
"client_table_header": "Cliente",
"empty_response_status": "Vazio",
"show_all_filter_type": "Mostrar todos",
"show_filtered_type": "Mostrar filtrados",
"no_logs_found": "Nenhum registro encontrado",
"disabled_log_btn": "Desativar registros",
"download_log_file_btn": "Baixar arquivo de registros",
"refresh_btn": "Atualizar",
"enabled_log_btn": "Ativar registros",
"last_dns_queries": "\u00daltimas 5000 consultas DNS",
"previous_btn": "Anterior",
"next_btn": "Pr\u00f3ximo",
"loading_table_status": "Carregando",
"page_table_footer_text": "P\u00e1gina",
"of_table_footer_text": "de",
"rows_table_footer_text": "linhas",
"updated_custom_filtering_toast": "Regras de filtragem personalizadas atualizadas",
"rule_removed_from_custom_filtering_toast": "Regra removida das regras de filtragem personalizadas",
"rule_added_to_custom_filtering_toast": "Regra adicionada \u00e0s regras de filtragem personalizadas",
"query_log_disabled_toast": "Registros de consultas desativado",
"query_log_enabled_toast": "Registros de consultas ativado",
"source_label": "Fonte",
"found_in_known_domain_db": "Encontrado no banco de dados de dom\u00ednios conhecidos.",
"category_label": "Categoria",
"rule_label": "Regra",
"filter_label": "Filtro",
"unknown_filter": "Filtro desconhecido {{filterId}}"
}

View File

@@ -0,0 +1,159 @@
{
"check_dhcp_servers": "\u041f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c DHCP-\u0441\u0435\u0440\u0432\u0435\u0440\u044b",
"save_config": "\u0421\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e",
"enabled_dhcp": "DHCP-\u0441\u0435\u0440\u0432\u0435\u0440 \u0432\u043a\u043b\u044e\u0447\u0435\u043d",
"disabled_dhcp": "DHCP-\u0441\u0435\u0440\u0432\u0435\u0440 \u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d",
"dhcp_title": "DHCP-\u0441\u0435\u0440\u0432\u0435\u0440 (\u044d\u043a\u0441\u043f\u0435\u0440\u0438\u043c\u0435\u043d\u0442\u0430\u043b\u044c\u043d\u044b\u0439!)",
"dhcp_description": "\u0415\u0441\u043b\u0438 \u0432\u0430\u0448 \u0440\u043e\u0443\u0442\u0435\u0440 \u043d\u0435 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 DHCP, \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u0432\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0439 DHCP-\u0441\u0435\u0440\u0432\u0435\u0440 AdGuard.",
"dhcp_enable": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c DHCP-\u0441\u0435\u0440\u0432\u0435\u0440",
"dhcp_disable": "\u041e\u0442\u043a\u043b\u044e\u0447\u0438\u0442\u044c DHCP-\u0441\u0435\u0440\u0432\u0435\u0440",
"dhcp_not_found": "\u0410\u043a\u0442\u0438\u0432\u043d\u044b\u0435 DHCP-\u0441\u0435\u0440\u0432\u0435\u0440\u044b \u0432 \u0441\u0435\u0442\u0438 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b. \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e \u0432\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0432\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0439 \u0441\u0435\u0440\u0432\u0435\u0440 DHCP.",
"dhcp_found": "\u041d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0435 DHCP-\u0441\u0435\u0440\u0432\u0435\u0440\u044b \u043d\u0430\u0439\u0434\u0435\u043d\u044b \u0432 \u0441\u0435\u0442\u0438. \u0412\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u0432\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u043e\u0433\u043e DHCP-\u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u043d\u0435\u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e.",
"dhcp_leases": "\u0410\u0440\u0435\u043d\u0434\u0430 DHCP",
"dhcp_leases_not_found": "\u0410\u0440\u0435\u043d\u0434\u0430 DHCP \u043d\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0430",
"dhcp_config_saved": "\u0421\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u043d\u0430\u044f \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f DHCP-\u0441\u0435\u0440\u0432\u0435\u0440\u0430",
"form_error_required": "\u041e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u043f\u043e\u043b\u0435",
"form_error_ip_format": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u0444\u043e\u0440\u043c\u0430\u0442 IPv4",
"form_error_positive": "\u0414\u043e\u043b\u0436\u043d\u043e \u0431\u044b\u0442\u044c \u0431\u043e\u043b\u044c\u0448\u0435 0",
"dhcp_form_gateway_input": "IP-\u0430\u0434\u0440\u0435\u0441 \u0448\u043b\u044e\u0437\u0430",
"dhcp_form_subnet_input": "\u041c\u0430\u0441\u043a\u0430 \u043f\u043e\u0434\u0441\u0435\u0442\u0438",
"dhcp_form_range_title": "\u0414\u0438\u0430\u043f\u0430\u0437\u043e\u043d IP-\u0430\u0434\u0440\u0435\u0441\u043e\u0432",
"dhcp_form_range_start": "\u041d\u0430\u0447\u0430\u043b\u043e \u0434\u0438\u0430\u043f\u0430\u0437\u043e\u043d\u0430",
"dhcp_form_range_end": "\u041a\u043e\u043d\u0435\u0446 \u0434\u0438\u0430\u043f\u0430\u0437\u043e\u043d\u0430",
"dhcp_form_lease_title": "\u0412\u0440\u0435\u043c\u044f \u0430\u0440\u0435\u043d\u0434\u044b DHCP (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)",
"dhcp_form_lease_input": "\u0421\u0440\u043e\u043a \u0430\u0440\u0435\u043d\u0434\u044b",
"dhcp_interface_select": "\u0412\u044b\u0431\u0440\u0430\u0442\u044c \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 DHCP",
"dhcp_hardware_address": "\u0410\u043f\u043f\u0430\u0440\u0430\u0442\u043d\u044b\u0439 \u0430\u0434\u0440\u0435\u0441",
"dhcp_ip_addresses": "IP-\u0430\u0434\u0440\u0435\u0441\u0430",
"dhcp_table_hostname": "\u0418\u043c\u044f \u0445\u043e\u0441\u0442\u0430",
"dhcp_table_expires": "\u0418\u0441\u0442\u0435\u043a\u0430\u0435\u0442",
"back": "\u041d\u0430\u0437\u0430\u0434",
"dashboard": "\u041f\u0430\u043d\u0435\u043b\u044c \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f",
"settings": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438",
"filters": "\u0424\u0438\u043b\u044c\u0442\u0440\u044b",
"query_log": "\u0416\u0443\u0440\u043d\u0430\u043b",
"faq": "FAQ",
"version": "\u0432\u0435\u0440\u0441\u0438\u044f",
"address": "\u0430\u0434\u0440\u0435\u0441",
"on": "\u0412\u043a\u043b",
"off": "\u0412\u044b\u043a\u043b",
"copyright": "\u0412\u0441\u0435 \u043f\u0440\u0430\u0432\u0430 \u0437\u0430\u0449\u0438\u0449\u0435\u043d\u044b",
"homepage": "\u0413\u043b\u0430\u0432\u043d\u0430\u044f",
"report_an_issue": "\u0421\u043e\u043e\u0431\u0449\u0438\u0442\u044c \u043e \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u0435",
"enable_protection": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0437\u0430\u0449\u0438\u0442\u0443",
"enabled_protection": "\u0417\u0430\u0449\u0438\u0442\u0430 \u0432\u043a\u043b.",
"disable_protection": "\u041e\u0442\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0437\u0430\u0449\u0438\u0442\u0443",
"disabled_protection": "\u0417\u0430\u0449\u0438\u0442\u0430 \u0432\u044b\u043a\u043b.",
"refresh_statics": "\u041e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u0441\u0442\u0430\u0442\u0438\u0441\u0442\u0438\u043a\u0443",
"dns_query": "DNS-\u0437\u0430\u043f\u0440\u043e\u0441\u044b",
"blocked_by": "\u0417\u0430\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u043d\u043e \u0444\u0438\u043b\u044c\u0442\u0440\u0430\u043c\u0438",
"stats_malware_phishing": "\u0417\u0430\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u0432\u0440\u0435\u0434\u043e\u043d\u043e\u0441\u043d\u044b\u0435 \u0438 \u0444\u0438\u0448\u0438\u043d\u0433\u043e\u0432\u044b\u0435 \u0441\u0430\u0439\u0442\u044b",
"stats_adult": "\u0417\u0430\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \"\u0432\u0437\u0440\u043e\u0441\u043b\u044b\u0435\" \u0441\u0430\u0439\u0442\u044b",
"stats_query_domain": "\u0427\u0430\u0441\u0442\u043e \u0437\u0430\u043f\u0440\u0430\u0448\u0438\u0432\u0430\u0435\u043c\u044b\u0435 \u0434\u043e\u043c\u0435\u043d\u044b",
"for_last_24_hours": "\u0437\u0430 24 \u0447\u0430\u0441\u0430",
"no_domains_found": "\u0414\u043e\u043c\u0435\u043d\u044b \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b",
"requests_count": "\u041a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432",
"top_blocked_domains": "\u0427\u0430\u0441\u0442\u043e \u0431\u043b\u043e\u043a\u0438\u0440\u0443\u0435\u043c\u044b\u0435 \u0434\u043e\u043c\u0435\u043d\u044b",
"top_clients": "\u0427\u0430\u0441\u0442\u044b\u0435 \u043a\u043b\u0438\u0435\u043d\u0442\u044b",
"no_clients_found": "\u041a\u043b\u0438\u0435\u043d\u0442\u043e\u0432 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e",
"general_statistics": "\u041e\u0431\u0449\u0430\u044f \u0441\u0442\u0430\u0442\u0438\u0441\u0442\u0438\u043a\u0430",
"number_of_dns_query_24_hours": "\u041a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e DNS-\u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u0437\u0430 24 \u0447\u0430\u0441\u0430",
"number_of_dns_query_blocked_24_hours": "\u041a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e DNS-\u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432, \u0437\u0430\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u0444\u0438\u043b\u044c\u0442\u0440\u0430\u043c\u0438 \u0438 \u0431\u043b\u043e\u043a-\u0441\u043f\u0438\u0441\u043a\u0430\u043c\u0438",
"number_of_dns_query_blocked_24_hours_by_sec": "\u041a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e DNS-\u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432, \u0437\u0430\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \u043c\u043e\u0434\u0443\u043b\u0435\u043c \u0410\u043d\u0442\u0438\u0444\u0438\u0448\u0438\u043d\u0433\u0430 AdGuard",
"number_of_dns_query_blocked_24_hours_adult": "\u041a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0437\u0430\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0445 \"\u0441\u0430\u0439\u0442\u043e\u0432 \u0434\u043b\u044f \u0432\u0437\u0440\u043e\u0441\u043b\u044b\u0445\"",
"enforced_save_search": "\u041f\u0440\u0438\u043c\u0435\u043d\u0435\u043d \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u044b\u0439 \u043f\u043e\u0438\u0441\u043a",
"number_of_dns_query_to_safe_search": "\u041a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 DNS \u0434\u043b\u044f \u043f\u043e\u0438\u0441\u043a\u043e\u0432\u044b\u0445 \u0441\u0438\u0441\u0442\u0435\u043c, \u0434\u043b\u044f \u043a\u043e\u0442\u043e\u0440\u044b\u0445 \u0431\u044b\u043b \u043f\u0440\u0438\u043c\u0435\u043d\u0435\u043d \u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u044b\u0439 \u043f\u043e\u0438\u0441\u043a",
"average_processing_time": "\u0421\u0440\u0435\u0434\u043d\u0435\u0435 \u0432\u0440\u0435\u043c\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u0430",
"average_processing_time_hint": "\u0421\u0440\u0435\u0434\u043d\u0435\u0435 \u0432\u0440\u0435\u043c\u044f \u0434\u043b\u044f \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438 \u0437\u0430\u043f\u0440\u043e\u0441\u0430 DNS \u0432 \u043c\u0438\u043b\u043b\u0438\u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445",
"block_domain_use_filters_and_hosts": "\u0411\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0434\u043e\u043c\u0435\u043d\u044b \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u0444\u0438\u043b\u044c\u0442\u0440\u043e\u0432 \u0438 \u0444\u0430\u0439\u043b\u043e\u0432 \u0445\u043e\u0441\u0442\u043e\u0432",
"filters_block_toggle_hint": "\u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043f\u0440\u0430\u0432\u0438\u043b\u0430 \u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u043a\u0438 \u0432 <a href='#filters'> \"\u0424\u0438\u043b\u044c\u0442\u0440\u0430\u0445\"<\/a>.",
"use_adguard_browsing_sec": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u0443\u044e \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u044e AdGuard",
"use_adguard_browsing_sec_hint": "AdGuard Home \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442, \u0432\u043a\u043b\u044e\u0447\u0435\u043d \u043b\u0438 \u0434\u043e\u043c\u0435\u043d \u0432 \u0432\u0435\u0431-\u0441\u043b\u0443\u0436\u0431\u0443 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u0438 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430. \u041e\u043d \u0431\u0443\u0434\u0435\u0442 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c API, \u0447\u0442\u043e\u0431\u044b \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443: \u043d\u0430 \u0441\u0435\u0440\u0432\u0435\u0440 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u044f\u0435\u0442\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u043a\u043e\u0440\u043e\u0442\u043a\u0438\u0439 \u043f\u0440\u0435\u0444\u0438\u043a\u0441 \u0438\u043c\u0435\u043d\u0438 \u0434\u043e\u043c\u0435\u043d\u0430 SHA256.",
"use_adguard_parental": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u043c\u043e\u0434\u0443\u043b\u044c \u0420\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u0441\u043a\u043e\u0433\u043e \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044f AdGuard ",
"use_adguard_parental_hint": "AdGuard Home \u043f\u0440\u043e\u0432\u0435\u0440\u0438\u0442, \u0441\u043e\u0434\u0435\u0440\u0436\u0438\u0442 \u043b\u0438 \u0434\u043e\u043c\u0435\u043d \u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b\u044b 18+. \u041e\u043d \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 \u0442\u043e\u0442 \u0436\u0435 API \u0434\u043b\u044f \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0435\u043d\u0438\u044f \u043a\u043e\u043d\u0444\u0438\u0434\u0435\u043d\u0446\u0438\u0430\u043b\u044c\u043d\u043e\u0441\u0442\u0438, \u0447\u0442\u043e \u0438 \u0432\u0435\u0431-\u0441\u043b\u0443\u0436\u0431\u0430 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e\u0441\u0442\u0438 \u0431\u0440\u0430\u0443\u0437\u0435\u0440\u0430.",
"enforce_safe_search": "\u0423\u0441\u0438\u043b\u0438\u0442\u044c \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u044b\u0439 \u043f\u043e\u0438\u0441\u043a",
"enforce_save_search_hint": "AdGuard Home \u043c\u043e\u0436\u0435\u0442 \u043e\u0431\u0435\u0441\u043f\u0435\u0447\u0438\u0442\u044c \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u044b\u0439 \u043f\u043e\u0438\u0441\u043a \u0432 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0438\u0445 \u0441\u0438\u0441\u0442\u0435\u043c\u0430\u0445: Google, Youtube, Bing \u0438 Yandex.",
"no_servers_specified": "\u041d\u0435\u0442 \u0443\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0445 \u0441\u0435\u0440\u0432\u0435\u0440\u043e\u0432",
"no_settings": "\u041d\u0435\u0442 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a",
"general_settings": "\u041e\u0441\u043d\u043e\u0432\u043d\u044b\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438",
"upstream_dns": "Upstream DNS-\u0441\u0435\u0440\u0432\u0435\u0440\u044b",
"upstream_dns_hint": "\u0415\u0441\u043b\u0438 \u0432\u044b \u043e\u0441\u0442\u0430\u0432\u0438\u0442\u0435 \u044d\u0442\u043e \u043f\u043e\u043b\u0435 \u043f\u0443\u0441\u0442\u044b\u043c, \u0442\u043e AdGuard Home \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442 <a href='https:\/\/1.1.1.1\/' target='_blank'>Cloudflare DNS<\/a> \u0432 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 upstream. \u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 tls:\/\/ \u0434\u043b\u044f DNS \u0447\u0435\u0440\u0435\u0437 \u0441\u0435\u0440\u0432\u0435\u0440\u044b TLS.",
"test_upstream_btn": "\u0422\u0435\u0441\u0442 upstream \u0441\u0435\u0440\u0432\u0435\u0440\u043e\u0432",
"apply_btn": "\u041f\u0440\u0438\u043c\u0435\u043d\u0438\u0442\u044c",
"disabled_filtering_toast": "\u0424\u0438\u043b\u044c\u0442\u0440\u0430\u0446\u0438\u044f \u0432\u044b\u043a\u043b.",
"enabled_filtering_toast": "\u0424\u0438\u043b\u044c\u0442\u0440\u0430\u0446\u0438\u044f \u0432\u043a\u043b.",
"disabled_safe_browsing_toast": "\u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u0430\u044f \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u044f \u0432\u044b\u043a\u043b.",
"enabled_safe_browsing_toast": "\u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u0430\u044f \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u044f \u0432\u043a\u043b.",
"disabled_parental_toast": "\u0420\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0439 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044c \u0432\u044b\u043a\u043b.",
"enabled_parental_toast": "\u0420\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0439 \u043a\u043e\u043d\u0442\u0440\u043e\u043b\u044c \u0432\u043a\u043b.",
"disabled_safe_search_toast": "\u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u044b\u0439 \u043f\u043e\u0438\u0441\u043a \u0432\u044b\u043a\u043b.",
"enabled_save_search_toast": "\u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u044b\u0439 \u043f\u043e\u0438\u0441\u043a \u0432\u043a\u043b.",
"enabled_table_header": "\u0412\u043a\u043b.",
"name_table_header": "\u0418\u043c\u044f",
"filter_url_table_header": "URL \u0444\u0438\u043b\u044c\u0442\u0440\u0430",
"rules_count_table_header": "\u041a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u043e \u043f\u0440\u0430\u0432\u0438\u043b:",
"last_time_updated_table_header": "\u041f\u043e\u0441\u043b\u0435\u0434\u043d\u0435\u0435 \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u0435",
"actions_table_header": "\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u044f",
"delete_table_action": "\u0423\u0434\u0430\u043b\u0438\u0442\u044c",
"filters_and_hosts": "\u0424\u0438\u043b\u044c\u0442\u0440\u044b \u0438 \u0447\u0435\u0440\u043d\u044b\u0435 \u0441\u043f\u0438\u0441\u043a\u0438 hosts",
"filters_and_hosts_hint": "AdGuard Home \u0440\u0430\u0441\u043f\u043e\u0437\u043d\u0430\u0435\u0442 \u0431\u0430\u0437\u043e\u0432\u044b\u0435 \u043f\u0440\u0430\u0432\u0438\u043b\u0430 \u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u043a\u0438 \u0438 \u0441\u0438\u043d\u0442\u0430\u043a\u0441\u0438\u0441 \u0444\u0430\u0439\u043b\u043e\u0432 hosts.",
"no_filters_added": "\u0424\u0438\u043b\u044c\u0442\u0440\u044b \u043d\u0435 \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u044b",
"add_filter_btn": "\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0444\u0438\u043b\u044c\u0442\u0440",
"cancel_btn": "\u041e\u0442\u043c\u0435\u043d\u0430",
"enter_name_hint": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0438\u043c\u044f",
"enter_url_hint": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 URL",
"check_updates_btn": "\u041f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u0438\u044f",
"new_filter_btn": "\u0414\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u0435 \u043d\u043e\u0432\u043e\u0433\u043e \u0444\u0438\u043b\u044c\u0442\u0440\u0430",
"enter_valid_filter_url": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0434\u0435\u0439\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0439 URL \u0434\u043b\u044f \u043f\u043e\u0434\u043f\u0438\u0441\u043a\u0438 \u043d\u0430 \u0444\u0438\u043b\u044c\u0442\u0440 \u0438\u043b\u0438 \u0444\u0430\u0439\u043b hosts.",
"custom_filter_rules": "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u043e\u0435 \u043f\u0440\u0430\u0432\u0438\u043b\u043e \u0444\u0438\u043b\u044c\u0442\u0440\u0430\u0446\u0438\u0438",
"custom_filter_rules_hint": "\u0412\u0432\u043e\u0434\u0438\u0442\u0435 \u043f\u043e \u043e\u0434\u043d\u043e\u043c\u0443 \u043f\u0440\u0430\u0432\u0438\u043b\u0443 \u043d\u0430 \u0441\u0442\u0440\u043e\u0447\u043a\u0443. \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u043f\u0440\u0430\u0432\u0438\u043b\u0430 \u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u043a\u0438 \u0438\u043b\u0438 \u0441\u0438\u043d\u0442\u0430\u043a\u0441\u0438\u0441 \u0444\u0430\u0439\u043b\u043e\u0432 hosts.",
"examples_title": "\u041f\u0440\u0438\u043c\u0435\u0440\u044b",
"example_meaning_filter_block": "\u0437\u0430\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u0434\u043e\u043c\u0435\u043d\u0443 example.org \u0438 \u0432\u0441\u0435\u043c \u0435\u0433\u043e \u043f\u043e\u0434\u0434\u043e\u043c\u0435\u043d\u0430\u043c",
"example_meaning_filter_whitelist": "\u0440\u0430\u0437\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f \u043a \u0434\u043e\u043c\u0435\u043d\u0443 example.org \u0438 \u0432\u0441\u0435\u043c \u0435\u0433\u043e \u043f\u043e\u0434\u0434\u043e\u043c\u0435\u043d\u0430\u043c",
"example_meaning_host_block": "\u0422\u0435\u043f\u0435\u0440\u044c AdGuard Home \u0432\u0435\u0440\u043d\u0435\u0442 127.0.0.1 \u0434\u043b\u044f \u0434\u043e\u043c\u0435\u043d\u0430 example.org (\u043d\u043e \u043d\u0435 \u0434\u043b\u044f \u0435\u0433\u043e \u043f\u043e\u0434\u0434\u043e\u043c\u0435\u043d\u043e\u0432).",
"example_comment": "! \u0422\u0430\u043a \u043c\u043e\u0436\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u044f\u0442\u044c \u043e\u043f\u0438\u0441\u0430\u043d\u0438\u0435",
"example_comment_meaning": "\u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0439",
"example_comment_hash": "# \u0418 \u0432\u043e\u0442 \u0442\u0430\u043a \u0442\u043e\u0436\u0435",
"example_upstream_regular": "\u043e\u0431\u044b\u0447\u043d\u044b\u0439 DNS (\u043f\u043e\u0432\u0435\u0440\u0445 UDP)",
"example_upstream_dot": "\u0437\u0430\u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_TLS' target='_blank'>DNS-\u043f\u043e\u0432\u0435\u0440\u0445-TLS<\/a>",
"example_upstream_doh": "\u0437\u0430\u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>DNS-\u043f\u043e\u0432\u0435\u0440\u0445-HTTPS<\/a>",
"example_upstream_sdns": "\u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c <a href='https:\/\/dnscrypt.info\/stamps\/' target='_blank'>DNS Stamps<\/a> \u0434\u043b\u044f <a href='https:\/\/dnscrypt.info\/' target='_blank'>DNSCrypt<\/a> \u0438\u043b\u0438 <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>DNS-over-HTTPS<\/a> \u0440\u0435\u0437\u043e\u043b\u0432\u0435\u0440\u043e\u0432",
"example_upstream_tcp": "\u043e\u0431\u044b\u0447\u043d\u044b\u0439 DNS (\u043f\u043e\u0432\u0435\u0440\u0445 TCP)",
"all_filters_up_to_date_toast": "\u0412\u0441\u0435 \u0444\u0438\u043b\u044c\u0442\u0440\u044b \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u044b",
"updated_upstream_dns_toast": "Upstream DNS-\u0441\u0435\u0440\u0432\u0435\u0440\u044b \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u044b",
"dns_test_ok_toast": "\u0423\u043a\u0430\u0437\u0430\u043d\u043d\u044b\u0435 \u0441\u0435\u0440\u0432\u0435\u0440\u044b DNS \u0440\u0430\u0431\u043e\u0442\u0430\u044e\u0442 \u043a\u043e\u0440\u0440\u0435\u043a\u0442\u043d\u043e",
"dns_test_not_ok_toast": "\u0421\u0435\u0440\u0432\u0435\u0440 \"{{key}}\": \u043d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c, \u043f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u043f\u0440\u0430\u0432\u0438\u043b\u044c\u043d\u043e\u0441\u0442\u044c \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u0438\u044f",
"unblock_btn": "\u0420\u0430\u0437\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u0442\u044c",
"block_btn": "\u0417\u0430\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u0442\u044c",
"time_table_header": "\u0412\u0440\u0435\u043c\u044f",
"domain_name_table_header": "\u0414\u043e\u043c\u0435\u043d",
"type_table_header": "\u0422\u0438\u043f",
"response_table_header": "\u041e\u0442\u0432\u0435\u0442",
"client_table_header": "\u041a\u043b\u0438\u0435\u043d\u0442",
"empty_response_status": "\u041f\u0443\u0441\u0442\u043e",
"show_all_filter_type": "\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u0432\u0441\u0435",
"show_filtered_type": "\u041f\u043e\u043a\u0430\u0437\u0430\u0442\u044c \u043e\u0442\u0444\u0438\u043b\u044c\u0442\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435",
"no_logs_found": "\u041b\u043e\u0433\u0438 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b",
"disabled_log_btn": "\u0416\u0443\u0440\u043d\u0430\u043b \u0444\u0438\u043b\u044c\u0442\u0440\u0430\u0446\u0438\u0438 \u0432\u044b\u043a\u043b.",
"download_log_file_btn": "\u0417\u0430\u0433\u0440\u0443\u0437\u0438\u0442\u044c \u043e\u0442\u0447\u0451\u0442",
"refresh_btn": "\u041e\u0431\u043d\u043e\u0432\u0438\u0442\u044c",
"enabled_log_btn": "\u0416\u0443\u0440\u043d\u0430\u043b \u0444\u0438\u043b\u044c\u0442\u0440\u0430\u0446\u0438\u0438 \u0432\u043a\u043b.",
"last_dns_queries": "\u041f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0435 5000 DNS-\u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432",
"previous_btn": "\u041d\u0430\u0437\u0430\u0434",
"next_btn": "\u0412\u043f\u0435\u0440\u0451\u0434",
"loading_table_status": "\u0417\u0430\u0433\u0440\u0443\u0437\u043a\u0430...",
"page_table_footer_text": "\u0421\u0442\u0440\u0430\u043d\u0438\u0446\u0430",
"of_table_footer_text": "\u0438\u0437",
"rows_table_footer_text": "\u0441\u0442\u0440\u043e\u043a",
"updated_custom_filtering_toast": "\u0412\u043d\u0435\u0441\u0435\u043d\u044b \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0432 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u0438\u0435 \u043f\u0440\u0430\u0432\u0438\u043b\u0430",
"rule_removed_from_custom_filtering_toast": "\u041f\u0440\u0430\u0432\u0438\u043b\u043e \u0443\u0434\u0430\u043b\u0435\u043d\u043e \u0438\u0437 \u0430\u0432\u0442\u043e\u0440\u0441\u043a\u043e\u0433\u043e \u0441\u043f\u0438\u0441\u043a\u0430 \u043f\u0440\u0430\u0432\u0438\u043b \u0444\u0438\u043b\u044c\u0442\u0440\u0430\u0446\u0438\u0438",
"rule_added_to_custom_filtering_toast": "\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c\u0441\u043a\u043e\u0435 \u043f\u0440\u0430\u0432\u0438\u043b\u043e \u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043e",
"query_log_disabled_toast": "\u0416\u0443\u0440\u043d\u0430\u043b \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u0432\u044b\u043a\u043b.",
"query_log_enabled_toast": "\u0416\u0443\u0440\u043d\u0430\u043b \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u0432 \u0432\u043a\u043b.",
"source_label": "\u0418\u0441\u0442\u043e\u0447\u043d\u0438\u043a",
"found_in_known_domain_db": "\u041d\u0430\u0439\u0434\u0435\u043d \u0432 \u0431\u0430\u0437\u0435 \u0438\u0437\u0432\u0435\u0441\u0442\u043d\u044b\u0445 \u0434\u043e\u043c\u0435\u043d\u043e\u0432.",
"category_label": "\u041a\u0430\u0442\u0435\u0433\u043e\u0440\u0438\u044f",
"rule_label": "\u041f\u0440\u0430\u0432\u0438\u043b\u043e",
"filter_label": "\u0424\u0438\u043b\u044c\u0442\u0440",
"unknown_filter": "\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u044b\u0439 \u0444\u0438\u043b\u044c\u0442\u0440 {{filterId}}"
}

View File

@@ -0,0 +1,160 @@
{
"url_added_successfully": "URL tillagd utan fel",
"check_dhcp_servers": "Letar efter DHCP-servrar",
"save_config": "Spara inst\u00e4llningar",
"enabled_dhcp": "DHCP-server aktiverad",
"disabled_dhcp": "Dhcp-server avaktiverad",
"dhcp_title": "DHCP-server (experimentell)",
"dhcp_description": "Om din router inte har inst\u00e4llningar f\u00f6r DHCP kan du anv\u00e4nda AdGuards inbyggda server.",
"dhcp_enable": "Aktivera DHCP.-server",
"dhcp_disable": "Avaktivera DHCP-server",
"dhcp_not_found": "Ingen aktiv DHCP-server hittades i n\u00e4tverkat.",
"dhcp_found": "N\u00e5gra aktiva DHCP-servar uppt\u00e4cktes. Det \u00e4r inte s\u00e4kert att aktivera inbyggda DHCP-servrar.",
"dhcp_leases": "DHCP-lease",
"dhcp_leases_not_found": "Ingen DHCP-lease hittad",
"dhcp_config_saved": "Sparade inst\u00e4llningar f\u00f6r DHCP-servern",
"form_error_required": "Obligatoriskt f\u00e4lt",
"form_error_ip_format": "Ogiltigt IPv4-format",
"form_error_positive": "M\u00e5ste vara st\u00f6rre \u00e4n noll",
"dhcp_form_gateway_input": "Gateway-IP",
"dhcp_form_subnet_input": "Subnetmask",
"dhcp_form_range_title": "IP-adressgr\u00e4nser",
"dhcp_form_range_start": "Startgr\u00e4ns",
"dhcp_form_range_end": "Gr\u00e4nsslut",
"dhcp_form_lease_title": "DHCP-leasetid (i sekunder)",
"dhcp_form_lease_input": "Leasetid",
"dhcp_interface_select": "V\u00e4lj DHCP-gr\u00e4nssnitt",
"dhcp_hardware_address": "H\u00e5rdvaruadress",
"dhcp_ip_addresses": "IP-adresser",
"dhcp_table_hostname": "V\u00e4rdnamn",
"dhcp_table_expires": "Utg\u00e5r",
"back": "Tiilbaka",
"dashboard": "Kontrollpanel",
"settings": "Inst\u00e4llningar",
"filters": "Filter",
"query_log": "F\u00f6rfr\u00e5gningslogg",
"faq": "FAQ",
"version": "version",
"address": "adress",
"on": "P\u00c5",
"off": "AV",
"copyright": "Copyright",
"homepage": "Hemsida",
"report_an_issue": "Rapportera ett problem",
"enable_protection": "Koppla p\u00e5 skydd",
"enabled_protection": "Kopplade p\u00e5 skydd",
"disable_protection": "Koppla bort skydd",
"disabled_protection": "Kopplade bort skydd",
"refresh_statics": "Uppdatera statistik",
"dns_query": "DNS-f\u00f6rfr\u00e5gningar",
"blocked_by": "Blockerat av filter",
"stats_malware_phishing": "Blockerad skadekod\/phising",
"stats_adult": "Blockerade vuxensajter",
"stats_query_domain": "Mest efters\u00f6kta dom\u00e4ner",
"for_last_24_hours": "under de senaste 24 timamrna",
"no_domains_found": "Inga dom\u00e4ner hittade",
"requests_count": "F\u00f6rfr\u00e5gningsantal",
"top_blocked_domains": "Flest blockerade dom\u00e4ner",
"top_clients": "Toppklienter",
"no_clients_found": "Inga hitatde klienter",
"general_statistics": "Allm\u00e4n statistik",
"number_of_dns_query_24_hours": "Ett antal DNS-f\u00f6rfr\u00e5gningar utf\u00f6rdes under de senaste 244 timamrna",
"number_of_dns_query_blocked_24_hours": "Ett antal DNS-f\u00f6rfr\u00e5gningar blockerades av annonsfilter och v\u00e4rdens bloceringsklistor",
"number_of_dns_query_blocked_24_hours_by_sec": "Ett antal DNS-f\u00f6rfr\u00e5gningar blockerades av AdGuards modul f\u00f6r surfs\u00e4kerhet",
"number_of_dns_query_blocked_24_hours_adult": "Ett anta vuxensajter blockerades",
"enforced_save_search": "Aktivering av S\u00e4ker surf",
"number_of_dns_query_to_safe_search": "Ett antal DNS-f\u00f6rfr\u00e5gningar genomf\u00f6rdes p\u00e5 s\u00f6kmotorer med S\u00e4ker surf aktiverat",
"average_processing_time": "Genomsnittlig processtid",
"average_processing_time_hint": "Genomsnittlig processtid i millisekunder f\u00f6r DNS-f\u00f6rfr\u00e5gning",
"block_domain_use_filters_and_hosts": "Blockera dom\u00e4ner med filter- och v\u00e4rdfiler",
"filters_block_toggle_hint": "Du kan st\u00e4lla in egna blockerings regler i <a href='#filters'>Filterinst\u00e4llningar<\/a>.",
"use_adguard_browsing_sec": "Amv\u00e4nd AdGuards webbservice f\u00f6r surfs\u00e4kerhet",
"use_adguard_browsing_sec_hint": "AdGuard Home kommer att kontrollera om en dom\u00e4n \u00e4r svartlistad i webbservicens surfs\u00e4kerhet. Med en integritetsv\u00e4nlig metod g\u00f6rs en API-lookup f\u00f6r att kontrollera : endast en kort prefix i dom\u00e4nnamnet SHA256 hash skickas till servern.",
"use_adguard_parental": "Anv\u00e4nda AdGuards webbservice f\u00f6r f\u00e4r\u00e4ldrakontroll",
"use_adguard_parental_hint": "AdGuard Home kommer att kontrollera dom\u00e4ner f\u00f6r inneh\u00e5ll av vuxenmaterial . Samma integritetsv\u00e4nliga metod f\u00f6r API-lookup som till\u00e4mpas i webbservicens surfs\u00e4kerhet anv\u00e4nds.",
"enforce_safe_search": "Till\u00e4mpa S\u00e4ker surf",
"enforce_save_search_hint": "AdGuard Home kan framtvinga s\u00e4ker surf i f\u00f6ljande s\u00f6kmoterer: Google, Youtube, Bing, och Yandex.",
"no_servers_specified": "Inga servrar angivna",
"no_settings": "Inga inst\u00e4llningar",
"general_settings": "Allm\u00e4nna inst\u00e4llningar",
"upstream_dns": "Upstream DNS-servrar",
"upstream_dns_hint": "Om du l\u00e5ter f\u00e4ltet vara tomt kommer AdGuard Home att anv\u00e4nda <a href='https:\/\/1.1.1.1\/' target='_blank'>Cloudflare DNS<\/a> f\u00f6r upstream. Anv\u00e4nd tls:\/\/ prefix f\u00f6r DNS \u00f6ver TLS-servrar.",
"test_upstream_btn": "Testa uppstr\u00f6mmar",
"apply_btn": "Till\u00e4mpa",
"disabled_filtering_toast": "Filtrering bortkopplad",
"enabled_filtering_toast": "Filtrering inkopplad",
"disabled_safe_browsing_toast": "S\u00e4ker surfning bortkopplat",
"enabled_safe_browsing_toast": "S\u00e4ker surfning inkopplat",
"disabled_parental_toast": "F\u00f6r\u00e4ldrakontroll bortkopplat",
"enabled_parental_toast": "F\u00f6r\u00e4ldrakontroll inkopplat",
"disabled_safe_search_toast": "S\u00e4ker webbs\u00f6kning bortkopplat",
"enabled_save_search_toast": "S\u00e4ker webbs\u00f6kning inkopplat",
"enabled_table_header": "Inkopplat",
"name_table_header": "Namn",
"filter_url_table_header": "Filtrerar URL",
"rules_count_table_header": "Regelantal",
"last_time_updated_table_header": "Uppdaterades senast",
"actions_table_header": "\u00c5tg\u00e4rder",
"delete_table_action": "Ta bort",
"filters_and_hosts": "Filtrerings- och v\u00e4rdlistor f\u00f6r blockering",
"filters_and_hosts_hint": "AdGuard till\u00e4mpar grundl\u00e4ggande annonsblockeringsregler och v\u00e4rdfiltersyntaxer",
"no_filters_added": "Inga filter tillagda",
"add_filter_btn": "L\u00e4gg till filter",
"cancel_btn": "Avbryt",
"enter_name_hint": "Skriv in namn",
"enter_url_hint": "Skriv in URL",
"check_updates_btn": "S\u00f6k efter uppdateringar",
"new_filter_btn": "Nytt filterabonemang",
"enter_valid_filter_url": "Skriv in en giltigt URL till ett filterabonnemang eller v\u00e4rdfil.",
"custom_filter_rules": "Egna filterregler",
"custom_filter_rules_hint": "Skriv en regel per rad. Du kan anv\u00e4nda antingen annonsblockeringsregler eller v\u00e4rdfilssyntax.",
"examples_title": "Exempel",
"example_meaning_filter_block": "blockera \u00e5tkomst till dom\u00e4n example.org domain och alla dess subdom\u00e4ner",
"example_meaning_filter_whitelist": "avblockera \u00e5tkomst till dom\u00e4n example.org domain och alla dess subdom\u00e4ner",
"example_meaning_host_block": "AdGuard Home kommer nu att returnera adress 127.0.0.1 f\u00f6r dom\u00e4nexemplet example.org (dock utan dess subdom\u00e4ner).",
"example_comment": "! H\u00e4r kommer en kommentar",
"example_comment_meaning": "Endast en kommentar",
"example_comment_hash": "# Ocks\u00e5 en kommentar",
"example_upstream_regular": "vanlig DNS (\u00f6ver UDP)",
"example_upstream_dot": "krypterat <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_TLS' target='_blank'>DNS-over-TLS<\/a>",
"example_upstream_doh": "krypterat <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>DNS-over-HTTPS<\/a>",
"example_upstream_sdns": "Du kan anv\u00e4nda <a href='https:\/\/dnscrypt.info\/stamps\/' target='_blank'>DNS-stamps<\/a> f\u00f6r <a href='https:\/\/dnscrypt.info\/' target='_blank'>DNSCrypt<\/a> eller <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>DNS-\u00f6ver-HTTPS<\/a>\n-resolvers",
"example_upstream_tcp": "vanlig DNS (\u00f6ver UDP)",
"all_filters_up_to_date_toast": "Alla filter \u00e4r redan aktuella",
"updated_upstream_dns_toast": "Uppdaterade uppstr\u00f6ms-dns-servrar",
"dns_test_ok_toast": "Angivna DNS servrar fungerar korrekt",
"dns_test_not_ok_toast": "Server \"{{key}}\": kunde inte anv\u00e4ndas. Var sn\u00e4ll och kolla att du skrivit in r\u00e4tt",
"unblock_btn": "Avblockera",
"block_btn": "Blockera",
"time_table_header": "Tid",
"domain_name_table_header": "Dom\u00e4nnamn",
"type_table_header": "Typ",
"response_table_header": "Svar",
"client_table_header": "Klient",
"empty_response_status": "Tomt",
"show_all_filter_type": "Visa alla",
"show_filtered_type": "Visa filtrerade",
"no_logs_found": "Inga logga funna",
"disabled_log_btn": "Koppla bort logg",
"download_log_file_btn": "Ladda ner loggfil",
"refresh_btn": "L\u00e4s in igen",
"enabled_log_btn": "Koppla in logg",
"last_dns_queries": "De senaste 5000 DNS-anropen",
"previous_btn": "F\u00f6reg\u00e5ende",
"next_btn": "N\u00e4sta",
"loading_table_status": "L\u00e4ser in...",
"page_table_footer_text": "Sida",
"of_table_footer_text": "av",
"rows_table_footer_text": "rader",
"updated_custom_filtering_toast": "Uppdaterade de egna filterreglerna",
"rule_removed_from_custom_filtering_toast": "Regel borttagen fr\u00e5n de egna filterreglerna",
"rule_added_to_custom_filtering_toast": "Regel tillagd till de egna filterreglerna",
"query_log_disabled_toast": "F\u00f6rfr\u00e5gningsloggen bortkopplad",
"query_log_enabled_toast": "F\u00f6rfr\u00e5gningsloggen inkopplad",
"source_label": "K\u00e4lla",
"found_in_known_domain_db": "Hittad i dom\u00e4ndatabas.",
"category_label": "Kategori",
"rule_label": "Regel",
"filter_label": "Filter",
"unknown_filter": "Ok\u00e4nt filter {{filterId}}"
}

View File

@@ -0,0 +1,158 @@
{
"check_dhcp_servers": "Ki\u1ec3m tra m\u00e1y ch\u1ee7 DHCP",
"save_config": "L\u01b0u thi\u1ebft l\u1eadp",
"enabled_dhcp": "M\u00e1y ch\u1ee7 DHCP \u0111\u00e3 k\u00edch ho\u1ea1t",
"disabled_dhcp": "M\u00e1y ch\u1ee7 DHCP \u0111\u00e3 t\u1eaft",
"dhcp_title": "M\u00e1y ch\u1ee7 DHCP (th\u1eed nghi\u1ec7m!)",
"dhcp_description": "N\u1ebfu b\u1ed9 \u0111\u1ecbnh tuy\u1ebfn kh\u00f4ng tr\u1ee3 c\u00e0i \u0111\u1eb7t DHCP, b\u1ea1n c\u00f3 th\u1ec3 d\u00f9ng m\u00e1y ch\u1ee7 DHCP d\u1ef1ng s\u1eb5n c\u1ee7a AdGuard",
"dhcp_enable": "B\u1eadt m\u00e1y ch\u1ee7 DHCP",
"dhcp_disable": "T\u1eaft m\u00e1y ch\u1ee7 DHCP",
"dhcp_not_found": "Kh\u00f4ng c\u00f3 m\u00e1y ch\u1ee7 DHCP n\u00e0o \u0111\u01b0\u1ee3c t\u00ecm th\u1ea5y trong m\u1ea1ng. C\u00f3 th\u1ec3 b\u1eadt m\u00e1y ch\u1ee7 DHCP m\u1ed9t c\u00e1ch an to\u00e0n",
"dhcp_found": "\u0110\u00e3 t\u00ecm th\u1ea5y m\u00e1y ch\u1ee7 DHCP trong m\u1ea1ng. C\u00f3 th\u1ec3 c\u00f3 r\u1ee7i ro n\u1ebfu k\u00edch ho\u1ea1t m\u00e1y ch\u1ee7 DHCP d\u1ef1ng s\u1eb5n",
"dhcp_leases": "DHCP leases",
"dhcp_leases_not_found": "No DHCP leases found",
"dhcp_config_saved": "Saved DHCP server config",
"form_error_required": "Required field",
"form_error_ip_format": "Invalid IPv4 format",
"form_error_positive": "Ph\u1ea3i l\u1edbn h\u01a1n 0",
"dhcp_form_gateway_input": "Gateway IP",
"dhcp_form_subnet_input": "Subnet mask",
"dhcp_form_range_title": "Range of IP addresses",
"dhcp_form_range_start": "Range start",
"dhcp_form_range_end": "IP k\u1ebft th\u00fac",
"dhcp_form_lease_title": "DHCP lease time (in seconds)",
"dhcp_form_lease_input": "Lease duration",
"dhcp_interface_select": "Ch\u1ecdn m\u1ed9t card m\u1ea1ng",
"dhcp_hardware_address": "Hardware address",
"dhcp_ip_addresses": "IP addresses",
"back": "Quay l\u1ea1i",
"dashboard": "T\u1ed5ng quan",
"settings": "C\u00e0i \u0111\u1eb7t",
"filters": "B\u1ed9 l\u1ecdc",
"query_log": "L\u1ecbch s\u1eed truy v\u1ea5n",
"faq": "H\u1ecfi \u0111\u00e1p",
"version": "phi\u00ean b\u1ea3n",
"address": "\u0111\u1ecba ch\u1ec9",
"on": "\u0110ang b\u1eadt",
"off": "\u0110ang t\u1eaft",
"copyright": "B\u1ea3n quy\u1ec1n",
"homepage": "Trang ch\u1ee7",
"report_an_issue": "B\u00e1o l\u1ed7i",
"enable_protection": "B\u1eadt b\u1ea3o v\u1ec7",
"enabled_protection": "\u0110\u00e3 b\u1eadt b\u1ea3o v\u1ec7",
"disable_protection": "T\u1eaft b\u1ea3o v\u1ec7",
"disabled_protection": "\u0110\u00e3 t\u1eaft b\u1ea3o v\u1ec7",
"refresh_statics": "L\u00e0m m\u1edbi th\u1ed1ng k\u00ea",
"dns_query": "Truy v\u1ea5n DNS",
"blocked_by": "Ch\u1eb7n b\u1edfi b\u1ed9 l\u1ecdc",
"stats_malware_phishing": "M\u00e3 \u0111\u1ed9c\/l\u1eeba \u0111\u1ea3o \u0111\u00e3 ch\u1eb7n",
"stats_adult": "Website ng\u01b0\u1eddi l\u1edbn \u0111\u00e3 ch\u1eb7n",
"stats_query_domain": "T\u00ean mi\u1ec1n truy v\u1ea5n nhi\u1ec1u",
"for_last_24_hours": "trong 24 gi\u1edd qua",
"no_domains_found": "Kh\u00f4ng c\u00f3 t\u00ean mi\u1ec1n n\u00e0o",
"requests_count": "S\u1ed1 l\u1ea7n y\u00eau c\u1ea7u",
"top_blocked_domains": "T\u00ean mi\u1ec1n ch\u1eb7n nhi\u1ec1u",
"top_clients": "Client d\u00f9ng nhi\u1ec1u",
"no_clients_found": "Kh\u00f4ng c\u00f3 client n\u00e0o",
"general_statistics": "Th\u1ed1ng k\u00ea chung",
"number_of_dns_query_24_hours": "S\u1ed1 y\u00eau c\u1ea7u DNS \u0111\u00e3 x\u1eed l\u00fd trong 24 gi\u1edd qua",
"number_of_dns_query_blocked_24_hours": "S\u1ed1 y\u00eau c\u1ea7u DNS b\u1ecb ch\u1eb7n b\u1edfi b\u1ed9 l\u1ecdc qu\u1ea3ng c\u00e1o v\u00e0 danh s\u00e1ch ch\u1eb7n host",
"number_of_dns_query_blocked_24_hours_by_sec": "S\u1ed1 y\u00eau c\u1ea7u DNS b\u1ecb ch\u1eb7n b\u1edfi ch\u1ebf \u0111\u1ed9 b\u1ea3o v\u1ec7 duy\u1ec7t web AdGuard",
"number_of_dns_query_blocked_24_hours_adult": "S\u1ed1 website ng\u01b0\u1eddi l\u1edbn \u0111\u00e3 ch\u1eb7n",
"enforced_save_search": "T\u00ecm ki\u1ebfm an to\u00e0n",
"number_of_dns_query_to_safe_search": "S\u1ed1 y\u00eau c\u1ea7u DNS t\u1edbi c\u00f4ng c\u1ee5 t\u00ecm ki\u1ebfm \u0111\u00e3 chuy\u1ec3n th\u00e0nh t\u00ecm ki\u1ebfm an to\u00e0n",
"average_processing_time": "Th\u1eddi gian x\u1eed l\u00fd trung b\u00ecnh",
"average_processing_time_hint": "Th\u1eddi gian trung b\u00ecnh cho m\u1ed9t y\u00eau c\u1ea7u DNS t\u00ednh b\u1eb1ng mili gi\u00e2y",
"block_domain_use_filters_and_hosts": "Ch\u1eb7n t\u00ean mi\u1ec1n s\u1eed d\u1ee5ng c\u00e1c b\u1ed9 l\u1ecdc v\u00e0 file hosts",
"filters_block_toggle_hint": "B\u1ea1n c\u00f3 th\u1ec3 thi\u1ebft l\u1eadp quy t\u1eafc ch\u1eb7n t\u1ea1i c\u00e0i \u0111\u1eb7t <a href='#filters'>B\u1ed9 l\u1ecdc<\/a>.",
"use_adguard_browsing_sec": "S\u1eed d\u1ee5ng d\u1ecbch v\u1ee5 b\u1ea3o v\u1ec7 duy\u1ec7t web AdGuard",
"use_adguard_browsing_sec_hint": "AdGuard Home s\u1ebd ki\u1ec3m tra t\u00ean mi\u1ec1n v\u1edbi d\u1ecbch v\u1ee5 b\u1ea3o v\u1ec7 duy\u1ec7t web. T\u00ednh n\u0103ng s\u1eed d\u1ee5ng m\u1ed9t API th\u00e2n thi\u1ec7n v\u1edbi quy\u1ec1n ri\u00eang t\u01b0: ch\u1ec9 m\u1ed9t ph\u1ea7n ng\u1eafn ti\u1ec1n t\u1ed1 m\u00e3 b\u0103m SHA256 \u0111\u01b0\u1ee3c g\u1eedi \u0111\u1ebfn m\u00e1y ch\u1ee7",
"use_adguard_parental": "S\u1eed d\u1ee5ng d\u1ecbch v\u1ee5 qu\u1ea3n l\u00fd c\u1ee7a ph\u1ee5 huynh AdGuard",
"use_adguard_parental_hint": "AdGuard Home s\u1ebd ki\u1ec3m tra n\u1ebfu t\u00ean mi\u1ec1n ch\u1ee9a t\u1eeb kho\u00e1 ng\u01b0\u1eddi l\u1edbn. T\u00ednh n\u0103ng s\u1eed d\u1ee5ng API th\u00e2n thi\u1ec7n v\u1edbi quy\u1ec1n ri\u00eang t\u01b0 t\u01b0\u01a1ng t\u1ef1 v\u1edbi d\u1ecbch v\u1ee5 b\u1ea3o v\u1ec7 duy\u1ec7t web",
"enforce_safe_search": "B\u1eaft bu\u1ed9c t\u00ecm ki\u1ebfm an to\u00e0n",
"enforce_save_search_hint": "AdGuard Home c\u00f3 th\u1ec3 b\u1eaft bu\u1ed9c t\u00ecm ki\u1ebfm an to\u00e0n v\u1edbi c\u00e1c d\u1ecbch v\u1ee5 t\u00ecm ki\u1ebfm: Google, Youtube, Bing, Yandex.",
"no_servers_specified": "Kh\u00f4ng c\u00f3 m\u00e1y ch\u1ee7 n\u00e0o \u0111\u01b0\u1ee3c li\u1ec7t k\u00ea",
"no_settings": "Kh\u00f4ng c\u00f3 c\u00e0i \u0111\u1eb7t n\u00e0o",
"general_settings": "C\u00e0i \u0111\u1eb7t chung",
"upstream_dns": "M\u00e1y ch\u1ee7 DNS t\u00ecm ki\u1ebfm",
"upstream_dns_hint": "N\u1ebfu b\u1ea1n \u0111\u1ec3 tr\u1ed1ng m\u1ee5c n\u00e0y, AdGuard Home s\u1ebd s\u1eed d\u1ee5ng <a href='https:\/\/1.1.1.1\/' target='_blank'>Cloudflare DNS<\/a> \u0111\u1ec3 t\u00ecm ki\u1ebfm. S\u1eed d\u1ee5ng ti\u1ec1n t\u1ed1 tls:\/\/ cho c\u00e1c m\u00e1y ch\u1ee7 DNS d\u1ef1a tr\u00ean TLS.",
"test_upstream_btn": "Ki\u1ec3m tra",
"apply_btn": "\u00c1p d\u1ee5ng",
"disabled_filtering_toast": "\u0110\u00e3 t\u1eaft ch\u1eb7n qu\u1ea3ng c\u00e1o",
"enabled_filtering_toast": "\u0110\u00e3 b\u1eadt ch\u1eb7n qu\u1ea3ng c\u00e1o",
"disabled_safe_browsing_toast": "\u0110\u00e3 t\u1eaft b\u1ea3o v\u1ec7 duy\u1ec7t web",
"enabled_safe_browsing_toast": "\u0110\u00e3 b\u1eadt b\u1ea3o v\u1ec7 duy\u1ec7t web",
"disabled_parental_toast": "\u0110\u00e3 t\u1eaft qu\u1ea3n l\u00fd c\u1ee7a ph\u1ee5 huynh",
"enabled_parental_toast": "\u0110\u00e3 b\u1eadt qu\u1ea3n l\u00fd c\u1ee7a ph\u1ee5 huynh",
"disabled_safe_search_toast": "\u0110\u00e3 t\u1eaft t\u00ecm ki\u1ebfm an to\u00e0n",
"enabled_save_search_toast": "\u0110\u00e3 b\u1eadt t\u00ecm ki\u1ebfm an to\u00e0n",
"enabled_table_header": "K\u00edch ho\u1ea1t",
"name_table_header": "T\u00ean",
"filter_url_table_header": "URL b\u1ed9 l\u1ecdc",
"rules_count_table_header": "S\u1ed1 quy t\u1eafc",
"last_time_updated_table_header": "C\u1eadp nh\u1eadt cu\u1ed1i",
"actions_table_header": "Thao t\u00e1c",
"delete_table_action": "Xo\u00e1",
"filters_and_hosts": "Danh s\u00e1ch b\u1ed9 l\u1ecdc v\u00e0 hosts",
"filters_and_hosts_hint": "AdGuard home hi\u1ec3u c\u00e1c quy t\u1eafc ch\u1eb7n qu\u1ea3ng c\u00e1o \u0111\u01a1n gi\u1ea3n v\u00e0 c\u00fa ph\u00e1p file hosts",
"no_filters_added": "Kh\u00f4ng c\u00f3 b\u1ed9 l\u1ecdc n\u00e0o \u0111\u01b0\u1ee3c th\u00eam",
"add_filter_btn": "Th\u00eam b\u1ed9 l\u1ecdc",
"cancel_btn": "Hu\u1ef7",
"enter_name_hint": "Nh\u1eadp t\u00ean",
"enter_url_hint": "Nh\u1eadp URL",
"check_updates_btn": "Ki\u1ec3m tra c\u1eadp nh\u1eadt",
"new_filter_btn": "\u0110\u0103ng k\u00fd b\u1ed9 l\u1ecdc m\u1edbi",
"enter_valid_filter_url": "Nh\u1eadp URL h\u1ee3p l\u1ec7 c\u1ee7a b\u1ed9 l\u1ecdc ho\u1eb7c file hosts",
"custom_filter_rules": "Quy t\u1eafc l\u1ecdc tu\u1ef3 ch\u1ec9nh",
"custom_filter_rules_hint": "Nh\u1eadp m\u1ed7i quy t\u1eafc 1 d\u00f2ng. C\u00f3 th\u1ec3 s\u1eed d\u1ee5ng quy t\u1eafc ch\u1eb7n qu\u1ea3ng c\u00e1o ho\u1eb7c c\u00fa ph\u00e1p file host",
"examples_title": "V\u00ed d\u1ee5",
"example_meaning_filter_block": "Ch\u1eb7n truy c\u1eadp t\u1edbi t\u00ean mi\u1ec1n example.org v\u00e0 t\u1ea5t c\u1ea3 t\u00ean mi\u1ec1n con",
"example_meaning_filter_whitelist": "Kh\u00f4ng ch\u1eb7n truy c\u1eadp t\u1edbi t\u00ean mi\u1ec1n example.org v\u00e0 t\u1ea5t c\u1ea3 t\u00ean mi\u1ec1n con",
"example_meaning_host_block": "AdGuard Home s\u1ebd ph\u1ea3n h\u1ed3i \u0111\u1ecba ch\u1ec9 IP 127.0.0.1 cho t\u00ean mi\u1ec1n example.org (kh\u00f4ng \u00e1p d\u1ee5ng t\u00ean mi\u1ec1n con)",
"example_comment": "! \u0110\u00e2y l\u00e0 m\u1ed9t ch\u00fa th\u00edch",
"example_comment_meaning": "Ch\u1ec9 l\u00e0 m\u1ed9t ch\u00fa th\u00edch",
"example_comment_hash": "# C\u0169ng l\u00e0 m\u1ed9t ch\u00fa th\u00edch",
"example_upstream_regular": "DNS th\u00f4ng th\u01b0\u1eddng (d\u00f9ng UDP)",
"example_upstream_dot": "\u0111\u01b0\u1ee3c m\u00e3 ho\u00e1 <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_TLS' target='_blank'>DNS-over-TLS<\/a>",
"example_upstream_doh": "\u0111\u01b0\u1ee3c m\u00e3 ho\u00e1 <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>DNS-over-HTTPS<\/a>",
"example_upstream_sdns": "b\u1ea1n c\u00f3 th\u1ec3 s\u1eed d\u1ee5ng <a href='https:\/\/dnscrypt.info\/stamps\/' target='_blank'>DNS Stamps<\/a> for <a href='https:\/\/dnscrypt.info\/' target='_blank'>DNSCrypt<\/a> ho\u1eb7c<a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>DNS-over-HTTPS<\/a> ",
"example_upstream_tcp": "DNS th\u00f4ng th\u01b0\u1eddng(d\u00f9ng TCP)",
"all_filters_up_to_date_toast": "T\u1ea5t c\u1ea3 b\u1ed9 l\u1ecdc \u0111\u00e3 \u0111\u01b0\u1ee3c c\u1eadp nh\u1eadt",
"updated_upstream_dns_toast": "\u0110\u00e3 c\u1eadp nh\u1eadt m\u00e1y ch\u1ee7 DNS t\u00ecm ki\u1ebfm",
"dns_test_ok_toast": "M\u00e1y ch\u1ee7 DNS c\u00f3 th\u1ec3 s\u1eed d\u1ee5ng",
"dns_test_not_ok_toast": "M\u00e1y ch\u1ee7 '{{key}}': kh\u00f4ng th\u1ec3 s\u1eed d\u1ee5ng, vui l\u00f2ng ki\u1ec3m tra b\u1ea1n \u0111\u00e3 \u0111i\u1ec1n ch\u00ednh x\u00e1c",
"unblock_btn": "B\u1ecf ch\u1eb7n",
"block_btn": "Ch\u1eb7n",
"time_table_header": "Th\u1eddi gian",
"domain_name_table_header": "T\u00ean mi\u1ec1n",
"type_table_header": "Lo\u1ea1i",
"response_table_header": "Ph\u1ea3n h\u1ed3i",
"client_table_header": "Ng\u01b0\u1eddi d\u00f9ng cu\u1ed1i",
"empty_response_status": "R\u1ed7ng",
"show_all_filter_type": "Hi\u1ec7n t\u1ea5t c\u1ea3",
"show_filtered_type": "Ch\u1ec9 hi\u1ec7n \u0111\u00e3 l\u1ecdc",
"no_logs_found": "Kh\u00f4ng c\u00f3 l\u1ecbch s\u1eed truy v\u1ea5n",
"disabled_log_btn": "T\u1eaft l\u1ecbch s\u1eed truy v\u1ea5n",
"download_log_file_btn": "T\u1ea3i t\u1eadp tin l\u1ecbch s\u1eed truy v\u1ea5n",
"refresh_btn": "L\u00e0m m\u1edbi",
"enabled_log_btn": "B\u1eadt l\u1ecbch s\u1eed truy v\u1ea5n",
"last_dns_queries": "5000 truy v\u1ea5n DNS g\u1ea7n nh\u1ea5t",
"previous_btn": "Trang tr\u01b0\u1edbc",
"next_btn": "Trang sau",
"loading_table_status": "\u0110ang t\u1ea3i...",
"page_table_footer_text": "Trang",
"of_table_footer_text": "c\u1ee7a",
"rows_table_footer_text": "h\u00e0ng",
"updated_custom_filtering_toast": "\u0110\u00e3 c\u1eadp nh\u1eadt quy t\u1eafc l\u1ecdc tu\u1ef3 ch\u1ec9nh",
"rule_removed_from_custom_filtering_toast": "Quy t\u1eafc \u0111\u00e3 \u0111\u01b0\u1ee3c xo\u00e1 kh\u1ecfi quy t\u1eafc l\u1ecdc tu\u1ef3 ch\u1ec9nh",
"rule_added_to_custom_filtering_toast": "Quy t\u1eafc \u0111\u00e3 \u0111\u01b0\u1ee3c th\u00eam v\u00e0o quy t\u1eafc l\u1ecdc tu\u1ef3 ch\u1ec9nh",
"query_log_disabled_toast": "\u0110\u00e3 t\u1eaft l\u1ecbch s\u1eed truy v\u1ea5n",
"query_log_enabled_toast": "\u0110\u00e3 b\u1eadt l\u1ecbch s\u1eed truy v\u1ea5n",
"source_label": "Ngu\u1ed3n",
"found_in_known_domain_db": "T\u00ecm th\u1ea5y trong c\u01a1 s\u1edf d\u1eef li\u1ec7u t\u00ean mi\u1ec1n",
"category_label": "Th\u1ec3 lo\u1ea1i",
"rule_label": "Quy t\u1eafc",
"filter_label": "B\u1ed9 l\u1ecdc",
"url_added_successfully": "Th\u00eam b\u1ed9 l\u1ecdc th\u00e0nh c\u00f4ng",
"unknown_filter": "B\u1ed9 l\u1ecdc kh\u00f4ng r\u00f5 {{filterId}}"
}

View File

@@ -0,0 +1,214 @@
{
"url_added_successfully": "\u7db2\u5740\u88ab\u6210\u529f\u5730\u52a0\u5165",
"check_dhcp_servers": "\u6aa2\u67e5\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u4f3a\u670d\u5668",
"save_config": "\u5132\u5b58\u914d\u7f6e",
"enabled_dhcp": "\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u4f3a\u670d\u5668\u88ab\u555f\u7528",
"disabled_dhcp": "\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u4f3a\u670d\u5668\u88ab\u7981\u7528",
"dhcp_title": "\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u4f3a\u670d\u5668\uff08\u5be6\u9a57\u6027\u7684\uff01\uff09",
"dhcp_description": "\u5982\u679c\u60a8\u7684\u8def\u7531\u5668\u672a\u63d0\u4f9b\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u8a2d\u5b9a\uff0c\u60a8\u53ef\u4f7f\u7528AdGuard\u81ea\u8eab\u5167\u5efa\u7684DHCP\u4f3a\u670d\u5668\u3002",
"dhcp_enable": "\u555f\u7528\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u4f3a\u670d\u5668",
"dhcp_disable": "\u7981\u7528\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u4f3a\u670d\u5668",
"dhcp_not_found": "\u65bc\u8a72\u7db2\u8def\u4e0a\u7121\u73fe\u884c\u7684\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u4f3a\u670d\u5668\u88ab\u767c\u73fe\u3002\u555f\u7528\u5167\u5efa\u7684DHCP\u4f3a\u670d\u5668\u70ba\u5b89\u5168\u7684\u3002",
"dhcp_found": "\u65bc\u8a72\u7db2\u8def\u4e0a\u67d0\u4e9b\u73fe\u884c\u7684\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u4f3a\u670d\u5668\u88ab\u767c\u73fe\u3002\u555f\u7528\u5167\u5efa\u7684DHCP\u4f3a\u670d\u5668\u70ba\u4e0d\u5b89\u5168\u7684\u3002",
"dhcp_leases": "\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u79df\u8cc3",
"dhcp_leases_not_found": "\u7121\u5df2\u767c\u73fe\u4e4b\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u79df\u8cc3",
"dhcp_config_saved": "\u5df2\u5132\u5b58\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u4f3a\u670d\u5668\u914d\u7f6e",
"form_error_required": "\u5fc5\u586b\u7684\u6b04\u4f4d",
"form_error_ip_format": "\u7121\u6548\u7684IPv4\u683c\u5f0f",
"form_error_positive": "\u5fc5\u9808\u5927\u65bc0",
"dhcp_form_gateway_input": "\u9598\u9053 IP",
"dhcp_form_subnet_input": "\u5b50\u7db2\u8def\u906e\u7f69",
"dhcp_form_range_title": "IP \u4f4d\u5740\u7bc4\u570d",
"dhcp_form_range_start": "\u7bc4\u570d\u958b\u59cb",
"dhcp_form_range_end": "\u7bc4\u570d\u7d50\u675f",
"dhcp_form_lease_title": "\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u79df\u8cc3\u6642\u9593\uff08\u4ee5\u79d2\u6578\uff09",
"dhcp_form_lease_input": "\u79df\u8cc3\u6301\u7e8c\u6642\u9593",
"dhcp_interface_select": "\u9078\u64c7\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u4ecb\u9762",
"dhcp_hardware_address": "\u786c\u9ad4\u4f4d\u5740",
"dhcp_ip_addresses": "IP \u4f4d\u5740",
"dhcp_table_hostname": "\u4e3b\u6a5f\u540d\u7a31",
"dhcp_table_expires": "\u5230\u671f",
"dhcp_warning": "\u5982\u679c\u60a8\u60f3\u8981\u555f\u7528\u5167\u5efa\u7684\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u4f3a\u670d\u5668\uff0c\u78ba\u4fdd\u7121\u5176\u5b83\u73fe\u884c\u7684DHCP\u4f3a\u670d\u5668\u3002\u5426\u5247\uff0c\u5b83\u53ef\u80fd\u6703\u7834\u58de\u4f9b\u5df2\u9023\u7dda\u7684\u88dd\u7f6e\u4e4b\u7db2\u969b\u7db2\u8def\uff01",
"back": "\u8fd4\u56de",
"dashboard": "\u5100\u8868\u677f",
"settings": "\u8a2d\u5b9a",
"filters": "\u904e\u6ffe\u5668",
"query_log": "\u67e5\u8a62\u8a18\u9304",
"faq": "\u5e38\u898b\u554f\u7b54\u96c6",
"version": "\u7248\u672c",
"address": "\u4f4d\u5740",
"on": "\u958b\u8457",
"off": "\u95dc\u8457",
"copyright": "\u7248\u6b0a",
"homepage": "\u9996\u9801",
"report_an_issue": "\u5831\u544a\u554f\u984c",
"enable_protection": "\u555f\u7528\u9632\u8b77",
"enabled_protection": "\u5df2\u555f\u7528\u9632\u8b77",
"disable_protection": "\u7981\u7528\u9632\u8b77",
"disabled_protection": "\u5df2\u7981\u7528\u9632\u8b77",
"refresh_statics": "\u91cd\u65b0\u6574\u7406\u7d71\u8a08\u8cc7\u6599",
"dns_query": "DNS \u67e5\u8a62",
"blocked_by": "\u5df2\u88ab\u904e\u6ffe\u5668\u5c01\u9396",
"stats_malware_phishing": "\u5df2\u5c01\u9396\u7684\u60e1\u610f\u8edf\u9ad4\/\u7db2\u8def\u91e3\u9b5a",
"stats_adult": "\u5df2\u5c01\u9396\u7684\u6210\u4eba\u7db2\u7ad9",
"stats_query_domain": "\u71b1\u9580\u5df2\u67e5\u8a62\u7684\u7db2\u57df",
"for_last_24_hours": "\u5728\u6700\u8fd1\u768424\u5c0f\u6642\u5167",
"no_domains_found": "\u7121\u5df2\u767c\u73fe\u4e4b\u7db2\u57df",
"requests_count": "\u8acb\u6c42\u7e3d\u6578",
"top_blocked_domains": "\u71b1\u9580\u5df2\u5c01\u9396\u7684\u7db2\u57df",
"top_clients": "\u71b1\u9580\u7528\u6236\u7aef",
"no_clients_found": "\u7121\u5df2\u767c\u73fe\u4e4b\u7528\u6236\u7aef",
"general_statistics": "\u4e00\u822c\u7684\u7d71\u8a08\u8cc7\u6599",
"number_of_dns_query_24_hours": "\u5728\u6700\u8fd1\u768424\u5c0f\u6642\u5167\u5df2\u8655\u7406\u7684DNS\u67e5\u8a62\u4e4b\u6578\u91cf",
"number_of_dns_query_blocked_24_hours": "\u5df2\u88ab\u5ee3\u544a\u5c01\u9396\u904e\u6ffe\u5668\u548c\u4e3b\u6a5f\u5c01\u9396\u6e05\u55ae\u5c01\u9396\u7684DNS\u8acb\u6c42\u4e4b\u6578\u91cf",
"number_of_dns_query_blocked_24_hours_by_sec": "\u5df2\u88abAdGuard\u700f\u89bd\u5b89\u5168\u6a21\u7d44\u5c01\u9396\u7684DNS\u8acb\u6c42\u4e4b\u6578\u91cf",
"number_of_dns_query_blocked_24_hours_adult": "\u5df2\u5c01\u9396\u7684\u6210\u4eba\u7db2\u7ad9\u4e4b\u6578\u91cf",
"enforced_save_search": "\u5df2\u5f37\u5236\u57f7\u884c\u7684\u5b89\u5168\u641c\u5c0b",
"number_of_dns_query_to_safe_search": "\u5c0d\u65bc\u90a3\u4e9b\u5b89\u5168\u641c\u5c0b\u5df2\u88ab\u5f37\u5236\u57f7\u884c\u4e4b\u5c6c\u65bc\u641c\u5c0b\u5f15\u64ce\u7684DNS\u8acb\u6c42\u4e4b\u6578\u91cf",
"average_processing_time": "\u5e73\u5747\u7684\u8655\u7406\u6642\u9593",
"average_processing_time_hint": "\u65bc\u8655\u7406\u4e00\u9805DNS\u8acb\u6c42\u4e0a\u4ee5\u6beb\u79d2\uff08ms\uff09\u8a08\u4e4b\u5e73\u5747\u7684\u6642\u9593",
"block_domain_use_filters_and_hosts": "\u900f\u904e\u904e\u6ffe\u5668\u548c\u4e3b\u6a5f\u6a94\u6848\u5c01\u9396\u7db2\u57df",
"filters_block_toggle_hint": "\u60a8\u53ef\u5728<a href='#filters'>\u904e\u6ffe\u5668<\/a>\u8a2d\u5b9a\u4e2d\u8a2d\u7f6e\u5c01\u9396\u898f\u5247\u3002",
"use_adguard_browsing_sec": "\u4f7f\u7528AdGuard\u700f\u89bd\u5b89\u5168\u7db2\u8def\u670d\u52d9",
"use_adguard_browsing_sec_hint": "AdGuard Home\u5c07\u6aa2\u67e5\u7db2\u57df\u662f\u5426\u88ab\u700f\u89bd\u5b89\u5168\u7db2\u8def\u670d\u52d9\u5217\u5165\u9ed1\u540d\u55ae\u3002\u5b83\u5c07\u4f7f\u7528\u53cb\u597d\u7684\u96b1\u79c1\u67e5\u627e\u61c9\u7528\u7a0b\u5f0f\u4ecb\u9762\uff08API\uff09\u4ee5\u57f7\u884c\u6aa2\u67e5\uff1a\u50c5\u57df\u540dSHA256\u96dc\u6e4a\u7684\u77ed\u524d\u7db4\u88ab\u50b3\u9001\u5230\u4f3a\u670d\u5668\u3002",
"use_adguard_parental": "\u4f7f\u7528AdGuard\u5bb6\u9577\u76e3\u63a7\u4e4b\u7db2\u8def\u670d\u52d9",
"use_adguard_parental_hint": "AdGuard Home\u5c07\u6aa2\u67e5\u7db2\u57df\u662f\u5426\u5305\u542b\u6210\u4eba\u8cc7\u6599\u3002\u5b83\u4f7f\u7528\u5982\u540c\u700f\u89bd\u5b89\u5168\u7db2\u8def\u670d\u52d9\u4e00\u6a23\u4e4b\u53cb\u597d\u7684\u96b1\u79c1\u61c9\u7528\u7a0b\u5f0f\u4ecb\u9762\uff08API\uff09\u3002",
"enforce_safe_search": "\u5f37\u5236\u57f7\u884c\u5b89\u5168\u641c\u5c0b",
"enforce_save_search_hint": "AdGuard Home\u53ef\u5728\u4e0b\u5217\u7684\u641c\u5c0b\u5f15\u64ce\uff1aGoogle\u3001YouTube\u3001Bing\u548cYandex\u4e2d\u5f37\u5236\u57f7\u884c\u5b89\u5168\u641c\u5c0b\u3002",
"no_servers_specified": "\u7121\u5df2\u660e\u78ba\u6307\u5b9a\u7684\u4f3a\u670d\u5668",
"no_settings": "\u7121\u8a2d\u5b9a",
"general_settings": "\u4e00\u822c\u7684\u8a2d\u5b9a",
"upstream_dns": "\u4e0a\u6e38\u7684DNS\u4f3a\u670d\u5668",
"upstream_dns_hint": "\u5982\u679c\u60a8\u4fdd\u7559\u8a72\u6b04\u4f4d\u7a7a\u767d\u7684\uff0cAdGuard Home\u5c07\u4f7f\u7528<a href='https:\/\/1.1.1.1\/' target='_blank'>Cloudflare DNS<\/a>\u4f5c\u70ba\u4e0a\u6e38\u3002\u5c0d\u65bcDNS over TLS\u4f3a\u670d\u5668\u4f7f\u7528 tls:\/\/ \u524d\u7db4\u3002",
"test_upstream_btn": "\u6e2c\u8a66\u4e0a\u884c\u8cc7\u6599\u6d41",
"apply_btn": "\u5957\u7528",
"disabled_filtering_toast": "\u5df2\u7981\u7528\u904e\u6ffe",
"enabled_filtering_toast": "\u5df2\u555f\u7528\u904e\u6ffe",
"disabled_safe_browsing_toast": "\u5df2\u7981\u7528\u5b89\u5168\u700f\u89bd",
"enabled_safe_browsing_toast": "\u5df2\u555f\u7528\u5b89\u5168\u700f\u89bd",
"disabled_parental_toast": "\u5df2\u7981\u7528\u5bb6\u9577\u76e3\u63a7",
"enabled_parental_toast": "\u5df2\u555f\u7528\u5bb6\u9577\u76e3\u63a7",
"disabled_safe_search_toast": "\u5df2\u7981\u7528\u5b89\u5168\u641c\u5c0b",
"enabled_save_search_toast": "\u5df2\u555f\u7528\u5b89\u5168\u641c\u5c0b",
"enabled_table_header": "\u5df2\u555f\u7528\u7684",
"name_table_header": "\u540d\u7a31",
"filter_url_table_header": "\u904e\u6ffe\u5668\u7db2\u5740",
"rules_count_table_header": "\u898f\u5247\u7e3d\u6578",
"last_time_updated_table_header": "\u6700\u8fd1\u7684\u66f4\u65b0\u6642\u9593",
"actions_table_header": "\u884c\u52d5",
"delete_table_action": "\u522a\u9664",
"filters_and_hosts": "\u904e\u6ffe\u5668\u548c\u4e3b\u6a5f\u5c01\u9396\u6e05\u55ae",
"filters_and_hosts_hint": "AdGuard Home\u61c2\u5f97\u57fa\u672c\u7684\u5ee3\u544a\u5c01\u9396\u898f\u5247\u548c\u4e3b\u6a5f\u6a94\u6848\u8a9e\u6cd5\u3002",
"no_filters_added": "\u7121\u5df2\u52a0\u5165\u7684\u904e\u6ffe\u5668",
"add_filter_btn": "\u589e\u52a0\u904e\u6ffe\u5668",
"cancel_btn": "\u53d6\u6d88",
"enter_name_hint": "\u8f38\u5165\u540d\u7a31",
"enter_url_hint": "\u8f38\u5165\u7db2\u5740",
"check_updates_btn": "\u6aa2\u67e5\u66f4\u65b0",
"new_filter_btn": "\u65b0\u7684\u904e\u6ffe\u5668\u8a02\u95b1",
"enter_valid_filter_url": "\u8f38\u5165\u95dc\u65bc\u904e\u6ffe\u5668\u8a02\u95b1\u6216\u4e3b\u6a5f\u6a94\u6848\u4e4b\u6709\u6548\u7684\u7db2\u5740\u3002",
"custom_filter_rules": "\u81ea\u8a02\u7684\u904e\u6ffe\u898f\u5247",
"custom_filter_rules_hint": "\u65bc\u4e00\u884c\u4e0a\u8f38\u5165\u4e00\u500b\u898f\u5247\u3002\u60a8\u53ef\u4f7f\u7528\u5ee3\u544a\u5c01\u9396\u898f\u5247\u6216\u4e3b\u6a5f\u6a94\u6848\u8a9e\u6cd5\u3002",
"examples_title": "\u7bc4\u4f8b",
"example_meaning_filter_block": "\u5c01\u9396\u81f3example.org\u7db2\u57df\u53ca\u5176\u6240\u6709\u7684\u5b50\u7db2\u57df\u4e4b\u5b58\u53d6",
"example_meaning_filter_whitelist": "\u89e3\u9664\u5c01\u9396\u81f3example.org\u7db2\u57df\u53ca\u5176\u6240\u6709\u7684\u5b50\u7db2\u57df\u4e4b\u5b58\u53d6",
"example_meaning_host_block": "AdGuard Home\u73fe\u5728\u5c07\u5c0dexample.org\u7db2\u57df\u8fd4\u56de127.0.0.1\u4f4d\u5740\uff08\u4f46\u975e\u5176\u5b50\u7db2\u57df\uff09\u3002",
"example_comment": "! \u770b\uff0c\u4e00\u500b\u8a3b\u89e3",
"example_comment_meaning": "\u53ea\u662f\u4e00\u500b\u8a3b\u89e3",
"example_comment_hash": "# \u4e5f\u662f\u4e00\u500b\u8a3b\u89e3",
"example_regex_meaning": "\u5c01\u9396\u81f3\u8207\u5df2\u660e\u78ba\u6307\u5b9a\u7684\u898f\u5247\u904b\u7b97\u5f0f\uff08Regular Expression\uff09\u76f8\u914d\u7684\u7db2\u57df\u4e4b\u5b58\u53d6",
"example_upstream_regular": "\u4e00\u822c\u7684 DNS\uff08\u900f\u904eUDP\uff09",
"example_upstream_dot": "\u52a0\u5bc6\u7684 <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_TLS' target='_blank'>DNS-over-TLS<\/a>",
"example_upstream_doh": "\u52a0\u5bc6\u7684 <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>DNS-over-HTTPS <\/a>",
"example_upstream_sdns": "\u60a8\u53ef\u4f7f\u7528\u95dc\u65bc <a href='https:\/\/dnscrypt.info\/' target='_blank'>DNSCrypt<\/a> \u6216 <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>DNS-over-HTTPS<\/a> \u89e3\u6790\u5668\u4e4b <a href='https:\/\/dnscrypt.info\/stamps\/' target='_blank'>DNS \u6233\u8a18<\/a>",
"example_upstream_tcp": "\u4e00\u822c\u7684 DNS\uff08\u900f\u904eTCP\uff09",
"all_filters_up_to_date_toast": "\u6240\u6709\u7684\u904e\u6ffe\u5668\u5df2\u662f\u6700\u65b0\u7684",
"updated_upstream_dns_toast": "\u5df2\u66f4\u65b0\u4e0a\u6e38\u7684DNS\u4f3a\u670d\u5668",
"dns_test_ok_toast": "\u5df2\u660e\u78ba\u6307\u5b9a\u7684DNS\u4f3a\u670d\u5668\u6b63\u5728\u6b63\u78ba\u5730\u904b\u4f5c",
"dns_test_not_ok_toast": "\u4f3a\u670d\u5668 \"{{key}}\"\uff1a\u7121\u6cd5\u88ab\u4f7f\u7528\uff0c\u8acb\u6aa2\u67e5\u60a8\u5df2\u6b63\u78ba\u5730\u586b\u5beb\u5b83",
"unblock_btn": "\u89e3\u9664\u5c01\u9396",
"block_btn": "\u5c01\u9396",
"time_table_header": "\u6642\u9593",
"domain_name_table_header": "\u57df\u540d",
"type_table_header": "\u985e\u578b",
"response_table_header": "\u56de\u61c9",
"client_table_header": "\u7528\u6236\u7aef",
"empty_response_status": "\u7a7a\u767d\u7684",
"show_all_filter_type": "\u986f\u793a\u5168\u90e8",
"show_filtered_type": "\u986f\u793a\u5df2\u904e\u6ffe\u7684",
"no_logs_found": "\u7121\u5df2\u767c\u73fe\u4e4b\u8a18\u9304",
"disabled_log_btn": "\u7981\u7528\u8a18\u9304",
"download_log_file_btn": "\u4e0b\u8f09\u8a18\u9304\u6a94\u6848",
"refresh_btn": "\u91cd\u65b0\u6574\u7406",
"enabled_log_btn": "\u555f\u7528\u8a18\u9304",
"last_dns_queries": "\u6700\u8fd1\u76845000\u7b46DNS\u67e5\u8a62",
"previous_btn": "\u4e0a\u4e00\u9801",
"next_btn": "\u4e0b\u4e00\u9801",
"loading_table_status": "\u6b63\u5728\u8f09\u5165...",
"page_table_footer_text": "\u9801\u9762",
"of_table_footer_text": "\u4e4b",
"rows_table_footer_text": "\u5217",
"updated_custom_filtering_toast": "\u5df2\u66f4\u65b0\u81ea\u8a02\u7684\u904e\u6ffe\u898f\u5247",
"rule_removed_from_custom_filtering_toast": "\u898f\u5247\u5f9e\u81ea\u8a02\u7684\u904e\u6ffe\u898f\u5247\u4e2d\u88ab\u79fb\u9664",
"rule_added_to_custom_filtering_toast": "\u898f\u5247\u88ab\u52a0\u81f3\u81ea\u8a02\u7684\u904e\u6ffe\u898f\u5247\u4e2d",
"query_log_disabled_toast": "\u67e5\u8a62\u8a18\u9304\u88ab\u7981\u7528",
"query_log_enabled_toast": "\u67e5\u8a62\u8a18\u9304\u88ab\u555f\u7528",
"source_label": "\u4f86\u6e90",
"found_in_known_domain_db": "\u5728\u5df2\u77e5\u7684\u57df\u540d\u8cc7\u6599\u5eab\u4e2d\u88ab\u767c\u73fe\u3002",
"category_label": "\u985e\u5225",
"rule_label": "\u898f\u5247",
"filter_label": "\u904e\u6ffe\u5668",
"unknown_filter": "\u672a\u77e5\u7684\u904e\u6ffe\u5668 {{filterId}}",
"install_welcome_title": "\u6b61\u8fce\u81f3AdGuard Home\uff01",
"install_welcome_desc": "AdGuard Home\u662f\u5168\u7db2\u8def\u7bc4\u570d\u5ee3\u544a\u548c\u8ffd\u8e64\u5668\u5c01\u9396\u7684DNS\u4f3a\u670d\u5668\u3002\u5b83\u7684\u76ee\u7684\u70ba\u8b93\u60a8\u63a7\u5236\u60a8\u6574\u500b\u7684\u7db2\u8def\u548c\u6240\u6709\u60a8\u7684\u88dd\u7f6e\uff0c\u4e14\u4e0d\u9700\u8981\u4f7f\u7528\u7528\u6236\u7aef\u7a0b\u5f0f\u3002",
"install_settings_title": "\u7ba1\u7406\u54e1\u7db2\u9801\u4ecb\u9762",
"install_settings_listen": "\u76e3\u807d\u4ecb\u9762",
"install_settings_port": "\u9023\u63a5\u57e0",
"install_settings_interface_link": "\u60a8\u7684AdGuard Home\u7ba1\u7406\u54e1\u7db2\u9801\u4ecb\u9762\u5c07\u65bc\u4e0b\u5217\u7684\u4f4d\u5740\u4e0a\u70ba\u53ef\u7528\u7684\uff1a",
"form_error_port": "\u8f38\u5165\u6709\u6548\u7684\u9023\u63a5\u57e0\u503c",
"install_settings_dns": "DNS \u4f3a\u670d\u5668",
"install_settings_dns_desc": "\u60a8\u5c07\u9700\u8981\u914d\u7f6e\u60a8\u7684\u88dd\u7f6e\u6216\u8def\u7531\u5668\u4ee5\u4f7f\u7528\u65bc\u4e0b\u5217\u7684\u4f4d\u5740\u4e0a\u4e4bDNS\u4f3a\u670d\u5668\uff1a",
"install_settings_all_interfaces": "\u6240\u6709\u7684\u4ecb\u9762",
"install_auth_title": "\u9a57\u8b49",
"install_auth_desc": "\u88ab\u975e\u5e38\u5efa\u8b70\u914d\u7f6e\u5c6c\u65bc\u60a8\u7684AdGuard Home\u7ba1\u7406\u54e1\u7db2\u9801\u4ecb\u9762\u4e4b\u5bc6\u78bc\u9a57\u8b49\u3002\u5373\u4f7f\u5b83\u50c5\u5728\u60a8\u7684\u5340\u57df\u7db2\u8def\u4e2d\u70ba\u53ef\u5b58\u53d6\u7684\uff0c\u8b93\u5b83\u53d7\u4fdd\u8b77\u514d\u65bc\u4e0d\u53d7\u9650\u5236\u7684\u5b58\u53d6\u70ba\u4ecd\u7136\u91cd\u8981\u7684\u3002",
"install_auth_username": "\u7528\u6236\u540d",
"install_auth_password": "\u5bc6\u78bc",
"install_auth_confirm": "\u78ba\u8a8d\u5bc6\u78bc",
"install_auth_username_enter": "\u8f38\u5165\u7528\u6236\u540d",
"install_auth_password_enter": "\u8f38\u5165\u5bc6\u78bc",
"install_step": "\u6b65\u9a5f",
"install_devices_title": "\u914d\u7f6e\u60a8\u7684\u88dd\u7f6e",
"install_devices_desc": "\u70ba\u4f7fAdGuard Home\u958b\u59cb\u904b\u4f5c\uff0c\u60a8\u9700\u8981\u914d\u7f6e\u60a8\u7684\u88dd\u7f6e\u4ee5\u4f7f\u7528\u5b83\u3002",
"install_submit_title": "\u606d\u559c\uff01",
"install_submit_desc": "\u8a72\u8a2d\u7f6e\u7a0b\u5e8f\u88ab\u5b8c\u6210\uff0c\u4e14\u60a8\u6e96\u5099\u597d\u958b\u59cb\u4f7f\u7528AdGuard Home\u3002",
"install_devices_router": "\u8def\u7531\u5668",
"install_devices_router_desc": "\u8a72\u8a2d\u7f6e\u5c07\u81ea\u52d5\u5730\u6db5\u84cb\u88ab\u9023\u7dda\u81f3\u60a8\u7684\u5bb6\u5ead\u8def\u7531\u5668\u4e4b\u6240\u6709\u7684\u88dd\u7f6e\uff0c\u4e14\u60a8\u5c07\u7121\u9700\u624b\u52d5\u5730\u914d\u7f6e\u5b83\u5011\u6bcf\u500b\u3002",
"install_devices_address": "AdGuard Home DNS\u4f3a\u670d\u5668\u6b63\u5728\u76e3\u807d\u4e0b\u5217\u7684\u4f4d\u5740",
"install_devices_router_list_1": "\u958b\u555f\u95dc\u65bc\u60a8\u7684\u8def\u7531\u5668\u4e4b\u504f\u597d\u8a2d\u5b9a\u3002\u901a\u5e38\u5730\uff0c\u60a8\u53ef\u900f\u904e\u7db2\u5740\uff08\u5982 http:\/\/192.168.0.1\/ \u6216 http:\/\/192.168.1.1\/\uff09\u5f9e\u60a8\u7684\u700f\u89bd\u5668\u4e2d\u5b58\u53d6\u5b83\u3002\u60a8\u53ef\u80fd\u88ab\u8981\u6c42\u8f38\u5165\u8a72\u5bc6\u78bc\u3002\u5982\u679c\u60a8\u4e0d\u8a18\u5f97\u5b83\uff0c\u60a8\u7d93\u5e38\u53ef\u900f\u904e\u6309\u58d3\u65bc\u8a72\u8def\u7531\u5668\u672c\u8eab\u4e0a\u7684\u6309\u9215\u4f86\u91cd\u7f6e\u5bc6\u78bc\u3002\u67d0\u4e9b\u8def\u7531\u5668\u9700\u8981\u7279\u5b9a\u7684\u61c9\u7528\u7a0b\u5f0f\uff0c\u65e2\u7136\u5982\u6b64\u5176\u61c9\u5df2\u88ab\u5b89\u88dd\u65bc\u60a8\u7684\u96fb\u8166\/\u624b\u6a5f\u4e0a\u3002",
"install_devices_router_list_2": "\u627e\u5230DHCP\/DNS\u8a2d\u5b9a\u3002\u5c0b\u627e\u7dca\u9130\u8457\u5141\u8a31\u5169\u7d44\u6216\u4e09\u7d44\u6578\u5b57\u96c6\u7684\u6b04\u4f4d\u4e4bDNS\u5b57\u6bcd\uff0c\u6bcf\u7d44\u88ab\u62c6\u6210\u56db\u500b\u542b\u6709\u4e00\u81f3\u4e09\u500b\u6578\u5b57\u7684\u7fa4\u96c6\u3002",
"install_devices_router_list_3": "\u5728\u90a3\u88e1\u8f38\u5165\u60a8\u7684AdGuard Home\u4f3a\u670d\u5668\u4f4d\u5740\u3002",
"install_devices_windows_list_1": "\u901a\u904e\u958b\u59cb\u529f\u80fd\u8868\u6216Windows \u641c\u5c0b\uff0c\u958b\u555f\u63a7\u5236\u53f0\u3002",
"install_devices_windows_list_2": "\u53bb\u7db2\u8def\u548c\u7db2\u969b\u7db2\u8def\u985e\u5225\uff0c\u7136\u5f8c\u53bb\u7db2\u8def\u548c\u5171\u7528\u4e2d\u5fc3\u3002",
"install_devices_windows_list_3": "\u65bc\u756b\u9762\u4e4b\u5de6\u5074\u4e0a\u627e\u5230\u8b8a\u66f4\u4ecb\u9762\u5361\u8a2d\u5b9a\u4e26\u65bc\u5b83\u4e0a\u9ede\u64ca\u3002",
"install_devices_windows_list_4": "\u9078\u64c7\u60a8\u73fe\u884c\u7684\u9023\u7dda\uff0c\u65bc\u5b83\u4e0a\u9ede\u64ca\u6ed1\u9f20\u53f3\u9375\uff0c\u7136\u5f8c\u9078\u64c7\u5167\u5bb9\u3002",
"install_devices_windows_list_5": "\u5728\u6e05\u55ae\u4e2d\u627e\u5230\u7db2\u969b\u7db2\u8def\u901a\u8a0a\u5354\u5b9a\u7b2c 4 \u7248\uff08TCP\/IPv4\uff09\uff0c\u9078\u64c7\u5b83\uff0c\u7136\u5f8c\u518d\u6b21\u65bc\u5167\u5bb9\u4e0a\u9ede\u64ca\u3002",
"install_devices_windows_list_6": "\u9078\u64c7\u4f7f\u7528\u4e0b\u5217\u7684DNS\u4f3a\u670d\u5668\u4f4d\u5740\uff0c\u7136\u5f8c\u8f38\u5165\u60a8\u7684AdGuard Home\u4f3a\u670d\u5668\u4f4d\u5740\u3002",
"install_devices_macos_list_1": "\u65bcApple\u5716\u50cf\u4e0a\u9ede\u64ca\uff0c\u7136\u5f8c\u53bb\u7cfb\u7d71\u504f\u597d\u8a2d\u5b9a\u3002",
"install_devices_macos_list_2": "\u65bc\u7db2\u8def\u4e0a\u9ede\u64ca\u3002",
"install_devices_macos_list_3": "\u9078\u64c7\u5728\u60a8\u7684\u6e05\u55ae\u4e2d\u4e4b\u9996\u8981\u7684\u9023\u7dda\uff0c\u7136\u5f8c\u9ede\u64ca\u9032\u968e\u7684\u3002",
"install_devices_macos_list_4": "\u9078\u64c7\u8a72DNS\u5206\u9801\uff0c\u7136\u5f8c\u8f38\u5165\u60a8\u7684AdGuard Home\u4f3a\u670d\u5668\u4f4d\u5740\u3002",
"install_devices_android_list_1": "\u5f9eAndroid\u9078\u55ae\u4e3b\u756b\u9762\u4e2d\uff0c\u8f15\u89f8\u8a2d\u5b9a\u3002",
"install_devices_android_list_2": "\u65bc\u8a72\u9078\u55ae\u4e0a\u8f15\u89f8Wi-Fi\u3002\u6b63\u5728\u5217\u51fa\u6240\u6709\u53ef\u7528\u7684\u7db2\u8def\u4e4b\u756b\u9762\u5c07\u88ab\u986f\u793a\uff08\u4e0d\u53ef\u80fd\u70ba\u884c\u52d5\u9023\u7dda\u8a2d\u5b9a\u81ea\u8a02\u7684DNS\uff09\u3002",
"install_devices_android_list_3": "\u9577\u6309\u60a8\u6240\u9023\u7dda\u81f3\u7684\u7db2\u8def\uff0c\u7136\u5f8c\u8f15\u89f8\u4fee\u6539\u7db2\u8def\u3002",
"install_devices_android_list_4": "\u65bc\u67d0\u4e9b\u88dd\u7f6e\u4e0a\uff0c\u60a8\u53ef\u80fd\u9700\u8981\u6aa2\u67e5\u95dc\u65bc\u9032\u968e\u7684\u65b9\u6846\u4ee5\u67e5\u770b\u9032\u4e00\u6b65\u7684\u8a2d\u5b9a\u3002\u70ba\u4e86\u8abf\u6574\u60a8\u7684Android DNS\u8a2d\u5b9a\uff0c\u60a8\u5c07\u9700\u8981\u628aIP \u8a2d\u5b9a\u5f9eDHCP\u8f49\u63db\u6210\u975c\u614b\u3002",
"install_devices_android_list_5": "\u4f7f\u8a2d\u5b9aDNS 1\u548cDNS 2\u503c\u66f4\u6539\u6210\u60a8\u7684AdGuard Home\u4f3a\u670d\u5668\u4f4d\u5740\u3002",
"install_devices_ios_list_1": "\u5f9e\u4e3b\u756b\u9762\u4e2d\uff0c\u8f15\u89f8\u8a2d\u5b9a\u3002",
"install_devices_ios_list_2": "\u5728\u5de6\u5074\u7684\u9078\u55ae\u4e2d\u9078\u64c7Wi-Fi\uff08\u4e0d\u53ef\u80fd\u70ba\u884c\u52d5\u7db2\u8def\u914d\u7f6eDNS\uff09\u3002",
"install_devices_ios_list_3": "\u65bc\u76ee\u524d\u73fe\u884c\u7684\u7db2\u8def\u4e4b\u540d\u7a31\u4e0a\u8f15\u89f8\u3002",
"install_devices_ios_list_4": "\u5728\u8a72DNS\u6b04\u4f4d\u4e2d\uff0c\u8f38\u5165\u60a8\u7684AdGuard Home\u4f3a\u670d\u5668\u4f4d\u5740\u3002",
"get_started": "\u958b\u59cb\u5427",
"next": "\u4e0b\u4e00\u6b65",
"open_dashboard": "\u958b\u555f\u5100\u8868\u677f",
"install_saved": "\u5df2\u6210\u529f\u5730\u5132\u5b58",
"form_error_password": "\u4e0d\u76f8\u7b26\u7684\u5bc6\u78bc"
}

View File

@@ -0,0 +1,73 @@
import { createAction } from 'redux-actions';
import Api from '../api/Api';
import { addErrorToast, addSuccessToast } from './index';
import { redirectToCurrentProtocol } from '../helpers/helpers';
const apiClient = new Api();
export const getTlsStatusRequest = createAction('GET_TLS_STATUS_REQUEST');
export const getTlsStatusFailure = createAction('GET_TLS_STATUS_FAILURE');
export const getTlsStatusSuccess = createAction('GET_TLS_STATUS_SUCCESS');
export const getTlsStatus = () => async (dispatch) => {
dispatch(getTlsStatusRequest());
try {
const status = await apiClient.getTlsStatus();
status.certificate_chain = atob(status.certificate_chain);
status.private_key = atob(status.private_key);
dispatch(getTlsStatusSuccess(status));
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(getTlsStatusFailure());
}
};
export const setTlsConfigRequest = createAction('SET_TLS_CONFIG_REQUEST');
export const setTlsConfigFailure = createAction('SET_TLS_CONFIG_FAILURE');
export const setTlsConfigSuccess = createAction('SET_TLS_CONFIG_SUCCESS');
export const setTlsConfig = config => async (dispatch, getState) => {
dispatch(setTlsConfigRequest());
try {
const { httpPort } = getState().dashboard;
const values = { ...config };
values.certificate_chain = btoa(values.certificate_chain);
values.private_key = btoa(values.private_key);
values.port_https = values.port_https || 0;
values.port_dns_over_tls = values.port_dns_over_tls || 0;
const response = await apiClient.setTlsConfig(values);
response.certificate_chain = atob(response.certificate_chain);
response.private_key = atob(response.private_key);
dispatch(setTlsConfigSuccess(response));
dispatch(addSuccessToast('encryption_config_saved'));
redirectToCurrentProtocol(response, httpPort);
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(setTlsConfigFailure());
}
};
export const validateTlsConfigRequest = createAction('VALIDATE_TLS_CONFIG_REQUEST');
export const validateTlsConfigFailure = createAction('VALIDATE_TLS_CONFIG_FAILURE');
export const validateTlsConfigSuccess = createAction('VALIDATE_TLS_CONFIG_SUCCESS');
export const validateTlsConfig = config => async (dispatch) => {
dispatch(validateTlsConfigRequest());
try {
const values = { ...config };
values.certificate_chain = btoa(values.certificate_chain);
values.private_key = btoa(values.private_key);
values.port_https = values.port_https || 0;
values.port_dns_over_tls = values.port_dns_over_tls || 0;
const response = await apiClient.validateTlsConfig(values);
response.certificate_chain = atob(response.certificate_chain);
response.private_key = atob(response.private_key);
dispatch(validateTlsConfigSuccess(response));
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(validateTlsConfigFailure());
}
};

View File

@@ -1,8 +1,10 @@
import { createAction } from 'redux-actions';
import round from 'lodash/round';
import { t } from 'i18next';
import { showLoading, hideLoading } from 'react-redux-loading-bar';
import { normalizeHistory, normalizeFilteringStatus, normalizeLogs } from '../helpers/helpers';
import { SETTINGS_NAMES } from '../helpers/constants';
import Api from '../api/Api';
const apiClient = new Api();
@@ -17,44 +19,43 @@ export const showSettingsFailure = createAction('SETTINGS_FAILURE_SHOW');
export const toggleSetting = (settingKey, status) => async (dispatch) => {
let successMessage = '';
try {
// TODO move setting keys to constants
switch (settingKey) {
case 'filtering':
case SETTINGS_NAMES.filtering:
if (status) {
successMessage = 'Disabled filtering';
successMessage = 'disabled_filtering_toast';
await apiClient.disableFiltering();
} else {
successMessage = 'Enabled filtering';
successMessage = 'enabled_filtering_toast';
await apiClient.enableFiltering();
}
dispatch(toggleSettingStatus({ settingKey }));
break;
case 'safebrowsing':
case SETTINGS_NAMES.safebrowsing:
if (status) {
successMessage = 'Disabled safebrowsing';
successMessage = 'disabled_safe_browsing_toast';
await apiClient.disableSafebrowsing();
} else {
successMessage = 'Enabled safebrowsing';
successMessage = 'enabled_safe_browsing_toast';
await apiClient.enableSafebrowsing();
}
dispatch(toggleSettingStatus({ settingKey }));
break;
case 'parental':
case SETTINGS_NAMES.parental:
if (status) {
successMessage = 'Disabled parental control';
successMessage = 'disabled_parental_toast';
await apiClient.disableParentalControl();
} else {
successMessage = 'Enabled parental control';
successMessage = 'enabled_parental_toast';
await apiClient.enableParentalControl();
}
dispatch(toggleSettingStatus({ settingKey }));
break;
case 'safesearch':
case SETTINGS_NAMES.safesearch:
if (status) {
successMessage = 'Disabled safe search';
successMessage = 'disabled_safe_search_toast';
await apiClient.disableSafesearch();
} else {
successMessage = 'Enabled safe search';
successMessage = 'enabled_save_search_toast';
await apiClient.enableSafesearch();
}
dispatch(toggleSettingStatus({ settingKey }));
@@ -123,10 +124,10 @@ export const toggleProtection = status => async (dispatch) => {
try {
if (status) {
successMessage = 'Disabled protection';
successMessage = 'disabled_protection';
await apiClient.disableGlobalProtection();
} else {
successMessage = 'Enabled protection';
successMessage = 'enabled_protection';
await apiClient.enableGlobalProtection();
}
@@ -271,14 +272,14 @@ export const toggleLogStatus = queryLogEnabled => async (dispatch) => {
let successMessage;
if (queryLogEnabled) {
toggleMethod = apiClient.disableQueryLog.bind(apiClient);
successMessage = 'disabled';
successMessage = 'query_log_disabled_toast';
} else {
toggleMethod = apiClient.enableQueryLog.bind(apiClient);
successMessage = 'enabled';
successMessage = 'query_log_enabled_toast';
}
try {
await toggleMethod();
dispatch(addSuccessToast(`Query log ${successMessage}`));
dispatch(addSuccessToast(successMessage));
dispatch(toggleLogStatusSuccess());
} catch (error) {
dispatch(addErrorToast({ error }));
@@ -297,7 +298,7 @@ export const setRules = rules => async (dispatch) => {
.replace(/^\n/g, '')
.replace(/\n\s*\n/g, '\n');
await apiClient.setRules(replacedLineEndings);
dispatch(addSuccessToast('Updated the custom filtering rules'));
dispatch(addSuccessToast('updated_custom_filtering_toast'));
dispatch(setRulesSuccess());
} catch (error) {
dispatch(addErrorToast({ error }));
@@ -351,15 +352,15 @@ export const refreshFiltersFailure = createAction('FILTERING_REFRESH_FAILURE');
export const refreshFiltersSuccess = createAction('FILTERING_REFRESH_SUCCESS');
export const refreshFilters = () => async (dispatch) => {
dispatch(refreshFiltersRequest);
dispatch(refreshFiltersRequest());
dispatch(showLoading());
try {
const refreshText = await apiClient.refreshFilters();
dispatch(refreshFiltersSuccess);
dispatch(refreshFiltersSuccess());
if (refreshText.includes('OK')) {
if (refreshText.includes('OK 0')) {
dispatch(addSuccessToast('All filters are already up-to-date'));
dispatch(addSuccessToast('all_filters_up_to_date_toast'));
} else {
dispatch(addSuccessToast(refreshText.replace(/OK /g, '')));
}
@@ -433,7 +434,6 @@ export const downloadQueryLogRequest = createAction('DOWNLOAD_QUERY_LOG_REQUEST'
export const downloadQueryLogFailure = createAction('DOWNLOAD_QUERY_LOG_FAILURE');
export const downloadQueryLogSuccess = createAction('DOWNLOAD_QUERY_LOG_SUCCESS');
// TODO create some common flasher with all server errors
export const downloadQueryLog = () => async (dispatch) => {
let data;
dispatch(downloadQueryLogRequest());
@@ -456,7 +456,7 @@ export const setUpstream = url => async (dispatch) => {
dispatch(setUpstreamRequest());
try {
await apiClient.setUpstream(url);
dispatch(addSuccessToast('Updated the upstream DNS servers'));
dispatch(addSuccessToast('updated_upstream_dns_toast'));
dispatch(setUpstreamSuccess());
} catch (error) {
dispatch(addErrorToast({ error }));
@@ -476,13 +476,13 @@ export const testUpstream = servers => async (dispatch) => {
const testMessages = Object.keys(upstreamResponse).map((key) => {
const message = upstreamResponse[key];
if (message !== 'OK') {
dispatch(addErrorToast({ error: `Server "${key}": could not be used, please check that you've written it correctly` }));
dispatch(addErrorToast({ error: t('dns_test_not_ok_toast', { key }) }));
}
return message;
});
if (testMessages.every(message => message === 'OK')) {
dispatch(addSuccessToast('Specified DNS servers are working correctly'));
dispatch(addSuccessToast('dns_test_ok_toast'));
}
dispatch(testUpstreamSuccess());
@@ -491,3 +491,162 @@ export const testUpstream = servers => async (dispatch) => {
dispatch(testUpstreamFailure());
}
};
export const changeLanguageRequest = createAction('CHANGE_LANGUAGE_REQUEST');
export const changeLanguageFailure = createAction('CHANGE_LANGUAGE_FAILURE');
export const changeLanguageSuccess = createAction('CHANGE_LANGUAGE_SUCCESS');
export const changeLanguage = lang => async (dispatch) => {
dispatch(changeLanguageRequest());
try {
await apiClient.changeLanguage(lang);
dispatch(changeLanguageSuccess());
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(changeLanguageFailure());
}
};
export const getLanguageRequest = createAction('GET_LANGUAGE_REQUEST');
export const getLanguageFailure = createAction('GET_LANGUAGE_FAILURE');
export const getLanguageSuccess = createAction('GET_LANGUAGE_SUCCESS');
export const getLanguage = () => async (dispatch) => {
dispatch(getLanguageRequest());
try {
const language = await apiClient.getCurrentLanguage();
dispatch(getLanguageSuccess(language));
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(getLanguageFailure());
}
};
export const getDhcpStatusRequest = createAction('GET_DHCP_STATUS_REQUEST');
export const getDhcpStatusSuccess = createAction('GET_DHCP_STATUS_SUCCESS');
export const getDhcpStatusFailure = createAction('GET_DHCP_STATUS_FAILURE');
export const getDhcpStatus = () => async (dispatch) => {
dispatch(getDhcpStatusRequest());
try {
const status = await apiClient.getDhcpStatus();
dispatch(getDhcpStatusSuccess(status));
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(getDhcpStatusFailure());
}
};
export const getDhcpInterfacesRequest = createAction('GET_DHCP_INTERFACES_REQUEST');
export const getDhcpInterfacesSuccess = createAction('GET_DHCP_INTERFACES_SUCCESS');
export const getDhcpInterfacesFailure = createAction('GET_DHCP_INTERFACES_FAILURE');
export const getDhcpInterfaces = () => async (dispatch) => {
dispatch(getDhcpInterfacesRequest());
try {
const interfaces = await apiClient.getDhcpInterfaces();
dispatch(getDhcpInterfacesSuccess(interfaces));
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(getDhcpInterfacesFailure());
}
};
export const findActiveDhcpRequest = createAction('FIND_ACTIVE_DHCP_REQUEST');
export const findActiveDhcpSuccess = createAction('FIND_ACTIVE_DHCP_SUCCESS');
export const findActiveDhcpFailure = createAction('FIND_ACTIVE_DHCP_FAILURE');
export const findActiveDhcp = name => async (dispatch) => {
dispatch(findActiveDhcpRequest());
try {
const activeDhcp = await apiClient.findActiveDhcp(name);
dispatch(findActiveDhcpSuccess(activeDhcp));
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(findActiveDhcpFailure());
}
};
export const setDhcpConfigRequest = createAction('SET_DHCP_CONFIG_REQUEST');
export const setDhcpConfigSuccess = createAction('SET_DHCP_CONFIG_SUCCESS');
export const setDhcpConfigFailure = createAction('SET_DHCP_CONFIG_FAILURE');
// TODO rewrite findActiveDhcp part
export const setDhcpConfig = values => async (dispatch, getState) => {
const { config } = getState().dhcp;
const updatedConfig = { ...config, ...values };
dispatch(setDhcpConfigRequest());
if (values.interface_name) {
dispatch(findActiveDhcpRequest());
try {
const activeDhcp = await apiClient.findActiveDhcp(values.interface_name);
dispatch(findActiveDhcpSuccess(activeDhcp));
if (!activeDhcp.found) {
try {
await apiClient.setDhcpConfig(updatedConfig);
dispatch(setDhcpConfigSuccess(updatedConfig));
dispatch(addSuccessToast('dhcp_config_saved'));
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(setDhcpConfigFailure());
}
} else {
dispatch(addErrorToast({ error: 'dhcp_found' }));
}
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(findActiveDhcpFailure());
}
} else {
try {
await apiClient.setDhcpConfig(updatedConfig);
dispatch(setDhcpConfigSuccess(updatedConfig));
dispatch(addSuccessToast('dhcp_config_saved'));
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(setDhcpConfigFailure());
}
}
};
export const toggleDhcpRequest = createAction('TOGGLE_DHCP_REQUEST');
export const toggleDhcpFailure = createAction('TOGGLE_DHCP_FAILURE');
export const toggleDhcpSuccess = createAction('TOGGLE_DHCP_SUCCESS');
// TODO rewrite findActiveDhcp part
export const toggleDhcp = config => async (dispatch) => {
dispatch(toggleDhcpRequest());
if (config.enabled) {
try {
await apiClient.setDhcpConfig({ ...config, enabled: false });
dispatch(toggleDhcpSuccess());
dispatch(addSuccessToast('disabled_dhcp'));
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(toggleDhcpFailure());
}
} else {
dispatch(findActiveDhcpRequest());
try {
const activeDhcp = await apiClient.findActiveDhcp(config.interface_name);
dispatch(findActiveDhcpSuccess(activeDhcp));
if (!activeDhcp.found) {
try {
await apiClient.setDhcpConfig({ ...config, enabled: true });
dispatch(toggleDhcpSuccess());
dispatch(addSuccessToast('enabled_dhcp'));
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(toggleDhcpFailure());
}
} else {
dispatch(addErrorToast({ error: 'dhcp_found' }));
}
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(findActiveDhcpFailure());
}
}
};

View File

@@ -0,0 +1,46 @@
import { createAction } from 'redux-actions';
import Api from '../api/Api';
import { addErrorToast, addSuccessToast } from './index';
const apiClient = new Api();
export const nextStep = createAction('NEXT_STEP');
export const prevStep = createAction('PREV_STEP');
export const getDefaultAddressesRequest = createAction('GET_DEFAULT_ADDRESSES_REQUEST');
export const getDefaultAddressesFailure = createAction('GET_DEFAULT_ADDRESSES_FAILURE');
export const getDefaultAddressesSuccess = createAction('GET_DEFAULT_ADDRESSES_SUCCESS');
export const getDefaultAddresses = () => async (dispatch) => {
dispatch(getDefaultAddressesRequest());
try {
const addresses = await apiClient.getDefaultAddresses();
dispatch(getDefaultAddressesSuccess(addresses));
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(getDefaultAddressesFailure());
}
};
export const setAllSettingsRequest = createAction('SET_ALL_SETTINGS_REQUEST');
export const setAllSettingsFailure = createAction('SET_ALL_SETTINGS_FAILURE');
export const setAllSettingsSuccess = createAction('SET_ALL_SETTINGS_SUCCESS');
export const setAllSettings = values => async (dispatch) => {
dispatch(setAllSettingsRequest());
try {
const {
confirm_password,
...config
} = values;
await apiClient.setAllSettings(config);
dispatch(setAllSettingsSuccess());
dispatch(addSuccessToast('install_saved'));
dispatch(nextStep());
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(setAllSettingsFailure());
dispatch(prevStep());
}
};

View File

@@ -15,7 +15,11 @@ export default class Api {
return response.data;
} catch (error) {
console.error(error);
throw new Error(`${this.baseUrl}/${path} | ${error.response.data} | ${error.response.status}`);
const errorPath = `${this.baseUrl}/${path}`;
if (error.response) {
throw new Error(`${errorPath} | ${error.response.data} | ${error.response.status}`);
}
throw new Error(`${errorPath} | ${error.message ? error.message : error}`);
}
}
@@ -284,4 +288,102 @@ export default class Api {
const { path, method } = this.SAFESEARCH_DISABLE;
return this.makeRequest(path, method);
}
// Language
CURRENT_LANGUAGE = { path: 'i18n/current_language', method: 'GET' };
CHANGE_LANGUAGE = { path: 'i18n/change_language', method: 'POST' };
getCurrentLanguage() {
const { path, method } = this.CURRENT_LANGUAGE;
return this.makeRequest(path, method);
}
changeLanguage(lang) {
const { path, method } = this.CHANGE_LANGUAGE;
const parameters = {
data: lang,
headers: { 'Content-Type': 'text/plain' },
};
return this.makeRequest(path, method, parameters);
}
// DHCP
DHCP_STATUS = { path: 'dhcp/status', method: 'GET' };
DHCP_SET_CONFIG = { path: 'dhcp/set_config', method: 'POST' };
DHCP_FIND_ACTIVE = { path: 'dhcp/find_active_dhcp', method: 'POST' };
DHCP_INTERFACES = { path: 'dhcp/interfaces', method: 'GET' };
getDhcpStatus() {
const { path, method } = this.DHCP_STATUS;
return this.makeRequest(path, method);
}
getDhcpInterfaces() {
const { path, method } = this.DHCP_INTERFACES;
return this.makeRequest(path, method);
}
setDhcpConfig(config) {
const { path, method } = this.DHCP_SET_CONFIG;
const parameters = {
data: config,
headers: { 'Content-Type': 'application/json' },
};
return this.makeRequest(path, method, parameters);
}
findActiveDhcp(name) {
const { path, method } = this.DHCP_FIND_ACTIVE;
const parameters = {
data: name,
headers: { 'Content-Type': 'text/plain' },
};
return this.makeRequest(path, method, parameters);
}
// Installation
INSTALL_GET_ADDRESSES = { path: 'install/get_addresses', method: 'GET' };
INSTALL_CONFIGURE = { path: 'install/configure', method: 'POST' };
getDefaultAddresses() {
const { path, method } = this.INSTALL_GET_ADDRESSES;
return this.makeRequest(path, method);
}
setAllSettings(config) {
const { path, method } = this.INSTALL_CONFIGURE;
const parameters = {
data: config,
headers: { 'Content-Type': 'application/json' },
};
return this.makeRequest(path, method, parameters);
}
// DNS-over-HTTPS and DNS-over-TLS
TLS_STATUS = { path: 'tls/status', method: 'GET' };
TLS_CONFIG = { path: 'tls/configure', method: 'POST' };
TLS_VALIDATE = { path: 'tls/validate', method: 'POST' };
getTlsStatus() {
const { path, method } = this.TLS_STATUS;
return this.makeRequest(path, method);
}
setTlsConfig(config) {
const { path, method } = this.TLS_CONFIG;
const parameters = {
data: config,
headers: { 'Content-Type': 'application/json' },
};
return this.makeRequest(path, method, parameters);
}
validateTlsConfig(config) {
const { path, method } = this.TLS_VALIDATE;
const parameters = {
data: config,
headers: { 'Content-Type': 'application/json' },
};
return this.makeRequest(path, method, parameters);
}
}

View File

@@ -1,7 +1,7 @@
body {
margin: 0;
padding: 0;
font-family: sans-serif;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
}
.status {
@@ -19,8 +19,14 @@ body {
}
.loading-bar {
position: absolute;
position: fixed;
top: 0;
left: 0;
z-index: 103;
height: 3px;
background: linear-gradient(45deg, rgba(99, 125, 120, 1) 0%, rgba(88, 177, 101, 1) 100%);
}
.hidden {
display: none;
}

View File

@@ -1,6 +1,7 @@
import React, { Component, Fragment } from 'react';
import { HashRouter, Route } from 'react-router-dom';
import PropTypes from 'prop-types';
import { withNamespaces } from 'react-i18next';
import LoadingBar from 'react-redux-loading-bar';
import 'react-table/react-table.css';
@@ -16,7 +17,9 @@ import Logs from '../../containers/Logs';
import Footer from '../ui/Footer';
import Toasts from '../Toasts';
import Status from '../ui/Status';
import Update from '../ui/Update';
import UpdateTopline from '../ui/UpdateTopline';
import EncryptionTopline from '../ui/EncryptionTopline';
import i18n from '../../i18n';
class App extends Component {
componentDidMount() {
@@ -24,12 +27,32 @@ class App extends Component {
this.props.getVersion();
}
componentDidUpdate(prevProps) {
if (this.props.dashboard.language !== prevProps.dashboard.language) {
this.setLanguage();
}
}
handleStatusChange = () => {
this.props.enableDns();
};
setLanguage = () => {
const { processing, language } = this.props.dashboard;
if (!processing) {
if (language) {
i18n.changeLanguage(language);
}
}
i18n.on('languageChanged', (lang) => {
this.props.changeLanguage(lang);
});
}
render() {
const { dashboard } = this.props;
const { dashboard, encryption } = this.props;
const updateAvailable =
!dashboard.processingVersions &&
dashboard.isCoreRunning &&
@@ -39,11 +62,14 @@ class App extends Component {
<HashRouter hashType='noslash'>
<Fragment>
{updateAvailable &&
<Update
announcement={dashboard.announcement}
announcementUrl={dashboard.announcementUrl}
<UpdateTopline
url={dashboard.announcementUrl}
version={dashboard.version}
/>
}
{!encryption.processing &&
<EncryptionTopline notAfter={encryption.not_after} />
}
<LoadingBar className="loading-bar" updateTime={1000} />
<Route component={Header} />
<div className="container container--wrap">
@@ -78,6 +104,8 @@ App.propTypes = {
isCoreRunning: PropTypes.bool,
error: PropTypes.string,
getVersion: PropTypes.func,
changeLanguage: PropTypes.func,
encryption: PropTypes.object,
};
export default App;
export default withNamespaces()(App);

View File

@@ -2,6 +2,7 @@ import React, { Component } from 'react';
import ReactTable from 'react-table';
import PropTypes from 'prop-types';
import map from 'lodash/map';
import { withNamespaces, Trans } from 'react-i18next';
import Card from '../ui/Card';
import Cell from '../ui/Cell';
@@ -20,8 +21,8 @@ class BlockedDomains extends Component {
const trackerData = getTrackerData(value);
return (
<div className="logs__row" title={value}>
<div className="logs__text">
<div className="logs__row">
<div className="logs__text" title={value}>
{value}
</div>
{trackerData && <Popover data={trackerData} />}
@@ -29,7 +30,7 @@ class BlockedDomains extends Component {
);
},
}, {
Header: 'Requests count',
Header: <Trans>requests_count</Trans>,
accessor: 'domain',
maxWidth: 190,
Cell: ({ value }) => {
@@ -48,15 +49,16 @@ class BlockedDomains extends Component {
}];
render() {
const { t } = this.props;
return (
<Card title="Top blocked domains" subtitle="for the last 24 hours" bodyType="card-table" refresh={this.props.refreshButton}>
<Card title={ t('top_blocked_domains') } subtitle={ t('for_last_24_hours') } bodyType="card-table" refresh={this.props.refreshButton}>
<ReactTable
data={map(this.props.topBlockedDomains, (value, prop) => (
{ ip: prop, domain: value }
))}
columns={this.columns}
showPagination={false}
noDataText="No domains found"
noDataText={ t('no_domains_found') }
minRows={6}
className="-striped -highlight card-table-overflow stats__table"
/>
@@ -71,6 +73,7 @@ BlockedDomains.propTypes = {
replacedSafebrowsing: PropTypes.number.isRequired,
replacedParental: PropTypes.number.isRequired,
refreshButton: PropTypes.node.isRequired,
t: PropTypes.func,
};
export default BlockedDomains;
export default withNamespaces()(BlockedDomains);

View File

@@ -2,6 +2,7 @@ import React, { Component } from 'react';
import ReactTable from 'react-table';
import PropTypes from 'prop-types';
import map from 'lodash/map';
import { Trans, withNamespaces } from 'react-i18next';
import Card from '../ui/Card';
import Cell from '../ui/Cell';
@@ -23,8 +24,9 @@ class Clients extends Component {
Header: 'IP',
accessor: 'ip',
Cell: ({ value }) => (<div className="logs__row logs__row--overflow"><span className="logs__text" title={value}>{value}</span></div>),
sortMethod: (a, b) => parseInt(a.replace(/\./g, ''), 10) - parseInt(b.replace(/\./g, ''), 10),
}, {
Header: 'Requests count',
Header: <Trans>requests_count</Trans>,
accessor: 'count',
Cell: ({ value }) => {
const percent = getPercent(this.props.dnsQueries, value);
@@ -37,15 +39,16 @@ class Clients extends Component {
}];
render() {
const { t } = this.props;
return (
<Card title="Top clients" subtitle="for the last 24 hours" bodyType="card-table" refresh={this.props.refreshButton}>
<Card title={ t('top_clients') } subtitle={ t('for_last_24_hours') } bodyType="card-table" refresh={this.props.refreshButton}>
<ReactTable
data={map(this.props.topClients, (value, prop) => (
{ ip: prop, count: value }
))}
columns={this.columns}
showPagination={false}
noDataText="No clients found"
noDataText={ t('no_clients_found') }
minRows={6}
className="-striped -highlight card-table-overflow"
/>
@@ -58,6 +61,7 @@ Clients.propTypes = {
topClients: PropTypes.object.isRequired,
dnsQueries: PropTypes.number.isRequired,
refreshButton: PropTypes.node.isRequired,
t: PropTypes.func,
};
export default Clients;
export default withNamespaces()(Clients);

View File

@@ -1,5 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Trans, withNamespaces } from 'react-i18next';
import Card from '../ui/Card';
import Tooltip from '../ui/Tooltip';
@@ -7,13 +8,13 @@ import Tooltip from '../ui/Tooltip';
const tooltipType = 'tooltip-custom--narrow';
const Counters = props => (
<Card title="General statistics" subtitle="for the last 24 hours" bodyType="card-table" refresh={props.refreshButton}>
<Card title={ props.t('general_statistics') } subtitle={ props.t('for_last_24_hours') } bodyType="card-table" refresh={props.refreshButton}>
<table className="table card-table">
<tbody>
<tr>
<td>
DNS Queries
<Tooltip text="A number of DNS quieries processed for the last 24 hours" type={tooltipType} />
<Trans>dns_query</Trans>
<Tooltip text={ props.t('number_of_dns_query_24_hours') } type={tooltipType} />
</td>
<td className="text-right">
<span className="text-muted">
@@ -23,8 +24,10 @@ const Counters = props => (
</tr>
<tr>
<td>
Blocked by <a href="#filters">Filters</a>
<Tooltip text="A number of DNS requests blocked by adblock filters and hosts blocklists" type={tooltipType} />
<a href="#filters">
<Trans>blocked_by</Trans>
</a>
<Tooltip text={ props.t('number_of_dns_query_blocked_24_hours') } type={tooltipType} />
</td>
<td className="text-right">
<span className="text-muted">
@@ -34,8 +37,8 @@ const Counters = props => (
</tr>
<tr>
<td>
Blocked malware/phishing
<Tooltip text="A number of DNS requests blocked by the AdGuard browsing security module" type={tooltipType} />
<Trans>stats_malware_phishing</Trans>
<Tooltip text={ props.t('number_of_dns_query_blocked_24_hours_by_sec') } type={tooltipType} />
</td>
<td className="text-right">
<span className="text-muted">
@@ -45,8 +48,8 @@ const Counters = props => (
</tr>
<tr>
<td>
Blocked adult websites
<Tooltip text="A number of adult websites blocked" type={tooltipType} />
<Trans>stats_adult</Trans>
<Tooltip text={ props.t('number_of_dns_query_blocked_24_hours_adult') } type={tooltipType} />
</td>
<td className="text-right">
<span className="text-muted">
@@ -56,8 +59,8 @@ const Counters = props => (
</tr>
<tr>
<td>
Enforced safe search
<Tooltip text="A number of DNS requests to search engines for which Safe Search was enforced" type={tooltipType} />
<Trans>enforced_save_search</Trans>
<Tooltip text={ props.t('number_of_dns_query_to_safe_search') } type={tooltipType} />
</td>
<td className="text-right">
<span className="text-muted">
@@ -67,8 +70,8 @@ const Counters = props => (
</tr>
<tr>
<td>
Average processing time
<Tooltip text="Average time in milliseconds on processing a DNS request" type={tooltipType} />
<Trans>average_processing_time</Trans>
<Tooltip text={ props.t('average_processing_time_hint') } type={tooltipType} />
</td>
<td className="text-right">
<span className="text-muted">
@@ -89,6 +92,7 @@ Counters.propTypes = {
replacedSafesearch: PropTypes.number.isRequired,
avgProcessingTime: PropTypes.number.isRequired,
refreshButton: PropTypes.node.isRequired,
t: PropTypes.func.isRequired,
};
export default Counters;
export default withNamespaces()(Counters);

View File

@@ -2,6 +2,7 @@ import React, { Component } from 'react';
import ReactTable from 'react-table';
import PropTypes from 'prop-types';
import map from 'lodash/map';
import { withNamespaces, Trans } from 'react-i18next';
import Card from '../ui/Card';
import Cell from '../ui/Cell';
@@ -29,8 +30,8 @@ class QueriedDomains extends Component {
const trackerData = getTrackerData(value);
return (
<div className="logs__row" title={value}>
<div className="logs__text">
<div className="logs__row">
<div className="logs__text" title={value}>
{value}
</div>
{trackerData && <Popover data={trackerData} />}
@@ -38,7 +39,7 @@ class QueriedDomains extends Component {
);
},
}, {
Header: 'Requests count',
Header: <Trans>requests_count</Trans>,
accessor: 'count',
maxWidth: 190,
Cell: ({ value }) => {
@@ -52,15 +53,16 @@ class QueriedDomains extends Component {
}];
render() {
const { t } = this.props;
return (
<Card title="Top queried domains" subtitle="for the last 24 hours" bodyType="card-table" refresh={this.props.refreshButton}>
<Card title={ t('stats_query_domain') } subtitle={ t('for_last_24_hours') } bodyType="card-table" refresh={this.props.refreshButton}>
<ReactTable
data={map(this.props.topQueriedDomains, (value, prop) => (
{ ip: prop, count: value }
))}
columns={this.columns}
showPagination={false}
noDataText="No domains found"
noDataText={ t('no_domains_found') }
minRows={6}
className="-striped -highlight card-table-overflow stats__table"
/>
@@ -73,6 +75,7 @@ QueriedDomains.propTypes = {
topQueriedDomains: PropTypes.object.isRequired,
dnsQueries: PropTypes.number.isRequired,
refreshButton: PropTypes.node.isRequired,
t: PropTypes.func,
};
export default QueriedDomains;
export default withNamespaces()(QueriedDomains);

View File

@@ -1,5 +1,6 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Trans, withNamespaces } from 'react-i18next';
import Card from '../ui/Card';
import Line from '../ui/Line';
@@ -24,13 +25,13 @@ class Statistics extends Component {
return (
<div className="row">
<div className="col-sm-6 col-lg-3">
<Card bodyType="card-wrap">
<Card type="card--full" bodyType="card-wrap">
<div className="card-body-stats">
<div className="card-value card-value-stats text-blue">
{dnsQueries}
</div>
<div className="card-title-stats">
DNS Queries
<Trans>dns_query</Trans>
</div>
</div>
<div className="card-chart-bg">
@@ -39,7 +40,7 @@ class Statistics extends Component {
</Card>
</div>
<div className="col-sm-6 col-lg-3">
<Card bodyType="card-wrap">
<Card type="card--full" bodyType="card-wrap">
<div className="card-body-stats">
<div className="card-value card-value-stats text-red">
{blockedFiltering}
@@ -48,7 +49,9 @@ class Statistics extends Component {
{getPercent(dnsQueries, blockedFiltering)}
</div>
<div className="card-title-stats">
Blocked by <a href="#filters">Filters</a>
<a href="#filters">
<Trans>blocked_by</Trans>
</a>
</div>
</div>
<div className="card-chart-bg">
@@ -57,7 +60,7 @@ class Statistics extends Component {
</Card>
</div>
<div className="col-sm-6 col-lg-3">
<Card bodyType="card-wrap">
<Card type="card--full" bodyType="card-wrap">
<div className="card-body-stats">
<div className="card-value card-value-stats text-green">
{replacedSafebrowsing}
@@ -66,7 +69,7 @@ class Statistics extends Component {
{getPercent(dnsQueries, replacedSafebrowsing)}
</div>
<div className="card-title-stats">
Blocked malware/phishing
<Trans>stats_malware_phishing</Trans>
</div>
</div>
<div className="card-chart-bg">
@@ -75,7 +78,7 @@ class Statistics extends Component {
</Card>
</div>
<div className="col-sm-6 col-lg-3">
<Card bodyType="card-wrap">
<Card type="card--full" bodyType="card-wrap">
<div className="card-body-stats">
<div className="card-value card-value-stats text-yellow">
{replacedParental}
@@ -84,7 +87,7 @@ class Statistics extends Component {
{getPercent(dnsQueries, replacedParental)}
</div>
<div className="card-title-stats">
Blocked adult websites
<Trans>stats_adult</Trans>
</div>
</div>
<div className="card-chart-bg">
@@ -106,4 +109,4 @@ Statistics.propTypes = {
refreshButton: PropTypes.node.isRequired,
};
export default Statistics;
export default withNamespaces()(Statistics);

View File

@@ -1,6 +1,6 @@
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import 'whatwg-fetch';
import { Trans, withNamespaces } from 'react-i18next';
import Statistics from './Statistics';
import Counters from './Counters';
@@ -24,31 +24,36 @@ class Dashboard extends Component {
}
getToggleFilteringButton = () => {
const { protectionEnabled } = this.props.dashboard;
const buttonText = protectionEnabled ? 'Disable' : 'Enable';
const { protectionEnabled, processingProtection } = this.props.dashboard;
const buttonText = protectionEnabled ? 'disable_protection' : 'enable_protection';
const buttonClass = protectionEnabled ? 'btn-gray' : 'btn-success';
return (
<button type="button" className={`btn btn-sm mr-2 ${buttonClass}`} onClick={() => this.props.toggleProtection(protectionEnabled)}>
{buttonText} protection
<button
type="button"
className={`btn btn-sm mr-2 ${buttonClass}`}
onClick={() => this.props.toggleProtection(protectionEnabled)}
disabled={processingProtection}
>
<Trans>{buttonText}</Trans>
</button>
);
}
render() {
const { dashboard } = this.props;
const { dashboard, t } = this.props;
const dashboardProcessing =
dashboard.processing ||
dashboard.processingStats ||
dashboard.processingStatsHistory ||
dashboard.processingTopStats;
const refreshFullButton = <button type="button" className="btn btn-outline-primary btn-sm" onClick={() => this.getAllStats()}>Refresh statistics</button>;
const refreshFullButton = <button type="button" className="btn btn-outline-primary btn-sm" onClick={() => this.getAllStats()}><Trans>refresh_statics</Trans></button>;
const refreshButton = <button type="button" className="btn btn-outline-primary btn-sm card-refresh" onClick={() => this.getAllStats()} />;
return (
<Fragment>
<PageTitle title="Dashboard">
<PageTitle title={ t('dashboard') }>
<div className="page-title__actions">
{this.getToggleFilteringButton()}
{refreshFullButton}
@@ -124,6 +129,8 @@ Dashboard.propTypes = {
isCoreRunning: PropTypes.bool,
getFiltering: PropTypes.func,
toggleProtection: PropTypes.func,
processingProtection: PropTypes.bool,
t: PropTypes.func,
};
export default Dashboard;
export default withNamespaces()(Dashboard);

View File

@@ -1,8 +1,9 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Trans, withNamespaces } from 'react-i18next';
import Card from '../ui/Card';
export default class UserRules extends Component {
class UserRules extends Component {
handleChange = (e) => {
const { value } = e.currentTarget;
this.props.handleRulesChange(value);
@@ -14,44 +15,45 @@ export default class UserRules extends Component {
};
render() {
const { t } = this.props;
return (
<Card
title="Custom filtering rules"
subtitle="Enter one rule on a line. You can use either adblock rules or hosts files syntax."
title={ t('custom_filter_rules') }
subtitle={ t('custom_filter_rules_hint') }
>
<form onSubmit={this.handleSubmit}>
<textarea className="form-control form-control--textarea-large" value={this.props.userRules} onChange={this.handleChange} />
<div className="card-actions">
<button
className="btn btn-success btn-standart"
className="btn btn-success btn-standard"
type="submit"
onClick={this.handleSubmit}
>
Apply
<Trans>apply_btn</Trans>
</button>
</div>
</form>
<hr/>
<div className="list leading-loose">
Examples:
<Trans>examples_title</Trans>:
<ol className="leading-loose">
<li>
<code>||example.org^</code> - block access to the example.org domain
and all its subdomains
<code>||example.org^</code> - { t('example_meaning_filter_block') }
</li>
<li>
<code> @@||example.org^</code> - unblock access to the example.org
domain and all its subdomains
<code> @@||example.org^</code> - { t('example_meaning_filter_whitelist') }
</li>
<li>
<code>127.0.0.1 example.org</code> - AdGuard Home will now return
127.0.0.1 address for the example.org domain (but not its subdomains).
<code>127.0.0.1 example.org</code> - { t('example_meaning_host_block') }
</li>
<li>
<code>! Here goes a comment</code> - just a comment
<code>{ t('example_comment') }</code> - { t('example_comment_meaning') }
</li>
<li>
<code># Also a comment</code> - just a comment
<code>{ t('example_comment_hash') }</code> - { t('example_comment_meaning') }
</li>
<li>
<code>/REGEX/</code> - { t('example_regex_meaning') }
</li>
</ol>
</div>
@@ -64,4 +66,7 @@ UserRules.propTypes = {
userRules: PropTypes.string,
handleRulesChange: PropTypes.func,
handleRulesSubmit: PropTypes.func,
t: PropTypes.func,
};
export default withNamespaces()(UserRules);

View File

@@ -1,6 +1,7 @@
import React, { Component } from 'react';
import ReactTable from 'react-table';
import PropTypes from 'prop-types';
import { Trans, withNamespaces } from 'react-i18next';
import Modal from '../ui/Modal';
import PageTitle from '../ui/PageTitle';
import Card from '../ui/Card';
@@ -33,59 +34,82 @@ class Filters extends Component {
};
columns = [{
Header: 'Enabled',
Header: <Trans>enabled_table_header</Trans>,
accessor: 'enabled',
Cell: this.renderCheckbox,
width: 90,
className: 'text-center',
}, {
Header: 'Name',
Header: <Trans>name_table_header</Trans>,
accessor: 'name',
Cell: ({ value }) => (<div className="logs__row logs__row--overflow"><span className="logs__text" title={value}>{value}</span></div>),
}, {
Header: 'Filter URL',
Header: <Trans>filter_url_table_header</Trans>,
accessor: 'url',
Cell: ({ value }) => (<div className="logs__row logs__row--overflow"><a href={value} target='_blank' rel='noopener noreferrer' className="link logs__text">{value}</a></div>),
}, {
Header: 'Rules count',
Header: <Trans>rules_count_table_header</Trans>,
accessor: 'rulesCount',
className: 'text-center',
Cell: props => props.value.toLocaleString(),
}, {
Header: 'Last time updated',
Header: <Trans>last_time_updated_table_header</Trans>,
accessor: 'lastUpdated',
className: 'text-center',
}, {
Header: 'Actions',
Header: <Trans>actions_table_header</Trans>,
accessor: 'url',
Cell: ({ value }) => (<span className='remove-icon fe fe-trash-2' onClick={() => this.props.removeFilter(value)}/>),
Cell: ({ value }) => (<span title={ this.props.t('delete_table_action') } className='remove-icon fe fe-trash-2' onClick={() => this.props.removeFilter(value)}/>),
className: 'text-center',
width: 75,
width: 80,
sortable: false,
},
];
render() {
const { filters, userRules } = this.props.filtering;
const { t } = this.props;
const { filters, userRules, processingRefreshFilters } = this.props.filtering;
return (
<div>
<PageTitle title="Filters" />
<PageTitle title={ t('filters') } />
<div className="content">
<div className="row">
<div className="col-md-12">
<Card
title="Filters and hosts blocklists"
subtitle="AdGuard Home understands basic adblock rules and hosts files syntax."
title={ t('filters_and_hosts') }
subtitle={ t('filters_and_hosts_hint') }
>
<ReactTable
data={filters}
columns={this.columns}
showPagination={false}
noDataText="No filters added"
minRows={4} // TODO find out what to show if rules.length is 0
showPagination={true}
defaultPageSize={10}
minRows={4}
// Text
previousText={ t('previous_btn') }
nextText={ t('next_btn') }
loadingText={ t('loading_table_status') }
pageText={ t('page_table_footer_text') }
ofText={ t('of_table_footer_text') }
rowsText={ t('rows_table_footer_text') }
noDataText={ t('no_filters_added') }
/>
<div className="card-actions">
<button className="btn btn-success btn-standart mr-2" type="submit" onClick={this.props.toggleFilteringModal}>Add filter</button>
<button className="btn btn-primary btn-standart" type="submit" onClick={this.props.refreshFilters}>Check updates</button>
<button
className="btn btn-success btn-standard mr-2"
type="submit"
onClick={this.props.toggleFilteringModal}
>
<Trans>add_filter_btn</Trans>
</button>
<button
className="btn btn-primary btn-standard"
type="submit"
onClick={this.props.refreshFilters}
disabled={processingRefreshFilters}
>
<Trans>check_updates_btn</Trans>
</button>
</div>
</Card>
</div>
@@ -103,8 +127,9 @@ class Filters extends Component {
toggleModal={this.props.toggleFilteringModal}
addFilter={this.props.addFilter}
isFilterAdded={this.props.filtering.isFilterAdded}
title="New filter subscription"
inputDescription="Enter a valid URL to a filter subscription or a hosts file."
processingAddFilter={this.props.filtering.processingAddFilter}
title={ t('new_filter_btn') }
inputDescription={ t('enter_valid_filter_url') }
/>
</div>
);
@@ -119,6 +144,8 @@ Filters.propTypes = {
filters: PropTypes.array,
isFilteringModalOpen: PropTypes.bool.isRequired,
isFilterAdded: PropTypes.bool,
processingAddFilter: PropTypes.bool,
processingRefreshFilters: PropTypes.bool,
}),
removeFilter: PropTypes.func.isRequired,
toggleFilterStatus: PropTypes.func.isRequired,
@@ -126,7 +153,8 @@ Filters.propTypes = {
toggleFilteringModal: PropTypes.func.isRequired,
handleRulesChange: PropTypes.func.isRequired,
refreshFilters: PropTypes.func.isRequired,
t: PropTypes.func,
};
export default Filters;
export default withNamespaces()(Filters);

View File

@@ -88,6 +88,8 @@
.nav-tabs .nav-link {
width: auto;
border-bottom: 1px solid transparent;
font-size: 13px;
white-space: nowrap;
}
.mobile-menu {
@@ -107,6 +109,15 @@
.nav-version {
padding: 0;
}
}
@media screen and (min-width: 1280px) {
.nav-tabs .nav-link {
font-size: 14px;
}
.nav-version {
font-size: 0.85rem;
}
}

View File

@@ -3,7 +3,7 @@ import { NavLink } from 'react-router-dom';
import PropTypes from 'prop-types';
import enhanceWithClickOutside from 'react-click-outside';
import classnames from 'classnames';
import { Trans, withNamespaces } from 'react-i18next';
import { REPOSITORY } from '../../helpers/constants';
class Menu extends Component {
@@ -17,48 +17,48 @@ class Menu extends Component {
render() {
const menuClass = classnames({
'col-lg mobile-menu': true,
'col-lg-6 mobile-menu': true,
'mobile-menu--active': this.props.isMenuOpen,
});
return (
<Fragment>
<div className={menuClass}>
<ul className="nav nav-tabs border-0 flex-column flex-lg-row">
<ul className="nav nav-tabs border-0 flex-column flex-lg-row flex-nowrap">
<li className="nav-item border-bottom d-lg-none" onClick={this.toggleMenu}>
<div className="nav-link nav-link--back">
<svg className="nav-icon" fill="none" height="24" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m19 12h-14"/><path d="m12 19-7-7 7-7"/></svg>
Back
<Trans>back</Trans>
</div>
</li>
<li className="nav-item">
<NavLink to="/" exact={true} className="nav-link">
<svg className="nav-icon" fill="none" height="24" stroke="#9aa0ac" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m3 9 9-7 9 7v11a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2-2z"/><path d="m9 22v-10h6v10"/></svg>
Dashboard
<Trans>dashboard</Trans>
</NavLink>
</li>
<li className="nav-item">
<NavLink to="/settings" className="nav-link">
<svg className="nav-icon" fill="none" height="24" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="3"/><path d="m19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1 -2.83 0l-.06-.06a1.65 1.65 0 0 0 -1.82-.33 1.65 1.65 0 0 0 -1 1.51v.17a2 2 0 0 1 -2 2 2 2 0 0 1 -2-2v-.09a1.65 1.65 0 0 0 -1.08-1.51 1.65 1.65 0 0 0 -1.82.33l-.06.06a2 2 0 0 1 -2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0 -1.51-1h-.17a2 2 0 0 1 -2-2 2 2 0 0 1 2-2h.09a1.65 1.65 0 0 0 1.51-1.08 1.65 1.65 0 0 0 -.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33h.08a1.65 1.65 0 0 0 1-1.51v-.17a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0 -.33 1.82v.08a1.65 1.65 0 0 0 1.51 1h.17a2 2 0 0 1 2 2 2 2 0 0 1 -2 2h-.09a1.65 1.65 0 0 0 -1.51 1z"/></svg>
Settings
<Trans>settings</Trans>
</NavLink>
</li>
<li className="nav-item">
<NavLink to="/filters" className="nav-link">
<svg className="nav-icon" fill="none" height="24" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m22 3h-20l8 9.46v6.54l4 2v-8.54z"/></svg>
Filters
<Trans>filters</Trans>
</NavLink>
</li>
<li className="nav-item">
<NavLink to="/logs" className="nav-link">
<svg className="nav-icon" fill="none" height="24" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m14 2h-8a2 2 0 0 0 -2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-12z"/><path d="m14 2v6h6"/><path d="m16 13h-8"/><path d="m16 17h-8"/><path d="m10 9h-1-1"/></svg>
Query Log
<Trans>query_log</Trans>
</NavLink>
</li>
<li className="nav-item">
<a href={`${REPOSITORY.URL}/wiki`} className="nav-link" target="_blank" rel="noopener noreferrer">
<svg className="nav-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#66b574" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><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>
FAQ
<Trans>faq</Trans>
</a>
</li>
</ul>
@@ -74,4 +74,4 @@ Menu.propTypes = {
toggleMenuOpen: PropTypes.func,
};
export default enhanceWithClickOutside(Menu);
export default withNamespaces()(enhanceWithClickOutside(Menu));

View File

@@ -1,15 +1,16 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Trans, withNamespaces } from 'react-i18next';
export default function Version(props) {
function Version(props) {
const { dnsVersion, dnsAddress, dnsPort } = props;
return (
<div className="nav-version">
<div className="nav-version__text">
version: <span className="nav-version__value">{dnsVersion}</span>
<Trans>version</Trans>: <span className="nav-version__value">{dnsVersion}</span>
</div>
<div className="nav-version__text">
address: <span className="nav-version__value">{dnsAddress}:{dnsPort}</span>
<Trans>address</Trans>: <span className="nav-version__value">{dnsAddress}:{dnsPort}</span>
</div>
</div>
);
@@ -20,3 +21,5 @@ Version.propTypes = {
dnsAddress: PropTypes.string,
dnsPort: PropTypes.number,
};
export default withNamespaces()(Version);

View File

@@ -2,16 +2,16 @@ import React, { Component } from 'react';
import { Link } from 'react-router-dom';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { Trans, withNamespaces } from 'react-i18next';
import Menu from './Menu';
import Version from './Version';
import logo from './logo.svg';
import logo from '../ui/svg/logo.svg';
import './Header.css';
class Header extends Component {
state = {
isMenuOpen: false,
isDropdownOpen: false,
};
toggleMenuOpen = () => {
@@ -24,6 +24,7 @@ class Header extends Component {
render() {
const { dashboard } = this.props;
const { isMenuOpen } = this.state;
const badgeClass = classnames({
'badge dns-status': true,
'badge-success': dashboard.protectionEnabled,
@@ -44,14 +45,14 @@ class Header extends Component {
</Link>
{!dashboard.proccessing && dashboard.isCoreRunning &&
<span className={badgeClass}>
{dashboard.protectionEnabled ? 'ON' : 'OFF'}
<Trans>{dashboard.protectionEnabled ? 'on' : 'off'}</Trans>
</span>
}
</div>
</div>
<Menu
location={this.props.location}
isMenuOpen={this.state.isMenuOpen}
isMenuOpen={isMenuOpen}
toggleMenuOpen={this.toggleMenuOpen}
closeMenu={this.closeMenu}
/>
@@ -72,4 +73,4 @@ Header.propTypes = {
location: PropTypes.object,
};
export default Header;
export default withNamespaces()(Header);

View File

@@ -1 +0,0 @@
<svg width="340" height="91" viewBox="0 0 340 91" xmlns="http://www.w3.org/2000/svg"><g fill="none" fill-rule="evenodd"><path d="M265.964 50l-2.615-6.675h-13.03L247.844 50H239l14.124-34h7.894L275 50h-9.036zm-9.035-24.924l-4.28 11.67h8.465l-4.185-11.67zM238 37.231c0 2.054-.342 3.924-1.027 5.609-.685 1.685-1.664 3.129-2.938 4.333-1.274 1.203-2.811 2.142-4.61 2.816-1.8.674-3.799 1.011-5.997 1.011-2.23 0-4.236-.337-6.02-1.011-1.783-.674-3.296-1.613-4.538-2.816-1.242-1.204-2.198-2.648-2.867-4.333S209 39.285 209 37.23V16h8.122v20.557c0 .93.12 1.813.358 2.648a6.82 6.82 0 0 0 1.1 2.239c.493.658 1.146 1.18 1.958 1.564.812.385 1.791.578 2.938.578 1.147 0 2.126-.193 2.938-.578.813-.385 1.473-.906 1.983-1.564a6.248 6.248 0 0 0 1.099-2.239c.223-.835.334-1.717.334-2.648V16H238v21.231zM204 47.134c-1.623.846-3.52 1.535-5.69 2.067-2.17.533-4.534.799-7.094.799-2.654 0-5.096-.423-7.329-1.268-2.232-.846-4.152-2.036-5.76-3.57-1.607-1.536-2.864-3.376-3.769-5.521-.905-2.145-1.358-4.534-1.358-7.164 0-2.663.46-5.074 1.381-7.235.921-2.161 2.194-4.002 3.817-5.52 1.623-1.52 3.528-2.686 5.713-3.5 2.185-.815 4.542-1.222 7.07-1.222 2.623 0 5.058.4 7.306 1.198 2.248.799 4.074 1.871 5.479 3.218l-5.058 5.779c-.78-.909-1.81-1.652-3.09-2.232-1.28-.58-2.732-.869-4.355-.869-1.405 0-2.7.258-3.887.775a9.345 9.345 0 0 0-3.09 2.161c-.875.924-1.554 2.02-2.038 3.289-.483 1.268-.725 2.654-.725 4.158 0 1.534.218 2.944.655 4.228.437 1.284 1.085 2.388 1.944 3.312.858.924 1.92 1.644 3.184 2.16 1.264.518 2.708.776 4.331.776.937 0 1.827-.07 2.67-.211a9.929 9.929 0 0 0 2.341-.682V36h-6.322v-6.483H204v17.617zM340 32.904c0 2.977-.54 5.547-1.618 7.708-1.079 2.16-2.501 3.937-4.268 5.33a17.637 17.637 0 0 1-5.98 3.074c-2.22.656-4.47.984-6.753.984H309V16h12.006c2.345 0 4.659.28 6.941.84 2.282.56 4.315 1.49 6.097 2.786s3.22 3.033 4.315 5.21c1.094 2.177 1.641 4.866 1.641 8.068zm-8.348 0c0-1.921-.305-3.514-.914-4.778-.61-1.265-1.423-2.273-2.44-3.026a9.649 9.649 0 0 0-3.47-1.608 16.677 16.677 0 0 0-4.01-.48h-3.986v19.88h3.799a16.86 16.86 0 0 0 4.15-.504c1.33-.336 2.502-.888 3.518-1.656 1.016-.769 1.829-1.793 2.439-3.074.61-1.28.914-2.865.914-4.754zM169 32.904c0 2.977-.54 5.547-1.618 7.708-1.079 2.16-2.501 3.937-4.268 5.33a17.637 17.637 0 0 1-5.98 3.074c-2.22.656-4.47.984-6.753.984H138V16h12.006c2.345 0 4.659.28 6.941.84 2.282.56 4.315 1.49 6.097 2.786s3.22 3.033 4.315 5.21c1.094 2.177 1.641 4.866 1.641 8.068zm-8.348 0c0-1.921-.305-3.514-.914-4.778-.61-1.265-1.423-2.273-2.44-3.026a9.649 9.649 0 0 0-3.47-1.608 16.677 16.677 0 0 0-4.01-.48h-3.986v19.88h3.799a16.86 16.86 0 0 0 4.15-.504c1.33-.336 2.502-.888 3.518-1.656 1.016-.769 1.829-1.793 2.439-3.074.61-1.28.914-2.865.914-4.754zM126.964 50l-2.615-6.675h-13.03L108.844 50H100l14.124-34h7.894L136 50h-9.036zm-9.035-24.924l-4.28 11.67h8.465l-4.185-11.67zM295.674 50l-7.135-13.494h-2.705V50H278V16h12.59c1.586 0 3.133.168 4.64.504 1.508.336 2.86.905 4.058 1.705 1.196.8 2.152 1.857 2.867 3.17.715 1.312 1.073 2.945 1.073 4.898 0 2.305-.606 4.242-1.819 5.81-1.212 1.57-2.89 2.69-5.036 3.362L305 50h-9.326zm-.327-23.58c0-.8-.163-1.448-.49-1.944a3.39 3.39 0 0 0-1.259-1.153 5.355 5.355 0 0 0-1.725-.552 12.364 12.364 0 0 0-1.842-.144h-4.243v7.924h3.777c.653 0 1.321-.056 2.005-.168a6.257 6.257 0 0 0 1.865-.6 3.596 3.596 0 0 0 1.376-1.25c.357-.543.536-1.248.536-2.112z" fill="#242424"/><path d="M44.477 0C30.575 0 13.805 3.255 0 10.419 0 25.89-.19 64.436 44.477 90.772 89.145 64.436 88.956 25.89 88.956 10.42 75.149 3.255 58.38 0 44.476 0z" fill="#68BC71"/><path d="M44.431 90.746C-.19 64.41 0 25.886 0 10.419 13.79 3.263 30.538.007 44.431 0v90.746z" fill="#67B279"/><path d="M42.854 60.566L69.75 24.477c-1.97-1.572-3.7-.462-4.65.397l-.036.003L42.64 48.102l-8.45-10.123c-4.03-4.636-9.51-1.1-10.79-.165l19.455 22.752" fill="#FFF"/><path d="M102.65 83V64.8h2.054v8.086h10.504V64.8h2.054V83h-2.054v-8.19h-10.504V83h-2.054zm28.21.312c-5.538 0-9.256-4.342-9.256-9.412 0-5.018 3.77-9.412 9.308-9.412s9.256 4.342 9.256 9.412c0 5.018-3.77 9.412-9.308 9.412zm.052-1.898c4.16 0 7.124-3.328 7.124-7.514 0-4.134-3.016-7.514-7.176-7.514s-7.124 3.328-7.124 7.514c0 4.134 3.016 7.514 7.176 7.514zM144.51 83V64.8h2.08l6.63 9.932 6.63-9.932h2.08V83h-2.054V68.258l-6.63 9.75h-.104l-6.63-9.724V83h-2.002zm22.568 0V64.8h13.156v1.872h-11.102v6.214h9.932v1.872h-9.932v6.37h11.232V83h-13.286z" fill="#4D4D4D"/></g></svg>

Before

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -4,13 +4,14 @@ import ReactTable from 'react-table';
import { saveAs } from 'file-saver/FileSaver';
import escapeRegExp from 'lodash/escapeRegExp';
import endsWith from 'lodash/endsWith';
import { Trans, withNamespaces } from 'react-i18next';
import { formatTime } from '../../helpers/helpers';
import { getTrackerData } from '../../helpers/trackers/trackers';
import PageTitle from '../ui/PageTitle';
import Card from '../ui/Card';
import Loading from '../ui/Loading';
import Tooltip from '../ui/Tooltip';
import PopoverFiltered from '../ui/PopoverFilter';
import Popover from '../ui/Popover';
import './Logs.css';
@@ -36,15 +37,16 @@ class Logs extends Component {
}
}
renderTooltip(isFiltered, rule) {
renderTooltip(isFiltered, rule, filter) {
if (rule) {
return (isFiltered && <Tooltip text={rule}/>);
return (isFiltered && <PopoverFiltered rule={rule} filter={filter}/>);
}
return '';
}
toggleBlocking = (type, domain) => {
const { userRules } = this.props.filtering;
const { t } = this.props;
const lineEnding = !endsWith(userRules, '\n') ? '\n' : '';
const baseRule = `||${domain}^$important`;
const baseUnblocking = `@@${baseRule}`;
@@ -55,10 +57,10 @@ class Logs extends Component {
if (userRules.match(preparedBlockingRule)) {
this.props.setRules(userRules.replace(`${blockingRule}`, ''));
this.props.addSuccessToast(`Rule removed from the custom filtering rules: ${blockingRule}`);
this.props.addSuccessToast(`${t('rule_removed_from_custom_filtering_toast')}: ${blockingRule}`);
} else if (!userRules.match(preparedUnblockingRule)) {
this.props.setRules(`${userRules}${lineEnding}${unblockingRule}\n`);
this.props.addSuccessToast(`Rule added to the custom filtering rules: ${unblockingRule}`);
this.props.addSuccessToast(`${t('rule_added_to_custom_filtering_toast')}: ${unblockingRule}`);
}
this.props.getFilteringStatus();
@@ -66,30 +68,33 @@ class Logs extends Component {
renderBlockingButton(isFiltered, domain) {
const buttonClass = isFiltered ? 'btn-outline-secondary' : 'btn-outline-danger';
const buttonText = isFiltered ? 'Unblock' : 'Block';
const buttonText = isFiltered ? 'unblock_btn' : 'block_btn';
const buttonType = isFiltered ? 'unblock' : 'block';
return (
<div className="logs__action">
<button
type="button"
className={`btn btn-sm ${buttonClass}`}
onClick={() => this.toggleBlocking(buttonText.toLowerCase(), domain)}
onClick={() => this.toggleBlocking(buttonType, domain)}
disabled={this.props.filtering.processingRules}
>
{buttonText}
<Trans>{buttonText}</Trans>
</button>
</div>
);
}
renderLogs(logs) {
const { t } = this.props;
const columns = [{
Header: 'Time',
Header: t('time_table_header'),
accessor: 'time',
maxWidth: 110,
filterable: false,
Cell: ({ value }) => (<div className="logs__row"><span className="logs__text" title={value}>{formatTime(value)}</span></div>),
}, {
Header: 'Domain name',
Header: t('domain_name_table_header'),
accessor: 'domain',
Cell: (row) => {
const response = row.value;
@@ -105,11 +110,11 @@ class Logs extends Component {
);
},
}, {
Header: 'Type',
Header: t('type_table_header'),
accessor: 'type',
maxWidth: 60,
}, {
Header: 'Response',
Header: t('response_table_header'),
accessor: 'response',
Cell: (row) => {
const responses = row.value;
@@ -117,14 +122,34 @@ class Logs extends Component {
const isFiltered = row ? reason.indexOf('Filtered') === 0 : false;
const parsedFilteredReason = reason.replace('Filtered', 'Filtered by ');
const rule = row && row.original && row.original.rule;
const { filterId } = row.original;
const { filters } = this.props.filtering;
let filterName = '';
if (reason === 'FilteredBlackList' || reason === 'NotFilteredWhiteList') {
if (filterId === 0) {
filterName = t('custom_filter_rules');
} else {
const filterItem = Object.keys(filters)
.filter(key => filters[key].id === filterId);
if (typeof filterItem !== 'undefined' && typeof filters[filterItem] !== 'undefined') {
filterName = filters[filterItem].name;
}
if (!filterName) {
filterName = t('unknown_filter', { filterId });
}
}
}
if (isFiltered) {
return (
<div className="logs__row">
{this.renderTooltip(isFiltered, rule)}
<span className="logs__text" title={parsedFilteredReason}>
{parsedFilteredReason}
</span>
{this.renderTooltip(isFiltered, rule, filterName)}
</div>
);
}
@@ -132,24 +157,26 @@ class Logs extends Component {
if (responses.length > 0) {
const liNodes = responses.map((response, index) =>
(<li key={index} title={response}>{response}</li>));
const isRenderTooltip = reason === 'NotFilteredWhiteList';
return (
<div className="logs__row">
{this.renderTooltip(isFiltered, rule)}
<ul className="list-unstyled">{liNodes}</ul>
{this.renderTooltip(isRenderTooltip, rule, filterName)}
</div>
);
}
return (
<div className="logs__row">
{this.renderTooltip(isFiltered, rule)}
<span>Empty</span>
<span><Trans>empty_response_status</Trans></span>
{this.renderTooltip(isFiltered, rule, filterName)}
</div>
);
},
filterMethod: (filter, row) => {
if (filter.value === 'filtered') {
// eslint-disable-next-line no-underscore-dangle
return row._original.reason.indexOf('Filtered') === 0;
return row._original.reason.indexOf('Filtered') === 0 || row._original.reason === 'NotFilteredWhiteList';
}
return true;
},
@@ -159,11 +186,11 @@ class Logs extends Component {
className="form-control"
value={filter ? filter.value : 'all'}
>
<option value="all">Show all</option>
<option value="filtered">Show filtered</option>
<option value="all">{ t('show_all_filter_type') }</option>
<option value="filtered">{ t('show_filtered_type') }</option>
</select>,
}, {
Header: 'Client',
Header: t('client_table_header'),
accessor: 'client',
maxWidth: 250,
Cell: (row) => {
@@ -191,7 +218,14 @@ class Logs extends Component {
showPagination={true}
defaultPageSize={50}
minRows={7}
noDataText="No logs found"
// Text
previousText={ t('previous_btn') }
nextText={ t('next_btn') }
loadingText={ t('loading_table_status') }
pageText={ t('page_table_footer_text') }
ofText={ t('of_table_footer_text') }
rowsText={ t('rows_table_footer_text') }
noDataText={ t('no_logs_found') }
defaultFilterMethod={(filter, row) => {
const id = filter.pivotId || filter.id;
return row[id] !== undefined ?
@@ -208,8 +242,19 @@ class Logs extends Component {
if (!rowInfo) {
return {};
}
if (rowInfo.original.reason.indexOf('Filtered') === 0) {
return {
className: 'red',
};
} else if (rowInfo.original.reason === 'NotFilteredWhiteList') {
return {
className: 'green',
};
}
return {
className: (rowInfo.original.reason.indexOf('Filtered') === 0 ? 'red' : ''),
className: '',
};
}}
/>);
@@ -225,7 +270,7 @@ class Logs extends Component {
saveAs(dataBlob, DOWNLOAD_LOG_FILENAME);
};
renderButtons(queryLogEnabled) {
renderButtons(queryLogEnabled, logStatusProcessing) {
if (queryLogEnabled) {
return (
<Fragment>
@@ -233,17 +278,18 @@ class Logs extends Component {
className="btn btn-gray btn-sm mr-2"
type="submit"
onClick={() => this.props.toggleLogStatus(queryLogEnabled)}
>Disable log</button>
disabled={logStatusProcessing}
><Trans>disabled_log_btn</Trans></button>
<button
className="btn btn-primary btn-sm mr-2"
type="submit"
onClick={this.handleDownloadButton}
>Download log file</button>
><Trans>download_log_file_btn</Trans></button>
<button
className="btn btn-outline-primary btn-sm"
type="submit"
onClick={this.getLogs}
>Refresh</button>
><Trans>refresh_btn</Trans></button>
</Fragment>
);
}
@@ -253,18 +299,19 @@ class Logs extends Component {
className="btn btn-success btn-sm mr-2"
type="submit"
onClick={() => this.props.toggleLogStatus(queryLogEnabled)}
>Enable log</button>
disabled={logStatusProcessing}
><Trans>enabled_log_btn</Trans></button>
);
}
render() {
const { queryLogs, dashboard } = this.props;
const { queryLogs, dashboard, t } = this.props;
const { queryLogEnabled } = dashboard;
return (
<Fragment>
<PageTitle title="Query Log" subtitle="Last 5000 DNS queries">
<PageTitle title={ t('query_log') } subtitle={ t('last_dns_queries') }>
<div className="page-title__actions">
{this.renderButtons(queryLogEnabled)}
{this.renderButtons(queryLogEnabled, dashboard.logStatusProcessing)}
</div>
</PageTitle>
<Card>
@@ -288,6 +335,9 @@ Logs.propTypes = {
userRules: PropTypes.string,
setRules: PropTypes.func,
addSuccessToast: PropTypes.func,
processingRules: PropTypes.bool,
logStatusProcessing: PropTypes.bool,
t: PropTypes.func,
};
export default Logs;
export default withNamespaces()(Logs);

View File

@@ -0,0 +1,112 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Field, reduxForm } from 'redux-form';
import { withNamespaces } from 'react-i18next';
import flow from 'lodash/flow';
import { renderField, required, ipv4, isPositive, toNumber } from '../../../helpers/form';
const Form = (props) => {
const {
t,
handleSubmit,
submitting,
invalid,
processingConfig,
} = props;
return (
<form onSubmit={handleSubmit}>
<div className="row">
<div className="col-lg-6">
<div className="form__group form__group--settings">
<label>{t('dhcp_form_gateway_input')}</label>
<Field
name="gateway_ip"
component={renderField}
type="text"
className="form-control"
placeholder={t('dhcp_form_gateway_input')}
validate={[ipv4, required]}
/>
</div>
<div className="form__group form__group--settings">
<label>{t('dhcp_form_subnet_input')}</label>
<Field
name="subnet_mask"
component={renderField}
type="text"
className="form-control"
placeholder={t('dhcp_form_subnet_input')}
validate={[ipv4, required]}
/>
</div>
</div>
<div className="col-lg-6">
<div className="form__group form__group--settings">
<div className="row">
<div className="col-12">
<label>{t('dhcp_form_range_title')}</label>
</div>
<div className="col">
<Field
name="range_start"
component={renderField}
type="text"
className="form-control"
placeholder={t('dhcp_form_range_start')}
validate={[ipv4, required]}
/>
</div>
<div className="col">
<Field
name="range_end"
component={renderField}
type="text"
className="form-control"
placeholder={t('dhcp_form_range_end')}
validate={[ipv4, required]}
/>
</div>
</div>
</div>
<div className="form__group form__group--settings">
<label>{t('dhcp_form_lease_title')}</label>
<Field
name="lease_duration"
component={renderField}
type="number"
className="form-control"
placeholder={t('dhcp_form_lease_input')}
validate={[required, isPositive]}
normalize={toNumber}
/>
</div>
</div>
</div>
<button
type="submit"
className="btn btn-success btn-standard"
disabled={submitting || invalid || processingConfig}
>
{t('save_config')}
</button>
</form>
);
};
Form.propTypes = {
handleSubmit: PropTypes.func,
submitting: PropTypes.bool,
invalid: PropTypes.bool,
interfaces: PropTypes.object,
initialValues: PropTypes.object,
processingConfig: PropTypes.bool,
t: PropTypes.func,
};
export default flow([
withNamespaces(),
reduxForm({ form: 'dhcpForm' }),
])(Form);

View File

@@ -0,0 +1,113 @@
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { Field, reduxForm, formValueSelector } from 'redux-form';
import { withNamespaces, Trans } from 'react-i18next';
import flow from 'lodash/flow';
const renderInterfaces = (interfaces => (
Object.keys(interfaces).map((item) => {
const option = interfaces[item];
const { name } = option;
const onlyIPv6 = option.ip_addresses.every(ip => ip.includes(':'));
let interfaceIP = option.ip_addresses[0];
if (!onlyIPv6) {
option.ip_addresses.forEach((ip) => {
if (!ip.includes(':')) {
interfaceIP = ip;
}
});
}
return (
<option value={name} key={name} disabled={onlyIPv6}>
{name} - {interfaceIP}
</option>
);
})
));
const renderInterfaceValues = (interfaceValues => (
<ul className="list-unstyled mt-1 mb-0">
<li>
<span className="interface__title">MTU: </span>
{interfaceValues.mtu}
</li>
<li>
<span className="interface__title"><Trans>dhcp_hardware_address</Trans>: </span>
{interfaceValues.hardware_address}
</li>
<li>
<span className="interface__title"><Trans>dhcp_ip_addresses</Trans>: </span>
{
interfaceValues.ip_addresses
.map(ip => <span key={ip} className="interface__ip">{ip}</span>)
}
</li>
</ul>
));
let Interface = (props) => {
const {
t,
handleChange,
interfaces,
processing,
interfaceValue,
enabled,
} = props;
return (
<form>
{!processing && interfaces &&
<div className="row">
<div className="col-sm-12 col-md-6">
<div className="form__group form__group--settings">
<label>{t('dhcp_interface_select')}</label>
<Field
name="interface_name"
component="select"
className="form-control custom-select"
onChange={handleChange}
>
<option value="" disabled={enabled}>{t('dhcp_interface_select')}</option>
{renderInterfaces(interfaces)}
</Field>
</div>
</div>
{interfaceValue &&
<div className="col-sm-12 col-md-6">
{renderInterfaceValues(interfaces[interfaceValue])}
</div>
}
</div>
}
<hr/>
</form>
);
};
Interface.propTypes = {
handleChange: PropTypes.func,
interfaces: PropTypes.object,
processing: PropTypes.bool,
interfaceValue: PropTypes.string,
initialValues: PropTypes.object,
enabled: PropTypes.bool,
t: PropTypes.func,
};
const selector = formValueSelector('dhcpInterface');
Interface = connect((state) => {
const interfaceValue = selector(state, 'interface_name');
return {
interfaceValue,
};
})(Interface);
export default flow([
withNamespaces(),
reduxForm({ form: 'dhcpInterface' }),
])(Interface);

View File

@@ -0,0 +1,36 @@
import React from 'react';
import PropTypes from 'prop-types';
import ReactTable from 'react-table';
import { Trans, withNamespaces } from 'react-i18next';
const columns = [{
Header: 'MAC',
accessor: 'mac',
}, {
Header: 'IP',
accessor: 'ip',
}, {
Header: <Trans>dhcp_table_hostname</Trans>,
accessor: 'hostname',
}, {
Header: <Trans>dhcp_table_expires</Trans>,
accessor: 'expires',
}];
const Leases = props => (
<ReactTable
data={props.leases || []}
columns={columns}
showPagination={false}
noDataText={ props.t('dhcp_leases_not_found') }
minRows={6}
className="-striped -highlight card-table-overflow"
/>
);
Leases.propTypes = {
leases: PropTypes.array,
t: PropTypes.func,
};
export default withNamespaces()(Leases);

View File

@@ -0,0 +1,173 @@
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { Trans, withNamespaces } from 'react-i18next';
import Form from './Form';
import Leases from './Leases';
import Interface from './Interface';
import Card from '../../ui/Card';
class Dhcp extends Component {
handleFormSubmit = (values) => {
this.props.setDhcpConfig(values);
};
handleToggle = (config) => {
this.props.toggleDhcp(config);
}
getToggleDhcpButton = () => {
const {
config, active, processingDhcp, processingConfig,
} = this.props.dhcp;
const activeDhcpFound = active && active.found;
const filledConfig = Object.keys(config).every((key) => {
if (key === 'enabled') {
return true;
}
return config[key];
});
if (config.enabled) {
return (
<button
type="button"
className="btn btn-standard mr-2 btn-gray"
onClick={() => this.props.toggleDhcp(config)}
disabled={processingDhcp || processingConfig}
>
<Trans>dhcp_disable</Trans>
</button>
);
}
return (
<button
type="button"
className="btn btn-standard mr-2 btn-success"
onClick={() => this.handleToggle(config)}
disabled={
!filledConfig
|| activeDhcpFound
|| processingDhcp
|| processingConfig
}
>
<Trans>dhcp_enable</Trans>
</button>
);
}
getActiveDhcpMessage = () => {
const { active } = this.props.dhcp;
if (active) {
if (active.error) {
return (
<div className="text-danger mb-2">
{active.error}
</div>
);
}
return (
<div className="mb-2">
{active.found ? (
<div className="text-danger">
<Trans>dhcp_found</Trans>
</div>
) : (
<div className="text-secondary">
<Trans>dhcp_not_found</Trans>
</div>
)}
</div>
);
}
return '';
}
render() {
const { t, dhcp } = 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>
<Card title={ t('dhcp_title') } subtitle={ t('dhcp_description') } bodyType="card-body box-body--settings">
<div className="dhcp">
{!dhcp.processing &&
<Fragment>
<Interface
onChange={this.handleFormSubmit}
initialValues={{ interface_name }}
interfaces={dhcp.interfaces}
processing={dhcp.processingInterfaces}
enabled={dhcp.config.enabled}
/>
<Form
onSubmit={this.handleFormSubmit}
initialValues={{ ...values }}
interfaces={dhcp.interfaces}
processingConfig={dhcp.processingConfig}
/>
<hr/>
<div className="card-actions mb-3">
{this.getToggleDhcpButton()}
<button
type="button"
className={statusButtonClass}
onClick={() =>
this.props.findActiveDhcp(dhcp.config.interface_name)
}
disabled={
!dhcp.config.interface_name
|| dhcp.processingConfig
}
>
<Trans>check_dhcp_servers</Trans>
</button>
</div>
{this.getActiveDhcpMessage()}
<div className="text-danger">
<Trans>dhcp_warning</Trans>
</div>
</Fragment>
}
</div>
</Card>
{!dhcp.processing && 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>
}
</Fragment>
);
}
}
Dhcp.propTypes = {
dhcp: PropTypes.object,
toggleDhcp: PropTypes.func,
getDhcpStatus: PropTypes.func,
setDhcpConfig: PropTypes.func,
findActiveDhcp: PropTypes.func,
handleSubmit: PropTypes.func,
t: PropTypes.func,
};
export default withNamespaces()(Dhcp);

View File

@@ -0,0 +1,364 @@
import React, { Fragment } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { Field, reduxForm, formValueSelector } from 'redux-form';
import { Trans, withNamespaces } from 'react-i18next';
import flow from 'lodash/flow';
import format from 'date-fns/format';
import { renderField, renderSelectField, toNumber, port, isSafePort } from '../../../helpers/form';
import { EMPTY_DATE } from '../../../helpers/constants';
import i18n from '../../../i18n';
const validate = (values) => {
const errors = {};
if (values.port_dns_over_tls && values.port_https) {
if (values.port_dns_over_tls === values.port_https) {
errors.port_dns_over_tls = i18n.t('form_error_equal');
errors.port_https = i18n.t('form_error_equal');
}
}
return errors;
};
const clearFields = (change, setTlsConfig, t) => {
const fields = {
private_key: '',
certificate_chain: '',
port_https: 443,
port_dns_over_tls: 853,
server_name: '',
force_https: false,
enabled: false,
};
// eslint-disable-next-line no-alert
if (window.confirm(t('encryption_reset'))) {
Object.keys(fields).forEach(field => change(field, fields[field]));
setTlsConfig(fields);
}
};
let Form = (props) => {
const {
t,
handleSubmit,
handleChange,
isEnabled,
certificateChain,
privateKey,
change,
invalid,
submitting,
processingConfig,
processingValidate,
not_after,
valid_chain,
valid_key,
valid_cert,
dns_names,
key_type,
issuer,
subject,
warning_validation,
setTlsConfig,
} = props;
return (
<form onSubmit={handleSubmit}>
<div className="row">
<div className="col-12">
<div className="form__group form__group--settings">
<Field
name="enabled"
type="checkbox"
component={renderSelectField}
placeholder={t('encryption_enable')}
onChange={handleChange}
/>
</div>
<div className="form__desc">
<Trans>encryption_enable_desc</Trans>
</div>
<hr/>
</div>
<div className="col-12">
<label className="form__label" htmlFor="server_name">
<Trans>encryption_server</Trans>
</label>
</div>
<div className="col-lg-6">
<div className="form__group form__group--settings">
<Field
id="server_name"
name="server_name"
component={renderField}
type="text"
className="form-control"
placeholder={t('encryption_server_enter')}
onChange={handleChange}
disabled={!isEnabled}
/>
<div className="form__desc">
<Trans>encryption_server_desc</Trans>
</div>
</div>
</div>
<div className="col-lg-6">
<div className="form__group form__group--settings">
<Field
name="force_https"
type="checkbox"
component={renderSelectField}
placeholder={t('encryption_redirect')}
onChange={handleChange}
disabled={!isEnabled}
/>
<div className="form__desc">
<Trans>encryption_redirect_desc</Trans>
</div>
</div>
</div>
</div>
<div className="row">
<div className="col-lg-6">
<div className="form__group form__group--settings">
<label className="form__label" htmlFor="port_https">
<Trans>encryption_https</Trans>
</label>
<Field
id="port_https"
name="port_https"
component={renderField}
type="number"
className="form-control"
placeholder={t('encryption_https')}
validate={[port, isSafePort]}
normalize={toNumber}
onChange={handleChange}
disabled={!isEnabled}
/>
<div className="form__desc">
<Trans>encryption_https_desc</Trans>
</div>
</div>
</div>
<div className="col-lg-6">
<div className="form__group form__group--settings">
<label className="form__label" htmlFor="port_dns_over_tls">
<Trans>encryption_dot</Trans>
</label>
<Field
id="port_dns_over_tls"
name="port_dns_over_tls"
component={renderField}
type="number"
className="form-control"
placeholder={t('encryption_dot')}
validate={[port]}
normalize={toNumber}
onChange={handleChange}
disabled={!isEnabled}
/>
<div className="form__desc">
<Trans>encryption_dot_desc</Trans>
</div>
</div>
</div>
</div>
<div className="row">
<div className="col-12">
<div className="form__group form__group--settings">
<label className="form__label form__label--bold" htmlFor="certificate_chain">
<Trans>encryption_certificates</Trans>
</label>
<div className="form__desc form__desc--top">
<Trans
values={{ link: 'letsencrypt.org' }}
components={[<a href="https://letsencrypt.org/" key="0">link</a>]}
>
encryption_certificates_desc
</Trans>
</div>
<Field
id="certificate_chain"
name="certificate_chain"
component="textarea"
type="text"
className="form-control form-control--textarea"
placeholder={t('encryption_certificates_input')}
onChange={handleChange}
disabled={!isEnabled}
/>
<div className="form__status">
{certificateChain &&
<Fragment>
<div className="form__label form__label--bold">
<Trans>encryption_status</Trans>:
</div>
<ul className="encryption__list">
<li className={valid_chain ? 'text-success' : 'text-danger'}>
{valid_chain ?
<Trans>encryption_chain_valid</Trans>
: <Trans>encryption_chain_invalid</Trans>
}
</li>
{valid_cert &&
<Fragment>
{subject &&
<li>
<Trans>encryption_subject</Trans>:&nbsp;
{subject}
</li>
}
{issuer &&
<li>
<Trans>encryption_issuer</Trans>:&nbsp;
{issuer}
</li>
}
{not_after && not_after !== EMPTY_DATE &&
<li>
<Trans>encryption_expire</Trans>:&nbsp;
{format(not_after, 'YYYY-MM-DD HH:mm:ss')}
</li>
}
{dns_names &&
<li>
<Trans>encryption_hostnames</Trans>:&nbsp;
{dns_names}
</li>
}
</Fragment>
}
</ul>
</Fragment>
}
</div>
</div>
</div>
</div>
<div className="row">
<div className="col-12">
<div className="form__group form__group--settings">
<label className="form__label form__label--bold" htmlFor="private_key">
<Trans>encryption_key</Trans>
</label>
<Field
id="private_key"
name="private_key"
component="textarea"
type="text"
className="form-control form-control--textarea"
placeholder="Copy/paste your PEM-encoded private key for your cerficate here."
onChange={handleChange}
disabled={!isEnabled}
/>
<div className="form__status">
{privateKey &&
<Fragment>
<div className="form__label form__label--bold">
<Trans>encryption_status</Trans>:
</div>
<ul className="encryption__list">
<li className={valid_key ? 'text-success' : 'text-danger'}>
{valid_key ?
<Trans values={{ type: key_type }}>
encryption_key_valid
</Trans>
: <Trans values={{ type: key_type }}>
encryption_key_invalid
</Trans>
}
</li>
</ul>
</Fragment>
}
</div>
</div>
</div>
{warning_validation &&
<div className="col-12">
<p className="text-danger">
{warning_validation}
</p>
</div>
}
</div>
<div className="btn-list mt-2">
<button
type="submit"
className="btn btn-success btn-standart"
disabled={
invalid
|| submitting
|| processingConfig
|| processingValidate
|| (isEnabled && (!privateKey || !certificateChain))
|| (privateKey && !valid_key)
|| (certificateChain && !valid_cert)
}
>
<Trans>save_config</Trans>
</button>
<button
type="button"
className="btn btn-secondary btn-standart"
disabled={submitting || processingConfig}
onClick={() => clearFields(change, setTlsConfig, t)}
>
<Trans>reset_settings</Trans>
</button>
</div>
</form>
);
};
Form.propTypes = {
handleSubmit: PropTypes.func.isRequired,
handleChange: PropTypes.func,
isEnabled: PropTypes.bool.isRequired,
certificateChain: PropTypes.string.isRequired,
privateKey: PropTypes.string.isRequired,
change: PropTypes.func.isRequired,
submitting: PropTypes.bool.isRequired,
invalid: PropTypes.bool.isRequired,
initialValues: PropTypes.object.isRequired,
processingConfig: PropTypes.bool.isRequired,
processingValidate: PropTypes.bool.isRequired,
status_key: PropTypes.string,
not_after: PropTypes.string,
warning_validation: PropTypes.string,
valid_chain: PropTypes.bool,
valid_key: PropTypes.bool,
valid_cert: PropTypes.bool,
dns_names: PropTypes.string,
key_type: PropTypes.string,
issuer: PropTypes.string,
subject: PropTypes.string,
t: PropTypes.func.isRequired,
setTlsConfig: PropTypes.func.isRequired,
};
const selector = formValueSelector('encryptionForm');
Form = connect((state) => {
const isEnabled = selector(state, 'enabled');
const certificateChain = selector(state, 'certificate_chain');
const privateKey = selector(state, 'private_key');
return {
isEnabled,
certificateChain,
privateKey,
};
})(Form);
export default flow([
withNamespaces(),
reduxForm({
form: 'encryptionForm',
validate,
}),
])(Form);

View File

@@ -0,0 +1,72 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { withNamespaces } from 'react-i18next';
import debounce from 'lodash/debounce';
import { DEBOUNCE_TIMEOUT } from '../../../helpers/constants';
import Form from './Form';
import Card from '../../ui/Card';
class Encryption extends Component {
componentDidMount() {
this.props.validateTlsConfig(this.props.encryption);
}
handleFormSubmit = (values) => {
this.props.setTlsConfig(values);
};
handleFormChange = debounce((values) => {
this.props.validateTlsConfig(values);
}, DEBOUNCE_TIMEOUT);
render() {
const { encryption, t } = this.props;
const {
enabled,
server_name,
force_https,
port_https,
port_dns_over_tls,
certificate_chain,
private_key,
} = encryption;
return (
<div className="encryption">
{encryption &&
<Card
title={t('encryption_title')}
subtitle={t('encryption_desc')}
bodyType="card-body box-body--settings"
>
<Form
initialValues={{
enabled,
server_name,
force_https,
port_https,
port_dns_over_tls,
certificate_chain,
private_key,
}}
onSubmit={this.handleFormSubmit}
onChange={this.handleFormChange}
setTlsConfig={this.props.setTlsConfig}
{...this.props.encryption}
/>
</Card>
}
</div>
);
}
}
Encryption.propTypes = {
setTlsConfig: PropTypes.func.isRequired,
validateTlsConfig: PropTypes.func.isRequired,
encryption: PropTypes.object.isRequired,
t: PropTypes.func.isRequired,
};
export default withNamespaces()(Encryption);

View File

@@ -1,4 +1,5 @@
.form__group {
position: relative;
margin-bottom: 15px;
}
@@ -6,7 +7,11 @@
margin-bottom: 0;
}
.btn-standart {
.form__group--settings:last-child {
margin-bottom: 20px;
}
.btn-standard {
padding-left: 20px;
padding-right: 20px;
}
@@ -18,3 +23,56 @@
.form-control--textarea-large {
min-height: 240px;
}
.form__message {
font-size: 11px;
}
.form__message--error {
color: #cd201f;
}
.interface__title {
font-size: 13px;
font-weight: 700;
}
.interface__ip:after {
content: ", ";
}
.interface__ip:last-child:after {
content: "";
}
.dhcp {
min-height: 450px;
}
.form__desc {
margin-top: 10px;
font-size: 13px;
color: rgba(74, 74, 74, 0.7);
}
.form__desc--top {
margin: 0 0 8px;
}
.form__label--bold {
font-weight: 700;
}
.form__status {
margin-top: 10px;
font-size: 14px;
line-height: 1.7;
}
.encryption__list {
padding-left: 0;
}
.encryption__list li {
list-style: inside;
}

View File

@@ -1,9 +1,10 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { Trans, withNamespaces } from 'react-i18next';
import Card from '../ui/Card';
export default class Upstream extends Component {
class Upstream extends Component {
handleChange = (e) => {
const { value } = e.currentTarget;
this.props.handleUpstreamChange(value);
@@ -20,14 +21,15 @@ export default class Upstream extends Component {
render() {
const testButtonClass = classnames({
'btn btn-primary btn-standart mr-2': true,
'btn btn-primary btn-standart mr-2 btn-loading': this.props.processingTestUpstream,
'btn btn-primary btn-standard mr-2': true,
'btn btn-primary btn-standard mr-2 btn-loading': this.props.processingTestUpstream,
});
const { t } = this.props;
return (
<Card
title="Upstream DNS servers"
subtitle="If you keep this field empty, AdGuard Home will use <a href='https://1.1.1.1/' target='_blank'>Cloudflare DNS</a> as an upstream. Use tls:// prefix for DNS over TLS servers."
title={ t('upstream_dns') }
subtitle={ t('upstream_dns_hint') }
bodyType="card-body box-body--settings"
>
<div className="row">
@@ -44,17 +46,38 @@ export default class Upstream extends Component {
type="button"
onClick={this.handleTest}
>
Test upstreams
<Trans>test_upstream_btn</Trans>
</button>
<button
className="btn btn-success btn-standart"
className="btn btn-success btn-standard"
type="submit"
onClick={this.handleSubmit}
>
Apply
<Trans>apply_btn</Trans>
</button>
</div>
</form>
<hr/>
<div className="list leading-loose">
<Trans>examples_title</Trans>:
<ol className="leading-loose">
<li>
<code>1.1.1.1</code> - { t('example_upstream_regular') }
</li>
<li>
<code>tls://1dot1dot1dot1.cloudflare-dns.com</code> - <span dangerouslySetInnerHTML={{ __html: t('example_upstream_dot') }} />
</li>
<li>
<code>https://cloudflare-dns.com/dns-query</code> - <span dangerouslySetInnerHTML={{ __html: t('example_upstream_doh') }} />
</li>
<li>
<code>tcp://1.1.1.1</code> - { t('example_upstream_tcp') }
</li>
<li>
<code>sdns://...</code> - <span dangerouslySetInnerHTML={{ __html: t('example_upstream_sdns') }} />
</li>
</ol>
</div>
</div>
</div>
</Card>
@@ -68,4 +91,7 @@ Upstream.propTypes = {
handleUpstreamChange: PropTypes.func,
handleUpstreamSubmit: PropTypes.func,
handleUpstreamTest: PropTypes.func,
t: PropTypes.func,
};
export default withNamespaces()(Upstream);

View File

@@ -1,38 +1,44 @@
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import { withNamespaces, Trans } from 'react-i18next';
import Upstream from './Upstream';
import Dhcp from './Dhcp';
import Encryption from './Encryption';
import Checkbox from '../ui/Checkbox';
import Loading from '../ui/Loading';
import PageTitle from '../ui/PageTitle';
import Card from '../ui/Card';
import './Settings.css';
export default class Settings extends Component {
class Settings extends Component {
settings = {
filtering: {
enabled: false,
title: 'Block domains using filters and hosts files',
subtitle: 'You can setup blocking rules in the <a href="#filters">Filters</a> settings.',
title: 'block_domain_use_filters_and_hosts',
subtitle: 'filters_block_toggle_hint',
},
safebrowsing: {
enabled: false,
title: 'Use AdGuard browsing security web service',
subtitle: 'AdGuard Home will check if domain is blacklisted by the browsing security web service. It will use privacy-friendly lookup API to perform the check: only a short prefix of the domain name SHA256 hash is sent to the server.',
title: 'use_adguard_browsing_sec',
subtitle: 'use_adguard_browsing_sec_hint',
},
parental: {
enabled: false,
title: 'Use AdGuard parental control web service',
subtitle: 'AdGuard Home will check if domain contains adult materials. It uses the same privacy-friendly API as the browsing security web service.',
title: 'use_adguard_parental',
subtitle: 'use_adguard_parental_hint',
},
safesearch: {
enabled: false,
title: 'Enforce safe search',
subtitle: 'AdGuard Home can enforce safe search in the following search engines: Google, Bing, Yandex.',
title: 'enforce_safe_search',
subtitle: 'enforce_save_search_hint',
},
};
componentDidMount() {
this.props.initSettings(this.settings);
this.props.getDhcpStatus();
this.props.getDhcpInterfaces();
this.props.getTlsStatus();
}
handleUpstreamChange = (value) => {
@@ -47,7 +53,7 @@ export default class Settings extends Component {
if (this.props.dashboard.upstreamDns.length > 0) {
this.props.testUpstream(this.props.dashboard.upstreamDns);
} else {
this.props.addErrorToast({ error: 'No servers specified' });
this.props.addErrorToast({ error: this.props.t('no_servers_specified') });
}
};
@@ -64,22 +70,22 @@ export default class Settings extends Component {
});
}
return (
<div>No settings</div>
<div><Trans>no_settings</Trans></div>
);
}
render() {
const { settings } = this.props;
const { settings, t } = this.props;
const { upstreamDns } = this.props.dashboard;
return (
<Fragment>
<PageTitle title="Settings" />
<PageTitle title={ t('settings') } />
{settings.processing && <Loading />}
{!settings.processing &&
<div className="content">
<div className="row">
<div className="col-md-12">
<Card title="General settings" bodyType="card-body box-body--settings">
<Card title={ t('general_settings') } bodyType="card-body box-body--settings">
<div className="form">
{this.renderSettings(settings.settingsList)}
</div>
@@ -91,6 +97,18 @@ export default class Settings extends Component {
handleUpstreamSubmit={this.handleUpstreamSubmit}
handleUpstreamTest={this.handleUpstreamTest}
/>
<Encryption
encryption={this.props.encryption}
setTlsConfig={this.props.setTlsConfig}
validateTlsConfig={this.props.validateTlsConfig}
/>
<Dhcp
dhcp={this.props.dhcp}
toggleDhcp={this.props.toggleDhcp}
getDhcpStatus={this.props.getDhcpStatus}
findActiveDhcp={this.props.findActiveDhcp}
setDhcpConfig={this.props.setDhcpConfig}
/>
</div>
</div>
</div>
@@ -108,4 +126,7 @@ Settings.propTypes = {
handleUpstreamChange: PropTypes.func,
setUpstream: PropTypes.func,
upstream: PropTypes.string,
t: PropTypes.func,
};
export default withNamespaces()(Settings);

View File

@@ -2,7 +2,7 @@
position: fixed;
right: 24px;
bottom: 24px;
z-index: 10;
z-index: 103;
width: 345px;
}

View File

@@ -1,5 +1,6 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Trans, withNamespaces } from 'react-i18next';
class Toast extends Component {
componentDidMount() {
@@ -18,7 +19,7 @@ class Toast extends Component {
return (
<div className={`toast toast--${this.props.type}`}>
<p className="toast__content">
{this.props.message}
<Trans>{this.props.message}</Trans>
</p>
<button className="toast__dismiss" onClick={() => this.props.removeToast(this.props.id)}>
<svg stroke="#fff" fill="none" width="20" height="20" strokeWidth="2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="m18 6-12 12"/><path d="m6 6 12 12"/></svg>
@@ -35,4 +36,4 @@ Toast.propTypes = {
removeToast: PropTypes.func.isRequired,
};
export default Toast;
export default withNamespaces()(Toast);

View File

@@ -49,15 +49,14 @@
}
.card-title-stats {
font-size: 13px;
color: #9aa0ac;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.card-body-stats {
position: relative;
flex: 1 1 auto;
height: calc(100% - 3rem);
margin: 0;
padding: 1rem 1.5rem;
}
@@ -84,3 +83,17 @@
.card-value-percent:after {
content: "%";
}
.card--full {
height: calc(100% - 1.5rem);
}
.card-wrap {
height: 100%;
}
@media screen and (min-width: 1280px) {
.card-title-stats {
font-size: 14px;
}
}

View File

@@ -4,8 +4,8 @@ import PropTypes from 'prop-types';
import './Card.css';
const Card = props => (
<div className="card">
{ props.title &&
<div className={props.type ? `card ${props.type}` : 'card'}>
{props.title &&
<div className="card-header with-border">
<div className="card-inner">
<div className="card-title">
@@ -33,6 +33,7 @@ Card.propTypes = {
title: PropTypes.string,
subtitle: PropTypes.string,
bodyType: PropTypes.string,
type: PropTypes.string,
refresh: PropTypes.node,
children: PropTypes.node.isRequired,
};

View File

@@ -22,6 +22,11 @@
font-weight: 600;
}
.checkbox--form .checkbox__label:before {
top: 2px;
margin-right: 10px;
}
.checkbox__label {
position: relative;
display: flex;
@@ -68,14 +73,19 @@
opacity: 0;
}
.checkbox__input:checked+.checkbox__label:before {
.checkbox__input:checked + .checkbox__label:before {
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMi4zIDkuMiIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiPjxwYXRoIGQ9Ik0xMS44IDAuNUw1LjMgOC41IDAuNSA0LjIiLz48L3N2Zz4=);
}
.checkbox__input:focus+.checkbox__label:before {
.checkbox__input:focus + .checkbox__label:before {
box-shadow: 0 0 1px 1px rgba(74, 74, 74, 0.32);
}
.checkbox__input:disabled + .checkbox__label {
opacity: 0.6;
cursor: default;
}
.checkbox__label-text {
max-width: 515px;
line-height: 1.5;

View File

@@ -1,5 +1,6 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { withNamespaces } from 'react-i18next';
import './Checkbox.css';
@@ -10,6 +11,7 @@ class Checkbox extends Component {
subtitle,
enabled,
handleChange,
t,
} = this.props;
return (
<div className="form__group">
@@ -18,8 +20,8 @@ class Checkbox extends Component {
<input type="checkbox" className="checkbox__input" onChange={handleChange} checked={enabled}/>
<span className="checkbox__label">
<span className="checkbox__label-text">
<span className="checkbox__label-title">{title}</span>
<span className="checkbox__label-subtitle" dangerouslySetInnerHTML={{ __html: subtitle }}/>
<span className="checkbox__label-title">{ t(title) }</span>
<span className="checkbox__label-subtitle" dangerouslySetInnerHTML={{ __html: t(subtitle) }}/>
</span>
</span>
</label>
@@ -33,6 +35,7 @@ Checkbox.propTypes = {
subtitle: PropTypes.string.isRequired,
enabled: PropTypes.bool.isRequired,
handleChange: PropTypes.func.isRequired,
t: PropTypes.func,
};
export default Checkbox;
export default withNamespaces()(Checkbox);

View File

@@ -0,0 +1,43 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Trans, withNamespaces } from 'react-i18next';
import isAfter from 'date-fns/is_after';
import addDays from 'date-fns/add_days';
import Topline from './Topline';
import { EMPTY_DATE } from '../../helpers/constants';
const EncryptionTopline = (props) => {
if (props.notAfter === EMPTY_DATE) {
return false;
}
const isAboutExpire = isAfter(addDays(Date.now(), 30), props.notAfter);
const isExpired = isAfter(Date.now(), props.notAfter);
if (isExpired) {
return (
<Topline type="danger">
<Trans components={[<a href="#settings" key="0">link</a>]}>
topline_expired_certificate
</Trans>
</Topline>
);
} else if (isAboutExpire) {
return (
<Topline type="warning">
<Trans components={[<a href="#settings" key="0">link</a>]}>
topline_expiring_certificate
</Trans>
</Topline>
);
}
return false;
};
EncryptionTopline.propTypes = {
notAfter: PropTypes.string.isRequired,
};
export default withNamespaces()(EncryptionTopline);

View File

@@ -0,0 +1,45 @@
.footer__row {
display: flex;
align-items: center;
flex-direction: column;
}
.footer__column {
margin-bottom: 15px;
}
.footer__column--language {
min-width: 220px;
margin-bottom: 0;
}
.footer__link {
display: inline-block;
vertical-align: middle;
margin-right: 15px;
}
.footer__link--report {
position: relative;
top: 1px;
margin-right: 0;
}
@media screen and (min-width: 768px) {
.footer__copyright {
margin-right: 25px;
}
.footer__row {
flex-direction: row;
}
.footer__column {
margin-bottom: 0;
}
.footer__column--language {
min-width: initial;
margin-left: auto;
}
}

View File

@@ -1,5 +1,10 @@
import React, { Component } from 'react';
import { REPOSITORY } from '../../helpers/constants';
import { Trans, withNamespaces } from 'react-i18next';
import { REPOSITORY, LANGUAGES } from '../../helpers/constants';
import i18n from '../../i18n';
import './Footer.css';
import './Select.css';
class Footer extends Component {
getYear = () => {
@@ -7,30 +12,36 @@ class Footer extends Component {
return today.getFullYear();
};
changeLanguage = (event) => {
i18n.changeLanguage(event.target.value);
}
render() {
return (
<footer className="footer">
<div className="container">
<div className="row align-items-center flex-row">
<div className="col-12 col-lg-auto mt-3 mt-lg-0 text-center">
<div className="row align-items-center justify-content-center">
<div className="col-auto">
Copyright © {this.getYear()} <a href="https://adguard.com/">AdGuard</a>
</div>
<div className="col-auto">
<ul className="list-inline text-center mb-0">
<li className="list-inline-item">
<a href={REPOSITORY.URL} target="_blank" rel="noopener noreferrer">Homepage</a>
</li>
</ul>
</div>
<div className="col-auto">
<a href={`${REPOSITORY.URL}/issues/new`} className="btn btn-outline-primary btn-sm" target="_blank" rel="noopener noreferrer">
Report an issue
</a>
</div>
<div className="footer__row">
<div className="footer__column">
<div className="footer__copyright">
<Trans>copyright</Trans> &copy; {this.getYear()} <a href="https://adguard.com/">AdGuard</a>
</div>
</div>
<div className="footer__column">
<a href={REPOSITORY.URL} className="footer__link" target="_blank" rel="noopener noreferrer">
<Trans>homepage</Trans>
</a>
<a href={`${REPOSITORY.URL}/issues/new`} className="btn btn-outline-primary btn-sm footer__link footer__link--report" target="_blank" rel="noopener noreferrer">
<Trans>report_an_issue</Trans>
</a>
</div>
<div className="footer__column footer__column--language">
<select className="form-control select select--language" value={i18n.language} onChange={this.changeLanguage}>
{LANGUAGES.map(language =>
<option key={language.key} value={language.key}>
{language.name}
</option>)}
</select>
</div>
</div>
</div>
</footer>
@@ -38,4 +49,4 @@ class Footer extends Component {
}
}
export default Footer;
export default withNamespaces()(Footer);

File diff suppressed because one or more lines are too long

View File

@@ -19,11 +19,11 @@ const Line = props => (
curve='linear'
axisBottom={{
tickSize: 0,
tickPadding: 0,
tickPadding: 10,
}}
axisLeft={{
tickSize: 0,
tickPadding: 0,
tickPadding: 10,
}}
enableGridX={false}
enableGridY={false}

View File

@@ -2,6 +2,7 @@ import React, { Component } from 'react';
import PropTypes from 'prop-types';
import ReactModal from 'react-modal';
import classnames from 'classnames';
import { Trans, withNamespaces } from 'react-i18next';
import { R_URL_REQUIRES_PROTOCOL } from '../../helpers/constants';
import './Modal.css';
@@ -13,7 +14,7 @@ const initialState = {
isUrlValid: false,
};
export default class Modal extends Component {
class Modal extends Component {
state = initialState;
// eslint-disable-next-line
@@ -54,6 +55,7 @@ export default class Modal extends Component {
isOpen,
title,
inputDescription,
processingAddFilter,
} = this.props;
const { isUrlValid, url, name } = this.state;
const inputUrlClass = classnames({
@@ -70,8 +72,8 @@ export default class Modal extends Component {
if (!this.props.isFilterAdded) {
return (
<React.Fragment>
<input type="text" className={inputNameClass} placeholder="Enter name" onChange={this.handleNameChange} />
<input type="text" className={inputUrlClass} placeholder="Enter URL" onChange={this.handleUrlChange} />
<input type="text" className={inputNameClass} placeholder={this.props.t('enter_name_hint')} onChange={this.handleNameChange} />
<input type="text" className={inputUrlClass} placeholder={this.props.t('enter_url_hint')} onChange={this.handleUrlChange} />
{inputDescription &&
<div className="description">
{inputDescription}
@@ -81,7 +83,7 @@ export default class Modal extends Component {
}
return (
<div className="description">
Url added successfully
<Trans>url_added_successfully</Trans>
</div>
);
};
@@ -92,7 +94,7 @@ export default class Modal extends Component {
<ReactModal
className="Modal__Bootstrap modal-dialog modal-dialog-centered"
closeTimeoutMS={0}
isOpen={ isOpen }
isOpen={isOpen}
onRequestClose={this.closeModal}
>
<div className="modal-content">
@@ -105,14 +107,26 @@ export default class Modal extends Component {
</button>
</div>
<div className="modal-body">
{ renderBody()}
{renderBody()}
</div>
{
!this.props.isFilterAdded &&
<div className="modal-footer">
<button type="button" className="btn btn-secondary" onClick={this.closeModal}>Cancel</button>
<button type="button" className="btn btn-success" onClick={this.handleNext} disabled={isValidForSubmit}>Add filter</button>
</div>
{!this.props.isFilterAdded &&
<div className="modal-footer">
<button
type="button"
className="btn btn-secondary"
onClick={this.closeModal}
>
<Trans>cancel_btn</Trans>
</button>
<button
type="button"
className="btn btn-success"
onClick={this.handleNext}
disabled={isValidForSubmit || processingAddFilter}
>
<Trans>add_filter_btn</Trans>
</button>
</div>
}
</div>
</ReactModal>
@@ -127,4 +141,8 @@ Modal.propTypes = {
inputDescription: PropTypes.string,
addFilter: PropTypes.func.isRequired,
isFilterAdded: PropTypes.bool,
processingAddFilter: PropTypes.bool,
t: PropTypes.func,
};
export default withNamespaces()(Modal);

View File

@@ -1,7 +1,9 @@
.popover-wrap {
position: relative;
top: 1px;
display: inline-block;
vertical-align: middle;
align-self: flex-start;
}
.popover__trigger {
@@ -24,9 +26,9 @@
content: "";
display: flex;
position: absolute;
min-width: 275px;
bottom: calc(100% + 3px);
left: 50%;
min-width: 275px;
padding: 10px 15px;
font-size: 0.8rem;
white-space: normal;
@@ -39,6 +41,10 @@
opacity: 0;
}
.popover__body--filter {
min-width: 100%;
}
.popover__body:after {
content: "";
position: absolute;
@@ -63,6 +69,10 @@
stroke: #9aa0ac;
}
.popover__icon--green {
stroke: #66b574;
}
.popover__list-title {
margin-bottom: 3px;
}
@@ -71,6 +81,13 @@
margin-bottom: 2px;
}
.popover__list-item--nowrap {
max-width: 300px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.popover__list-item:last-child {
margin-bottom: 0;
}

View File

@@ -1,5 +1,6 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Trans, withNamespaces } from 'react-i18next';
import { getSourceData } from '../../helpers/trackers/trackers';
import { captitalizeWords } from '../../helpers/helpers';
@@ -13,13 +14,13 @@ class Popover extends Component {
const source = (
<div className="popover__list-item">
Source: <a className="popover__link" target="_blank" rel="noopener noreferrer" href={sourceData.url}><strong>{sourceData.name}</strong></a>
<Trans>source_label</Trans>: <a className="popover__link" target="_blank" rel="noopener noreferrer" href={sourceData.url}><strong>{sourceData.name}</strong></a>
</div>
);
const tracker = (
<div className="popover__list-item">
Name: <a className="popover__link" target="_blank" rel="noopener noreferrer" href={data.url}><strong>{data.name}</strong></a>
<Trans>name_table_header</Trans>: <a className="popover__link" target="_blank" rel="noopener noreferrer" href={data.url}><strong>{data.name}</strong></a>
</div>
);
@@ -33,11 +34,12 @@ class Popover extends Component {
<div className="popover__body">
<div className="popover__list">
<div className="popover__list-title">
Found in the known domains database.
<Trans>found_in_known_domain_db</Trans>
</div>
{tracker}
<div className="popover__list-item">
Category: <strong>{categoryName}</strong>
<Trans>category_label</Trans>: <strong>
<Trans>{categoryName}</Trans></strong>
</div>
{source}
</div>
@@ -51,4 +53,4 @@ Popover.propTypes = {
data: PropTypes.object.isRequired,
};
export default Popover;
export default withNamespaces()(Popover);

View File

@@ -0,0 +1,34 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Trans, withNamespaces } from 'react-i18next';
import './Popover.css';
class PopoverFilter extends Component {
render() {
return (
<div className="popover-wrap">
<div className="popover__trigger popover__trigger--filter">
<svg className="popover__icon popover__icon--green" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><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>
</div>
<div className="popover__body popover__body--filter">
<div className="popover__list">
<div className="popover__list-item popover__list-item--nowrap">
<Trans>rule_label</Trans>: <strong>{this.props.rule}</strong>
</div>
{this.props.filter && <div className="popover__list-item popover__list-item--nowrap">
<Trans>filter_label</Trans>: <strong>{this.props.filter}</strong>
</div>}
</div>
</div>
</div>
);
}
}
PopoverFilter.propTypes = {
rule: PropTypes.string.isRequired,
filter: PropTypes.string,
};
export default withNamespaces()(PopoverFilter);

View File

@@ -11,3 +11,7 @@
.rt-tr-group .red {
background-color: #fff4f2;
}
.rt-tr-group .green {
background-color: #f1faf3;
}

View File

@@ -0,0 +1,16 @@
.select.select--language {
height: 45px;
padding: 0 32px 2px 33px;
outline: 0;
border-color: rgba(0, 40, 100, 0.12);
background-image: url("./svg/globe.svg"), url("./svg/chevron-down.svg");
background-repeat: no-repeat, no-repeat;
background-position: left 11px center, right 9px center;
background-size: 14px, 17px 20px;
appearance: none;
cursor: pointer;
}
.select--language::-ms-expand {
opacity: 0;
}

View File

@@ -0,0 +1,41 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
class Tab extends Component {
handleClick = () => {
this.props.onClick(this.props.label);
}
render() {
const {
activeTab,
label,
} = this.props;
const tabClass = classnames({
tab__control: true,
'tab__control--active': activeTab === label,
});
return (
<div
className={tabClass}
onClick={this.handleClick}
>
<svg className="tab__icon">
<use xlinkHref={`#${label.toLowerCase()}`} />
</svg>
{label}
</div>
);
}
}
Tab.propTypes = {
activeTab: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
onClick: PropTypes.func.isRequired,
};
export default Tab;

View File

@@ -3783,7 +3783,7 @@ tbody.collapse.show {
line-height: 1.5;
color: #495057;
vertical-align: middle;
background: #fff url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 5'%3E%3Cpath fill='#999' d='M0 0L10 0L5 5L0 0'/%3E%3C/svg%3E") no-repeat right 0.75rem center;
background: #fff url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAxMCA1Jz48cGF0aCBmaWxsPScjOTk5JyBkPSdNMCAwTDEwIDBMNSA1TDAgMCcvPjwvc3ZnPg==") no-repeat right 0.75rem center;
background-size: 8px 10px;
border: 1px solid rgba(0, 40, 100, 0.12);
border-radius: 3px;
@@ -10373,6 +10373,8 @@ body.fixed-header .header {
font-size: 0.875rem;
padding: 1.25rem 0;
color: #9aa0ac;
position: relative;
z-index: 102;
}
.footer a:not(.btn) {

View File

@@ -0,0 +1,51 @@
.tabs__controls {
display: flex;
justify-content: space-between;
margin-bottom: 15px;
padding: 15px 0;
border-bottom: 1px solid #e8e8e8;
}
.tab__control {
display: flex;
flex-direction: column;
align-items: center;
min-width: 70px;
font-size: 13px;
color: #555555;
cursor: pointer;
opacity: 0.6;
}
.tab__control:hover,
.tab__control:focus {
opacity: 1;
}
.tab__control--active {
font-weight: 700;
color: #4a4a4a;
opacity: 1;
}
.tab__title {
margin-bottom: 10px;
font-size: 16px;
font-weight: 700;
}
.tab__icon {
width: 24px;
height: 24px;
margin-bottom: 6px;
fill: #4a4a4a;
}
.tab__text {
line-height: 1.7;
}
.tab__text li,
.tab__text p {
margin-bottom: 5px;
}

View File

@@ -0,0 +1,59 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Tab from './Tab';
import './Tabs.css';
class Tabs extends Component {
state = {
activeTab: this.props.children[0].props.label,
};
onClickTabControl = (tab) => {
this.setState({ activeTab: tab });
}
render() {
const {
props: {
children,
},
state: {
activeTab,
},
} = this;
return (
<div className="tabs">
<div className="tabs__controls">
{children.map((child) => {
const { label } = child.props;
return (
<Tab
key={label}
label={label}
activeTab={activeTab}
onClick={this.onClickTabControl}
/>
);
})}
</div>
<div className="tabs__content">
{children.map((child) => {
if (child.props.label !== activeTab) {
return false;
}
return child.props.children;
})}
</div>
</div>
);
}
}
Tabs.propTypes = {
children: PropTypes.array.isRequired,
};
export default Tabs;

View File

@@ -50,5 +50,5 @@
}
.tooltip-custom--narrow:before {
width: 206px;
width: 220px;
}

View File

@@ -1,4 +1,4 @@
.update {
.topline {
position: relative;
z-index: 102;
margin-bottom: 0;

View File

@@ -0,0 +1,19 @@
import React from 'react';
import PropTypes from 'prop-types';
import './Topline.css';
const Topline = props => (
<div className={`alert alert-${props.type} topline`}>
<div className="container">
{props.children}
</div>
</div>
);
Topline.propTypes = {
children: PropTypes.node.isRequired,
type: PropTypes.string.isRequired,
};
export default Topline;

View File

@@ -1,19 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import './Update.css';
const Update = props => (
<div className="alert alert-info update">
<div className="container">
{props.announcement} <a href={props.announcementUrl} target="_blank" rel="noopener noreferrer">Click here</a> for more info.
</div>
</div>
);
Update.propTypes = {
announcement: PropTypes.string.isRequired,
announcementUrl: PropTypes.string.isRequired,
};
export default Update;

View File

@@ -0,0 +1,27 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Trans, withNamespaces } from 'react-i18next';
import Topline from './Topline';
const UpdateTopline = props => (
<Topline type="info">
<Trans
values={{ version: props.version }}
components={[
<a href={props.url} target="_blank" rel="noopener noreferrer" key="0">
Click here
</a>,
]}
>
update_announcement
</Trans>
</Topline>
);
UpdateTopline.propTypes = {
version: PropTypes.string.isRequired,
url: PropTypes.string.isRequired,
};
export default withNamespaces()(UpdateTopline);

View File

@@ -0,0 +1 @@
<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>

After

Width:  |  Height:  |  Size: 264 B

View File

@@ -0,0 +1 @@
<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>

After

Width:  |  Height:  |  Size: 354 B

View File

@@ -0,0 +1 @@
<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>

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@@ -3,8 +3,8 @@ import * as actionCreators from '../actions';
import App from '../components/App';
const mapStateToProps = (state) => {
const { dashboard } = state;
const props = { dashboard };
const { dashboard, encryption } = state;
const props = { dashboard, encryption };
return props;
};

View File

@@ -1,10 +1,37 @@
import { connect } from 'react-redux';
import { initSettings, toggleSetting, handleUpstreamChange, setUpstream, testUpstream, addErrorToast } from '../actions';
import {
initSettings,
toggleSetting,
handleUpstreamChange,
setUpstream,
testUpstream,
addErrorToast,
toggleDhcp,
getDhcpStatus,
getDhcpInterfaces,
setDhcpConfig,
findActiveDhcp,
} from '../actions';
import {
getTlsStatus,
setTlsConfig,
validateTlsConfig,
} from '../actions/encryption';
import Settings from '../components/Settings';
const mapStateToProps = (state) => {
const { settings, dashboard } = state;
const props = { settings, dashboard };
const {
settings,
dashboard,
dhcp,
encryption,
} = state;
const props = {
settings,
dashboard,
dhcp,
encryption,
};
return props;
};
@@ -15,6 +42,14 @@ const mapDispatchToProps = {
setUpstream,
testUpstream,
addErrorToast,
toggleDhcp,
getDhcpStatus,
getDhcpInterfaces,
setDhcpConfig,
findActiveDhcp,
getTlsStatus,
setTlsConfig,
validateTlsConfig,
};
export default connect(

View File

@@ -1,12 +1,13 @@
export const R_URL_REQUIRES_PROTOCOL = /^https?:\/\/\w[\w_\-.]*\.[a-z]{2,8}[^\s]*$/;
export const R_IPV4 = /^(?:(?:^|\.)(?:2(?:5[0-5]|[0-4]\d)|1?\d?\d)){4}$/g;
export const STATS_NAMES = {
avg_processing_time: 'Average processing time',
avg_processing_time: 'average_processing_time',
blocked_filtering: 'Blocked by filters',
dns_queries: 'DNS queries',
replaced_parental: 'Blocked adult websites',
replaced_safebrowsing: 'Blocked malware/phishing',
replaced_safesearch: 'Enforced safe search',
replaced_parental: 'stats_adult',
replaced_safebrowsing: 'stats_malware_phishing',
replaced_safesearch: 'enforced_save_search',
};
export const STATUS_COLORS = {
@@ -20,3 +21,129 @@ export const REPOSITORY = {
URL: 'https://github.com/AdguardTeam/AdGuardHome',
TRACKERS_DB: 'https://github.com/AdguardTeam/AdGuardHome/tree/master/client/src/helpers/trackers/adguard.json',
};
export const LANGUAGES = [
{
key: 'en',
name: 'English',
},
{
key: 'es',
name: 'Español',
},
{
key: 'fr',
name: 'Français',
},
{
key: 'pt-br',
name: 'Português (BR)',
},
{
key: 'sv',
name: 'Svenska',
},
{
key: 'vi',
name: 'Tiếng Việt',
},
{
key: 'ru',
name: 'Русский',
},
{
key: 'ja',
name: '日本語',
},
{
key: 'zh-tw',
name: '正體中文',
},
];
export const INSTALL_FIRST_STEP = 1;
export const INSTALL_TOTAL_STEPS = 5;
export const SETTINGS_NAMES = {
filtering: 'filtering',
safebrowsing: 'safebrowsing',
parental: 'parental',
safesearch: 'safesearch',
};
export const STANDARD_DNS_PORT = 53;
export const STANDARD_WEB_PORT = 80;
export const STANDARD_HTTPS_PORT = 443;
export const EMPTY_DATE = '0001-01-01T00:00:00Z';
export const DEBOUNCE_TIMEOUT = 300;
export const CHECK_TIMEOUT = 1000;
export const STOP_TIMEOUT = 10000;
export const UNSAFE_PORTS = [
1,
7,
9,
11,
13,
15,
17,
19,
20,
21,
22,
23,
25,
37,
42,
43,
53,
77,
79,
87,
95,
101,
102,
103,
104,
109,
110,
111,
113,
115,
117,
119,
123,
135,
139,
143,
179,
389,
465,
512,
513,
514,
515,
526,
530,
531,
532,
540,
556,
563,
587,
601,
636,
993,
995,
2049,
3659,
4045,
6000,
6665,
6666,
6667,
6668,
6669,
];

View File

@@ -0,0 +1,79 @@
import React, { Fragment } from 'react';
import { Trans } from 'react-i18next';
import { R_IPV4, UNSAFE_PORTS } from '../helpers/constants';
export const renderField = ({
input, id, className, placeholder, type, disabled, meta: { touched, error },
}) => (
<Fragment>
<input
{...input}
id={id}
placeholder={placeholder}
type={type}
className={className}
disabled={disabled}
/>
{!disabled && touched && (error && <span className="form__message form__message--error">{error}</span>)}
</Fragment>
);
export const renderSelectField = ({
input, placeholder, disabled, meta: { touched, error },
}) => (
<Fragment>
<label className="checkbox checkbox--form">
<span className="checkbox__marker"/>
<input
{...input}
type="checkbox"
className="checkbox__input"
disabled={disabled}
/>
<span className="checkbox__label">
<span className="checkbox__label-text">
<span className="checkbox__label-title">{placeholder}</span>
</span>
</span>
</label>
{!disabled && touched && (error && <span className="form__message form__message--error">{error}</span>)}
</Fragment>
);
export const required = (value) => {
if (value || value === 0) {
return false;
}
return <Trans>form_error_required</Trans>;
};
export const ipv4 = (value) => {
if (value && !new RegExp(R_IPV4).test(value)) {
return <Trans>form_error_ip_format</Trans>;
}
return false;
};
export const isPositive = (value) => {
if ((value || value === 0) && (value <= 0)) {
return <Trans>form_error_positive</Trans>;
}
return false;
};
export const port = (value) => {
if ((value || value === 0) && (value < 80 || value > 65535)) {
return <Trans>form_error_port_range</Trans>;
}
return false;
};
export const isSafePort = (value) => {
if (UNSAFE_PORTS.includes(value)) {
return <Trans>form_error_port_unsafe</Trans>;
}
return false;
};
export const toNumber = value => value && parseInt(value, 10);

View File

@@ -3,8 +3,15 @@ import dateFormat from 'date-fns/format';
import subHours from 'date-fns/sub_hours';
import addHours from 'date-fns/add_hours';
import round from 'lodash/round';
import axios from 'axios';
import { STATS_NAMES } from './constants';
import {
STATS_NAMES,
STANDARD_DNS_PORT,
STANDARD_WEB_PORT,
STANDARD_HTTPS_PORT,
CHECK_TIMEOUT,
} from './constants';
export const formatTime = (time) => {
const parsedTime = dateParse(time);
@@ -18,6 +25,7 @@ export const normalizeLogs = logs => logs.map((log) => {
answer: response,
reason,
client,
filterId,
rule,
} = log;
const { host: domain, type } = question;
@@ -32,6 +40,7 @@ export const normalizeLogs = logs => logs.map((log) => {
response: responsesArray,
reason,
client,
filterId,
rule,
};
});
@@ -64,11 +73,11 @@ export const normalizeFilteringStatus = (filteringStatus) => {
const { enabled, filters, user_rules: userRules } = filteringStatus;
const newFilters = filters ? filters.map((filter) => {
const {
url, enabled, last_updated: lastUpdated = Date.now(), name = 'Default name', rules_count: rulesCount = 0,
id, url, enabled, lastUpdated: lastUpdated = Date.now(), name = 'Default name', rulesCount: rulesCount = 0,
} = filter;
return {
url, enabled, lastUpdated: formatTime(lastUpdated), name, rulesCount,
id, url, enabled, lastUpdated: formatTime(lastUpdated), name, rulesCount,
};
}) : [];
const newUserRules = Array.isArray(userRules) ? userRules.join('\n') : '';
@@ -83,3 +92,112 @@ export const getPercent = (amount, number) => {
};
export const captitalizeWords = text => text.split(/[ -_]/g).map(str => str.charAt(0).toUpperCase() + str.substr(1)).join(' ');
export const getInterfaceIp = (option) => {
const onlyIPv6 = option.ip_addresses.every(ip => ip.includes(':'));
let interfaceIP = option.ip_addresses[0];
if (!onlyIPv6) {
option.ip_addresses.forEach((ip) => {
if (!ip.includes(':')) {
interfaceIP = ip;
}
});
}
return interfaceIP;
};
export const getIpList = (interfaces) => {
let list = [];
Object.keys(interfaces).forEach((item) => {
list = [...list, ...interfaces[item].ip_addresses];
});
return list.sort();
};
export const getDnsAddress = (ip, port = '') => {
const isStandardDnsPort = port === STANDARD_DNS_PORT;
let address = ip;
if (port) {
if (ip.includes(':') && !isStandardDnsPort) {
address = `[${ip}]:${port}`;
} else if (!isStandardDnsPort) {
address = `${ip}:${port}`;
}
}
return address;
};
export const getWebAddress = (ip, port = '') => {
const isStandardWebPort = port === STANDARD_WEB_PORT;
let address = `http://${ip}`;
if (port) {
if (ip.includes(':') && !isStandardWebPort) {
address = `http://[${ip}]:${port}`;
} else if (!isStandardWebPort) {
address = `http://${ip}:${port}`;
}
}
return address;
};
export const checkRedirect = (url, attempts) => {
let count = attempts || 1;
if (count > 10) {
window.location.replace(url);
return false;
}
const rmTimeout = t => t && clearTimeout(t);
const setRecursiveTimeout = (time, ...args) => setTimeout(
checkRedirect,
time,
...args,
);
let timeout;
axios.get(url)
.then((response) => {
rmTimeout(timeout);
if (response) {
window.location.replace(url);
return;
}
timeout = setRecursiveTimeout(CHECK_TIMEOUT, url, count += 1);
})
.catch((error) => {
rmTimeout(timeout);
if (error.response) {
window.location.replace(url);
return;
}
timeout = setRecursiveTimeout(CHECK_TIMEOUT, url, count += 1);
});
return false;
};
export const redirectToCurrentProtocol = (values, httpPort = 80) => {
const {
protocol, hostname, hash, port,
} = window.location;
const { enabled, port_https } = values;
const httpsPort = port_https !== STANDARD_HTTPS_PORT ? `:${port_https}` : '';
if (protocol !== 'https:' && enabled && port_https) {
checkRedirect(`https://${hostname}${httpsPort}/${hash}`);
} else if (protocol === 'https:' && enabled && port_https && port_https !== parseInt(port, 10)) {
checkRedirect(`https://${hostname}${httpsPort}/${hash}`);
} else if (protocol === 'https:' && (!enabled || !port_https)) {
window.location.replace(`http://${hostname}:${httpPort}/${hash}`);
}
};

View File

@@ -61,6 +61,11 @@
"name": "Branch.io",
"categoryId": 101,
"url": "https://branch.io/"
},
"adocean": {
"name": "Gemius Adocean",
"categoryId": 4,
"url": "https://adocean-global.com/"
}
},
"trackerDomains": {
@@ -72,6 +77,7 @@
"appsflyer.com": "appsflyer",
"appmetrica.yandex.com": "yandex_appmetrica",
"adjust.com": "adjust",
"mobileapptracking.com": "branch"
"mobileapptracking.com": "branch",
"adocean.cz": "adocean"
}
}

View File

@@ -62,6 +62,7 @@ const getTrackerDataFromDb = (domainName, trackersDb, source) => {
/**
* Gets the source metadata for the specified tracker
* @param {TrackerData} trackerData tracker data
* @returns {source} source metadata or null if no matching tracker found
*/
export const getSourceData = (trackerData) => {
if (!trackerData || !trackerData.source) {

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,63 @@
/*
* 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;

63
client/src/i18n.js Normal file
View File

@@ -0,0 +1,63 @@
import i18n from 'i18next';
import { reactI18nextModule } from 'react-i18next';
import { initReactI18n } from 'react-i18next/hooks';
import langDetect from 'i18next-browser-languagedetector';
import vi from './__locales/vi.json';
import en from './__locales/en.json';
import ru from './__locales/ru.json';
import es from './__locales/es.json';
import fr from './__locales/fr.json';
import ja from './__locales/ja.json';
import sv from './__locales/sv.json';
import ptBR from './__locales/pt-br.json';
import zhTW from './__locales/zh-tw.json';
const resources = {
en: {
translation: en,
},
vi: {
translation: vi,
},
ru: {
translation: ru,
},
es: {
translation: es,
},
fr: {
translation: fr,
},
ja: {
translation: ja,
},
sv: {
translation: sv,
},
'pt-BR': {
translation: ptBR,
},
'zh-TW': {
translation: zhTW,
},
};
i18n
.use(langDetect)
.use(initReactI18n)
.use(reactI18nextModule) // passes i18n down to react-i18next
.init({
resources,
fallbackLng: 'en',
keySeparator: false, // we use content as keys
nsSeparator: false, // Fix character in content
interpolation: {
escapeValue: false, // not needed for react!!
},
react: {
wait: true,
},
});
export default i18n;

View File

@@ -5,6 +5,7 @@ import './components/App/index.css';
import App from './containers/App';
import configureStore from './configureStore';
import reducers from './reducers';
import './i18n';
const store = configureStore(reducers, {}); // set initial state
ReactDOM.render(

View File

@@ -0,0 +1,60 @@
import React from 'react';
import PropTypes from 'prop-types';
import { getIpList, getDnsAddress, getWebAddress } from '../../helpers/helpers';
const AddressList = (props) => {
let webAddress = getWebAddress(props.address, props.port);
let dnsAddress = getDnsAddress(props.address, props.port);
if (props.address === '0.0.0.0') {
return getIpList(props.interfaces).map((ip) => {
webAddress = getWebAddress(ip, props.port);
dnsAddress = getDnsAddress(ip, props.port);
if (props.isDns) {
return (
<li key={ip}>
<strong>
{dnsAddress}
</strong>
</li>
);
}
return (
<li key={ip}>
<a href={webAddress}>
{webAddress}
</a>
</li>
);
});
}
if (props.isDns) {
return (
<strong>
{dnsAddress}
</strong>
);
}
return (
<a href={webAddress}>
{webAddress}
</a>
);
};
AddressList.propTypes = {
interfaces: PropTypes.object.isRequired,
address: PropTypes.string.isRequired,
port: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
]),
isDns: PropTypes.bool,
};
export default AddressList;

View File

@@ -0,0 +1,108 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Field, reduxForm } from 'redux-form';
import { withNamespaces, Trans } from 'react-i18next';
import flow from 'lodash/flow';
import i18n from '../../i18n';
import Controls from './Controls';
import renderField from './renderField';
const required = (value) => {
if (value || value === 0) {
return false;
}
return <Trans>form_error_required</Trans>;
};
const validate = (values) => {
const errors = {};
if (values.confirm_password !== values.password) {
errors.confirm_password = i18n.t('form_error_password');
}
return errors;
};
const Auth = (props) => {
const {
handleSubmit,
pristine,
invalid,
t,
} = props;
return (
<form className="setup__step" onSubmit={handleSubmit}>
<div className="setup__group">
<div className="setup__subtitle">
<Trans>install_auth_title</Trans>
</div>
<p className="setup__desc">
<Trans>install_auth_desc</Trans>
</p>
<div className="form-group">
<label>
<Trans>install_auth_username</Trans>
</label>
<Field
name="username"
component={renderField}
type="text"
className="form-control"
placeholder={ t('install_auth_username_enter') }
validate={[required]}
autoComplete="username"
/>
</div>
<div className="form-group">
<label>
<Trans>install_auth_password</Trans>
</label>
<Field
name="password"
component={renderField}
type="password"
className="form-control"
placeholder={ t('install_auth_password_enter') }
validate={[required]}
autoComplete="new-password"
/>
</div>
<div className="form-group">
<label>
<Trans>install_auth_confirm</Trans>
</label>
<Field
name="confirm_password"
component={renderField}
type="password"
className="form-control"
placeholder={ t('install_auth_confirm') }
validate={[required]}
autoComplete="new-password"
/>
</div>
</div>
<Controls pristine={pristine} invalid={invalid} />
</form>
);
};
Auth.propTypes = {
handleSubmit: PropTypes.func.isRequired,
pristine: PropTypes.bool.isRequired,
invalid: PropTypes.bool.isRequired,
t: PropTypes.func.isRequired,
};
export default flow([
withNamespaces(),
reduxForm({
form: 'install',
destroyOnUnmount: false,
forceUnregisterOnUnmount: true,
validate,
}),
])(Auth);

View File

@@ -0,0 +1,113 @@
import React, { Component } from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { Trans } from 'react-i18next';
import * as actionCreators from '../../actions/install';
class Controls extends Component {
renderPrevButton(step) {
switch (step) {
case 2:
case 3:
return (
<button
type="button"
className="btn btn-secondary btn-lg setup__button"
onClick={this.props.prevStep}
>
<Trans>back</Trans>
</button>
);
default:
return false;
}
}
renderNextButton(step) {
switch (step) {
case 1:
return (
<button
type="button"
className="btn btn-success btn-lg setup__button"
onClick={this.props.nextStep}
>
<Trans>get_started</Trans>
</button>
);
case 2:
case 3:
return (
<button
type="submit"
className="btn btn-success btn-lg setup__button"
disabled={
this.props.invalid
|| this.props.pristine
|| this.props.install.processingSubmit
}
>
<Trans>next</Trans>
</button>
);
case 4:
return (
<button
type="button"
className="btn btn-success btn-lg setup__button"
onClick={this.props.nextStep}
>
<Trans>next</Trans>
</button>
);
case 5:
return (
<button
type="button"
className="btn btn-success btn-lg setup__button"
onClick={() => this.props.openDashboard(this.props.address)}
>
<Trans>open_dashboard</Trans>
</button>
);
default:
return false;
}
}
render() {
const { install } = this.props;
return (
<div className="setup__nav">
<div className="btn-list">
{this.renderPrevButton(install.step)}
{this.renderNextButton(install.step)}
</div>
</div>
);
}
}
Controls.propTypes = {
install: PropTypes.object.isRequired,
nextStep: PropTypes.func,
prevStep: PropTypes.func,
openDashboard: PropTypes.func,
submitting: PropTypes.bool,
invalid: PropTypes.bool,
pristine: PropTypes.bool,
address: PropTypes.string,
};
const mapStateToProps = (state) => {
const { install } = state;
const props = { install };
return props;
};
export default connect(
mapStateToProps,
actionCreators,
)(Controls);

View File

@@ -0,0 +1,134 @@
import React from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { reduxForm, formValueSelector } from 'redux-form';
import { Trans, withNamespaces } from 'react-i18next';
import flow from 'lodash/flow';
import Tabs from '../../components/ui/Tabs';
import Icons from '../../components/ui/Icons';
import Controls from './Controls';
import AddressList from './AddressList';
let Devices = props => (
<div className="setup__step">
<div className="setup__group">
<div className="setup__subtitle">
<Trans>install_devices_title</Trans>
</div>
<div className="setup__desc">
<Trans>install_devices_desc</Trans>
<div className="mt-1">
<Trans>install_devices_address</Trans>:
</div>
<div className="mt-1">
<AddressList
interfaces={props.interfaces}
address={props.dnsIp}
port={props.dnsPort}
isDns={true}
/>
</div>
</div>
<Icons />
<Tabs>
<div label="Router">
<div className="tab__title">
<Trans>install_devices_router</Trans>
</div>
<div className="tab__text">
<p><Trans>install_devices_router_desc</Trans></p>
<ol>
<li><Trans>install_devices_router_list_1</Trans></li>
<li><Trans>install_devices_router_list_2</Trans></li>
<li><Trans>install_devices_router_list_3</Trans></li>
</ol>
</div>
</div>
<div label="Windows">
<div className="tab__title">
Windows
</div>
<div className="tab__text">
<ol>
<li><Trans>install_devices_windows_list_1</Trans></li>
<li><Trans>install_devices_windows_list_2</Trans></li>
<li><Trans>install_devices_windows_list_3</Trans></li>
<li><Trans>install_devices_windows_list_4</Trans></li>
<li><Trans>install_devices_windows_list_5</Trans></li>
<li><Trans>install_devices_windows_list_6</Trans></li>
</ol>
</div>
</div>
<div label="macOS">
<div className="tab__title">
macOS
</div>
<div className="tab__text">
<ol>
<li><Trans>install_devices_macos_list_1</Trans></li>
<li><Trans>install_devices_macos_list_2</Trans></li>
<li><Trans>install_devices_macos_list_3</Trans></li>
<li><Trans>install_devices_macos_list_4</Trans></li>
</ol>
</div>
</div>
<div label="Android">
<div className="tab__title">
Android
</div>
<div className="tab__text">
<ol>
<li><Trans>install_devices_android_list_1</Trans></li>
<li><Trans>install_devices_android_list_2</Trans></li>
<li><Trans>install_devices_android_list_3</Trans></li>
<li><Trans>install_devices_android_list_4</Trans></li>
<li><Trans>install_devices_android_list_5</Trans></li>
</ol>
</div>
</div>
<div label="iOS">
<div className="tab__title">
iOS
</div>
<div className="tab__text">
<ol>
<li><Trans>install_devices_ios_list_1</Trans></li>
<li><Trans>install_devices_ios_list_2</Trans></li>
<li><Trans>install_devices_ios_list_3</Trans></li>
<li><Trans>install_devices_ios_list_4</Trans></li>
</ol>
</div>
</div>
</Tabs>
</div>
<Controls />
</div>
);
Devices.propTypes = {
interfaces: PropTypes.object.isRequired,
dnsIp: PropTypes.string.isRequired,
dnsPort: PropTypes.number.isRequired,
};
const selector = formValueSelector('install');
Devices = connect((state) => {
const dnsIp = selector(state, 'dns.ip');
const dnsPort = selector(state, 'dns.port');
return {
dnsIp,
dnsPort,
};
})(Devices);
export default flow([
withNamespaces(),
reduxForm({
form: 'install',
destroyOnUnmount: false,
forceUnregisterOnUnmount: true,
}),
])(Devices);

View File

@@ -0,0 +1,19 @@
import React from 'react';
import { Trans, withNamespaces } from 'react-i18next';
import Controls from './Controls';
const Greeting = () => (
<div className="setup__step">
<div className="setup__group">
<h1 className="setup__title">
<Trans>install_welcome_title</Trans>
</h1>
<p className="setup__desc text-center">
<Trans>install_welcome_desc</Trans>
</p>
</div>
<Controls />
</div>
);
export default withNamespaces()(Greeting);

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