Compare commits

..

1229 Commits

Author SHA1 Message Date
Andrey Meshkov
9eb6da05ad Bump version to 0.97.0 and fix #798 2019-07-12 15:57:20 +03:00
Simon Zolin
3f796a5d05 Merge: - fix tests
* commit '0a1d7fd70732c306f39c777cbe60bb8bf1ab9da5':
  - fix tests
2019-07-09 11:35:49 +03:00
Simon Zolin
0a1d7fd707 - fix tests 2019-07-09 11:35:39 +03:00
Ildar Kamalov
26db906e54 Merge: + client: add link to the DNS filterting rules article
Closes #721

* commit '3a5f9a7ad35aa7f0f0dc5eac5dfcb9b9a3276fc0':
  + client: add link to the DNS filterting rules article
2019-07-08 18:05:02 +03:00
Ildar Kamalov
3a5f9a7ad3 + client: add link to the DNS filterting rules article 2019-07-08 17:36:32 +03:00
Ildar Kamalov
bcbfa43ea2 Merge: * client: remove /clients and /stats_top request from global requests
* commit '2520a62e2430dac1d9bf689c567b95d419a78339':
  * client: remove /clients and /stats_top request from global requests
2019-07-08 13:47:37 +03:00
Ildar Kamalov
2520a62e24 * client: remove /clients and /stats_top request from global requests 2019-07-08 12:49:03 +03:00
Simon Zolin
fce551dcaf Merge: - client: fix version line break
#815

* commit 'cf4616cbee3f835ed4db0875f1183c5dcac347f5':
  - client: fix version line break
2019-07-05 18:37:46 +03:00
Ildar Kamalov
cf4616cbee - client: fix version line break 2019-07-05 18:37:57 +03:00
Simon Zolin
f0c9ffcbeb Merge: - client: fix update now button and notification
#815

* commit '71c1157ef56fa456698e828b3d4ff992d62199ff':
  * client: remove version truncate for desktop
  - client: fix update now button and notification
2019-07-05 18:26:53 +03:00
Ildar Kamalov
71c1157ef5 * client: remove version truncate for desktop 2019-07-05 18:27:15 +03:00
Ildar Kamalov
2fe9819150 - client: fix update now button and notification 2019-07-05 18:21:46 +03:00
Simon Zolin
4af635e58a Merge: - dnsfilter: fix post-install error "filter file not found"
* commit 'b0cfd7228eaae0719f40d37f766c541c06bfb1b0':
  - dnsfilter: fix post-install error "filter file not found"
2019-07-05 17:53:43 +03:00
Simon Zolin
b0cfd7228e - dnsfilter: fix post-install error "filter file not found"
Right after installation we don't have the filter files downloaded.
While they are being downloaded, we replace them with an empty filter.
2019-07-05 17:35:40 +03:00
Simon Zolin
e03efbcdd1 Merge: + release.sh: add freebsd/amd64 distrib
Close #873

* commit '124d73bd3202efbed1e0774d79576f4e7a160fd9':
  + release.sh: add freebsd/amd64 distrib
2019-07-05 15:50:37 +03:00
Simon Zolin
2897bb983f Merge: Print DOH/DOT addresses if it's configured
Close #761

* commit '387783cf91acb8a78b9c9b22f5373187e4dfc16b':
  * client: remove /dns-query from string on client
  * client: fix description
  - client: fix page lang issue with Portuguese
  * client: show DNS-over-HTTPS and DNS-over-TLS addresses
  + client: add DNS privacy tab to setup guide
  + /status: "dns_addresses": add "tls://" or "https://" prefix
  * /status: "dns_addresses": add port if not 53
2019-07-05 15:49:55 +03:00
Ildar Kamalov
387783cf91 * client: remove /dns-query from string on client 2019-07-05 15:47:21 +03:00
Ildar Kamalov
d4bd53a824 * client: fix description 2019-07-05 15:47:21 +03:00
Ildar Kamalov
f1a6912092 - client: fix page lang issue with Portuguese 2019-07-05 15:47:21 +03:00
Ildar Kamalov
531ee20988 * client: show DNS-over-HTTPS and DNS-over-TLS addresses 2019-07-05 15:47:21 +03:00
Ildar Kamalov
5c7c9964b8 + client: add DNS privacy tab to setup guide 2019-07-05 15:47:21 +03:00
Simon Zolin
425f3c87d0 + /status: "dns_addresses": add "tls://" or "https://" prefix 2019-07-05 15:47:21 +03:00
Simon Zolin
ad7c5cb9dc * /status: "dns_addresses": add port if not 53 2019-07-05 15:47:21 +03:00
Simon Zolin
124d73bd32 + release.sh: add freebsd/amd64 distrib 2019-07-05 14:42:32 +03:00
Simon Zolin
1445940473 Merge: * use urlfilter v0.4.0
Close #866

* commit '134d9275bba7de7d1550412310bc275c52bb340e':
  * use urlfilter v0.4.0
2019-07-05 12:33:30 +03:00
Simon Zolin
df30248870 Merge: - freebsd: fix build
Close #870

* commit '98ff11e1c781a373768f01c54f6c7c29d8096d32':
  - freebsd: fix build
2019-07-04 15:12:38 +03:00
Simon Zolin
b419a1e3d8 Merge: * dns: fail on starting DNS server if upstream servers configuration is incorrect
* commit 'e2675e9a3bb54263d991ed4e9260d5acfedd63da':
  - client: fix link to dhcp settings page
  * dns: fail on starting DNS server if upstream servers configuration is incorrect
2019-07-04 14:57:35 +03:00
Simon Zolin
98ff11e1c7 - freebsd: fix build
Go's "syscall" package file for FreeBSD (incorrectly?) uses int64
 types in syscall.Rlimit struct.
2019-07-04 14:26:34 +03:00
Simon Zolin
134d9275bb * use urlfilter v0.4.0
Now we pass filtering rules to urlfilter as filer file names,
 rather than the list of rule strings.
(Note: user rules are still passed as the list of rule strings).

As a result, we don't store the contents of filter files in memory.
2019-07-04 14:10:01 +03:00
Ildar Kamalov
e2675e9a3b - client: fix link to dhcp settings page 2019-07-03 17:59:26 +03:00
Simon Zolin
dc43ad9910 * dns: fail on starting DNS server if upstream servers configuration is incorrect 2019-07-03 17:59:19 +03:00
Simon Zolin
ceac4cbdd5 Merge: - service stop: fix race
Close #799

* commit '131aa4c93ccd6e2603e8025dfd4d3693aa9dd561':
  - service stop: fix race
2019-07-02 14:45:21 +03:00
Simon Zolin
131aa4c93c - service stop: fix race
Service Stop handler sends SIGINT to the main thread,
 which begins the stops the app.
2019-07-02 12:56:23 +03:00
Ildar Kamalov
5abf0b5a53 Merge: - client: request tls status on app load
Closes #851

* commit '640620288892afad7b84cc3b25d96bab10cdb5d6':
  - client: fix version alignment
  - client: request tls status on app load
2019-07-02 09:40:49 +03:00
Ildar Kamalov
5cddde53c3 Merge: * client: allow ip address in filter
Closes #832

* commit 'e616d843bfbef044372c4968559f02b71f5d8210':
  * client: allow ip address in filter
2019-07-02 09:39:33 +03:00
Simon Zolin
1c9abd6107 Merge: + dhcpd, clients, dnsfilter: add more tests
#788

* commit '25da23497a19118a22b97d64749fa70337544116':
  + dnsfilter: more tests
  + dhcpd, clients: add more tests
2019-07-01 19:26:27 +03:00
Simon Zolin
8e3f05e538 Merge: * dnsfilter: fix tests: pass config object to NewForTest()
* commit '64f66cfb5d71e34d59977925fd9453a21fe2cd1a':
  * dnsfilter: fix tests: pass config object to NewForTest()
2019-07-01 19:24:53 +03:00
Simon Zolin
64f66cfb5d * dnsfilter: fix tests: pass config object to NewForTest() 2019-07-01 19:24:52 +03:00
Ildar Kamalov
e616d843bf * client: allow ip address in filter 2019-07-01 15:52:24 +03:00
Ildar Kamalov
6406202888 - client: fix version alignment 2019-07-01 15:07:29 +03:00
Ildar Kamalov
b3c2b3a21b - client: request tls status on app load 2019-07-01 15:04:07 +03:00
Simon Zolin
b45e8e80fb Merge: * auto-update: use backup directory format without version: "agh-backup"
Close #801

* commit '885b660808a848277f080c78dc7e6107afdbabb7':
  * auto-update: refactor test;  test getUpdateInfo()
  * auto-update: use backup directory format without version: "agh-backup"
2019-06-27 18:04:37 +03:00
Simon Zolin
885b660808 * auto-update: refactor test; test getUpdateInfo() 2019-06-27 15:23:48 +03:00
Simon Zolin
bdc9a0b906 * auto-update: use backup directory format without version: "agh-backup" 2019-06-27 15:23:16 +03:00
Simon Zolin
c631a6832f Merge: + clients: parse 'arp -a' output; periodically update info
Close #826

* commit 'db7efc24d381f6c8d88e14f485475e812ff5fb7b':
  + clients: parse 'arp -a' output;  periodically update info
2019-06-27 11:59:19 +03:00
Simon Zolin
db7efc24d3 + clients: parse 'arp -a' output; periodically update info
* prioritize a client source: etc/hosts > ARP > rDNS
2019-06-27 11:39:53 +03:00
Simon Zolin
b4b11406cf Merge: * /control/version.json: add "recheck_now" parameter
Close #815

* commit 'd2258cb66de32092f145f2803a7be3d7869970f2':
  * openapi.yaml: update /version.json
  + client: add button for check updates
  * /control/version.json: add "recheck_now" parameter
2019-06-27 11:23:29 +03:00
Simon Zolin
eb8c531ae1 Merge: * dnsfilter: use a single global context object
Close #807

* commit '42b76ada9d42f01aace4c6f47cb32f3d77d53a0b':
  rename dnsfContext -> dnsFilterContext
  * dnsfilter: use a single global context object
2019-06-27 11:22:57 +03:00
Simon Zolin
d1987e711d Merge: - dhcp: store lease data in database on each change rather than once on app stop
Close #852

* commit '0b3ba8224255247fa751a9922f83154e71a26c02':
  - dhcp: store lease data in database on each change rather than once on app stop
  - dhcp: fix race introduced by static lease add/remove from UI thread
2019-06-27 10:56:32 +03:00
Simon Zolin
e50b4fd185 Merge: - rDNS: don't try to resolve loopback IP addresses
Close #838

* commit '6a1edc45be51a16bc1e8b63bb1661a6e4196fe5a':
  - rDNS: don't try to resolve loopback IP addresses
2019-06-27 10:55:25 +03:00
Simon Zolin
d2258cb66d * openapi.yaml: update /version.json 2019-06-27 10:53:03 +03:00
Simon Zolin
42b76ada9d rename dnsfContext -> dnsFilterContext 2019-06-27 10:48:12 +03:00
Simon Zolin
25da23497a + dnsfilter: more tests 2019-06-26 18:13:09 +03:00
Simon Zolin
efaaeb58eb + dhcpd, clients: add more tests 2019-06-26 17:53:05 +03:00
Simon Zolin
0b3ba82242 - dhcp: store lease data in database on each change rather than once on app stop 2019-06-26 14:02:41 +03:00
Simon Zolin
eff23f3b62 - dhcp: fix race introduced by static lease add/remove from UI thread 2019-06-26 14:01:59 +03:00
Ildar Kamalov
0e9df33a40 + client: add button for check updates 2019-06-25 17:56:50 +03:00
Simon Zolin
6a1edc45be - rDNS: don't try to resolve loopback IP addresses 2019-06-25 16:14:52 +03:00
Simon Zolin
5d60bb05ab * /control/version.json: add "recheck_now" parameter 2019-06-25 16:06:55 +03:00
Simon Zolin
2307f55715 * dnsfilter: use a single global context object 2019-06-24 19:00:03 +03:00
Andrey Meshkov
f1e6a30931 Fix version/channel linking 2019-06-20 14:36:26 +03:00
Andrey Meshkov
4ddae72faf Fix Makefile -- VersionString and updateChannel 2019-06-20 14:18:29 +03:00
Andrey Meshkov
082354204b Fix #831
This commit fixes panic when customDialContext fails to resolve the host's address.
2019-06-18 16:18:13 +03:00
Simon Zolin
6187871e3b Merge: * move ./*.go files into ./home/ directory
* commit 'dc682763ff61874eb6043eaac5fa0eba17f7ddec':
  * move ./*.go files into ./home/ directory
2019-06-10 12:07:57 +03:00
Simon Zolin
dc682763ff * move ./*.go files into ./home/ directory 2019-06-10 11:51:53 +03:00
Andrey Meshkov
9fe34818e3 Fix #770 - dnsproxy v0.15.0
* commit '9a77bb3a0acddf88f32991e13891270deea5725c':
  go mod tidy
  * dnsproxy v0.15.0
2019-06-07 20:13:59 +03:00
Andrey Meshkov
9a77bb3a0a go mod tidy 2019-06-07 20:10:07 +03:00
Simon Zolin
86890a8609 * dnsproxy v0.15.0 2019-06-07 19:59:11 +03:00
Simon Zolin
1fd0f78612 Merge: - clients: fix race introduced by commit 07db927; update tech doc
Close #727

* commit '1fcb69d3a913dec9b53f148acab45b1f621faa24':
  - clients: fix race introduced by commit 07db927; update tech doc
2019-06-07 19:11:28 +03:00
Simon Zolin
1fcb69d3a9 - clients: fix race introduced by commit 07db927; update tech doc 2019-06-07 11:37:55 +03:00
Andrey Meshkov
07db927246 Fix #727 - use default parental sensitivity when it's not set 2019-06-06 22:42:17 +03:00
Andrey Meshkov
f9807e4011 Fix #806 2019-06-06 21:06:19 +03:00
Andrey Meshkov
5647bc1fc9 Fix #727 - apply client settings properly 2019-06-06 21:04:17 +03:00
Andrey Meshkov
0f7235f217 fix string id 2019-06-06 17:48:46 +03:00
Andrey Meshkov
db67fb6c6a rename client settings 2019-06-06 17:45:24 +03:00
Simon Zolin
087d2f68c2 Merge: - client: fix versions check
Close #428

* commit '02fa39226c442a23e9cba9816382f55c2b276589':
  * client: pull locales
  - client: fix versions check
2019-06-06 17:27:38 +03:00
Ildar Kamalov
02fa39226c * client: pull locales 2019-06-06 17:25:50 +03:00
Andrey Meshkov
edfa104710 add urlfilter to the list of software 2019-06-06 16:18:12 +03:00
Andrey Meshkov
7a124213e5 Merge branch 'master' of ssh://bit.adguard.com:7999/dns/adguard-dns 2019-06-06 16:00:42 +03:00
Andrey Meshkov
0a2a7ca630 Fix #566 -- added comparison table 2019-06-06 16:00:35 +03:00
Ildar Kamalov
1f164c7005 - client: fix versions check 2019-06-06 15:54:19 +03:00
Simon Zolin
44f224d69e Merge: Auto-update: improve algorithm, fix bugs
Close #428

* commit '22469bb83bcf902804191d4896c2224e5d5b9f75':
  * control_update_test.go: "+build ignore"
  - client: check version on update before page reload
  * auto-update: remove the update directory after successful update
  + auto-update: copy supporting files (README, etc.)
  * release.sh: add AdGuardHome/ directory to .tar.gz output file
  * auto-update: zipFileUnpack() returns list of unpacked files
  * auto-update: use native code to unpack .tar.gz
  * auto-update: use 'selfupdate_min_version' from version.json
  - control: outgoing HTTP requests didn't work if target IP is IPv6
2019-06-06 14:05:43 +03:00
Simon Zolin
22469bb83b * control_update_test.go: "+build ignore" 2019-06-06 12:20:26 +03:00
Ildar Kamalov
10a0873bc8 - client: check version on update before page reload 2019-06-06 11:41:53 +03:00
Simon Zolin
a165410c9f * auto-update: remove the update directory after successful update 2019-06-06 11:41:53 +03:00
Simon Zolin
ec5e2be31f + auto-update: copy supporting files (README, etc.) 2019-06-06 11:41:53 +03:00
Simon Zolin
6d3099acd3 * release.sh: add AdGuardHome/ directory to .tar.gz output file 2019-06-06 11:41:53 +03:00
Simon Zolin
66c670c6ff * auto-update: zipFileUnpack() returns list of unpacked files 2019-06-06 11:41:14 +03:00
Simon Zolin
c2a31f9503 * auto-update: use native code to unpack .tar.gz 2019-06-06 11:41:14 +03:00
Simon Zolin
466f553bbe * auto-update: use 'selfupdate_min_version' from version.json 2019-06-06 11:41:14 +03:00
Simon Zolin
ddb1bc0fee - control: outgoing HTTP requests didn't work if target IP is IPv6 2019-06-06 11:41:14 +03:00
Andrey Meshkov
a36630e5a8 Merge: Fix #773 - preparing the new update channel
* commit '395833056097fa44c1221da33319cd64e4a5ca62':
  Fix #773 - preparing the new update channel
2019-06-06 11:23:55 +03:00
Andrey Meshkov
3958330560 Fix #773 - preparing the new update channel 2019-06-06 03:00:15 +03:00
Ildar Kamalov
86ba6d4332 Merge pull request in DNS/adguard-dns from fix/793 to master
Closes #793

* commit 'b2364e465f7c7b19ad455b07864ddaf2848e6664':
  * client: reload list on opening Clients settings
2019-06-05 16:06:54 +03:00
Ildar Kamalov
b2364e465f * client: reload list on opening Clients settings 2019-06-05 15:49:01 +03:00
Andrey Meshkov
a3b8d4d923 Fix #706 -- rDNS for DOH/DOT clients 2019-06-04 20:38:53 +03:00
Andrey Meshkov
3454bf9243 Merge branch 'master' of ssh://bit.adguard.com:7999/dns/adguard-dns 2019-06-04 19:58:17 +03:00
Andrey Meshkov
64a4443d0c Fix #706 - logging for resolveRDNS 2019-06-04 19:58:13 +03:00
Simon Zolin
d24b78db0e Merge: + dns: resolve hosts via rDNS for the top clients after the server has been started
Close #706

* commit 'f7150e6a19bfc5643fef4106bd068f6ba099f8e2':
  + dns: resolve hosts via rDNS for the top clients after the server has been started
2019-06-04 18:59:14 +03:00
Simon Zolin
f7150e6a19 + dns: resolve hosts via rDNS for the top clients after the server has been started 2019-06-04 18:12:45 +03:00
Andrey Meshkov
85046abb15 Merge branch 'master' of ssh://bit.adguard.com:7999/dns/adguard-dns 2019-06-04 16:30:59 +03:00
Andrey Meshkov
454e26db7f Update ports in docker files 2019-06-04 16:30:55 +03:00
Andrey Meshkov
b74438bf83 Merge pull request 235, Fix #778
* commit '7d40d3bfeaa2bea83ca50b41550b16907e447a47':
  + docker: use --no-check-update
2019-06-04 16:28:17 +03:00
Simon Zolin
7d40d3bfea + docker: use --no-check-update 2019-06-04 16:06:00 +03:00
Simon Zolin
6261fb79ab Merge: fix docker build
Close #789

* commit '27bffef9400615f502f4799fb318254b585fad99':
  Fix #789
2019-06-04 11:34:16 +03:00
Eugene Zbiranik
27bffef940 Fix #789 2019-06-04 11:03:13 +03:00
Simon Zolin
450e2ac549 Merge: * minor API changes
Close #785

* commit '7f5ac19b592c2a63e52a5e5319e9e8eb687c7410':
  * client: use JSON for filtering/remove_url
  * /remove_url: use JSON input data format
  - openapi: correct format
  - openapi: fix /add_url
2019-06-03 19:39:40 +03:00
Simon Zolin
6ac466e430 Merge: + client: split settings page into several pages
Close #726

* commit 'f7d88f6976ae8328bc47c0df4686ae6a38ed7bb0':
  * client: check initial access settings
  * client: remove unused addErrorToast method
  * client: move access settings to DNS settings page
  + client: split settings page into several pages
2019-06-03 19:38:21 +03:00
Ildar Kamalov
f7d88f6976 * client: check initial access settings 2019-06-03 16:39:02 +03:00
Ildar Kamalov
7f5ac19b59 * client: use JSON for filtering/remove_url 2019-06-03 16:33:15 +03:00
Ildar Kamalov
54f6710b8f * client: remove unused addErrorToast method 2019-06-03 16:18:49 +03:00
Ildar Kamalov
757bb7285a * client: move access settings to DNS settings page 2019-06-03 16:08:50 +03:00
Ildar Kamalov
af041bcbd7 Merge pull request in DNS/adguard-dns from fix/702 to master
* commit 'df9864ec00fe9b4c7f4912a6122d1308f06fa844':
  * client: replace favicon.ico with favicon.png
2019-06-03 15:47:42 +03:00
Ildar Kamalov
cf53653cfa + client: split settings page into several pages 2019-06-03 15:44:29 +03:00
Simon Zolin
1d09ff0562 Merge: + dnsforward: add access settings for blocking DNS requests
Close #728

* commit 'e4532a27cd2a6f92aaf724fddbffa00fcecb064c':
  - openapi: correct format
  + client: handle access settings
  * go.mod: update dnsproxy
  + control: /access/list, /access/set handlers
  + dnsforward: add access settings for blocking DNS requests
2019-06-03 15:04:52 +03:00
Simon Zolin
c93cb43db8 * /remove_url: use JSON input data format 2019-06-03 12:05:08 +03:00
Simon Zolin
276d87a218 - openapi: correct format 2019-06-03 12:05:08 +03:00
Simon Zolin
fcf609ac1e - openapi: fix /add_url 2019-06-03 12:05:08 +03:00
Simon Zolin
e4532a27cd - openapi: correct format 2019-06-03 11:21:57 +03:00
Simon Zolin
302a11a6a3 Merge: - fix tests and linter issues
* commit 'b8d9ca942c23b37133dbb894d42a8b3f310a86a1':
  - app: fix crash on starting DNS server after installation
  - fix tests and linter issues
2019-05-31 18:49:07 +03:00
Simon Zolin
b8d9ca942c - app: fix crash on starting DNS server after installation 2019-05-31 16:39:18 +03:00
Ildar Kamalov
df9864ec00 * client: replace favicon.ico with favicon.png 2019-05-31 16:36:48 +03:00
Simon Zolin
3baa6919dc - fix tests and linter issues 2019-05-31 12:27:13 +03:00
Ildar Kamalov
02db488b30 + client: handle access settings 2019-05-30 18:45:56 +03:00
Simon Zolin
821ad3edd9 * go.mod: update dnsproxy 2019-05-30 18:22:29 +03:00
Simon Zolin
d18c222b1a + control: /access/list, /access/set handlers 2019-05-30 18:21:56 +03:00
Simon Zolin
36ffcf7d22 + dnsforward: add access settings for blocking DNS requests
Block by client IP or target domain name.
2019-05-30 18:21:36 +03:00
Simon Zolin
147344afa3 Merge: - dns: fix crash (rDNS)
* commit '1abd9da27d7ced46a92e2b0cb85224a1d7d9025b':
  - dns: fix crash (rDNS)
2019-05-29 12:50:04 +03:00
Simon Zolin
1abd9da27d - dns: fix crash (rDNS) 2019-05-28 19:51:49 +03:00
Simon Zolin
d9e70f5244 Merge: + DHCP: Support statically configured leases
Close #687

* commit 'b1fbd7c40c640eef575e6c2babc7eab26a525cf8':
  * openapi: add new dhcp methods
  * client: fix page scrolling on adding/deleting leases
  + client: handle static leases form
  + client: add table to show static leases
  + doc: DHCP static leases
  * dhcpd: refactor: use separate objects for ServerConfig and RWMutex
  + dhcp: /dhcp/status: return static leases
  * dhcpd: minor improvements
  * control: refactor: move DHCP lease -> json convertor to a separate function
  + dhcp: /dhcp/add_static_lease, /dhcp/remove_static_lease: control static lease table
  + helpers: parseIPv4()
  * control: use new DHCP functions: CheckConfig, Init, Start
  * control,dhcp: use dhcpServerConfigJSON struct
  + dhcpd: CheckConfig()
  * dhcpd: move code from Start() to Init()
2019-05-28 19:34:42 +03:00
Simon Zolin
a1ceb83da0 Merge: + clients: find DNS client's hostname by IP using rDNS
Close #706

* commit 'a12f01793ff97e0ea53bc6f751bee758d1df6bb2':
  + clients: find DNS client's hostname by IP using rDNS
2019-05-28 19:33:30 +03:00
Simon Zolin
a12f01793f + clients: find DNS client's hostname by IP using rDNS 2019-05-28 19:07:57 +03:00
Simon Zolin
b1fbd7c40c * openapi: add new dhcp methods 2019-05-28 19:01:24 +03:00
Ildar Kamalov
2976726f99 * client: fix page scrolling on adding/deleting leases 2019-05-28 19:01:24 +03:00
Ildar Kamalov
6f2503a09f + client: handle static leases form 2019-05-28 19:01:24 +03:00
Ildar Kamalov
a8384c004e + client: add table to show static leases 2019-05-28 19:01:24 +03:00
Simon Zolin
49b91b4fc9 + doc: DHCP static leases 2019-05-28 19:01:24 +03:00
Simon Zolin
fa47fa3f9c * dhcpd: refactor: use separate objects for ServerConfig and RWMutex 2019-05-28 19:01:24 +03:00
Simon Zolin
763b986955 + dhcp: /dhcp/status: return static leases 2019-05-28 18:59:15 +03:00
Simon Zolin
342699d933 * dhcpd: minor improvements 2019-05-28 18:59:15 +03:00
Simon Zolin
fd593f5282 * control: refactor: move DHCP lease -> json convertor to a separate function 2019-05-28 18:59:15 +03:00
Simon Zolin
725aeeb910 + dhcp: /dhcp/add_static_lease, /dhcp/remove_static_lease: control static lease table 2019-05-28 18:59:15 +03:00
Simon Zolin
564a41d598 + helpers: parseIPv4() 2019-05-28 18:59:15 +03:00
Simon Zolin
c3204664c3 * control: use new DHCP functions: CheckConfig, Init, Start 2019-05-28 18:59:15 +03:00
Simon Zolin
626c1ae753 * control,dhcp: use dhcpServerConfigJSON struct 2019-05-28 18:59:15 +03:00
Simon Zolin
cc366495d3 + dhcpd: CheckConfig() 2019-05-28 18:59:15 +03:00
Simon Zolin
0d405c0af8 * dhcpd: move code from Start() to Init() 2019-05-28 18:59:15 +03:00
Simon Zolin
c038e4cf14 Merge: + Per-client settings
Close #727

* commit 'a83bc5eeeb4107f2157443b7b40636036fe2a7cc':
  * client: add source column
  * client: remove redundant table formatting for runtime clients table
  * client: show MAC address as default
  + client: add runtime clients table
  * client: add icons for table buttons
  * client: remove unused api method
  * client: confirm before deleting
  * client: remove table column min-width
  * client: fix no data text
  * client: fix sort helper
  + client: handle per-client settings
  - openapi.yaml: fix HTTP methods
  + openapi.yaml: add /clients handlers
  + dnsfilter: use callback function for applying per-client settings
  + dhcp: FindIPbyMAC()
  + dns: use per-client filtering settings
  + clients: config: save/restore clients info array
  + clients API
  + doc: clients
2019-05-28 18:52:51 +03:00
Ildar Kamalov
a83bc5eeeb * client: add source column 2019-05-28 18:44:27 +03:00
Ildar Kamalov
702db84e39 * client: remove redundant table formatting for runtime clients table 2019-05-28 18:44:27 +03:00
Ildar Kamalov
9cc824d852 * client: show MAC address as default 2019-05-28 18:44:27 +03:00
Ildar Kamalov
8a8c7329f7 + client: add runtime clients table 2019-05-28 18:44:27 +03:00
Ildar Kamalov
cbef338592 * client: add icons for table buttons 2019-05-28 18:44:27 +03:00
Ildar Kamalov
bd2c4269db * client: remove unused api method 2019-05-28 18:44:27 +03:00
Ildar Kamalov
f40141bbbc * client: confirm before deleting 2019-05-28 18:44:27 +03:00
Ildar Kamalov
c7b5830336 * client: remove table column min-width 2019-05-28 18:44:27 +03:00
Ildar Kamalov
bb34381a0d * client: fix no data text 2019-05-28 18:44:27 +03:00
Ildar Kamalov
68a4cc597f * client: fix sort helper 2019-05-28 18:44:27 +03:00
Ildar Kamalov
22d3c38df2 + client: handle per-client settings 2019-05-28 18:44:27 +03:00
Simon Zolin
22c7efd2d1 - openapi.yaml: fix HTTP methods 2019-05-28 18:44:27 +03:00
Simon Zolin
eb159e6997 + openapi.yaml: add /clients handlers 2019-05-28 18:44:27 +03:00
Simon Zolin
8bf76c331d + dnsfilter: use callback function for applying per-client settings 2019-05-28 18:44:27 +03:00
Simon Zolin
4bb7b654ab + dhcp: FindIPbyMAC() 2019-05-28 18:44:27 +03:00
Simon Zolin
3f89335ed2 + dns: use per-client filtering settings 2019-05-28 18:44:27 +03:00
Simon Zolin
8f7aff93d7 + clients: config: save/restore clients info array 2019-05-28 18:44:27 +03:00
Simon Zolin
5fb7e44e79 + clients API
* /clients handler: new format
+ /clients/add handler
+ /clients/delete handler
+ /clients/update handler
2019-05-28 18:44:27 +03:00
Simon Zolin
6a7b1aba8b + doc: clients 2019-05-28 18:44:27 +03:00
Simon Zolin
218f51092c Merge: + app: disable new version check and auto-update by command line switch
Close #778

* commit '9f75146eaba9d9a3ce085c884d7f18a2c628dc50':
  * client: check for empty versions response
  * docker: use --no-check-update
  * openapi: update /version.json description
  + app: disable new version check and auto-update by command line switch
2019-05-28 18:18:40 +03:00
Ildar Kamalov
9f75146eab * client: check for empty versions response 2019-05-28 15:22:48 +03:00
Simon Zolin
6ab8aa4da1 * docker: use --no-check-update 2019-05-28 11:42:50 +03:00
Simon Zolin
386886cec2 Merge: * control: 🚑 Corrects typo in parental control API error message
Close #781

* commit '517ebc0251d6bfe43b7b4d31c7c4e7e91ea928fa':
  🚑 Corrects typo in parental control API error message
2019-05-28 11:42:12 +03:00
Simon Zolin
5b29cae133 * openapi: update /version.json description 2019-05-28 11:41:36 +03:00
Simon Zolin
4df8868787 Merge: * dnsfilter: parental/safebrowsing: add setting to switch between HTTP and HTTPS #646
* commit 'f23507a5546229d8ce8f69d56667cd5212f026d3':
  * dnsfilter: parental/safebrowsing: add setting to switch between HTTP and HTTPS
2019-05-28 11:31:51 +03:00
Franck Nijhof
517ebc0251 🚑 Corrects typo in parental control API error message 2019-05-27 22:51:51 +02:00
Simon Zolin
f25639f1fc + app: disable new version check and auto-update by command line switch 2019-05-27 18:48:33 +03:00
Simon Zolin
f23507a554 * dnsfilter: parental/safebrowsing: add setting to switch between HTTP and HTTPS 2019-05-27 18:11:05 +03:00
Simon Zolin
b9df476c5d Merge: dnsforward: support IPv6
Close #735

* commit 'e2579c72bdcafed41d5be1250fb38aeda0a8184e':
  * dnsfilter: fix tests
  + dnsforward: support IPv6 (AAAA response)
  * dnsfilter: return the correct IP address (host rules)
2019-05-27 12:35:31 +03:00
Simon Zolin
e2579c72bd * dnsfilter: fix tests 2019-05-24 18:08:08 +03:00
Simon Zolin
ac8f703407 + dnsforward: support IPv6 (AAAA response)
If question type is AAAA:
 Before this patch we responded with NXDOMAIN.
 Now we send an empty response if host rule is IPv4;
 or we send an AAAA answer if host rule is IPv6.

+ block ipv6 if rule is "0.0.0.0 blockdomain"
2019-05-24 18:08:08 +03:00
Simon Zolin
9ad4bba9ab * dnsfilter: return the correct IP address (host rules) 2019-05-24 18:08:08 +03:00
Simon Zolin
452c930dd0 Merge: * dnsfilter: use 'https' for safe-browsing and parental control
Close #646

* commit '00e1b6ca089705e7fdb4764133058e04111e36df':
  * dnsfilter: use 'https' for safe-browsing and parental control
2019-05-24 18:03:00 +03:00
Simon Zolin
fdd0f594fb Merge: - control: allow requests to "/favicon.ico" while we are in install mode
Close #766

* commit 'dece393d6acfa21f588505e494eb1225adc8376a':
  - control: allow requests to "/favicon.ico" while we are in install mode
2019-05-24 18:02:07 +03:00
Simon Zolin
00e1b6ca08 * dnsfilter: use 'https' for safe-browsing and parental control 2019-05-23 17:26:50 +03:00
Simon Zolin
dece393d6a - control: allow requests to "/favicon.ico" while we are in install mode 2019-05-23 16:28:20 +03:00
Simon Zolin
aa2d942783 Merge: Update by command from UI
Close #428

* commit '70e329956776cc381fdb28805375d5b2f0e22dbf':
  * openapi: update
  * client: add link to the update error
  * client: add update timeout
  * client: add error message if update failed
  + client: handle update
  * go linter
  * control: /version.json: use new JSON format
  + set config.runningAsService
  * app: --help: more pretty help info
  + app: add --check-config command-line argument
  * app: optimize config file reading
  + /control/update handler
  * control: don't use custom resolver for tests
  + doc: Update algorithm
  - control: fix race in /control/version.json handler
2019-05-20 13:38:23 +03:00
Simon Zolin
e3ee7a0c3e Merge: dnsfilter: use urlfilter package #714
* commit '096a95998749b673bc9be638bc9c8f6f0d13be41':
  * dnsforward: use new dnsfilter interface
  * dnsfilter: adapt tests to new interface
  * dnsfilter: use urlfilter package
  * dnsfilter: remove code for filtering rules
  * dns: rename dnsfilter.Filter.Rule -> dnsfilter.Filter.Data
  * dnsforward: use separate ServerConfig object
  * use urlfilter
2019-05-20 11:00:45 +03:00
Simon Zolin
70e3299567 * openapi: update 2019-05-20 10:57:07 +03:00
Ildar Kamalov
967517316f * client: add link to the update error 2019-05-17 18:33:34 +03:00
Ildar Kamalov
24f582d36d * client: add update timeout 2019-05-17 18:33:34 +03:00
Ildar Kamalov
9cffe865ec * client: add error message if update failed 2019-05-17 18:33:34 +03:00
Ildar Kamalov
cb3f7f2834 + client: handle update 2019-05-17 18:33:34 +03:00
Simon Zolin
096a959987 * dnsforward: use new dnsfilter interface 2019-05-17 18:22:57 +03:00
Simon Zolin
5ec747b30b * dnsfilter: adapt tests to new interface 2019-05-17 18:22:57 +03:00
Simon Zolin
829415da5b * dnsfilter: use urlfilter package
+ new config setting 'filtering_temp_filename'

* remove AddRules(), modify New()
2019-05-17 18:22:57 +03:00
Simon Zolin
3396d68019 * dnsfilter: remove code for filtering rules 2019-05-17 18:22:57 +03:00
Simon Zolin
bd68bf2e25 * dns: rename dnsfilter.Filter.Rule -> dnsfilter.Filter.Data 2019-05-17 18:22:57 +03:00
Simon Zolin
9644f79a03 * dnsforward: use separate ServerConfig object 2019-05-17 18:22:57 +03:00
Simon Zolin
36e273dfd5 * use urlfilter 2019-05-17 18:22:57 +03:00
Simon Zolin
068072bc5a * go linter 2019-05-17 15:39:56 +03:00
Simon Zolin
b72ca4d127 * control: /version.json: use new JSON format 2019-05-17 15:37:38 +03:00
Simon Zolin
28440fc3ac + set config.runningAsService 2019-05-17 15:37:38 +03:00
Simon Zolin
6d14ec18ac * app: --help: more pretty help info 2019-05-17 15:37:38 +03:00
Simon Zolin
5fd35254a8 + app: add --check-config command-line argument 2019-05-17 15:37:38 +03:00
Simon Zolin
3ee8051e97 * app: optimize config file reading
* read config file just once (even when upgrading)
* don't call os.Stat()
2019-05-17 15:37:38 +03:00
Simon Zolin
2dd6ea5161 + /control/update handler 2019-05-17 15:37:38 +03:00
Simon Zolin
788e91a51e * control: don't use custom resolver for tests 2019-05-17 15:34:55 +03:00
Simon Zolin
d4fcef8d04 + doc: Update algorithm 2019-05-17 15:34:55 +03:00
Simon Zolin
392c7b6ee1 - control: fix race in /control/version.json handler 2019-05-17 10:20:41 +03:00
Simon Zolin
7bb40bca0f Merge: dns query log: robust file flushing mechanism #708
* commit 'd5f6dd1a46446ebb440811691a6ee8ce2443320d':
  - dns query log: robust file flushing mechanism
  * improve logging
2019-05-15 14:08:01 +03:00
Simon Zolin
f20cb65189 Merge: + dnsfilter: cache IP addresses of safebrowsing and parental control servers
Close #745

* commit 'd918e5b418de232d95ba1e3d642dca00664f0304':
  use maxDialCacheSize constant
  rename functions and container
  + dnsfilter: cache IP addresses of safebrowsing and parental control servers
2019-05-15 14:01:01 +03:00
Simon Zolin
d5f6dd1a46 - dns query log: robust file flushing mechanism
Before this patch we could exit the process without waiting for
 file writing task to complete.
As a result a file could become corrupted or a large chunk of data
 could be missing.

Now the main thread either waits until file writing task completes
 or it writes log buffer to file itself.
2019-05-15 13:12:03 +03:00
Simon Zolin
0f28a989e9 * improve logging 2019-05-15 13:12:03 +03:00
Simon Zolin
d918e5b418 use maxDialCacheSize constant 2019-05-15 12:03:20 +03:00
Simon Zolin
3a0f608402 Merge: dnsforward, config: add unspecified IP blocking option
Close #742, #743

* commit 'cd2dd00da300c24a88a51082ee9622a332a5b72b':
  * dnsforward_test: add test for null filter
  * dnsforward, config: add unspecified IP blocking option
2019-05-15 11:58:40 +03:00
Alexander Turcic
cd2dd00da3 * dnsforward_test: add test for null filter 2019-05-14 16:53:09 +03:00
Alexander Turcic
07ffcbec3d * dnsforward, config: add unspecified IP blocking option
* dnsforward: prioritize host files over null filter

* dnsforward, config: adjust setting variable to blocking_mode

* dnsforward: use net.IPv4zero for null IP
2019-05-14 16:53:06 +03:00
Simon Zolin
b3461d37ca rename functions and container 2019-05-13 14:47:55 +03:00
Simon Zolin
2178546e7b Merge: docker: Run as non-root user
Close #720

* commit '68f9ec70fb0f8ff2a73bf382bc15257c367b7967':
  Optimize Docker image layers; comment out runtime user; add sample docker-compose.yml
  Run as non-root user
2019-05-13 14:42:50 +03:00
Simon Zolin
24ae61de3e + dnsfilter: cache IP addresses of safebrowsing and parental control servers 2019-05-13 14:16:07 +03:00
Simon Zolin
68f9ec70fb Merge branch 'docker-improvements' of git://github.com/javabean/AdGuardHome into javabean-docker-improvements 2019-05-13 11:12:21 +03:00
Cédrik LIME
17aa46c4d2 Optimize Docker image layers; comment out runtime user; add sample docker-compose.yml 2019-05-08 21:17:14 +02:00
Ildar Kamalov
a45f0c519e Merge pull request #209 in DNS/adguard-dns from feature/734 to master
* commit '6ac9509d64e82e48bc22ccd23ac3a25776d05565':
  + client: Add a link to the list of known DNS providers to Upstream DNS settings
2019-05-06 09:35:51 +03:00
Ildar Kamalov
2cb2b3585f Merge pull request #208 in DNS/adguard-dns from fix/729 to master
* commit 'd24f208f98c08155282eb3061fd28e2e149e296b':
  - client: fixed values for settings validation
2019-05-06 09:35:42 +03:00
Ildar Kamalov
d24f208f98 - client: fixed values for settings validation
Closes #729
2019-04-28 11:43:15 +03:00
Ildar Kamalov
6ac9509d64 + client: Add a link to the list of known DNS providers to Upstream DNS settings
Closes #734
2019-04-28 11:18:56 +03:00
Simon Zolin
7d2df26335 Merge: Bump version to v0.95-hotfix
* commit 'ae403fb13752df1fcdf33839d0747e44722382db':
  Bump version to v0.95-hotfix
2019-04-24 14:39:52 +03:00
Simon Zolin
ae403fb137 Bump version to v0.95-hotfix 2019-04-24 14:38:00 +03:00
Simon Zolin
e1bb89c393 Merge: dnsfilter: prevent recursion when both parental control and safebrowsing are enabled
Close #732

* commit 'c4e67690f4fcceb055cbea73610b5974855db96f':
  * dnsfilter: don't use global variable for custom resolver function
  - dnsfilter: prevent recursion when both parental control and safebrowsing are enabled
2019-04-24 12:52:16 +03:00
Simon Zolin
c4e67690f4 * dnsfilter: don't use global variable for custom resolver function 2019-04-24 12:49:12 +03:00
Simon Zolin
f6023b395e - dnsfilter: prevent recursion when both parental control and safebrowsing are enabled 2019-04-24 12:38:05 +03:00
Simon Zolin
cedab695c2 Merge: Bump version to v0.95
* commit '1c339e5fcd73a4ce070f7284b1ea7d1b44d63803':
  Bump version to v0.95
2019-04-23 20:28:54 +03:00
Simon Zolin
1c339e5fcd Bump version to v0.95 2019-04-23 20:23:39 +03:00
Andrey Meshkov
4eb910c35e Merge pull request #203 in DNS/adguard-dns from fix/647 to master
* commit 'c6957bed64acd5b2265b8b94bb07f8bb77520084':
  - install: immediately schedule filters update procedure after installation is complete
2019-04-23 20:15:12 +03:00
Simon Zolin
c6957bed64 - install: immediately schedule filters update procedure after installation is complete 2019-04-23 19:56:21 +03:00
Andrey Meshkov
8e6f7be5b8 Merge pull request #202 in DNS/adguard-dns from fix/647 to master
* commit '528c1a72cac331f08de5fdee4538529c70dff1bb':
  - use 127.0.0.1 as a resolver address when DNS binding address is 0.0.0.0
  - app: don't print filter update error messages on first launch before  DNS server is set up
2019-04-23 19:48:05 +03:00
Simon Zolin
528c1a72ca - use 127.0.0.1 as a resolver address when DNS binding address is 0.0.0.0 2019-04-23 19:37:14 +03:00
Simon Zolin
3087c54a15 - app: don't print filter update error messages on first launch before
DNS server is set up
2019-04-23 19:26:51 +03:00
Simon Zolin
5c65d0cabe Merge: various bugfixes
* commit '4231920ee84074e573539c05c53cb357b1a22154':
  * client: add "DuckDuckGo" to safesearch label text
  - dhcp: fix build on macos #704
  - dnsfilter: fix npe in dnsfilter test
  - dnsfilter: fix safesearch issue #268
2019-04-23 16:20:46 +03:00
Simon Zolin
4231920ee8 * client: add "DuckDuckGo" to safesearch label text 2019-04-23 15:25:09 +03:00
Simon Zolin
d5f46f51b8 - dhcp: fix build on macos #704 2019-04-23 15:14:26 +03:00
Aleksey Dmitrevskiy
a860c8e6ff - dnsfilter: fix npe in dnsfilter test 2019-04-23 15:09:23 +03:00
Aleksey Dmitrevskiy
0794704f74 - dnsfilter: fix safesearch issue #268 2019-04-23 15:08:41 +03:00
Simon Zolin
173ab2a3c1 Merge: - /control/dhcp/find_active_dhcp: fix DHCP server detection
Close #704

* commit '68dc8a13411c09cd8c382a1bf986953659166506':
  * control: update "DHCP server not found" message
  * update openapi.yaml
  - /control/dhcp/find_active_dhcp: fix DHCP server detection
2019-04-23 12:29:15 +03:00
Simon Zolin
68dc8a1341 * control: update "DHCP server not found" message 2019-04-23 12:26:14 +03:00
Simon Zolin
043e89a1a4 * update openapi.yaml 2019-04-23 12:26:14 +03:00
Simon Zolin
c5ed6da5bd - /control/dhcp/find_active_dhcp: fix DHCP server detection
Before this patch we couldn't receive incoming DHCP packets.
Now we bind() to 0.0.0.0 and set the required network interface
 using SO_BINDTODEVICE option.

As an improvement, we now wait until a reply to our request is
 received and skip all unknown packets.
2019-04-23 12:26:14 +03:00
Simon Zolin
69c5f175e8 Merge: Installation wizard #685
* commit '79b0fac01a544e35207420fed78e1c2a63d428d8':
  * control: move /install handlers to a separate file
  + add technical document
  * app: move code for http server loop to a separate function
  * client: fixed getDefaultAddresses structure
  - client: npm audit fix
  * client: validate form on load
  * client: installation wizard additional checks
  * update openapi.yaml
  + service install: a post-install guide of what to do next
  * control: /install/configure: validate port number
  * control: /install/configure: reset configuration back to its current state on error
  + control: /install/*: test TCP port availability for DNS server
  + control: /install/check_config: Check and deactivate DNSStubListener
  * control: /install/configure: refactor
  + control: add /install/check_config handler
  * control: /install/get_addresses: don't check if ports are available
  + app: unix, windows: require root user on first launch
  * setRlimit(): move OS-specific code to separate files
2019-04-23 12:16:34 +03:00
Simon Zolin
79b0fac01a * control: move /install handlers to a separate file 2019-04-23 11:48:30 +03:00
Simon Zolin
bc81a0ecff + add technical document 2019-04-23 11:45:09 +03:00
Simon Zolin
1ac9419c94 * app: move code for http server loop to a separate function 2019-04-23 11:41:53 +03:00
Ildar Kamalov
5f88abb322 * client: fixed getDefaultAddresses structure 2019-04-23 11:41:53 +03:00
Ildar Kamalov
33db419384 - client: npm audit fix 2019-04-23 11:41:53 +03:00
Ildar Kamalov
ceabad0fd0 * client: validate form on load 2019-04-23 11:41:53 +03:00
Ildar Kamalov
f76b7c3d94 * client: installation wizard additional checks 2019-04-23 11:41:53 +03:00
Simon Zolin
9e68a522cb * update openapi.yaml 2019-04-23 11:41:53 +03:00
Simon Zolin
faa7c7b2d4 + service install: a post-install guide of what to do next 2019-04-23 11:41:53 +03:00
Simon Zolin
d326d1bc8b * control: /install/configure: validate port number 2019-04-23 11:41:53 +03:00
Simon Zolin
73fbe8b95a * control: /install/configure: reset configuration back to its current state on error 2019-04-23 11:41:53 +03:00
Simon Zolin
87147ac89f + control: /install/*: test TCP port availability for DNS server 2019-04-23 11:41:53 +03:00
Simon Zolin
8d936b5756 + control: /install/check_config: Check and deactivate DNSStubListener 2019-04-23 11:41:53 +03:00
Simon Zolin
4ca24b7707 * control: /install/configure: refactor 2019-04-23 11:41:53 +03:00
Simon Zolin
133dd75ec3 + control: add /install/check_config handler 2019-04-23 11:41:53 +03:00
Simon Zolin
00c128f0a4 * control: /install/get_addresses: don't check if ports are available
* always use port 80
2019-04-23 11:41:53 +03:00
Simon Zolin
e4b53db558 + app: unix, windows: require root user on first launch 2019-04-23 11:41:53 +03:00
Simon Zolin
1611057852 * setRlimit(): move OS-specific code to separate files 2019-04-22 17:41:10 +03:00
Simon Zolin
00ba63341b Merge: Don't use the system default resolver implicitly
Close #647

* commit 'a1b18776678ee894cd0b558cf8683845056a2dfa':
  + parental, safesearch: use our own DNS resolver instead of system default
  + control: use our own DNS resolver instead of system default
2019-04-22 11:28:43 +03:00
Simon Zolin
a1b1877667 + parental, safesearch: use our own DNS resolver instead of system default 2019-04-18 14:31:13 +03:00
Simon Zolin
bebdc1b5bc + control: use our own DNS resolver instead of system default 2019-04-18 12:47:22 +03:00
Cédrik LIME
58868b75af Run as non-root user 2019-04-17 20:48:56 +02:00
Ildar Kamalov
1836e56e6e Merge pull request in DNS/adguard-dns from feature/393 to master
* commit 'f83d026c330ec56b76dc995afabe67bc45f654c7':
  + client: privacy policy link
2019-04-17 18:59:05 +03:00
Ildar Kamalov
f83d026c33 + client: privacy policy link
Closes #393
2019-04-17 15:03:25 +03:00
Alexey Dmitrievskiy
61554ba4e0 Merge: * app, dnsforward: add MinVersion for TLS configs #651
* commit 'c82887d3aa3332cf4d7a7dda22f51b160be306b3':
  * app, dnsforward: add MinVersion for TLS configs
2019-04-17 13:56:20 +03:00
Aleksey Dmitrevskiy
c82887d3aa * app, dnsforward: add MinVersion for TLS configs 2019-04-17 12:02:56 +03:00
Alexey Dmitrievskiy
faeda3f075 Merge: * safesearch: Enfore safe search for duckduckgo #268
* commit 'dfa39293a189f59dfd5da335aa66d80b04568a10':
  Update safesearch.go
  Update safesearch.go
  Update safesearch.go
2019-04-16 18:44:51 +03:00
Alexey Dmitrievskiy
afeadbb454 Merge: increase the default timeout used for downloading filters updates #643
* commit 'c78cee339628c8f91a1c698e731c25d99dec8c8b':
  * control: increase http.Client timeout to 5 minutes
  * control: increase the default timeout used for downloading filters updates
2019-04-16 18:40:44 +03:00
Ildar Kamalov
0e031a4921 Merge pull request in DNS/adguard-dns from fix/710 to master
* commit 'd0942c88c8968b7e410356b1ae89ec7d3e69f2bb':
  * client: replace PUT and DELETE with POST
  * control: replace ensurePUT and ensureDELETE with ensurePOST
2019-04-16 12:19:27 +03:00
Ildar Kamalov
d0942c88c8 * client: replace PUT and DELETE with POST 2019-04-16 12:11:31 +03:00
Ildar Kamalov
794d302ce5 * control: replace ensurePUT and ensureDELETE with ensurePOST 2019-04-16 12:11:12 +03:00
Simon Zolin
7f1f85b08c Merge: + http server: enable gzip compression
Close #626

* commit '3f404bc37e14bbd781f6feb62502a44188b4c7ef':
  + http server: enable gzip compression for /control/querylog
  + http server: enable gzip compression for static documents
2019-04-16 11:11:01 +03:00
Simon Zolin
c8e4f61534 Merge: + config: add "rlimit_nofile" setting - Maximum number of opened fd's per process
* commit '1f1e26f67b00ee4983c65e4e3aabf59a488f687b':
  + config: add "rlimit_nofile" setting - Maximum number of opened fd's per process
2019-04-15 18:34:53 +03:00
Simon Zolin
3f404bc37e + http server: enable gzip compression for /control/querylog 2019-04-15 16:21:12 +03:00
Simon Zolin
7911a24dc8 Merge: app: add '--pidfile FILE' command-line parameter
Close #609

* commit '7746a3e6a9f204e7b7ab498c2f484cf426fb1a18':
  + app: add '--pidfile FILE' command-line parameter
2019-04-15 15:36:22 +03:00
Simon Zolin
7746a3e6a9 + app: add '--pidfile FILE' command-line parameter 2019-04-15 13:41:46 +03:00
Simon Zolin
08bedacf0a Merge: DHCP: check/set static IP
Close #686

* commit '828d3121be807daa8f839dfa7a7ac4ba8a6e7cd8':
  * client: show message if there is no static ip
  * client: rename constant
  * hasStaticIP: use properly named boolean variable
  + client: static_ip warnings
  * client: error text
  * client: disable DHCP check if server enabled and hide errors on disable
  * client: hide error if DHCP enabled and require check DHCP before enabling
  * client: accordion styles
  - client: fix DHCP fields validation
  * client: fix DHCP error message
  + config: set default parameters for DHCP server
  + /control/dhcp/set_config: set static IP
  + /control/dhcp/find_active_dhcp: detect static IP on Linux
  * /control/dhcp/find_active_dhcp: new JSON response format
2019-04-15 12:59:16 +03:00
Ildar Kamalov
74a0938038 Merge pull request in DNS/adguard-dns from fix/700 to master
* commit '4d217583b099da6a2a2773d0870ab39e36a9b141':
  - client: validate encryption if enabled
2019-04-11 09:56:49 +03:00
Ildar Kamalov
828d3121be * client: show message if there is no static ip 2019-04-05 15:15:56 +03:00
Ildar Kamalov
b6ae539c36 * client: rename constant 2019-04-05 14:32:56 +03:00
Simon Zolin
fa5ff053b7 * hasStaticIP: use properly named boolean variable 2019-04-05 12:47:26 +03:00
Ildar Kamalov
6bf57ae84e + client: static_ip warnings 2019-04-04 17:13:51 +03:00
Ildar Kamalov
472dc0b77d * client: error text 2019-04-04 17:13:51 +03:00
Ildar Kamalov
4b821d67f5 * client: disable DHCP check if server enabled and hide errors on disable 2019-04-04 17:13:51 +03:00
Ildar Kamalov
24fc2957c5 * client: hide error if DHCP enabled and require check DHCP before enabling 2019-04-04 17:13:51 +03:00
Ildar Kamalov
6ba0e4686a * client: accordion styles 2019-04-04 17:13:51 +03:00
Ildar Kamalov
b92fb34f37 - client: fix DHCP fields validation 2019-04-04 17:13:51 +03:00
Ildar Kamalov
ffd9f1aaa9 * client: fix DHCP error message 2019-04-04 17:13:51 +03:00
Simon Zolin
d43290fe31 + config: set default parameters for DHCP server 2019-04-04 17:13:51 +03:00
Simon Zolin
ef22a31a93 + /control/dhcp/set_config: set static IP 2019-04-04 17:13:51 +03:00
Simon Zolin
0ed619c9c8 + /control/dhcp/find_active_dhcp: detect static IP on Linux 2019-04-04 17:13:51 +03:00
Simon Zolin
8b6e9ef5f9 * /control/dhcp/find_active_dhcp: new JSON response format 2019-04-04 17:13:51 +03:00
Simon Zolin
cca61a33c6 + http server: enable gzip compression for static documents 2019-04-04 16:58:31 +03:00
Ildar Kamalov
4d217583b0 - client: validate encryption if enabled
Closes #700
2019-04-04 14:54:57 +03:00
Aleksey Dmitrevskiy
c78cee3396 * control: increase http.Client timeout to 5 minutes 2019-04-04 11:04:14 +03:00
Ildar Kamalov
b453d9f41d Merge pull request in DNS/adguard-dns from fix/688 to master
* commit 'e231230f1b2045b705602b2c9d3a14c47f276273':
  - client: fixed leases table on mobile
2019-04-03 12:35:06 +03:00
Ildar Kamalov
e231230f1b - client: fixed leases table on mobile
Closes #688
2019-04-02 13:59:03 +03:00
Aleksey Dmitrevskiy
850e856e6e * control: increase the default timeout used for downloading filters updates 2019-04-01 11:30:26 +03:00
Ildar Kamalov
d6b83d4a63 Merge pull request #187 in DNS/adguard-dns from fix/655 to master
* commit '0c973334be33c77e509676d65394d99ead4b26e2':
  * client: if 0.0.0.0 is selected, then redirect to the current IP
2019-03-29 17:38:02 +03:00
Ildar Kamalov
0c973334be * client: if 0.0.0.0 is selected, then redirect to the current IP
Closes #655
2019-03-29 16:24:59 +03:00
Ildar Kamalov
23ac1726b7 Merge pull request #184 in DNS/adguard-dns from fix/660 to master
* commit '91af0cddcea531dfb7972f56371edf17ef763065':
  - client: header links overflow
  - client: fix settings page if current interface doesn't exist
  - client: request version after DNS status request
  - client: fixed Chrome translation popup issue with Portuguese
  - client: missed translation
2019-03-28 13:59:56 +03:00
Simon Zolin
1f1e26f67b + config: add "rlimit_nofile" setting - Maximum number of opened fd's per process 2019-03-28 13:56:12 +03:00
Ildar Kamalov
91af0cddce - client: header links overflow 2019-03-28 12:28:57 +03:00
Ildar Kamalov
6f014fa53d - client: fix settings page if current interface doesn't exist
Closes #664
2019-03-28 11:53:39 +03:00
Ildar Kamalov
6f2e852e09 - client: request version after DNS status request
Closes #660
2019-03-28 11:45:31 +03:00
Ildar Kamalov
fd82e7c26a - client: fixed Chrome translation popup issue with Portuguese
Closes #656
2019-03-28 11:44:10 +03:00
Ildar Kamalov
2bb5b24d4e - client: missed translation
Closes #652
2019-03-28 11:43:26 +03:00
Simon Zolin
92e70515ae Merge: filter: name was reset after an update
close #661

* commit '3367b9fb2a5e541766fa00a68b72121ac00efcff':
  - filter: name was reset after an update
2019-03-27 14:14:37 +03:00
Simon Zolin
51aec7cbbc Merge: Bump version to v0.94
* commit '395ddb7b3caf97c4c281badf0882dc3baf7f3b88':
  Bump version to v0.94
2019-03-27 11:36:21 +03:00
Simon Zolin
3367b9fb2a - filter: name was reset after an update
After filter is updated its name is reset to the value of Title from
 the filter's contents, which may be ""
2019-03-26 19:04:50 +03:00
Johann Richard
dfa39293a1 Update safesearch.go
* Beautify again with`gofmt -s`
2019-03-24 19:34:29 +01:00
Johann Richard
65364930f7 Update safesearch.go
* Beautify the addition
2019-03-24 19:26:40 +01:00
Johann Richard
53ea2d28cf Update safesearch.go
* Add SafeSearch for duckduckgo.com
2019-03-24 19:24:31 +01:00
Andrey Meshkov
395ddb7b3c Bump version to v0.94 2019-03-22 16:57:40 +03:00
Andrey Meshkov
1448b7ab16 Merge pull request 156
Closes #593
Closes #589

* commit '6edfe1bb8e5db8a38104b69bb436736195fa6980':
  Add GOMIPS=softfloat
  More platforms
2019-03-22 16:48:36 +03:00
Andrey Meshkov
abd58004b8 Closes #649 2019-03-22 14:43:43 +03:00
Andrey Meshkov
800cb177f3 Merge pull request #179 in DNS/adguard-dns from feature/637 to master
* commit 'bc0b0af06b71197d75c8a7f8c3a401d4c8a49d61':
  + client: added name for client
  - openapi: fix typo
  + control: /clients: get the list of clients' IP addresses and names from /etc/hosts
2019-03-22 14:27:15 +03:00
Andrey Meshkov
31a0dde515 fix link 2019-03-22 13:57:58 +03:00
Andrey Meshkov
6edfe1bb8e Add GOMIPS=softfloat 2019-03-22 10:40:38 +03:00
Andrey Meshkov
7ebfaccc6e Merge with master 2019-03-22 10:36:48 +03:00
Ildar Kamalov
bc0b0af06b + client: added name for client 2019-03-20 18:37:00 +03:00
Simon Zolin
5210d214ec - openapi: fix typo 2019-03-20 18:37:00 +03:00
Simon Zolin
ed942f3e31 + control: /clients: get the list of clients' IP addresses and names from /etc/hosts 2019-03-20 18:37:00 +03:00
Alexey Dmitrievskiy
5b417d9f17 Merge: add ability to set DNS upstream per domain #445
* commit 'f7860c893da81bc23220bcdec8161ce7b36dc558':
  * client: removed links from upstream DNS translations
  * control, client: fix issues from review
  + control, dns, client: add ability to set DNS upstream per domain
2019-03-20 18:15:31 +03:00
Ildar Kamalov
f7860c893d * client: removed links from upstream DNS translations 2019-03-20 15:23:36 +03:00
Aleksey Dmitrevskiy
a01ba5dd4d * control, client: fix issues from review 2019-03-20 15:19:34 +03:00
Simon Zolin
b6d0d94990 Merge: * DHCP: fix and update tests
* commit '9bc5a4570e76873984a4fda21a4d307499b4980a':
  * DHCP: fix and update tests
2019-03-20 14:57:39 +03:00
Aleksey Dmitrevskiy
9ea5c1abe1 + control, dns, client: add ability to set DNS upstream per domain 2019-03-20 14:24:33 +03:00
Simon Zolin
91ec996ffb Merge: Fix update mechanism for filter rules #604 #620
* commit '0647f3fe8666a38cbdc7a747279513338e49568a':
  * filters: rework update mechanism so that UI doesn't get locked while update is in progress
  - filter: update 'LastUpdated' field and 'last-modified' file time  even when filter's content is up to date
  * control: refactor: move filter adding code to a separate function
  * app: refactor: don't rewrite config file after filters are updated
  * filters: refactor: remove unused if-branches
  * control: enable/disable filter: move code to a separate function
  * filters: refactor: don't check Enabled flag inside filter.update() & filter.load()
  - control: filtering/add_url: don't call httpError() twice on error while reconfiguring
  - control: use locks when operating on config.Filters array
  * refactor: move code to loadFilters()
  * filter: use CRC32 to check whether filter data should be updated
  * filter: refactor
2019-03-20 14:22:25 +03:00
Simon Zolin
9bc5a4570e * DHCP: fix and update tests
for:
 + dhcp: handle lease expiration (d68600c5d0)
2019-03-20 14:19:19 +03:00
Simon Zolin
8defb3b39e Merge: control: DHCP: don't return expired leases #584 #567
* commit 'ef789acee4ef990d9f93fa7f718f819b5bdea03d':
  * control: DHCP: don't return expired leases
2019-03-20 12:58:03 +03:00
Simon Zolin
d5e57248a0 Merge: Add "Setup guide" menu item #605
* commit 'c091d10a416b0ea9c72fb1addd95e7194281d9ce':
  * client: update translations
  + client: added setup guide page and DNS addresses popover
  + control: use the list of IP addresses instead of single string in "dns_address"
2019-03-20 12:56:47 +03:00
Simon Zolin
0647f3fe86 * filters: rework update mechanism so that UI doesn't get locked while update is in progress 2019-03-19 17:30:17 +03:00
Simon Zolin
d664a9de1d - filter: update 'LastUpdated' field and 'last-modified' file time
even when filter's content is up to date

* filters: refactor: don't check 'LastUpdated' inside update()
2019-03-19 17:30:17 +03:00
Simon Zolin
b54f540f71 * control: refactor: move filter adding code to a separate function 2019-03-19 17:30:17 +03:00
Simon Zolin
0884116de3 * app: refactor: don't rewrite config file after filters are updated 2019-03-19 17:30:17 +03:00
Simon Zolin
eefdf8449a * filters: refactor: remove unused if-branches
filter.ID == 0:
Useless, because filter ID is assigned either on application load
 or on filter add.

len(filter.Rules) == 0:
Useless, because rules are added either on application load
 or on filter add or on filter enable.
2019-03-19 17:30:17 +03:00
Simon Zolin
ae2c7d00a9 * control: enable/disable filter: move code to a separate function
* don't start updating all filters after 1 filter has been enabled
* unload filter data on disable
2019-03-19 17:30:17 +03:00
Simon Zolin
afa54a1339 * filters: refactor: don't check Enabled flag inside filter.update() & filter.load() 2019-03-19 17:29:07 +03:00
Simon Zolin
56271819ea - control: filtering/add_url: don't call httpError() twice on error while reconfiguring 2019-03-19 17:29:07 +03:00
Simon Zolin
a9b329daf6 - control: use locks when operating on config.Filters array 2019-03-19 17:29:07 +03:00
Simon Zolin
61b1a30aa1 * refactor: move code to loadFilters() 2019-03-19 17:29:07 +03:00
Simon Zolin
b4732c83c5 * filter: use CRC32 to check whether filter data should be updated 2019-03-19 17:29:07 +03:00
Simon Zolin
783ac967a1 * filter: refactor 2019-03-19 17:29:07 +03:00
Ildar Kamalov
c091d10a41 * client: update translations 2019-03-19 16:27:23 +03:00
Ildar Kamalov
756c5ac0d3 + client: added setup guide page and DNS addresses popover
Closes #605
2019-03-19 16:19:53 +03:00
Simon Zolin
ef789acee4 * control: DHCP: don't return expired leases
- fix potential race when lease's data can be modified while UI thread is reading it
2019-03-19 15:54:35 +03:00
Simon Zolin
b1c11a718e Merge: * control: filtering/refresh: force update filters #537
* commit 'd7b1825cf59cb71a3bdb39fe488527b670e3a90c':
  * control: filtering/refresh: force update filters
2019-03-19 15:09:51 +03:00
Simon Zolin
d7b1825cf5 * control: filtering/refresh: force update filters 2019-03-19 14:28:12 +03:00
Simon Zolin
b5eb840d22 + control: use the list of IP addresses instead of single string in "dns_address"
"dns_address":"0.0.0.0" -> "dns_addresses":["127.0.0.1", "::1", ...]
2019-03-19 14:14:58 +03:00
Alexey Dmitrievskiy
6f56eb4c12 Merge pull request #172 in DNS/adguard-dns from fix/525 to master
* commit '6b223e2992f45aaba7ae9d685a741bd76c23d284':
  * dnsfilter: extend logging
  Fix #606, Fix #610 [change] app, config: add symlink support, allow to specify absolute path in log_file
2019-03-18 14:57:24 +03:00
Aleksey Dmitrevskiy
6b223e2992 * dnsfilter: extend logging 2019-03-18 14:50:33 +03:00
Aleksey Dmitrevskiy
43e5d42070 Merge branch 'master' of ssh://bit.adguard.com:7999/dns/adguard-dns 2019-03-15 20:15:14 +03:00
Simon Zolin
a58bf0e24e Merge: Fix function comments based on best practices from Effective Go; #638
* commit 'f432eb360952e9bbf95448798ea6a89c4d93dec7':
  Fix function comments based on best practices from Effective Go
2019-03-15 15:06:09 +03:00
CodeLingo Bot
f432eb3609 Fix function comments based on best practices from Effective Go
Signed-off-by: CodeLingo Bot <bot@codelingo.io>
2019-03-15 14:59:05 +03:00
Simon Zolin
6e16654344 Merge: DHCP: refactoring, unit-test, readme, on-disk cache, safe restart, expiration handling; #584 #539
* commit 'ef637e1313ce548eab28c9b9f36c6db20a1c62c9':
  + DHCP: step-by-step guide for test setup with Virtual Box
  * control: add logs
  + dhcp: handle lease expiration
  + dhcp: use ICMP for IP conflict detection
  * dhcp: don't allocate a new lease when processing Request message
  * dhcp: don't process Discover/Request packets with empty client HW address
  * dhcp: refactor
  * DHCP: Stop(): wait until the worker is stopped
  * control: safely restart DHCP server
  + DHCP: On-disk database for lease table
  * use golibs v0.1.1: file.SafeWrite()
  + dhcp: test
  * dhcp: remove code which forces an update of current lease's IP in  Request message handler
  * dhcp: refactor;  log client's HW addr
2019-03-15 14:52:18 +03:00
Simon Zolin
ef637e1313 + DHCP: step-by-step guide for test setup with Virtual Box 2019-03-15 14:00:32 +03:00
Simon Zolin
9494b87ca5 * control: add logs 2019-03-15 14:00:32 +03:00
Simon Zolin
d68600c5d0 + dhcp: handle lease expiration 2019-03-15 14:00:32 +03:00
Simon Zolin
8fa2f48136 + dhcp: use ICMP for IP conflict detection
+ 'icmp_timeout_msec' YAML config setting
2019-03-15 14:00:32 +03:00
Simon Zolin
542a67b84e * dhcp: don't allocate a new lease when processing Request message 2019-03-15 14:00:32 +03:00
Simon Zolin
d832d7ce95 * dhcp: don't process Discover/Request packets with empty client HW address 2019-03-15 14:00:32 +03:00
Simon Zolin
92cf7c1aca * dhcp: refactor 2019-03-15 14:00:04 +03:00
Simon Zolin
b5f0d48e7f * DHCP: Stop(): wait until the worker is stopped 2019-03-15 13:56:45 +03:00
Simon Zolin
6f69fb73af * control: safely restart DHCP server
* control: use mutex in all POST,PUT,DELETE handlers
2019-03-15 13:56:45 +03:00
Simon Zolin
67014c40f7 + DHCP: On-disk database for lease table 2019-03-15 13:56:45 +03:00
Simon Zolin
a2e9d69452 * use golibs v0.1.1: file.SafeWrite() 2019-03-15 13:56:45 +03:00
Simon Zolin
e164cff02b + dhcp: test 2019-03-15 13:56:45 +03:00
Simon Zolin
08bf9b0acb * dhcp: remove code which forces an update of current lease's IP in
Request message handler
2019-03-15 13:56:45 +03:00
Simon Zolin
60fa3b2e95 * dhcp: refactor; log client's HW addr 2019-03-15 13:56:45 +03:00
Aleksey Dmitrevskiy
3f93cb3397 Merge branch 'master' of ssh://bit.adguard.com:7999/dns/adguard-dns 2019-03-15 11:55:34 +03:00
Alexey Dmitrievskiy
10daf29e2f Merge: [feature] app, config: add symlink support, allow to specify absolute path in log_file; Fix #606, Fix #610
* commit '70c56f7a180e1113c289574e69dd148bc76db511':
  + app, config: add symlink support, allow to specify absolute path in log_file
2019-03-15 11:55:01 +03:00
Aleksey Dmitrevskiy
70c56f7a18 + app, config: add symlink support, allow to specify absolute path in log_file 2019-03-15 11:49:58 +03:00
Aleksey Dmitrevskiy
e9d20651e9 Fix #606, Fix #610 [change] app, config: add symlink support, allow to specify absolute path in log_file 2019-03-14 18:06:53 +03:00
Ildar Kamalov
466807638c Merge pull request #168 in DNS/adguard-dns from fix/625 to master
* commit 'b1f707d18cf6ce63fe1ed56efbd18d4f614d0543':
  - client: load favicon from local server
2019-03-14 11:30:21 +03:00
Alexey Dmitrievskiy
991717e150 Merge pull request #166 in DNS/adguard-dns from fix/598 to master
* commit 'd06cc0f8ee89a0142c493b33d295272ba100aaf5':
  Fix #598 - [change] service: windows: register sercive to work under local system user
2019-03-13 11:22:28 +03:00
Ildar Kamalov
b1f707d18c - client: load favicon from local server
Closes #625
2019-03-12 18:59:58 +03:00
Alexey Dmitrievskiy
f857ed74ec Merge pull request #154 in DNS/adguard-dns from fix/596 to master
* commit 'b74eded414cf3f31da6feb185619afab85233b5a': (34 commits)
  [change] control: fix issues from review
  [change] config: fix default upstreams list
  * client: typo
  * client: remove log
  * client: fix grammar
  [change] control: add upstreams validation before dns config test
  [change] control: add upstreams validation
  [change] control: update bootstrap DNS check
  * client: remove empty elements from upstream and bootstrap
  * client: locales and pass object to testUpstream
  [change] config, control, openapi: fix issues from reviw
  [fix] control: fix json decode for upstream config
  * client: upstream form
  [change] control, openapi: Handle upstreams test with JSON
  [change] upgrade_test: rework tests
  [change] upgrade_test: add test for upgrade
  [change] control: Remove unuseful check
  [change] control: Fix issues from review
  [change] dnsforward: Add comments for public fields
  [change] control: Handle upstream config with JSON
  ...
2019-03-12 14:30:31 +03:00
Ildar Kamalov
2f497cf5d0 Merge pull request #167 in DNS/adguard-dns from feature/611 to master
* commit '21db166be048bb9d99455838f2ce0a24e85162cd':
  + client: added Simplified Chinese
2019-03-12 12:30:03 +03:00
Ildar Kamalov
21db166be0 + client: added Simplified Chinese
Closes #611
2019-03-12 12:04:41 +03:00
Aleksey Dmitrevskiy
d06cc0f8ee Fix #598 - [change] service: windows: register sercive to work under local system user 2019-03-11 19:18:18 +03:00
Ildar Kamalov
b74eded414 Merge branch 'master' into fix/596 2019-03-11 11:07:28 +03:00
Ildar Kamalov
ecf0b0c5c1 Merge pull request #163 in DNS/adguard-dns from feature/615 to master
* commit '10fcfefcfd7220d68880cd735a3844ec4e617555':
  * client: bulgarian language translation
2019-03-11 11:03:02 +03:00
Alexey Dmitrievskiy
64c2f2d89c Merge pull request #164 in DNS/adguard-dns from fix/621 to master
* commit '0b96bd17c47a87bce8eba2913fdd4b6e0e68d290':
  Fix #621 - [change] config: change the default MalwareDomainList URL protocol
2019-03-11 10:56:24 +03:00
Aleksey Dmitrevskiy
0b96bd17c4 Fix #621 - [change] config: change the default MalwareDomainList URL protocol 2019-03-07 19:12:41 +03:00
Aleksey Dmitrevskiy
392c16cd27 [change] control: fix issues from review 2019-03-07 16:32:52 +03:00
Ildar Kamalov
10fcfefcfd * client: bulgarian language translation 2019-03-07 12:38:17 +03:00
Aleksey Dmitrevskiy
2bd9923691 Merge branch 'fix/596' of ssh://bit.adguard.com:7999/dns/adguard-dns into fix/596 2019-03-07 12:04:58 +03:00
Aleksey Dmitrevskiy
c8c663f3f0 [change] config: fix default upstreams list 2019-03-07 12:04:22 +03:00
Ildar Kamalov
bf82ccede9 * client: typo 2019-03-07 12:02:49 +03:00
Ildar Kamalov
982f8dc1e1 * client: remove log 2019-03-06 18:50:17 +03:00
Ildar Kamalov
83877fb871 * client: fix grammar 2019-03-06 18:39:14 +03:00
Aleksey Dmitrevskiy
ac131923a2 [change] control: add upstreams validation before dns config test 2019-03-06 18:36:31 +03:00
Aleksey Dmitrevskiy
bc4c2e2ff7 Merge branch 'master' into fix/596 2019-03-06 18:25:42 +03:00
Aleksey Dmitrevskiy
1b15bee2b0 [change] control: add upstreams validation 2019-03-06 18:24:14 +03:00
Aleksey Dmitrevskiy
8e09424774 Merge branch 'fix/596' of ssh://bit.adguard.com:7999/dns/adguard-dns into fix/596 2019-03-06 18:06:33 +03:00
Aleksey Dmitrevskiy
89b6323f03 [change] control: update bootstrap DNS check 2019-03-06 18:06:26 +03:00
Ildar Kamalov
cdc568d8d9 * client: remove empty elements from upstream and bootstrap 2019-03-06 17:53:04 +03:00
Aleksey Dmitrevskiy
bda6f777c4 Merge branch 'fix/596' of ssh://bit.adguard.com:7999/dns/adguard-dns into fix/596 2019-03-06 17:14:58 +03:00
Ildar Kamalov
bf2781d465 * client: locales and pass object to testUpstream 2019-03-06 16:35:21 +03:00
Aleksey Dmitrevskiy
f2e547a54e [change] config, control, openapi: fix issues from reviw 2019-03-06 16:17:15 +03:00
Aleksey Dmitrevskiy
ceaa1e4ebf [fix] control: fix json decode for upstream config 2019-03-06 15:35:22 +03:00
Ildar Kamalov
5d6c980ac7 * client: upstream form 2019-03-06 14:45:21 +03:00
Aleksey Dmitrevskiy
e973c4b174 [change] control, openapi: Handle upstreams test with JSON 2019-03-05 12:29:52 +03:00
Aleksey Dmitrevskiy
d9d641941c [change] upgrade_test: rework tests 2019-03-01 11:27:15 +03:00
Aleksey Dmitrevskiy
d318545913 [change] upgrade_test: add test for upgrade 2019-03-01 09:33:00 +03:00
Alexey Dmitrievskiy
7ec4aa91ea Merge pull request #153 in DNS/adguard-dns from fix/595 to master
* commit '2dc2a0946a28f41736228db1065654716907c5b8':
  Fix #595 - Start using GO 1.12
2019-02-28 18:05:27 +03:00
Alexey Dmitrievskiy
681fb4e705 Merge pull request #160 in DNS/adguard-dns from fix/597 to master
* commit '53d680a5dfb027831bdbfc91bde5272d4d849a1a':
  Fix #597 - [bugfix] querylog_top: Empty domain gets to the Top Queried domains
2019-02-28 18:05:19 +03:00
Simon Zolin
c443672e35 Merge pull request #157 in DNS/adguard-dns from feature/600 to master
* commit '6b7a8078b0155ba81678e71fe53f447f444dce8b':
  * control: use single line comment
  * disable config saving if invalid pair
  * control: TLS: don't return empty error messages
  * control: move TLS handlers to a separate file
  * helper functions return 'error', not 'int'
  * tlsConfigStatus.usable is public, renamed ("ValidPair") and is exported to json ("valid_pair")
  * validateCertificates(): split the function's code
  * validateCertificates(): change input parameters;  added short description
  + add tests for validateCertificates()
2019-02-28 18:04:37 +03:00
Aleksey Dmitrevskiy
53d680a5df Fix #597 - [bugfix] querylog_top: Empty domain gets to the Top Queried domains 2019-02-28 16:19:23 +03:00
Simon Zolin
6b7a8078b0 * control: use single line comment 2019-02-28 15:28:49 +03:00
Aleksey Dmitrevskiy
91f8ab0549 [change] control: Remove unuseful check 2019-02-28 15:18:51 +03:00
Aleksey Dmitrevskiy
a8812908c1 [change] control: Fix issues from review 2019-02-28 15:06:30 +03:00
Ildar Kamalov
b8595b87c4 * disable config saving if invalid pair 2019-02-28 14:59:25 +03:00
Aleksey Dmitrevskiy
acb4a98466 [change] dnsforward: Add comments for public fields 2019-02-28 13:40:40 +03:00
Aleksey Dmitrevskiy
3929f0da44 [change] control: Handle upstream config with JSON 2019-02-28 13:01:41 +03:00
Simon Zolin
224c2a891d * control: TLS: don't return empty error messages 2019-02-28 11:55:16 +03:00
Aleksey Dmitrevskiy
81e88472cb Merge branch 'fix/542' into fix/596 2019-02-28 11:16:03 +03:00
Aleksey Dmitrevskiy
6b2baba3c7 Add set_upstreams_config function 2019-02-28 11:10:43 +03:00
Aleksey Dmitrevskiy
967a1e6b87 Merge branch 'master' into fix/596 2019-02-27 18:56:36 +03:00
Simon Zolin
241e7ca20c * control: move TLS handlers to a separate file 2019-02-27 18:53:16 +03:00
Aleksey Dmitrevskiy
bc325de13f Add missed logging 2019-02-27 18:49:53 +03:00
Aleksey Dmitrevskiy
ffa4429818 Merge branch 'master' into fix/542 2019-02-27 18:47:01 +03:00
Simon Zolin
24edf7eeb6 * helper functions return 'error', not 'int' 2019-02-27 18:46:04 +03:00
Simon Zolin
99c8cd06c9 Merge remote-tracking branch 'origin/master' into feature/600 2019-02-27 18:40:22 +03:00
Simon Zolin
9a6266588d Merge pull request #158 in DNS/adguard-dns from feature/600-logger to master
* commit 'f21daae02368841bdb7b8bff05bf208e3b9ab136':
  * control: print HTTP request with log.Tracef()
  * use dnsproxy v0.11.2
  * use golibs v0.1.0
  * update go.sum
  * use new logger - AdguardTeam/golibs/log
2019-02-27 18:35:34 +03:00
Simon Zolin
f21daae023 * control: print HTTP request with log.Tracef() 2019-02-27 18:28:52 +03:00
Simon Zolin
7b64f9ff42 * use dnsproxy v0.11.2 2019-02-27 18:28:09 +03:00
Aleksey Dmitrevskiy
1626b6bd5a Fix empty logging 2019-02-27 18:09:57 +03:00
Simon Zolin
eb22d9cdd9 * use golibs v0.1.0 2019-02-27 17:55:23 +03:00
Aleksey Dmitrevskiy
1ed3a9673d Add handles logging 2019-02-27 17:39:07 +03:00
Simon Zolin
5ad9f8ead2 * tlsConfigStatus.usable is public, renamed ("ValidPair") and is exported to json ("valid_pair") 2019-02-27 17:36:02 +03:00
Aleksey Dmitrevskiy
523c5ef10a Refactor httpErrors 2019-02-27 17:28:10 +03:00
Aleksey Dmitrevskiy
a9839e95a0 pointer is unuseful for httpError func 2019-02-27 16:50:19 +03:00
Aleksey Dmitrevskiy
1223965cd4 Code simplify 2019-02-27 16:42:50 +03:00
Aleksey Dmitrevskiy
141b14c94a Remove unuseful library 2019-02-27 16:19:45 +03:00
Aleksey Dmitrevskiy
3a9d436f8a Add schema migration 2019-02-27 16:15:36 +03:00
Simon Zolin
01548c236e * update go.sum 2019-02-27 16:04:05 +03:00
Simon Zolin
5cb6d97cd7 * use new logger - AdguardTeam/golibs/log 2019-02-27 15:02:11 +03:00
Simon Zolin
f4a6ca726c * validateCertificates(): split the function's code 2019-02-27 14:31:53 +03:00
Simon Zolin
766fbab071 * validateCertificates(): change input parameters; added short description 2019-02-27 14:21:15 +03:00
Aleksey Dmitrevskiy
87c8114291 Use gotools 2019-02-27 13:12:06 +03:00
Simon Zolin
d218e047a3 + add tests for validateCertificates() 2019-02-27 13:07:29 +03:00
Aleksey Dmitrevskiy
bf893d488a Refactoring for set upstream and bootstrap DNS 2019-02-27 12:58:42 +03:00
Andrey Meshkov
bd31151c1f More platforms 2019-02-27 12:41:37 +03:00
Simon Zolin
4476262357 Merge pull request #155 in DNS/adguard-dns from carlbennett-carlbennett-patch-1 to master
* commit '2a93e45b671b422bf384548b221360d0c8e22d33':
  Fix typo in client/src/__locales/en.json
2019-02-27 12:06:13 +03:00
Carl Bennett
2a93e45b67 Fix typo in client/src/__locales/en.json 2019-02-27 12:02:06 +03:00
Aleksey Dmitrevskiy
5ba43a59f4 Fix server reconfig 2019-02-27 11:31:25 +03:00
Aleksey Dmitrevskiy
dc05556c5a Fix #542 - Add Bootstrap DNS resolver settings 2019-02-27 11:15:18 +03:00
Aleksey Dmitrevskiy
5bc6d00aa0 Fix #596 - Intelligent Optimal DNS Resolution 2019-02-26 18:19:05 +03:00
Aleksey Dmitrevskiy
2dc2a0946a Fix #595 - Start using GO 1.12 2019-02-26 15:32:56 +03:00
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
Eugene Bujak
fc2f01f933 Release v0.9 2018-10-16 15:39:27 +03:00
Eugene Bujak
a267dbf625 Merge pull request #85 in DNS/adguard-dns from feature/361 to master
* commit 'c46fcce87ddc71c201079dba3df6267a80493a27':
  README -- Add whotracks.me to acknowledgments
  README -- add acknowledgements and fix release badge
2018-10-16 15:17:02 +03:00
Eugene Bujak
c46fcce87d README -- Add whotracks.me to acknowledgments 2018-10-15 22:21:42 +03:00
Eugene Bujak
af9c47c40a README -- add acknowledgements and fix release badge 2018-10-15 22:04:38 +03:00
Eugene Bujak
59323b2008 Merge pull request #84 in DNS/adguard-dns from avg_time_fix to master
* commit 'f0823f119573ac17f0bc1cacd10b527227b128c3':
  Fixup of previous commit.
2018-10-15 19:36:10 +03:00
Eugene Bujak
f0823f1195 Fixup of previous commit. 2018-10-15 19:34:31 +03:00
Eugene Bujak
dca9aebccb Merge pull request #83 in DNS/adguard-dns from avg_time_fix to master
* commit '1ed9faa0c22c1ae9ec2043c80e697133688cea40':
  Fix API returning wrong average request time
2018-10-15 19:31:33 +03:00
Eugene Bujak
1ed9faa0c2 Fix API returning wrong average request time 2018-10-15 19:30:10 +03:00
Andrey Meshkov
0d44e3ccdc Merge pull request #82 in DNS/adguard-dns from fix/update_readme to master
* commit 'cf9414c107a06de8b277d417387cd6a835c94b9d':
  Update the README
2018-10-15 17:29:04 +03:00
Andrey Meshkov
cf9414c107 Update the README 2018-10-15 17:20:57 +03:00
Eugene Bujak
47ce0f3e98 Merge pull request #81 in DNS/adguard-dns from hotfix to master
* commit '2e1acc2bac2b30219065afeeb66390d7f2b83f2c':
  Hotfix -- fix broken links in readme
2018-10-15 16:33:50 +03:00
Eugene Bujak
2e1acc2bac Hotfix -- fix broken links in readme 2018-10-15 16:32:38 +03:00
Eugene Bujak
96142b4164 Merge pull request #80 in DNS/adguard-dns from rename to master
* commit '1af11c4e45e13bcbeb9fe37f7867760f09ae8c2b':
  Name migration -- rename config from AdguardDNS.yaml to AdGuardHome.yaml
  Rename from 'Adguard DNS' to 'AdGuard Home'.
2018-10-15 16:15:37 +03:00
Eugene Bujak
1af11c4e45 Name migration -- rename config from AdguardDNS.yaml to AdGuardHome.yaml
It's done only if user didn't specify it in parameters and target filename doesn't exist yet.
2018-10-15 16:13:03 +03:00
Eugene Bujak
3e2a3afc52 Rename from 'Adguard DNS' to 'AdGuard Home'. 2018-10-15 16:02:19 +03:00
Andrey Meshkov
40d1b18b28 Merge pull request #79 in DNS/adguard-dns from fix/370 to master
* commit 'c2ba8de2062775decf09c94360d303e742e4ff44':
  Fix column max width
2018-10-15 15:06:20 +03:00
Eugene Bujak
a126a3868c Merge pull request #78 in DNS/adguard-dns from fix/368 to master
* commit 'aa691a068add3a29dd06b65d595b633750120eeb':
  Fix #368
2018-10-15 15:00:03 +03:00
Ildar Kamalov
c2ba8de206 Fix column max width 2018-10-15 14:57:36 +03:00
Andrey Meshkov
82269bcf33 Merge pull request #77 in DNS/adguard-dns from fix/365 to master
* commit 'bdaea88bf0b79d84da717393a447f71bd78e3cff':
  Add "FAQ" link to the header
  more trackers
  fix url
  tooltip
  capitalize category name
  fix gitignore
  remove extra file
  Add trackers, rework some text
  Fix some UI issues, rename DNS->Home
2018-10-15 14:46:18 +03:00
Andrey Meshkov
aa691a068a Fix #368 2018-10-15 14:31:21 +03:00
Ildar Kamalov
bdaea88bf0 Add "FAQ" link to the header
Closes #370
2018-10-15 13:49:07 +03:00
Andrey Meshkov
8c11449d23 more trackers 2018-10-15 00:08:38 +03:00
Andrey Meshkov
0e04954673 fix url 2018-10-15 00:00:18 +03:00
Andrey Meshkov
1059669b57 tooltip 2018-10-14 23:50:13 +03:00
Andrey Meshkov
c9736ec0fa capitalize category name 2018-10-14 23:42:25 +03:00
Andrey Meshkov
dcbee729fb fix gitignore 2018-10-14 23:26:00 +03:00
Andrey Meshkov
c35f260e53 remove extra file 2018-10-14 23:25:40 +03:00
Andrey Meshkov
2f61b42e90 Add trackers, rework some text 2018-10-14 23:24:11 +03:00
Andrey Meshkov
e67695df8b Fix some UI issues, rename DNS->Home 2018-10-14 17:49:07 +03:00
Andrey Meshkov
e356540872 fix crash 2018-10-12 20:36:57 +03:00
Andrey Meshkov
3d01c3512e Merge pull request #76 in DNS/adguard-dns from single_binary to master
* commit '880ad362a849cc53729128b614a4d4cc1d696750':
  Makefile -- use variable for target binary
  coredns plugin -- remove debug logging
  single binary -- coredns also tries to parse arguments, it kills itself on unknown flags
  Update makefile to build only one binary instead of two
  WIP -- single binary -- works, replies to DNS, but need to check what got broken
2018-10-12 19:58:33 +03:00
Andrey Meshkov
49567219dc Merge pull request #74 in DNS/adguard-dns from feature/363 to master
* commit 'd2e5692694e4537038434b56020f4e6c9e782955':
  Remove debug logging added by previous commit.
  If running from terminal, ask for username/password if config file does not exists
2018-10-12 19:56:41 +03:00
Eugene Bujak
3f85625dc6 Merge pull request #75 in DNS/adguard-dns from feature/364 to master
* commit 'd0d98ba762e9482f91a385488d22865ea00ff2a5':
  Add whotracksme info popover
  trackers module
  Added a script for updating the whotracksme database
2018-10-12 19:56:33 +03:00
Ildar Kamalov
d0d98ba762 Add whotracksme info popover
Closes #364
2018-10-12 19:52:19 +03:00
Eugene Bujak
d2e5692694 Remove debug logging added by previous commit.
Closes #363.
2018-10-12 19:49:17 +03:00
Eugene Bujak
9d030f38b7 If running from terminal, ask for username/password if config file does not exists 2018-10-12 19:43:09 +03:00
Andrey Meshkov
5fb603f6c9 trackers module 2018-10-12 17:32:38 +03:00
Andrey Meshkov
11e8853a34 Added a script for updating the whotracksme database 2018-10-12 17:32:38 +03:00
Eugene Bujak
880ad362a8 Makefile -- use variable for target binary 2018-10-12 17:11:57 +03:00
Eugene Bujak
5192e95a0d coredns plugin -- remove debug logging 2018-10-12 17:11:57 +03:00
Eugene Bujak
ac6e0add31 single binary -- coredns also tries to parse arguments, it kills itself on unknown flags 2018-10-12 17:11:57 +03:00
Eugene Bujak
7ff89baf45 Update makefile to build only one binary instead of two 2018-10-12 17:11:57 +03:00
Eugene Bujak
bad88961e9 WIP -- single binary -- works, replies to DNS, but need to check what got broken 2018-10-12 17:11:57 +03:00
Andrey Meshkov
838406353b Merge pull request #73 in DNS/adguard-dns from fix/362 to master
* commit '1cdbe3f879566f8e08ad0fa88daad5b568750820':
  Fix sorting issue and show loader
2018-10-12 17:05:58 +03:00
Ildar Kamalov
1cdbe3f879 Fix sorting issue and show loader
Closes #362
2018-10-12 16:58:48 +03:00
Andrey Meshkov
47a9c6555e Merge pull request #72 in DNS/adguard-dns from feature/360 to master
* commit '35368619b0d7ed0fb838d749cd7b2f63ae9c9aa3':
  Fix footer links
  Add progress bar to the stats tables
  Add new logo
  Replace the main Statistics graph with 4 blocks instead
  Clean static folder on build
2018-10-12 16:38:03 +03:00
Ildar Kamalov
35368619b0 Fix footer links
Closes #360
2018-10-12 16:27:59 +03:00
Ildar Kamalov
6ca881ee86 Add progress bar to the stats tables 2018-10-12 16:02:01 +03:00
Ildar Kamalov
1233901822 Add new logo 2018-10-12 15:23:57 +03:00
Ildar Kamalov
bc11f872fa Replace the main Statistics graph with 4 blocks instead 2018-10-12 15:23:21 +03:00
Ildar Kamalov
599426a1f9 Clean static folder on build 2018-10-12 15:20:59 +03:00
Eugene Bujak
fb2d90832c Merge pull request #68 in DNS/adguard-dns from feature/357 to master
* commit '8d13770b0d4b6d8fde0a61bbfdaa7bf9817142f6':
  Remove unneeded debug prints
  API filtering/add_url -- accept JSON instead of name=value lines
  Remove unused module
  Send json for addFilter request
  Fix params
  Add name field to the filter subscription dialog
2018-10-11 18:35:30 +03:00
Eugene Bujak
8d13770b0d Remove unneeded debug prints 2018-10-11 18:33:56 +03:00
Eugene Bujak
751be05a31 API filtering/add_url -- accept JSON instead of name=value lines 2018-10-11 18:33:56 +03:00
Ildar Kamalov
33958c5a25 Remove unused module 2018-10-11 18:33:56 +03:00
Ildar Kamalov
1e18235c1d Send json for addFilter request 2018-10-11 18:33:56 +03:00
Ildar Kamalov
4b8ee9ce83 Fix params 2018-10-11 18:33:56 +03:00
Ildar Kamalov
5be66e7dc7 Add name field to the filter subscription dialog 2018-10-11 18:33:56 +03:00
Eugene Bujak
8cf898e8d9 Merge pull request #70 in DNS/adguard-dns from hotfix to master
* commit '4995c1a1a8411445390a77235f2ebfbc97b12ff3':
  Hotfix -- fix querylog verifier that got broken by 5ae2a32d6e
2018-10-11 18:12:28 +03:00
Eugene Bujak
4995c1a1a8 Hotfix -- fix querylog verifier that got broken by 5ae2a32d6e 2018-10-11 18:06:33 +03:00
Eugene Bujak
557c2268dc Merge pull request #67 in DNS/adguard-dns from feature/333 to master
* commit '383f1c2fb38437e682ec9fc6623672730dae4581':
  Hide badge if core is not running
  Add client requests for toggle protection
  API backend -- implement ability to turn toggle all protection in one go, helpful to temporarily disable all kinds of filtering
2018-10-11 16:23:01 +03:00
Eugene Bujak
aa7b99d78c Merge pull request #69 in DNS/adguard-dns from fix/blocked_ttl_10 to master
* commit 'e7b6ab47501cf14d3a7b5cf212c88bde0c6604d7':
  Change blocked ttl to 10 sec, we don't need it to be large in a home network
2018-10-11 15:11:43 +03:00
Andrey Meshkov
e7b6ab4750 Change blocked ttl to 10 sec, we don't need it to be large in a home network 2018-10-11 13:24:06 +03:00
Ildar Kamalov
383f1c2fb3 Hide badge if core is not running 2018-10-11 11:08:07 +03:00
Ildar Kamalov
3a74dfdfa4 Add client requests for toggle protection
Closes #333
2018-10-11 10:57:36 +03:00
Eugene Bujak
413228e6ec API backend -- implement ability to turn toggle all protection in one go, helpful to temporarily disable all kinds of filtering 2018-10-10 20:13:03 +03:00
Eugene Bujak
c3df81bb8d Merge pull request #66 in DNS/adguard-dns from bugfix/356 to master
* commit 'e689c7d940e9a20bc13f024e18b86f3c1e5ba759':
  Do not lose filter name when saving to yaml
  coredns querylog -- since we read entire querylog json once at startup, fill querylog cache from it and then rotate it on each incoming DNS query
  Cache DNS lookups when resolving safebrowsing or parental servers, also cache replacement hostnames as well.
2018-10-10 19:50:20 +03:00
Eugene Bujak
e689c7d940 Do not lose filter name when saving to yaml 2018-10-10 19:49:18 +03:00
Eugene Bujak
5ae2a32d6e coredns querylog -- since we read entire querylog json once at startup, fill querylog cache from it and then rotate it on each incoming DNS query 2018-10-10 19:44:07 +03:00
Eugene Bujak
a5d1053520 Cache DNS lookups when resolving safebrowsing or parental servers, also cache replacement hostnames as well. 2018-10-10 19:10:38 +03:00
Eugene Bujak
e2295c1a11 Merge pull request #65 in DNS/adguard-dns from fix/strings to master
* commit 'd591ea6264bb287a6e57f815a0bc75b7b920bb87':
  Makefile -- run npm build whenever any .js file changes inside client/
  fix strings
  Fix strings
  Fix strings
2018-10-10 17:57:36 +03:00
Eugene Bujak
d591ea6264 Makefile -- run npm build whenever any .js file changes inside client/ 2018-10-10 17:56:48 +03:00
Andrey Meshkov
ee8759f063 tMerge branch 'master' into fix/strings 2018-10-10 17:55:03 +03:00
Andrey Meshkov
151944bc27 fix strings 2018-10-10 17:54:21 +03:00
Andrey Meshkov
a6172d1966 Fix strings 2018-10-10 17:32:36 +03:00
Andrey Meshkov
90bef94500 Fix strings 2018-10-10 17:24:12 +03:00
Eugene Bujak
f5deff63ba Merge pull request #64 in DNS/adguard-dns from remove_old_stats to master
* commit '903b20dcab855f0dec7af3b305e8362fa4255b8a':
  Remove dead code
2018-10-10 16:19:30 +03:00
Eugene Bujak
903b20dcab Remove dead code 2018-10-10 15:47:08 +03:00
Eugene Bujak
945bd24f67 Merge pull request #63 in DNS/adguard-dns from hotfix to master
* commit 'ae9964c445e200d22b159a47bfc6c00af990653e':
  Makefile -- fix build failure on systems where /bin/sh is not alias to /bin/bash
2018-10-10 01:26:21 +03:00
Eugene Bujak
ae9964c445 Makefile -- fix build failure on systems where /bin/sh is not alias to /bin/bash 2018-10-10 01:23:32 +03:00
Andrey Meshkov
3a5ecb9fc1 Merge pull request #62 in DNS/adguard-dns from hotfix to master
* commit 'c499c435c3c91960abd695b4919554937a3df5d1':
  Fixup of previous merge -- fix build failure.
2018-10-10 01:13:38 +03:00
Eugene Bujak
c499c435c3 Fixup of previous merge -- fix build failure. 2018-10-10 01:13:00 +03:00
Andrey Meshkov
2113bb5436 Merge pull request #60 in DNS/adguard-dns from feature/342 to master
* commit '8503f76747d47160d5d8239e275a7d6d8965dacd':
  Add default disabled hosts filters.
  Change default filter URL to github-hosted version
2018-10-10 01:05:58 +03:00
Eugene Bujak
8503f76747 Add default disabled hosts filters. 2018-10-10 00:59:37 +03:00
Eugene Bujak
c2be5917ef Change default filter URL to github-hosted version 2018-10-10 00:44:39 +03:00
Eugene Bujak
a54984f688 Merge pull request #58 in DNS/adguard-dns from feature/348 to master
* commit '5533b434dac98ba610028ca98327992d18078da0':
  coredns plugin -- give out to browser last entries from querylog file, not first
  Update .gitignore to ignore non-gzipped querylog
  Makefile -- Fix bug introduced by 93c451cb0c
  coredns plugin -- Increase querylog given out to web UI from 1000 to 5000.
2018-10-10 00:35:58 +03:00
Eugene Bujak
5533b434da coredns plugin -- give out to browser last entries from querylog file, not first 2018-10-10 00:23:15 +03:00
Eugene Bujak
4984c55bce Update .gitignore to ignore non-gzipped querylog 2018-10-10 00:05:48 +03:00
Eugene Bujak
9b489c8ddb Makefile -- Fix bug introduced by 93c451cb0c
make would always run webpack, even if output was generated already.
2018-10-10 00:05:48 +03:00
Eugene Bujak
eb5f66ad9e coredns plugin -- Increase querylog given out to web UI from 1000 to 5000. 2018-10-09 22:53:19 +03:00
Eugene Bujak
75d74a017b Merge pull request #56 in DNS/adguard-dns from feature/348 to master
* commit 'ca794aed6344cf0f5570bb359c731a96ca90739f':
  querylog file -- disable gzip compression
  Implement online stats calculation in coredns plugin instead of scraping prometheus.
2018-10-09 21:16:03 +03:00
Eugene Bujak
93c451cb0c Merge pull request #57 in DNS/adguard-dns from feature/354 to master
* commit '0545aeff3fb8368238e8c61d43589ef8b4a6d8e8':
  Fix variable
  Add hash to the static JS/CSS
  Fix tooltip width
  Fix default filtering for query log
  Fix textarea width
2018-10-09 21:15:26 +03:00
Ildar Kamalov
0545aeff3f Fix variable 2018-10-09 11:00:48 +03:00
Ildar Kamalov
814005021c Add hash to the static JS/CSS
Closes #354
2018-10-09 10:25:21 +03:00
Eugene Bujak
ca794aed63 querylog file -- disable gzip compression 2018-10-09 05:02:16 +03:00
Eugene Bujak
37f6d38c49 Implement online stats calculation in coredns plugin instead of scraping prometheus. 2018-10-09 04:45:05 +03:00
Konstantin 🦄 Zamyakin
165722585f Merge pull request #55 in DNS/adguard-dns from hotfix to master
* commit '7dea729656fe60aefcd81b7c1b808866d3c334a7':
  Fix build failure of coredns plugin introduced by previous merge
2018-10-08 21:30:47 +03:00
Eugene Bujak
7dea729656 Fix build failure of coredns plugin introduced by previous merge 2018-10-08 20:35:22 +03:00
Eugene Bujak
16b1a343a0 Merge pull request #54 in DNS/adguard-dns from feature/348 to master
* commit 'a15f21ca1cd0acabf77a4e9750dd77b2b870a6f4':
  code review -- move constants into named constants
  coredns plugin -- Cache /querylog API result
  coredns plugin -- Final fix for deadlock during coredns reload
  coredns plugin -- change rlock to lock when loading top stats to avoid doing it in parallel
  coredns plugin -- Fix deadlock during coredns reload
  stats -- Clamp number of rotations to sane value and prevent from going into (very long) loop
  querylog API -- when manually generating json, don't forget to escape strings
  coredns plugin -- don't reload from querylog on SIGUSR, we already have it in memory
  Fix some lint warnings
  coredns plugin -- Calculate top for domains, clients and blocked both from querylog and running requests.
  Fix more race conditions found by race detector
  Querylog -- Omit empty fields when writing json
  Querylog -- Read from querylog files when answering to /querylog API, it now survives restarts.
  querylog -- Add querylog files to gitignore
  if coredns unexpectedly quits, restart it
  Fix race conditions found by go's race detector
  Querylog -- Implement file writing and update /querylog handler for changed structures.
2018-10-08 20:15:06 +03:00
Eugene Bujak
a15f21ca1c code review -- move constants into named constants 2018-10-08 20:04:36 +03:00
Eugene Bujak
a15c59e24e coredns plugin -- Cache /querylog API result 2018-10-08 19:51:43 +03:00
Ildar Kamalov
5718f55b9a Fix tooltip width 2018-10-08 18:55:30 +03:00
Ildar Kamalov
6de0871f2c Fix default filtering for query log 2018-10-08 18:44:12 +03:00
Ildar Kamalov
6a90efe957 Fix textarea width 2018-10-08 18:42:55 +03:00
Eugene Bujak
763dcc46e9 coredns plugin -- Final fix for deadlock during coredns reload 2018-10-08 17:49:08 +03:00
Eugene Bujak
3109529dbb coredns plugin -- change rlock to lock when loading top stats to avoid doing it in parallel 2018-10-08 17:14:11 +03:00
Eugene Bujak
2c84cd6448 coredns plugin -- Fix deadlock during coredns reload 2018-10-08 17:07:45 +03:00
Eugene Bujak
0440ef016a stats -- Clamp number of rotations to sane value and prevent from going into (very long) loop 2018-10-08 05:55:33 +03:00
Eugene Bujak
182fa37e5f querylog API -- when manually generating json, don't forget to escape strings 2018-10-08 05:07:02 +03:00
Eugene Bujak
ea1125f57d coredns plugin -- don't reload from querylog on SIGUSR, we already have it in memory 2018-10-08 04:24:37 +03:00
Eugene Bujak
4ecb84f9ad Fix some lint warnings 2018-10-07 23:43:24 +03:00
Eugene Bujak
a2434d4574 coredns plugin -- Calculate top for domains, clients and blocked both from querylog and running requests.
This moves the functionality from frontend to coredns plugin.
2018-10-07 23:42:17 +03:00
Eugene Bujak
3b1faa1365 Fix more race conditions found by race detector 2018-10-07 21:24:22 +03:00
Eugene Bujak
dc1042c3e9 Querylog -- Omit empty fields when writing json 2018-10-07 02:21:47 +03:00
Eugene Bujak
a63fe958ae Querylog -- Read from querylog files when answering to /querylog API, it now survives restarts. 2018-10-07 02:21:33 +03:00
Eugene Bujak
0ee112e8a0 querylog -- Add querylog files to gitignore 2018-10-07 02:21:27 +03:00
Eugene Bujak
656d092ad6 if coredns unexpectedly quits, restart it 2018-10-07 02:21:27 +03:00
Eugene Bujak
2244c21b76 Fix race conditions found by go's race detector 2018-10-07 02:21:27 +03:00
Eugene Bujak
2c33905a79 Querylog -- Implement file writing and update /querylog handler for changed structures. 2018-10-07 02:21:12 +03:00
Eugene Bujak
16fd1359cd Merge pull request #53 in DNS/adguard-dns from bugfix/shoult_not_happen_spam to master
* commit '3a7a80f15f0180836077b4f63e504a659133adbb':
  coredns plugin -- fix SHOULD NOT HAPPEN spam when incoming request is for root servers
  Makefile -- update pprof plugin to survive coredns reloads
2018-10-05 17:12:24 +03:00
Eugene Bujak
3a7a80f15f coredns plugin -- fix SHOULD NOT HAPPEN spam when incoming request is for root servers 2018-10-05 07:36:03 +03:00
Eugene Bujak
5b9a5fff97 Makefile -- update pprof plugin to survive coredns reloads 2018-10-05 07:25:44 +03:00
Eugene Bujak
3f8450337f Merge pull request #52 in DNS/adguard-dns from feature/persistent-stats to master
* commit '19e76b693803220dffcd0a1fb1fe1e654309a11a':
  Add API call to reset stats
  Periodically flush stats.json
  Web UI -- persistent stats by writing them into stats.json at exit
2018-10-04 14:53:05 +03:00
Eugene Bujak
19e76b6938 Add API call to reset stats 2018-10-04 14:29:17 +03:00
Eugene Bujak
856e26edcf Periodically flush stats.json 2018-10-04 14:29:17 +03:00
Eugene Bujak
51ec58b0ce Web UI -- persistent stats by writing them into stats.json at exit 2018-10-04 14:29:16 +03:00
Eugene Bujak
c6eabb5b67 Merge pull request #51 in DNS/adguard-dns from feature/regexp_leak to master
* commit '1cc1e3749df6ccefb741232d7949fd5893d84f66':
  dnsfilter -- lazily initialize safebrowsing and parental lookup cache
  dnsfilter -- avoid using regexps when simple suffix match is enough.
2018-10-04 13:52:31 +03:00
Eugene Bujak
1cc1e3749d dnsfilter -- lazily initialize safebrowsing and parental lookup cache 2018-10-04 13:38:52 +03:00
Eugene Bujak
cb97a254a5 dnsfilter -- avoid using regexps when simple suffix match is enough.
This covers 96.98% of all adguard dns rules.
2018-10-04 13:19:43 +03:00
Eugene Bujak
9e939e5754 Merge pull request #49 in DNS/adguard-dns from features/memleak-test to master
* commit '3aac7e7bc9b4bb3ecff697b7748499a14bc64a0d':
  Add a test to demonstrate huge memory usage due from having too many regexps
2018-10-04 12:51:09 +03:00
Eugene Bujak
b72d6f68e6 Merge pull request #47 in DNS/adguard-dns from feature/349 to master
* commit '57ade2c3c3804d24857a45a8ab31c10534154dc7':
  Increase querylog size from 1000 to 10000 -- that'll use 32MB of memory.
  Web UI API -- Give out 24-hour stat instead of last 3 minutes.
2018-10-04 12:16:43 +03:00
Eugene Bujak
3aac7e7bc9 Add a test to demonstrate huge memory usage due from having too many regexps 2018-10-04 02:06:23 +03:00
Eugene Bujak
57ade2c3c3 Increase querylog size from 1000 to 10000 -- that'll use 32MB of memory. 2018-10-03 22:44:57 +03:00
Eugene Bujak
7d7360c700 Web UI API -- Give out 24-hour stat instead of last 3 minutes. 2018-10-03 22:44:50 +03:00
Ildar Kamalov
8c76e17b1b Merge pull request #46 in DNS/adguard-dns from feature/332 to master
* commit '991574f236ba691548839104a4218d749fbef10a':
  Fix row original
  Add query log filtering
2018-10-03 12:38:14 +03:00
Ildar Kamalov
991574f236 Fix row original 2018-10-02 18:30:34 +03:00
Ildar Kamalov
d7596fe860 Add query log filtering
Closes #322
2018-10-02 18:14:41 +03:00
Eugene Bujak
0c3c8dba9b Merge pull request #43 in DNS/adguard-dns from feature/341 to master
* commit 'e20bfe9d08d6c60c8f37ec49dcda2f446bdf0ce5':
  Replace line endings on save
  Add "block" and "unblock" buttons to the Query Log
2018-09-28 20:07:38 +03:00
Eugene Bujak
04e9f74435 Merge pull request #45 in DNS/adguard-dns from less-chatty to master
* commit '7b7f7138806b0b743d4fb1c4fef3c40f513be8b4':
  Be less noisy during long periods of time
2018-09-28 20:04:25 +03:00
Eugene Bujak
7b7f713880 Be less noisy during long periods of time 2018-09-28 18:08:26 +03:00
Ildar Kamalov
e20bfe9d08 Replace line endings on save 2018-09-28 17:47:34 +03:00
Ildar Kamalov
c40f7b4d5c Add "block" and "unblock" buttons to the Query Log 2018-09-28 16:30:52 +03:00
Eugene Bujak
d7039d9222 Merge pull request #42 in DNS/adguard-dns from feature/344 to master
* commit '2c720350006f607958540a672d2fa4cf927010bb':
  Add list of upstream servers
2018-09-26 18:55:28 +03:00
Eugene Bujak
3282a45978 Merge pull request #41 in DNS/adguard-dns from feature/346 to master
* commit '98994916b58faddb210b0776bdd7b5b6de43a8dc':
  Code review request -- set safebrowsing default to disabled
  web backend -- generate corefile with blocked_ttl config parameter
  coredns plugin -- Add option "blocked_ttl" that can change default nxdomain response TTL
  Makefile -- avoid stale copy of this repo inside build/gopath
2018-09-26 18:52:30 +03:00
Eugene Bujak
98994916b5 Code review request -- set safebrowsing default to disabled 2018-09-26 18:41:45 +03:00
Eugene Bujak
f1ae5d78d2 web backend -- generate corefile with blocked_ttl config parameter
Closes #346.
2018-09-26 18:38:35 +03:00
Ildar Kamalov
2c72035000 Add list of upstream servers
Closes #344
2018-09-26 18:38:06 +03:00
Eugene Bujak
c7790a8d9f coredns plugin -- Add option "blocked_ttl" that can change default nxdomain response TTL 2018-09-26 18:38:06 +03:00
Eugene Bujak
c9e10c9de7 Makefile -- avoid stale copy of this repo inside build/gopath 2018-09-26 18:38:06 +03:00
Eugene Bujak
de7b2d5e6b Merge pull request #40 in DNS/adguard-dns from feature/347 to master
* commit 'ff86d6b7dc31e463651c11f02330630e35676e05':
  Set default servers to tls://1.1.1.1 and tls://1.0.0.1
2018-09-26 18:00:34 +03:00
Eugene Bujak
ff86d6b7dc Set default servers to tls://1.1.1.1 and tls://1.0.0.1
Also add support for tls:// in webUI API
2018-09-26 17:47:23 +03:00
Eugene Bujak
3afd8fccc7 Merge pull request #39 in DNS/adguard-dns from feature/333 to master
* commit '2cf22898dd1418d1659340a95c94c8c9a6a7cf04':
  Add button to the dashboard page for enable/disable filtering
2018-09-26 17:24:30 +03:00
Ildar Kamalov
2cf22898dd Add button to the dashboard page for enable/disable filtering
Closes #333
2018-09-26 17:12:31 +03:00
Eugene Bujak
381b96a4b1 Merge pull request #38 in DNS/adguard-dns from bugfix/344 to master
* commit 'a65a40c6beb00176f46e7187ba0c4b678b17f6d8':
  Update /status to return currently set upstream DNS servers.
  web UI -- Fix engrish when checking upstream DNS servers succeeds
2018-09-25 20:54:35 +03:00
Eugene Bujak
a65a40c6be Update /status to return currently set upstream DNS servers. 2018-09-25 19:53:36 +03:00
Eugene Bujak
da62fac76e web UI -- Fix engrish when checking upstream DNS servers succeeds 2018-09-25 19:52:50 +03:00
Eugene Bujak
6a53dd0f00 Merge pull request #37 in DNS/adguard-dns from bugfix/333 to master
* commit '09a39cce03f69b1f9801e66d763d7b5208411336':
  Allow disabling of filtering but keeping querylog, safebrowsing, safesearch and parental working.
  Makefile -- make it a bit less noisy during build and much less noisy during clean
2018-09-25 19:44:07 +03:00
Eugene Bujak
09a39cce03 Allow disabling of filtering but keeping querylog, safebrowsing, safesearch and parental working. 2018-09-25 19:26:26 +03:00
Eugene Bujak
50b188a086 Makefile -- make it a bit less noisy during build and much less noisy during clean 2018-09-25 19:25:54 +03:00
Eugene Bujak
dd8396cec1 Merge pull request #36 in DNS/adguard-dns from bugfix/343 to master
* commit 'ea320f5ee35fda7744e86a1bd77b948e534eeb1e':
  Fix test failures introduced by previous commit afd1fe21f6.
2018-09-25 19:14:59 +03:00
Eugene Bujak
ea320f5ee3 Fix test failures introduced by previous commit afd1fe21f6. 2018-09-25 19:12:50 +03:00
Eugene Bujak
afd1fe21f6 Merge pull request #35 in DNS/adguard-dns from bugfix/343 to master
* commit '119d38fa8e8b5c5193fe20ad215a6daac833354b':
  Add trace() for debugging
  coredns -- don't try to be smart and replace 127.0.0.1 with NXDOMAIN yet -- need research on that first
  Fix 'index out of range' panic when adding a filter URL that has empty line in contents
  web UI -- Fix description of hosts rule syntax, it's other way around
2018-09-25 18:44:41 +03:00
Eugene Bujak
119d38fa8e Add trace() for debugging 2018-09-25 18:34:34 +03:00
Eugene Bujak
620212ad37 coredns -- don't try to be smart and replace 127.0.0.1 with NXDOMAIN yet -- need research on that first 2018-09-25 18:34:01 +03:00
Eugene Bujak
bd0fa4cc4f Fix 'index out of range' panic when adding a filter URL that has empty line in contents 2018-09-25 18:23:02 +03:00
Eugene Bujak
b0549a8e5b web UI -- Fix description of hosts rule syntax, it's other way around 2018-09-25 18:22:41 +03:00
Eugene Bujak
92399b8ebf Merge pull request #34 in DNS/adguard-dns from better-builds to master
* commit 'd8fbb2cd3b688b3890d60bc3923db50696dd9d59':
  Remove leftover from old internal repo
  Rewrite Makefile
2018-09-21 20:16:56 +03:00
Eugene Bujak
d8fbb2cd3b Remove leftover from old internal repo 2018-09-21 20:06:33 +03:00
Eugene Bujak
469b93eaa4 Rewrite Makefile
* fixes building outdated coredns plugin from inside GOPATH
 * make clean now cleans all build output, including node_modules and webpack output
 * smarter invocation of `npm install` -- only if package.json or package-lock.json changed
 * use separate gopath because coredns build system requires custom checkout of prometheus dependency
2018-09-21 20:01:55 +03:00
Ildar Kamalov
92b681cb41 Merge pull request #33 in DNS/adguard-dns from feature/321 to master
* commit '1c1b952d485e572eb2320b641429e07757b2d65f':
  Fix message checking
  Check upstream length in component
  Add a test upstreams button
2018-09-21 19:06:25 +03:00
Ildar Kamalov
1c1b952d48 Fix message checking 2018-09-21 18:57:27 +03:00
Ildar Kamalov
c2a2b3ea6a Check upstream length in component 2018-09-21 18:50:06 +03:00
Ildar Kamalov
f727f999f9 Add a test upstreams button
Closes #321
2018-09-21 18:08:39 +03:00
Eugene Bujak
02b28f4511 Merge pull request #32 in DNS/adguard-dns from feature/338 to master
* commit '43fcf4117db0e1e26085a0cc20a574edc8bd6255':
  Add update check
2018-09-21 15:32:11 +03:00
Ildar Kamalov
43fcf4117d Add update check
Closes #338
2018-09-21 15:20:41 +03:00
Eugene Bujak
68422b8399 Merge pull request #31 in DNS/adguard-dns from feature/338 to master
* commit 'c3f6a96f2f36f751d1315d83d65bff9396ea71d4':
  Add API endpoint to fetch version.json from github.io
2018-09-21 12:13:11 +03:00
Eugene Bujak
c3f6a96f2f Add API endpoint to fetch version.json from github.io 2018-09-20 20:02:25 +03:00
Eugene Bujak
2c2b951fd6 Merge pull request #30 in DNS/adguard-dns from feature/339 to master
* commit 'fba70b8b730187b8a60e724c1495ecc72e6f2d5e':
  Add version.json -- contains v0.1
2018-09-20 18:42:09 +03:00
Eugene Bujak
fba70b8b73 Add version.json -- contains v0.1 2018-09-20 18:35:09 +03:00
Eugene Bujak
38cfe95280 Merge pull request #29 in DNS/adguard-dns from readme to master
* commit 'a76fd7618abce834aa4eced0200e3ecfe77298e0':
  Proofreading by @vbagirov
  Update readme.
2018-09-20 18:27:15 +03:00
Eugene Bujak
a76fd7618a Proofreading by @vbagirov 2018-09-20 18:15:52 +03:00
Eugene Bujak
8d23e29190 Update readme.
* Link directly to binary downloads
 * Move "how to run" section right after binaries.
 * Add "How does AdGuard DNS work" and "How is this different from public AdGuard DNS servers" sections.
 * Update phrasing to emphasize that it's "your AdGuard DNS" server.
 * Remove yarn from `brew install` line.
 * Mention terminal for bash commands.
 * Add 32-bit ARM and 32-bit Intel binary links.
2018-09-20 15:21:02 +03:00
Eugene Bujak
a185161ad4 Merge pull request #28 in DNS/adguard-dns from feature/321 to master
* commit 'e733c1950484969c553b92e80ced6e585cc07bea':
  Implement API to test for upstream DNS servers.
2018-09-20 13:38:12 +03:00
Ildar Kamalov
81c7dbbc16 Merge pull request #27 in DNS/adguard-dns from feature/316 to master
* commit '0e173d2f705752c335c7fa78fdd5c578420e8a43':
  add progress bar and filters notifications
2018-09-19 19:14:19 +03:00
Eugene Bujak
e733c19504 Implement API to test for upstream DNS servers. 2018-09-19 19:12:09 +03:00
Ildar Kamalov
0e173d2f70 add progress bar and filters notifications 2018-09-19 18:58:55 +03:00
Eugene Bujak
0292d2b32b Merge pull request #26 in DNS/adguard-dns from basicauth to master
* commit 'ba56d6c01d7b241829d3115e1ce87241737cf571':
  Reorganize config file.
  Update README to explain config file settings
  Implement simple basic auth.
2018-09-19 15:54:49 +03:00
Eugene Bujak
ba56d6c01d Reorganize config file. 2018-09-19 15:51:44 +03:00
Eugene Bujak
b8213bf88a Update README to explain config file settings 2018-09-19 15:51:28 +03:00
Eugene Bujak
4548eb8d11 Implement simple basic auth.
Closes #326.
2018-09-18 20:59:41 +03:00
Eugene Bujak
a2f06aadc0 Merge pull request #25 in DNS/adguard-dns from feature/331 to master
* commit 'df12038f33b7acf6a3e9329054f1b5ad4cb02cd8':
  Add refresh button to querylog page
2018-09-17 19:03:45 +03:00
Ildar Kamalov
df12038f33 Add refresh button to querylog page
Closes #331
2018-09-17 17:44:32 +03:00
Eugene Bujak
c2aa39efe5 Merge pull request #23 in DNS/adguard-dns from gometalinter to master
* commit '076c9de68e73bbddc63cf6f7212818c91f3e5c08':
  Fix many lint warnings found by gometalinter
2018-09-17 11:26:17 +03:00
Eugene Bujak
5d046c5c16 Merge pull request #24 in DNS/adguard-dns from parental_fullhash to master
* commit 'dcbe3dd4051f421553eb7782af67378f3d9ce85b':
  dnsfilter -- compare full hashes when parsing parental lookup result.
2018-09-17 01:50:09 +03:00
Eugene Bujak
ae50a2f827 Merge pull request #20 in DNS/adguard-dns from feature/315 to master
* commit 'ded02d112c0e5b6d9585ec5506f24746abffdff3':
  Add console error
  Fix timeout
  Handle settings errors
  Show toast on failed request
  Fix clear interval
  Add alert on failed requests
2018-09-17 01:44:48 +03:00
Eugene Bujak
dcbe3dd405 dnsfilter -- compare full hashes when parsing parental lookup result.
Closes #337.
2018-09-17 01:42:01 +03:00
Ildar Kamalov
ded02d112c Add console error 2018-09-14 21:31:20 +03:00
Eugene Bujak
076c9de68e Fix many lint warnings found by gometalinter 2018-09-14 18:40:05 +03:00
Ildar Kamalov
d237df6389 Fix timeout 2018-09-14 16:43:27 +03:00
Ildar Kamalov
22a5abb7b8 Handle settings errors 2018-09-14 16:41:34 +03:00
Ildar Kamalov
828bb40084 Show toast on failed request 2018-09-14 15:37:35 +03:00
Eugene Bujak
548010e002 Merge pull request #22 in DNS/adguard-dns from fixtravis to master
* commit '5c6aa910efc9d90c5435a9eda19866a7d48be032':
  Fix a missed argument that breaks go test (which invokes go vet and fails if that fails)
2018-09-14 14:48:30 +03:00
Eugene Bujak
5c6aa910ef Fix a missed argument that breaks go test (which invokes go vet and fails if that fails) 2018-09-14 14:47:27 +03:00
Eugene Bujak
b9999f155e Merge pull request #21 in DNS/adguard-dns from add_url_verifier to master
* commit '3b44efc8e3f8fac534fbec37e1537ee4bd646141':
  /add_url -- it fetches the URL and checks if contents are valid filter, fails if it is not, and returns number of rules if it is
2018-09-14 11:23:51 +03:00
Eugene Bujak
3b44efc8e3 /add_url -- it fetches the URL and checks if contents are valid filter, fails if it is not, and returns number of rules if it is 2018-09-14 04:33:54 +03:00
Ildar Kamalov
9258fada47 Fix clear interval
Closes #315
2018-09-12 15:38:54 +03:00
Ildar Kamalov
6c70d8ca37 Add alert on failed requests 2018-09-12 12:58:55 +03:00
Eugene Bujak
5554643cd0 Merge pull request #19 in DNS/adguard-dns from version to master
* commit '7c71d4b44597ca7b22d3db60960c67b6e23ff2a6':
  web interface -- avoid having 'v.v0.1', saying 'version v0.1' seems more natural than that
2018-09-11 19:08:27 +03:00
Eugene Bujak
7c71d4b445 web interface -- avoid having 'v.v0.1', saying 'version v0.1' seems more natural than that 2018-09-11 19:04:49 +03:00
Eugene Bujak
3a92520764 Merge pull request #18 in DNS/adguard-dns from nonfqdn to master
* commit 'aa2e5500e72864727a0dcd196f37e84931cfa30a':
  coredns plugin -- do not filter out non-FQDN's -- otherwise it breaks serving /etc/hosts
2018-09-11 18:00:06 +03:00
Eugene Bujak
aa2e5500e7 coredns plugin -- do not filter out non-FQDN's -- otherwise it breaks serving /etc/hosts 2018-09-11 17:57:20 +03:00
Eugene Bujak
e2cf9ffd84 Merge pull request #17 in DNS/adguard-dns from feature/328 to master
* commit 'd8a3ee36764e4c3e33f5c73a3c5f9e73cdd5ec13':
  change graph stats to 24 hours
2018-09-11 15:05:47 +03:00
Ildar Kamalov
d8a3ee3676 change graph stats to 24 hours
Closes #328
2018-09-11 12:40:01 +03:00
Eugene Bujak
46e447589c Merge pull request #16 in DNS/adguard-dns from footer to master
* commit '97161ab4f0694367458933bc6ba44efe4d5e0509':
  web interface -- Update footer from placeholder to actual values.
2018-09-10 21:17:57 +03:00
Eugene Bujak
97161ab4f0 web interface -- Update footer from placeholder to actual values. 2018-09-10 21:14:28 +03:00
Eugene Bujak
3901dda39c Merge pull request #14 in DNS/adguard-dns from dnsfilter_recursion to master
* commit 'd49e3769a105f4dee639b9dec1112b123b7a23aa':
  dnsfilter -- do not check lookup hosts against themselves to avoid recursion
  Add support for serving /etc/hosts
  Makefile -- Fix cross-compilation
2018-09-10 20:52:20 +03:00
Eugene Bujak
d49e3769a1 dnsfilter -- do not check lookup hosts against themselves to avoid recursion 2018-09-10 20:43:22 +03:00
Eugene Bujak
c1e16cc584 Add support for serving /etc/hosts 2018-09-10 20:43:22 +03:00
Eugene Bujak
9c1dc6d373 Makefile -- Fix cross-compilation 2018-09-10 17:46:42 +03:00
Eugene Bujak
fced9178b8 Merge pull request #13 in DNS/adguard-dns from fix_coredns_build to master
* commit 'd34049b5135ff5dac2015dd93a5a624abdfe2cb4':
  travis -- Fix go test failure.
2018-09-10 16:10:44 +03:00
Eugene Bujak
d34049b513 travis -- Fix go test failure. 2018-09-10 15:57:47 +03:00
Eugene Bujak
43dbef8935 Merge pull request #12 in DNS/adguard-dns from fix_coredns_build to master
* commit 'f6d7d6a37ac9f8a184794d08c5b8a43bdc24e75a':
  Fix coredns build failure.
2018-09-10 15:33:05 +03:00
Eugene Bujak
f6d7d6a37a Fix coredns build failure. 2018-09-10 15:27:46 +03:00
Eugene Bujak
b54f9a7a36 Merge pull request #11 in DNS/adguard-dns from feature/313 to master
* commit '9e6ed7f273e996b2a89d9eda2302117adcc2389d':
  fix filtered reason overflow
  fix table cell overflow in filters
2018-09-07 19:12:05 +03:00
Ildar Kamalov
9e6ed7f273 fix filtered reason overflow 2018-09-07 19:06:09 +03:00
Ildar Kamalov
04faff3e2c fix table cell overflow in filters 2018-09-07 18:58:48 +03:00
Eugene Bujak
5fdaf7cb66 Merge pull request #10 in DNS/adguard-dns from feature/313 to master
* commit '76f98e2950463d4e176f9a5e2bf2c2c1c8aee51c':
  fix default page size in query logs
2018-09-07 18:44:34 +03:00
Ildar Kamalov
76f98e2950 fix default page size in query logs
Closes #313
2018-09-07 18:41:53 +03:00
Konstantin 🦄 Zamyakin
ba836220b8 Merge pull request #9 in DNS/adguard-dns from consistent-stats to master
* commit '31893410892bd047c9f6ea8f602717e6996c9491':
  web interface -- Make refresh buttons reload all data, not just counters
  web interface -- change text from 'general counters' to 'general statistics'
  Fixup of previous commit -- errand keystroke crept in
  API /stats_top -- sort top entries by value
  API /stats_top -- show only top entries for last 3 minutes
2018-09-07 18:21:46 +03:00
Eugene Bujak
3189341089 web interface -- Make refresh buttons reload all data, not just counters 2018-09-07 18:05:10 +03:00
Eugene Bujak
4ba8293c06 web interface -- change text from 'general counters' to 'general statistics' 2018-09-07 18:05:02 +03:00
Eugene Bujak
7094ed4f28 Fixup of previous commit -- errand keystroke crept in 2018-09-07 17:59:24 +03:00
Eugene Bujak
f623c3d909 API /stats_top -- sort top entries by value 2018-09-07 17:50:03 +03:00
Eugene Bujak
8198b65f29 API /stats_top -- show only top entries for last 3 minutes 2018-09-07 17:49:33 +03:00
Konstantin 🦄 Zamyakin
38b3fe6718 Merge pull request #8 in DNS/adguard-dns from parental_metrics to master
* commit '9682dc6bc19ea940cf71911f6281450c7027eb16':
  travis -- npm installation of dependencies belongs in install section
  makefile -- use npm --prefix syntax instead of doing cd into subdir
  travis -- don't use slow master or tip builds, just specify 1.x for latest go version
  travis -- move dependency installation to install section, simplify go test invocation to test all subdirs in one go
  dnsfilter -- small code cleanup
  coredns plugin metrics -- deduplicate code
  dnsfilter metrics -- parental cache hits were counted as safebrowsing cache hits
2018-09-07 17:11:21 +03:00
Eugene Bujak
9682dc6bc1 travis -- npm installation of dependencies belongs in install section 2018-09-07 16:14:43 +03:00
Eugene Bujak
659b530381 makefile -- use npm --prefix syntax instead of doing cd into subdir 2018-09-07 16:14:25 +03:00
Eugene Bujak
1b5748e328 travis -- don't use slow master or tip builds, just specify 1.x for latest go version 2018-09-07 16:13:23 +03:00
Eugene Bujak
ebf2380af4 travis -- move dependency installation to install section, simplify go test invocation to test all subdirs in one go 2018-09-07 16:13:03 +03:00
Eugene Bujak
6fc50cd743 dnsfilter -- small code cleanup 2018-09-07 16:10:43 +03:00
Eugene Bujak
3b9aaff861 coredns plugin metrics -- deduplicate code 2018-09-07 16:10:11 +03:00
Eugene Bujak
c572c7b0e9 dnsfilter metrics -- parental cache hits were counted as safebrowsing cache hits 2018-09-07 15:46:38 +03:00
Eugene Bujak
74275bebdc Merge pull request #7 in DNS/adguard-dns from metrics to master
* commit '1f0fdef8d6b2ce324e7009bb3f95626d87438d61':
  Fix invalid element order for historical stats -- in API declaration values are from oldest to newest, not other way around.
  Rewrite how historical stats are stored and calculated.
  coredns plugin -- convert logic into switch, logging unexpected non-covered cases
  After filters were redownloaded and deemed to be fresh, tell coredns server to reload
  coredns plugin -- on server reload, metrics disappeared, therefore they must be registered on each reload instead of once
  coredns plugin -- give feedback how many rules were in rulefile
  dnsfilter -- Update tests to check for expected filter/nofilter reason as well.
  Remove debug logging during checks if coredns is alive
  Be more atomic during writing of files -- this prevents other processes from seeing empty or impartial files
  Start coredns on launch before we serve HTTP -- this checks if port is available
  Move starting of coredns server into separate function
  sometimes answer can be empty, therefore question could be lost -- pass both to querylog
  Reduce binary size of coredns by 60% by removing orchestration plugins like kubernetes, route53, trace, etcd and federation
  Fix registration of metrics if querylog is enabled
2018-09-06 14:44:40 +03:00
Eugene Bujak
1f0fdef8d6 Fix invalid element order for historical stats -- in API declaration values are from oldest to newest, not other way around. 2018-09-06 02:20:51 +03:00
Eugene Bujak
04562dece3 Rewrite how historical stats are stored and calculated.
Closes #310.
2018-09-06 02:11:36 +03:00
Eugene Bujak
c7a5275d42 coredns plugin -- convert logic into switch, logging unexpected non-covered cases 2018-09-06 02:09:57 +03:00
Eugene Bujak
fe397943d6 After filters were redownloaded and deemed to be fresh, tell coredns server to reload 2018-09-06 02:09:05 +03:00
Eugene Bujak
876854d403 coredns plugin -- on server reload, metrics disappeared, therefore they must be registered on each reload instead of once 2018-09-06 02:08:49 +03:00
Eugene Bujak
c143e3d57f coredns plugin -- give feedback how many rules were in rulefile 2018-09-06 02:07:23 +03:00
Eugene Bujak
1102963fa0 dnsfilter -- Update tests to check for expected filter/nofilter reason as well. 2018-09-06 02:06:40 +03:00
Eugene Bujak
f2621c4a9a Remove debug logging during checks if coredns is alive 2018-09-06 02:04:16 +03:00
Eugene Bujak
859f1590dd Be more atomic during writing of files -- this prevents other processes from seeing empty or impartial files 2018-09-06 02:03:03 +03:00
Eugene Bujak
0ce40fd46e Start coredns on launch before we serve HTTP -- this checks if port is available 2018-09-06 02:00:57 +03:00
Eugene Bujak
33fbccf0ba Move starting of coredns server into separate function 2018-09-06 02:00:44 +03:00
Eugene Bujak
e122d9138b sometimes answer can be empty, therefore question could be lost -- pass both to querylog 2018-09-05 21:25:11 +03:00
Eugene Bujak
606bed9d20 Reduce binary size of coredns by 60% by removing orchestration plugins like kubernetes, route53, trace, etcd and federation 2018-09-05 21:23:08 +03:00
Eugene Bujak
3b11648e14 Fix registration of metrics if querylog is enabled 2018-09-05 21:21:46 +03:00
Konstantin 🦄 Zamyakin
e62050fb7e Merge pull request #6 in DNS/adguard-dns from feature/319 to master
* commit 'fa8bc570827cae708266901754712fb0c9a1f1ea':
  add reason status
  fix tooltip styles
  add client column and tooltip to blocked requests
2018-09-05 19:30:33 +03:00
Maxim Topchu
fa8bc57082 add reason status 2018-09-05 18:20:38 +03:00
Ildar Kamalov
0e99a65687 fix tooltip styles 2018-09-04 12:43:13 +03:00
Maxim Topchu
bed92f89f0 add client column and tooltip to blocked requests 2018-09-03 15:55:20 +03:00
Konstantin 🦄 Zamyakin
f12ef5d504 Merge pull request #5 in DNS/adguard-dns from querylog_client to master
* commit '379e14c28b18c0632e42760aca94aa7b72b05885':
  coredns plugin -- forgot to pass client's IP to querylog, fix that.
2018-08-31 21:08:20 +03:00
Eugene Bujak
379e14c28b coredns plugin -- forgot to pass client's IP to querylog, fix that.
Closes #311
2018-08-31 19:59:04 +03:00
Maxim Topchu
2ca1a0e586 fix link and name 2018-08-31 18:48:14 +03:00
Eugene Bujak
30553c6a9a Show 50 top blocked/requestsed/clients instead of 3.
Closes #312
2018-08-31 18:21:07 +03:00
Eugene Bujak
7bf513b638 Readme -- remove yarn from prerequisites since it's no longer needed 2018-08-31 18:13:45 +03:00
Konstantin Zamyakin
c4fefa10b0 Travis -- fix build on OSX, simplify travis and get rid of yarn 2018-08-31 18:11:45 +03:00
Eugene Bujak
3af62e463a travis -- install lts/* version of node via travis configuration instead of brew 2018-08-31 13:30:41 +03:00
Eugene Bujak
ad91ba8f43 travis -- upgrade nodejs on osx 2018-08-31 13:07:37 +03:00
Eugene Bujak
f054dcede4 travis -- build binaries as a test as well 2018-08-30 19:47:29 +03:00
Eugene Bujak
d53f9bafe9 Makefile -- replace sed with perl, add check that plugin.cfg has dnsfilter 2018-08-30 19:05:15 +03:00
Eugene Bujak
0421e1f4f8 Add travis testing 2018-08-30 18:18:18 +03:00
247 changed files with 51143 additions and 14837 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

23
.gitignore vendored
View File

@@ -1,7 +1,20 @@
/AdguardDNS
/AdguardDNS.yaml
.DS_Store
/.vscode
/.idea
/AdGuardHome
/AdGuardHome.exe
/AdGuardHome.yaml
/AdGuardHome.log
/data/
/build/
/dist/
/client/node_modules/
/coredns
/Corefile
/dnsfilter.txt
/querylog.json
/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
}

81
.travis.yml Normal file
View File

@@ -0,0 +1,81 @@
language: go
sudo: false
go:
- 1.12.x
- 1.x
os:
- linux
- osx
before_install:
- nvm install node
- npm install -g npm
install:
- npm --prefix client install
cache:
directories:
- $HOME/.cache/go-build
- $HOME/gopath/pkg/mod
- $HOME/Library/Caches/go-build
script:
- 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.12.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.12.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

684
AGHTechDoc.md Normal file
View File

@@ -0,0 +1,684 @@
# AdGuard Home Technical Document
The document describes technical details and internal algorithms of AdGuard Home.
Contents:
* First startup
* Installation wizard
* "Get install settings" command
* "Check configuration" command
* Disable DNSStubListener
* "Apply configuration" command
* Updating
* Get version command
* Update command
* Device Names and Per-client Settings
* Per-client settings
* Get list of clients
* Add client
* Update client
* Delete client
* Enable DHCP server
* "Show DHCP status" command
* "Check DHCP" command
* "Enable DHCP" command
* Static IP check/set
* Add a static lease
* DNS access settings
* List access settings
* Set access settings
## First startup
The first application startup is detected when there's no .yaml configuration file.
We check if the user is root, otherwise we fail with an error.
Web server is started up on port 3000 and automatically redirects requests to `/` to Installation wizard.
After Installation wizard steps are completed, we write configuration to a file and start normal operation.
## Installation wizard
This is the collection of UI screens that are shown to a user on first application startup.
The screens are:
1. Welcome
2. Set up network interface and listening ports for Web and DNS servers
3. Set up administrator username and password
4. Configuration complete
5. Done
Algorithm:
Screen 2:
* UI asks server for initial information and shows it
* User edits the default settings, clicks on "Next" button
* UI asks server to check new settings
* Server searches for the known issues
* UI shows information about the known issues and the means to fix them
* Server applies automatic fixes of the known issues on command from UI
Screen 3:
* UI asks server to apply the configuration
* Server restarts DNS server
### "Get install settings" command
Request:
GET /control/install/get_addresses
Response:
200 OK
{
"web_port":80,
"dns_port":53,
"interfaces":{
"enp2s0":{"name":"enp2s0","mtu":1500,"hardware_address":"","ip_addresses":["",""],"flags":"up|broadcast|multicast"},
"lo":{"name":"lo","mtu":65536,"hardware_address":"","ip_addresses":["127.0.0.1","::1"],"flags":"up|loopback"},
}
}
If `interfaces.flags` doesn't contain `up` flag, UI must show `(Down)` status next to its IP address in interfaces selector.
### "Check configuration" command
Request:
POST /control/install/check_config
{
"web":{"port":80,"ip":"192.168.11.33"},
"dns":{"port":53,"ip":"127.0.0.1","autofix":false},
}
Server should check whether a port is available only in case it itself isn't already listening on that port.
Server replies on success:
200 OK
{
"web":{"status":""},
"dns":{"status":""},
}
Server replies on error:
200 OK
{
"web":{"status":"ERROR MESSAGE"},
"dns":{"status":"ERROR MESSAGE", "can_autofix": true|false},
}
### Disable DNSStubListener
On Linux, if 53 port is not available, server performs several additional checks to determine if the issue can be fixed automatically.
#### Phase 1
Request:
POST /control/install/check_config
{
"dns":{"port":53,"ip":"127.0.0.1","autofix":false}
}
Check if DNSStubListener is enabled:
systemctl is-enabled systemd-resolved
Check if DNSStubListener is active:
grep -E '#?DNSStubListener=yes' /etc/systemd/resolved.conf
If the issue can be fixed automatically, server replies with `"can_autofix":true`
200 OK
{
"dns":{"status":"ERROR MESSAGE", "can_autofix":true},
}
In this case UI shows "Fix" button next to error message.
#### Phase 2
If user clicks on "Fix" button, UI sends request to perform an automatic fix
POST /control/install/check_config
{
"dns":{"port":53,"ip":"127.0.0.1","autofix":true},
}
Deactivate (save backup as `resolved.conf.orig`) and stop DNSStubListener:
sed -r -i.orig 's/#?DNSStubListener=yes/DNSStubListener=no/g' /etc/systemd/resolved.conf
systemctl reload-or-restart systemd-resolved
Server replies:
200 OK
{
"dns":{"status":""},
}
### "Apply configuration" command
Request:
POST /control/install/configure
{
"web":{"port":80,"ip":"192.168.11.33"},
"dns":{"port":53,"ip":"127.0.0.1"},
"username":"u",
"password":"p",
}
Server checks the parameters once again, restarts DNS server, replies:
200 OK
On error, server responds with code 400 or 500. In this case UI should show error message and reset to the beginning.
400 Bad Request
ERROR MESSAGE
## Updating
Algorithm of an update by command:
* UI requests the latest version information from Server
* Server requests information from Internet; stores the data in cache for several hours; sends data to UI
* If UI sees that a new version is available, it shows notification message and "Update Now" button
* When user clicks on "Update Now" button, UI sends Update command to Server
* UI shows "Please wait, AGH is being updated..." message
* Server performs an update:
* Use working directory from `--work-dir` if necessary
* Download new package for the current OS and CPU
* Unpack the package to a temporary directory `update-vXXX`
* Copy the current configuration file to the directory we unpacked new AGH to
* Check configuration compatibility by executing `./AGH --check-config`. If this command fails, we won't be able to update.
* Create `backup-vXXX` directory and copy the current configuration file there
* Copy supporting files (README, LICENSE, etc.) to backup directory
* Copy supporting files from the update directory to the current directory
* Move the current binary file to backup directory
* Note: if power fails here, AGH won't be able to start at system boot. Administrator has to fix it manually
* Move new binary file to the current directory
* Send response to UI
* Stop all tasks, including DNS server, DHCP server, HTTP server
* If AGH is running as a service, use service control functionality to restart
* If AGH is not running as a service, use the current process arguments to start a new process
* Exit process
* UI resends Get Status command until Server responds to it with the new version. This means that Server is successfully restarted after update.
* UI reloads itself
### Get version command
On receiving this request server downloads version.json data from github and stores it in cache for several hours.
Example of version.json data:
{
"version": "v0.95-hotfix",
"announcement": "AdGuard Home v0.95-hotfix is now available!",
"announcement_url": "",
"download_windows_amd64": "",
"download_windows_386": "",
"download_darwin_amd64": "",
"download_linux_amd64": "",
"download_linux_386": "",
"download_linux_arm": "",
"download_linux_arm64": "",
"download_linux_mips": "",
"download_linux_mipsle": "",
"selfupdate_min_version": "v0.0"
}
Server can only auto-update if the current version is equal or higher than `selfupdate_min_version`.
Request:
POST /control/version.json
{
"recheck_now": true | false // if false, server will check for a new version data only once in several hours
}
Response:
200 OK
{
"new_version": "v0.95",
"announcement": "AdGuard Home v0.95 is now available!",
"announcement_url": "http://...",
"can_autoupdate": true
}
If `can_autoupdate` is true, then the server can automatically upgrade to a new version.
Response with empty body:
200 OK
It means that update check is disabled by user. UI should do nothing.
### Update command
Perform an update procedure to the latest available version
Request:
POST /control/update
Response:
200 OK
Error response:
500
UI shows error message "Auto-update has failed"
## Enable DHCP server
Algorithm:
* UI shows DHCP configuration screen with "Enabled DHCP" button disabled, and "Check DHCP" button enabled
* User clicks on "Check DHCP"; UI sends request to server
* Server may fail to detect whether there is another DHCP server working in the network. In this case UI shows a warning.
* Server may detect that a dynamic IP configuration is used for this interface. In this case UI shows a warning.
* UI enables "Enable DHCP" button
* User clicks on "Enable DHCP"; UI sends request to server
* Server sets a static IP (if necessary), enables DHCP server, sends the status back to UI
* UI shows the status
### "Show DHCP status" command
Request:
GET /control/dhcp/status
Response:
200 OK
{
"config":{
"enabled":false,
"interface_name":"...",
"gateway_ip":"...",
"subnet_mask":"...",
"range_start":"...",
"range_end":"...",
"lease_duration":60,
"icmp_timeout_msec":0
},
"leases":[
{"ip":"...","mac":"...","hostname":"...","expires":"..."}
...
],
"static_leases":[
{"ip":"...","mac":"...","hostname":"..."}
...
]
}
### "Check DHCP" command
Request:
POST /control/dhcp/find_active_dhcp
vboxnet0
Response:
200 OK
{
"other_server": {
"found": "yes|no|error",
"error": "Error message", // set if found=error
},
"static_ip": {
"static": "yes|no|error",
"ip": "<Current dynamic IP address>", // set if static=no
}
}
If `other_server.found` is:
* `no`: everything is fine - there is no other DHCP server
* `yes`: we found another DHCP server. UI shows a warning.
* `error`: we failed to determine whether there's another DHCP server. `other_server.error` contains error details. UI shows a warning.
If `static_ip.static` is:
* `yes`: everything is fine - server uses static IP address.
* `no`: `static_ip.ip` contains the current dynamic IP address which we may set as static. In this case UI shows a warning:
Your system uses dynamic IP address configuration for interface <CURRENT INTERFACE NAME>. In order to use DHCP server a static IP address must be set. Your current IP address is <static_ip.ip>. We will automatically set this IP address as static if you press Enable DHCP button.
* `error`: this means that the server failed to check for a static IP. In this case UI shows a warning:
In order to use DHCP server a static IP address must be set. We failed to determine if this network interface is configured using static IP address. Please set a static IP address manually.
### "Enable DHCP" command
Request:
POST /control/dhcp/set_config
{
"enabled":true,
"interface_name":"vboxnet0",
"gateway_ip":"192.169.56.1",
"subnet_mask":"255.255.255.0",
"range_start":"192.169.56.3",
"range_end":"192.169.56.3",
"lease_duration":60,
"icmp_timeout_msec":0
}
Response:
200 OK
OK
### Static IP check/set
Before enabling DHCP server we have to make sure the network interface we use has a static IP configured.
#### Phase 1
On Debian systems DHCP is configured by `/etc/dhcpcd.conf`.
To detect if a static IP is used currently we search for line
interface eth0
and then look for line
static ip_address=...
If the interface already has a static IP, everything is set up, we don't have to change anything.
To get the current IP address along with netmask we execute
ip -oneline -family inet address show eth0
which will print:
2: eth0 inet 192.168.0.1/24 brd 192.168.0.255 scope global eth0\ valid_lft forever preferred_lft forever
To get the current gateway address:
ip route show dev enp2s0
which will print:
default via 192.168.0.1 proto dhcp metric 100
#### Phase 2
This method only works on Raspbian.
On Ubuntu DHCP for a network interface can't be disabled via `dhcpcd.conf`. This must be configured in `/etc/netplan/01-netcfg.yaml`.
Fedora doesn't use `dhcpcd.conf` configuration at all.
Step 1.
To set a static IP address we add these lines to `dhcpcd.conf`:
interface eth0
static ip_address=192.168.0.1/24
static routers=192.168.0.1
static domain_name_servers=192.168.0.1
* Don't set 'routers' if we couldn't find gateway IP
* Set 'domain_name_servers' equal to our IP
Step 2.
If we would set a different IP address, we'd need to replace the IP address for the current network configuration. But currently this step isn't necessary.
ip addr replace dev eth0 192.168.0.1/24
### Add a static lease
Request:
POST /control/dhcp/add_static_lease
{
"mac":"...",
"ip":"...",
"hostname":"..."
}
Response:
200 OK
### Remove a static lease
Request:
POST /control/dhcp/remove_static_lease
{
"mac":"...",
"ip":"...",
"hostname":"..."
}
Response:
200 OK
## Device Names and Per-client Settings
When a client requests information from DNS server, he's identified by IP address.
Administrator can set a name for a client with a known IP and also override global settings for this client. The name is used to improve readability of DNS logs: client's name is shown in UI next to its IP address. The names are loaded from 3 sources:
* automatically from "/etc/hosts" file. It's a list of `IP<->Name` entries which is loaded once on AGH startup from "/etc/hosts" file.
* automatically using rDNS. It's a list of `IP<->Name` entries which is added in runtime using rDNS mechanism when a client first makes a DNS request.
* manually configured via UI. It's a list of client's names and their settings which is loaded from configuration file and stored on disk.
### Per-client settings
UI provides means to manage the list of known clients (List/Add/Update/Delete) and their settings. These settings are stored in configuration file as an array of objects.
Notes:
* `name`, `ip` and `mac` values are unique.
* `ip` & `mac` values can't be set both at the same time.
* If `mac` is set and DHCP server is enabled, IP is taken from DHCP lease table.
* If `use_global_settings` is true, then DNS responses for this client are processed and filtered using global settings.
* If `use_global_settings` is false, then the client-specific settings are used to override (enable or disable) global settings.
### Get list of clients
Request:
GET /control/clients
Response:
200 OK
{
clients: [
{
name: "client1"
ip: "..."
mac: "..."
use_global_settings: true
filtering_enabled: false
parental_enabled: false
safebrowsing_enabled: false
safesearch_enabled: false
}
]
auto_clients: [
{
name: "host"
ip: "..."
source: "etc/hosts" || "rDNS"
}
]
}
### Add client
Request:
POST /control/clients/add
{
name: "client1"
ip: "..."
mac: "..."
use_global_settings: true
filtering_enabled: false
parental_enabled: false
safebrowsing_enabled: false
safesearch_enabled: false
}
Response:
200 OK
Error response (Client already exists):
400
### Update client
Request:
POST /control/clients/update
{
name: "client1"
data: {
name: "client1"
ip: "..."
mac: "..."
use_global_settings: true
filtering_enabled: false
parental_enabled: false
safebrowsing_enabled: false
safesearch_enabled: false
}
}
Response:
200 OK
Error response (Client not found):
400
### Delete client
Request:
POST /control/clients/delete
{
name: "client1"
}
Response:
200 OK
Error response (Client not found):
400
## DNS access settings
There are low-level settings that can block undesired DNS requests. "Blocking" means not responding to request.
There are 3 types of access settings:
* allowed_clients: Only these clients are allowed to make DNS requests.
* disallowed_clients: These clients are not allowed to make DNS requests.
* blocked_hosts: These hosts are not allowed to be resolved by a DNS request.
### List access settings
Request:
GET /control/access/list
Response:
200 OK
{
allowed_clients: ["127.0.0.1", ...]
disallowed_clients: ["127.0.0.1", ...]
blocked_hosts: ["host.com", ...]
}
### Set access settings
Request:
POST /control/access/set
{
allowed_clients: ["127.0.0.1", ...]
disallowed_clients: ["127.0.0.1", ...]
blocked_hosts: ["host.com", ...]
}
Response:
200 OK

32
Dockerfile Normal file
View File

@@ -0,0 +1,32 @@
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 libcap && \
rm -rf /var/cache/apk/* && \
mkdir -p /opt/adguardhome/conf /opt/adguardhome/work && \
chown -R nobody: /opt/adguardhome
COPY --from=build --chown=nobody:nogroup /src/AdGuardHome/AdGuardHome /opt/adguardhome/AdGuardHome
RUN setcap 'cap_net_bind_service=+eip' /opt/adguardhome/AdGuardHome
EXPOSE 53/tcp 53/udp 67/udp 68/udp 80/tcp 443/tcp 853/tcp 3000/tcp
VOLUME ["/opt/adguardhome/conf", "/opt/adguardhome/work"]
WORKDIR /opt/adguardhome/work
#USER nobody
ENTRYPOINT ["/opt/adguardhome/AdGuardHome"]
CMD ["-c", "/opt/adguardhome/conf/AdGuardHome.yaml", "-w", "/opt/adguardhome/work", "--no-check-update"]

23
Dockerfile.travis Normal file
View File

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

View File

@@ -1,33 +1,35 @@
GIT_VERSION := $(shell git describe --abbrev=4 --dirty --always --tags)
GOPATH := $(shell go env GOPATH)
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)))
STATIC := build/static/bundle.css build/static/bundle.js build/static/index.html
GOPATH := $(shell go env GOPATH)
JSFILES = $(shell find client -path client/node_modules -prune -o -type f -name '*.js')
STATIC = build/static/index.html
CHANNEL ?= release
TARGET=AdGuardHome
.PHONY: all build clean
all: build
build: AdguardDNS coredns
build: $(TARGET)
$(STATIC):
yarn --cwd client install
yarn --cwd client run build-prod
client/node_modules: client/package.json client/package-lock.json
npm --prefix client install
touch client/node_modules
AdguardDNS: $(STATIC) *.go
echo mkfile_dir = $(mkfile_dir)
go get -v -d .
GOOS=$(NATIVE_GOOS) GOARCH=$(NATIVE_GOARCH) go get -v github.com/gobuffalo/packr/...
PATH=$(GOPATH)/bin:$(PATH) packr build -ldflags="-X main.VersionString=$(GIT_VERSION)" -o AdguardDNS
$(STATIC): $(JSFILES) client/node_modules
npm --prefix client run build-prod
coredns: coredns_plugin/*.go dnsfilter/*.go
echo mkfile_dir = $(mkfile_dir)
go get -v -d github.com/coredns/coredns
cd $(GOPATH)/src/github.com/coredns/coredns && grep -q 'dnsfilter:' plugin.cfg || sed -E -i.bak $$'s|^log:log|log:log\\\ndnsfilter:github.com/AdguardTeam/AdguardDNS/coredns_plugin|g' plugin.cfg
cd $(GOPATH)/src/github.com/coredns/coredns && GOOS=$(NATIVE_GOOS) GOARCH=$(NATIVE_GOARCH) go generate
cd $(GOPATH)/src/github.com/coredns/coredns && go get -v -d .
cd $(GOPATH)/src/github.com/coredns/coredns && go build -o $(mkfile_dir)/coredns
$(TARGET): $(STATIC) *.go home/*.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.version=$(GIT_VERSION) -X main.channel=$(CHANNEL)" -asmflags="-trimpath=$(PWD)" -gcflags="-trimpath=$(PWD)"
PATH=$(GOPATH)/bin:$(PATH) packr clean
clean:
rm -vf coredns AdguardDNS
$(MAKE) cleanfast
rm -rf build
rm -rf client/node_modules
cleanfast:
rm -f $(TARGET)

240
README.md
View File

@@ -1,66 +1,234 @@
# Self-hosted AdGuard DNS
&nbsp;
<p align="center">
<img src="https://cdn.adguard.com/public/Adguard/Common/adguard_home.svg" width="300px" alt="AdGuard Home" />
</p>
<h3 align="center">Privacy protection center for you and your devices</h3>
<p align="center">
Free and open source, powerful network-wide ads & trackers blocking DNS server.
</p>
AdGuard DNS is an ad-filtering DNS server with built-in phishing protection and optional family-friendly protection.
<p align="center">
<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://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>
</p>
This repository describes how to set up and run your self-hosted instance of AdGuard DNS -- it comes with a web dashboard that can be accessed from browser to control the DNS server and change its settings, it also allows you to add your filters in both AdGuard and hosts format.
<br />
If this seems too complicated, you can always use AdGuard DNS servers that provide same functionality — https://adguard.com/en/adguard-dns/overview.html
<p align="center">
<img src="https://cdn.adguard.com/public/Adguard/Common/adguard_home.gif" width="800" />
</p>
## Installation
<hr />
Go to https://github.com/AdguardTeam/AdguardDNS/releases and download the binaries for your platform:
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.
### Mac
Download file `AdguardDNS_*_darwin_amd64.tar.gz`, then unpack it and follow [how to run](#How-to-run) instructions below.
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.
### Linux
Download file `AdguardDNS_*_linux_amd64.tar.gz`, then unpack it and follow [how to run](#How-to-run) instructions below.
* [Getting Started](#getting-started)
* [Comparing AdGuard Home to other solutions](#comparison)
* [How is this different from public AdGuard DNS servers?](#comparison-adguard-dns)
* [How does AdGuard Home compare to Pi-Hole](#comparison-pi-hole)
* [How does AdGuard Home compare to traditional ad blockers](#comparison-adblock)
* [How to build from source](#how-to-build)
* [Contributing](#contributing)
* [Test unstable versions](#test-unstable-versions)
* [Reporting issues](#reporting-issues)
* [Help with translations](#translate)
* [Acknowledgments](#acknowledgments)
## How to build your own
<a id="getting-started"></a>
## Getting Started
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.
Alternatively, you can use our [official Docker image](https://hub.docker.com/r/adguard/adguardhome).
### Guides
* [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)
### API
If you want to integrate with AdGuard Home, you can use our [REST API](https://github.com/AdguardTeam/AdGuardHome/tree/master/openapi).
Alternatively, you can use this [python client](https://pypi.org/project/adguardhome/), which is used to build the [AdGuard Home Hass.io Add-on](https://community.home-assistant.io/t/community-hass-io-add-on-adguard-home).
<a id="comparison"></a>
## Comparing AdGuard Home to other solutions
<a id="comparison-adguard-dns"></a>
### How is this different from public AdGuard DNS servers?
Running your own AdGuard Home server allows you to do much more than using a public DNS server. It's a completely different level. See for yourself:
* Choose what exactly will the server block or not block.
* Monitor your network activity.
* Add your own custom filtering rules.
* **Most importantly, this is your own server, and you are the only one who's in control.**
<a id="comparison-pi-hole"></a>
### How does AdGuard Home compare to Pi-Hole
At this point, AdGuard Home has a lot in common with Pi-Hole. Both block ads and trackers using "DNS sinkholing" method, and both allow customizing what's blocked.
> We're not going to stop here. DNS sinkholing is not a bad starting point, but this is just the beginning.
AdGuard Home provides a lot of features out-of-the-box with no need to install and configure additional software. We want it to be simple to the point when even casual users can set it up with minimal effort.
> Disclaimer: some of the listed features can be added to Pi-Hole by installing additional software or by manually using SSH terminal and reconfiguring one of the utilities Pi-Hole consists of. However, in our opinion, this cannot be legitimately counted as a Pi-Hole's feature.
| Feature | AdGuard&nbsp;Home | Pi-Hole |
|-------------------------------------------------------------------------|--------------|--------------------------------------------------------|
| Blocking ads and trackers | ✅ | ✅ |
| Customizing blocklists | ✅ | ✅ |
| Built-in DHCP server | ✅ | ✅ |
| HTTPS for the Admin interface | ✅ | Kind of, but you'll need to manually configure lighthttp |
| Encrypted DNS upstream servers (DNS-over-HTTPS, DNS-over-TLS, DNSCrypt) | ✅ | ❌ (requires additional software) |
| Cross-platform | ✅ | ❌ (not natively, only via Docker) |
| Running as a DNS-over-HTTPS or DNS-over-TLS server | ✅ | ❌ (requires additional software) |
| Blocking phishing and malware domains | ✅ | ❌ |
| Parental control (blocking adult domains) | ✅ | ❌ |
| Force Safe search on search engines | ✅ | ❌ |
| Per-client (device) configuration | ✅ | ❌ |
| Access settings (choose who can use AGH DNS) | ✅ | ❌ |
<a id="comparison-adblock"></a>
### How does AdGuard Home compare to traditional ad blockers
It depends.
"DNS sinkholing" is capable of blocking a big percentage of ads, but it lacks flexibility and power of traditional ad blockers. You can get a good impression about the difference between these methods by reading [this article](https://adguard.com/en/blog/adguard-vs-adaway-dns66/). It compares AdGuard for Android (a traditional ad blocker) to hosts-level ad blockers (which are almost identical to DNS-based blockers in their capabilities). However, this level of protection is enough for some users.
<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/)
* [yarn](https://yarnpkg.com/en/docs/install)
You can either install it from these websites or use [brew.sh](https://brew.sh/) if you're on Mac:
* [go](https://golang.org/dl/) v1.12 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:
```bash
brew install go node yarn
brew install go node
```
### Building
Open Terminal and execute these commands:
```bash
git clone https://github.com/AdguardTeam/AdguardDNS
cd AdguardDNS
git clone https://github.com/AdguardTeam/AdGuardHome
cd AdGuardHome
make
```
## How to run
DNS works on port 53, which requires superuser privileges. Therefore, you need to run it with sudo:
```bash
sudo ./AdguardDNS
#### (For devs) Upload translations
```
node upload.js
```
Now open the browser and point it to http://localhost:3000/ to control AdGuard DNS server.
## Running without superuser
You can run it without superuser privileges, but you need to instruct it to use other port rather than 53. You can do that by opening `AdguardDNS.yaml` and adding this line:
```yaml
coredns:
port: 53535
#### (For devs) Download translations
```
node download.js
```
If the file does not exist, create it and put these two lines down.
<a id="contributing"></a>
## Contributing
You are welcome to fork this repository, make your changes and submit a pull request — https://github.com/AdguardTeam/AdguardDNS/pulls
You are welcome to fork this repository, make your changes and submit a pull request — https://github.com/AdguardTeam/AdGuardHome/pulls
## Reporting issues
<a id="test-unstable-versions"></a>
### Test unstable versions
If you come across any problem, or have a suggestion, head to [this page](https://github.com/AdguardTeam/AdguardDNS/issues) and click on the New issue button.
There are two options how you can install an unstable version.
You can either install a beta version of AdGuard Home which we update periodically,
or you can use the Docker image from the `edge` tag, which is synced with the repo master branch.
* [Docker Hub](https://hub.docker.com/r/adguard/adguardhome)
* Beta builds
* [Rapsberry Pi (32-bit ARM)](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_arm.tar.gz)
* [MacOS](https://static.adguard.com/adguardhome/beta/AdGuardHome_MacOS.zip)
* [Windows 64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_Windows_amd64.zip)
* [Windows 32-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_Windows_386.zip)
* [Linux 64-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_amd64.tar.gz)
* [Linux 32-bit](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_386.tar.gz)
* [64-bit ARM](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_arm64.tar.gz)
* [MIPS](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mips.tar.gz)
* [MIPSLE](https://static.adguard.com/adguardhome/beta/AdGuardHome_linux_mipsle.tar.gz)
<a id="reporting-issues"></a>
### Report 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="translate"></a>
### Help with 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>
}
```
<a id="acknowledgments"></a>
## Acknowledgments
This software wouldn't have been possible without:
* [Go](https://golang.org/dl/) and it's libraries:
* [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)
* [urlfilter](https://github.com/AdguardTeam/urlfilter)
* [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.

128
app.go
View File

@@ -1,128 +0,0 @@
package main
import (
"fmt"
"log"
"net"
"net/http"
"os"
"path/filepath"
"strconv"
"github.com/gobuffalo/packr"
)
// VersionString will be set through ldflags, contains current version
var VersionString = "undefined"
func main() {
log.Printf("AdGuard DNS 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)
}
// 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.ourConfigFilename = *configFilename
}
// parse from config file
err := parseConfig()
if err != nil {
log.Fatal(err)
}
if bindHost != nil {
config.BindHost = *bindHost
}
if bindPort != nil {
config.BindPort = *bindPort
}
}
err := writeConfig()
if err != nil {
log.Fatal(err)
}
address := net.JoinHostPort(config.BindHost, strconv.Itoa(config.BindPort))
runStatsCollectors()
runFilterRefreshers()
http.Handle("/", http.FileServer(box))
registerControlHandlers()
URL := fmt.Sprintf("http://%s", address)
log.Println("Go to " + URL)
log.Fatal(http.ListenAndServe(address, 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

13
client/.eslintrc vendored
View File

@@ -13,6 +13,13 @@
"commonjs": true
},
"settings": {
"react": {
"pragma": "React",
"version": "16.4"
}
},
"rules": {
"indent": ["error", 4, {
"SwitchCase": 1,
@@ -38,11 +45,9 @@
}],
"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",
"import/prefer-default-export": "off"
}
}

13647
client/package-lock.json generated vendored Normal file

File diff suppressed because it is too large Load Diff

31
client/package.json vendored
View File

@@ -3,46 +3,52 @@
"version": "0.1.0",
"private": true,
"scripts": {
"build-dev": "NODE_ENV=development webpack --config webpack.dev.js",
"watch": "NODE_ENV=development webpack --config webpack.dev.js --watch",
"build-prod": "NODE_ENV=production webpack --config webpack.prod.js",
"build-dev": "NODE_ENV=development ./node_modules/.bin/webpack --config webpack.dev.js",
"watch": "NODE_ENV=development ./node_modules/.bin/webpack --config webpack.dev.js --watch",
"build-prod": "NODE_ENV=production ./node_modules/.bin/webpack --config webpack.prod.js",
"lint": "eslint frontend/"
},
"dependencies": {
"@nivo/line": "^0.42.1",
"@nivo/line": "^0.49.1",
"axios": "^0.18.0",
"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",
"react-router-dom": "^4.2.2",
"react-table": "^6.8.6",
"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",
"tabler-react": "^1.10.0",
"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",
"babel-preset-react": "^6.24.1",
"babel-preset-stage-2": "^6.24.1",
"babel-runtime": "6.26.0",
"clean-webpack-plugin": "^0.1.19",
"compression-webpack-plugin": "^1.1.11",
"css-loader": "^0.28.11",
"copy-webpack-plugin": "^4.6.0",
"css-loader": "^2.1.1",
"eslint": "^4.19.1",
"eslint-config-airbnb-base": "^12.1.0",
"eslint-config-react-app": "^2.1.0",
@@ -53,7 +59,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",
@@ -61,12 +66,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"
}
}

BIN
client/public/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -1,16 +1,17 @@
<!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 DNS</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">
<meta name="google" content="notranslate">
<link rel="icon" type="image/png" href="favicon.png" sizes="48x48">
<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,17 @@
<!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">
<meta name="google" content="notranslate">
<link rel="icon" type="image/png" href="favicon.png" sizes="48x48">
<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": "\u0423\u0441\u043f\u0435\u0448\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0435\u043d URL",
"check_dhcp_servers": "\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0437\u0430 \u0430\u043a\u0442\u0438\u0432\u0435\u043d DHCP \u0441\u044a\u0440\u0432\u044a\u0440",
"save_config": "\u0417\u0430\u043f\u0438\u0448\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u0442\u0435",
"enabled_dhcp": "DHCP \u0435 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d",
"disabled_dhcp": "DHCP \u0435 \u0437\u0430\u0431\u0440\u0430\u043d\u0435\u043d",
"dhcp_title": "DHCP \u0441\u044a\u0440\u0432\u044a\u0440 (\u0442\u0435\u0441\u0442\u043e\u0432\u0438!)",
"dhcp_description": "\u0410\u043a\u043e \u0440\u0443\u0442\u0435\u0440\u0430 \u0432\u0438 \u043d\u0435 \u0440\u0430\u0437\u0434\u0430\u0432\u0430 DHCP \u0430\u0434\u0440\u0435\u0441\u0438, \u043c\u043e\u0436\u0435 \u0434\u0430 \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u0442\u0435 \u0432\u0433\u0440\u0430\u0434\u0435\u043d\u0438\u044f \u0432 AdGuard DHCP \u0441\u044a\u0440\u0432\u044a\u0440.",
"dhcp_enable": "\u0420\u0437\u0440\u0435\u0448\u0438 DHCP \u0441\u044a\u0440\u0432\u044a\u0440\u0430",
"dhcp_disable": "\u0417\u0430\u0431\u0440\u0430\u043d\u0438 DHCP \u0441\u044a\u0440\u0432\u044a\u0440\u0430",
"dhcp_not_found": "\u0412\u0430\u0448\u0430\u0442\u0430 \u043c\u0440\u0435\u0436\u0430 \u043d\u044f\u043c\u0430 \u0430\u043a\u0442\u0438\u0432\u0435\u043d DHCP \u0441\u044a\u0440\u0432\u044a\u0440. \u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e \u0435 \u043f\u043e\u043b\u0437\u0432\u0430\u043d\u0435\u0442\u043e \u043d\u0430 \u0432\u0433\u0440\u0430\u0434\u0435\u043d\u0438\u044f DHCP \u0441\u044a\u0440\u0432\u044a\u0440.",
"dhcp_found": "\u0412\u0430\u0448\u0430\u0442\u0430 \u043c\u0440\u0435\u0436\u0430 \u0432\u0435\u0447\u0435 \u0438\u043c\u0430 \u0430\u043a\u0442\u0438\u0432\u0435\u043d DHCP \u0441\u044a\u0440\u0432\u044a\u0440. \u041d\u0435 \u0435 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e \u043f\u043e\u043b\u0437\u0432\u0430\u043d\u0435\u0442\u043e \u043d\u0430 \u0432\u0442\u043e\u0440\u0438!",
"dhcp_leases": "DHCP \u0440\u0430\u0437\u0434\u0430\u0434\u0435\u043d\u0438 \u0430\u0434\u0440\u0435\u0441\u0438",
"dhcp_leases_not_found": "\u041d\u044f\u043c\u0430 \u043d\u0430\u043c\u0435\u0440\u0435\u043d\u0438 \u0430\u043a\u0442\u0438\u0432\u043d\u0438 DHCP \u0430\u0434\u0440\u0435\u0441\u0438",
"dhcp_config_saved": "\u0417\u0430\u043f\u0438\u0448\u0438 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f\u0442\u0430 \u043d\u0430 DHCP \u0441\u044a\u0440\u0432\u044a\u0440\u0430",
"form_error_required": "\u0417\u0430\u0434\u044a\u043b\u0436\u0438\u0442\u0435\u043b\u043d\u043e \u043f\u043e\u043b\u0435",
"form_error_ip_format": "\u041d\u0435\u0432\u0430\u043b\u0438\u0434\u0435\u043d IPv4 \u0430\u0434\u0440\u0435\u0441",
"form_error_positive": "\u041f\u0440\u043e\u0432\u0435\u0440\u0435\u0442\u0435 \u0434\u0430\u043b\u0438 \u0435 \u043f\u043e\u043b\u043e\u0436\u0438\u0442\u0435\u043b\u043d\u043e \u0447\u0438\u0441\u043b\u043e",
"dhcp_form_gateway_input": "IP \u0448\u043b\u044e\u0437",
"dhcp_form_subnet_input": "\u041c\u0440\u0435\u0436\u043e\u0432\u0430 \u043c\u0430\u0441\u043a\u0430",
"dhcp_form_range_title": "\u0413\u0440\u0443\u043f\u0430 \u043e\u0442 IP \u0430\u0434\u0440\u0435\u0441\u0438",
"dhcp_form_range_start": "\u041f\u044a\u0440\u0432\u0438 \u0430\u0434\u0440\u0435\u0441",
"dhcp_form_range_end": "\u041f\u043e\u0441\u043b\u0435\u0434\u0435\u043d \u0430\u0434\u0440\u0435\u0441",
"dhcp_form_lease_title": "\u041e\u0442\u0434\u0430\u0434\u0435\u043d\u0438 \u0430\u0434\u0440\u0435\u0441\u0438 (\u0441\u0435\u043a\u0443\u043d\u0434\u0438)",
"dhcp_form_lease_input": "\u041e\u0442\u0447\u0435\u0442 \u0437\u0430 \u0440\u0430\u0437\u0434\u0430\u0434\u0435\u043d\u0438 \u0430\u0434\u0440\u0435\u0441\u0438",
"dhcp_interface_select": "\u0418\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u043c\u0440\u0435\u0436\u043e\u0432 \u0430\u0434\u0430\u043f\u0442\u0435\u0440 \u0437\u0430 DHCP",
"dhcp_hardware_address": "\u0425\u0430\u0440\u0434\u0443\u0435\u0440\u043d\u0438 \u0430\u0434\u0440\u0435\u0441\u0438 (MAC)",
"dhcp_ip_addresses": "IP \u0430\u0434\u0440\u0435\u0441\u0438",
"dhcp_table_hostname": "\u0418\u043c\u0435 \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e",
"dhcp_table_expires": "\u0418\u0441\u0442\u043e\u0440\u0438\u044f",
"dhcp_warning": "\u0410\u043a\u043e \u0438\u0441\u043a\u0430\u0442\u0435 \u0434\u0430 \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u0442\u0435 \u0432\u0433\u0440\u0430\u0434\u0435\u043d\u0438\u044f DHCP \u0441\u044a\u0440\u0432\u044a\u0440, \u0442\u0440\u044f\u0431\u0432\u0430 \u0434\u0430 \u043d\u044f\u043c\u0430 \u0434\u0440\u0443\u0433 \u0430\u043a\u0442\u0438\u0432\u0435\u043d DHCP \u0432 \u043c\u0440\u0435\u0436\u0430\u0442\u0430 \u0412\u0438!",
"back": "\u041d\u0430\u0437\u0430\u0434",
"dashboard": "\u0422\u0430\u0431\u043b\u043e",
"settings": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438",
"filters": "\u0424\u0438\u043b\u0442\u0440\u0438",
"query_log": "\u0418\u0441\u0442\u043e\u0440\u0438\u044f \u043d\u0430 \u0437\u0430\u044f\u0432\u043a\u0438\u0442\u0435",
"faq": "\u0427\u0417\u0412",
"version": "\u0432\u0435\u0440\u0441\u0438\u044f",
"address": "\u0430\u0434\u0440\u0435\u0441",
"on": "\u0412\u041a\u041b\u042e\u0427\u0415\u041d\u041e",
"off": "\u0418\u0417\u041a\u041b\u042e\u0427\u0415\u041d\u041e",
"copyright": "\u0410\u0432\u0442\u043e\u0440\u0441\u043a\u043e \u043f\u0440\u0430\u0432\u043e",
"homepage": "\u0414\u043e\u043c\u0430\u0448\u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430",
"report_an_issue": "\u0421\u044a\u043e\u0431\u0449\u0438 \u0437\u0430 \u043f\u0440\u043e\u0431\u043b\u0435\u043c",
"enable_protection": "\u0420\u0430\u0437\u0440\u0435\u0448\u0438 \u0437\u0430\u0449\u0438\u0442\u0430",
"enabled_protection": "\u0417\u0430\u0449\u0438\u0442\u0430\u0442\u0430 \u0435 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0430",
"disable_protection": "\u0417\u0430\u0431\u0440\u0430\u043d\u0438 \u0437\u0430\u0449\u0438\u0442\u0430",
"disabled_protection": "\u0417\u0430\u0449\u0438\u0442\u0430\u0442\u0430 \u0435 \u0437\u0430\u0431\u0440\u0430\u043d\u0435\u043d\u0430",
"refresh_statics": "\u041e\u0431\u043d\u043e\u0432\u0438 \u0441\u0442\u0430\u0442\u0438\u0441\u0442\u0438\u043a\u0430\u0442\u0430",
"dns_query": "DNS \u0437\u0430\u043f\u0438\u0442\u0432\u0430\u043d\u0438\u044f",
"blocked_by": "\u0411\u043b\u043e\u043a\u0438\u0440\u0430\u043d\u0438 \u043e\u0442",
"stats_malware_phishing": "\u0432\u0438\u0440\u0443\u0441\u0438\/\u0430\u0442\u0430\u043a\u0438",
"stats_adult": "\u0441\u0430\u0439\u0442\u043e\u0432\u0435 \u0437\u0430 \u0432\u044a\u0437\u0440\u0430\u0441\u0442\u043d\u0438",
"stats_query_domain": "\u041d\u0430\u0439-\u043e\u0442\u0432\u0430\u0440\u044f\u043d\u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0438",
"for_last_24_hours": "\u0437\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0442\u0435 24 \u0447\u0430\u0441\u0430",
"no_domains_found": "\u041d\u044f\u043c\u0430 \u043d\u0430\u043c\u0435\u0440\u0435\u043d\u0438 \u0440\u0435\u0437\u0443\u043b\u0442\u0430\u0442\u0438",
"requests_count": "\u0421\u0443\u043c\u0430 \u043d\u0430 \u0437\u0430\u044f\u0432\u043a\u0438\u0442\u0435",
"top_blocked_domains": "\u041d\u0430\u0439-\u0431\u043b\u043e\u043a\u0438\u0440\u0430\u043d\u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0438",
"top_clients": "\u041d\u0430\u0439-\u0430\u043a\u0442\u0438\u0432\u043d\u0438 IP \u0430\u0434\u0440\u0435\u0441\u0438",
"no_clients_found": "\u041d\u044f\u043ca \u043d\u0430\u043c\u0435\u0440\u0435\u043d\u0438 \u0430\u0434\u0440\u0435\u0441\u0438",
"general_statistics": "\u041e\u0431\u0449\u0430 \u0441\u0442\u0430\u0442\u0438\u0441\u0438\u043a\u0430",
"number_of_dns_query_24_hours": "\u0421\u0443\u043c\u0430 \u043d\u0430 DNS \u0437\u0430\u044f\u0432\u043a\u0438 \u0437\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0442\u0435 24 \u0447\u0430\u0441\u0430",
"number_of_dns_query_blocked_24_hours": "\u0421\u0443\u043c\u0430 \u043d\u0430 \u0431\u043b\u043e\u043a\u0438\u0440\u0430\u043d\u0438 DNS \u0437\u0430\u044f\u0432\u043a\u0438 \u043e\u0442 \u0444\u0438\u043b\u0442\u0440\u0438\u0442\u0435 \u0437\u0430 \u0440\u0435\u043a\u043b\u0430\u043c\u0430 \u0438 \u043c\u0435\u0441\u0442\u043d\u0438",
"number_of_dns_query_blocked_24_hours_by_sec": "\u0421\u0443\u043c\u0430 \u043d\u0430 \u0431\u043b\u043e\u043a\u0438\u0440\u0430\u043d\u0438 DNS \u0437\u0430\u044f\u0432\u043a\u0438 \u043e\u0442 AdGuard \u0441\u0432\u044a\u0440\u0437\u0430\u043d\u0438 \u0441\u044a\u0441 \u0441\u0438\u0433\u0443\u0440\u043d\u043e\u0441\u0442\u0442\u0430",
"number_of_dns_query_blocked_24_hours_adult": "\u0421\u0443\u043c\u0430 \u043d\u0430 \u0431\u043b\u043e\u043a\u0438\u0440\u0430\u043d\u0438 \u0441\u0430\u0439\u0442\u043e\u0432\u0435 \u0437\u0430 \u0432\u044a\u0437\u0440\u0430\u0441\u0442\u043d\u0438",
"enforced_save_search": "\u0410\u043a\u0442\u0438\u0432\u0438\u0440\u0430\u043d\u043e \u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e \u0422\u044a\u0440\u0441\u0435\u043d\u0435",
"number_of_dns_query_to_safe_search": "\u0421\u0443\u043c\u0430 \u043d\u0430 DNS \u0437\u0430\u044f\u0432\u043a\u0438 \u043f\u0440\u0438 \u043a\u043e\u0439\u0442\u043e \u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u043e \u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e \u0422\u044a\u0440\u0441\u0435\u043d\u0435",
"average_processing_time": "\u0421\u0440\u0435\u0434\u043d\u043e \u0432\u0440\u0435\u043c\u0435 \u0437\u0430 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430",
"average_processing_time_hint": "\u0421\u0440\u0435\u0434\u043d\u043e \u0432\u0440\u0435\u043c\u0435 \u0437\u0430 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u043d\u0430 DNS \u0437\u0430\u044f\u0432\u043a\u0438 \u0432 \u043c\u0438\u043b\u0438\u0441\u0435\u043a\u0443\u043d\u0434\u0438",
"block_domain_use_filters_and_hosts": "\u0411\u043b\u043e\u043a\u0438\u0440\u0430\u043d\u0438 \u0434\u043e\u043c\u0435\u0439\u043d\u0438 - \u043e\u0431\u0449\u0438 \u0438 \u043c\u0435\u0441\u0442\u043d\u0438 \u0444\u0438\u043b\u0442\u0440\u0438",
"filters_block_toggle_hint": "\u041c\u043e\u0436\u0435 \u0434\u0430 \u0437\u0430\u0434\u0430\u0434\u0435\u0442\u0435 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0432 <a href='#filters'>\u0424\u0438\u043b\u0442\u0440\u0438<\/a>.",
"use_adguard_browsing_sec": "\u0418\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u0439\u0442\u0435 AdGuard \u043c\u043e\u0434\u0443\u043b \u0437\u0430 \u0441\u0438\u0433\u0443\u0440\u043d\u043e\u0441\u0442\u0442\u0430",
"use_adguard_browsing_sec_hint": "\u041c\u043e\u0434\u0443\u043b \u0421\u0438\u0433\u0443\u0440\u043d\u043e\u0441\u0442 \u0432 AdGuard Home \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0432\u0430 \u0432\u0441\u044f\u043a\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u043a\u043e\u044f\u0442\u043e \u043e\u0442\u0432\u0430\u0440\u044f\u0442\u0435 \u0434\u0430\u043b\u0438 \u0435 \u0432 \u0447\u0435\u0440\u043d\u0438\u0442\u0435 \u0441\u043f\u0438\u0441\u044a\u0446\u0438 \u0437\u0430\u0441\u0442\u0440\u0430\u0448\u0430\u0432\u0430\u0449\u0438 \u0432\u0430\u0448\u0430\u0442\u0430 \u0441\u0438\u0433\u0443\u0440\u043d\u043e\u0441\u0442. \u0418\u0437\u043f\u043e\u043b\u0437\u0432\u0430 \u0441\u0435 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u0435\u043d \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u043a\u043e\u0439\u0442\u043e \u0437\u0430\u0449\u0438\u0442\u0430\u0432\u0430 \u0432\u0430\u0448\u0430\u0442\u0430 \u0430\u043d\u043e\u043d\u0438\u043c\u043d\u043e\u0441\u0442 \u0438 \u0438\u0437\u043f\u0440\u0430\u0449\u0430 \u0441\u0430\u043c\u043e SHA256 \u0441\u0443\u043c\u0430 \u0431\u0430\u0437\u0438\u0440\u0430\u043d\u0430 \u043d\u0430 \u0447\u0430\u0441\u0442 \u043e\u0442 \u0434\u043e\u043c\u0435\u0439\u043d\u0430 \u043a\u043e\u0439\u0442\u043e \u043f\u043e\u0441\u0435\u0449\u0430\u0432\u0430\u0442\u0435.",
"use_adguard_parental": "\u0412\u043a\u043b\u044e\u0447\u0438 AdGuard \u0420\u043e\u0434\u0438\u0442\u0435\u043b\u0441\u043a\u0438 \u041d\u0430\u0434\u0437\u043e\u0440",
"use_adguard_parental_hint": "\u041c\u043e\u0434\u0443\u043b XXX \u0432 AdGuard Home \u0449\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u0438 \u0434\u0430\u043b\u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0442\u0430 \u0438\u043c\u0430 \u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b\u0438 \u0437\u0430 \u0432\u044a\u0437\u0432\u044a\u0441\u0442\u043d\u0438. \u0418\u0437\u043f\u043e\u043b\u0437\u0432\u0430 \u0441\u0435 \u0441\u044a\u0449\u0438\u044f API \u0437\u0430 \u0430\u043d\u043e\u043d\u0438\u043c\u043d\u043e\u0441\u0442 \u043a\u0430\u0442\u043e \u043f\u0440\u0438 \u043c\u043e\u0434\u0443\u043b\u0430 \u0437\u0430 \u0421\u0438\u0433\u0443\u0440\u043d\u043e\u0441\u0442.",
"enforce_safe_search": "\u0412\u043a\u043b\u044e\u0447\u0438 \u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e \u0422\u044a\u0440\u0441\u0435\u043d\u0435",
"enforce_save_search_hint": "AdGuard Home \u043f\u0440\u0438\u043b\u0430\u0433\u0430 \u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e \u0422\u044a\u0440\u0441\u0435\u043d\u0435 \u0432 \u0441\u043b\u0435\u0434\u043d\u0438\u0442\u0435 \u0442\u044a\u0440\u0441\u0430\u0447\u043a\u0438 \u0438 \u0441\u0430\u0439\u0442\u043e\u0432\u0435: Google, Youtube, Bing, \u0438 Yandex.",
"no_servers_specified": "\u041d\u044f\u043c\u0430 \u0438\u0437\u0431\u0440\u0430\u043d\u0438 \u0443\u0441\u043b\u0443\u0433\u0438",
"no_settings": "\u041d\u044f\u043c\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438",
"general_settings": "\u041e\u0431\u0449\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438",
"upstream_dns": "\u0413\u043b\u0430\u0432\u0435\u043d DNS \u0441\u044a\u0440\u0432\u044a\u0440",
"upstream_dns_hint": "\u0410\u043a\u043e \u043e\u0441\u0442\u0430\u0432\u0438\u0442\u0435 \u043f\u0440\u0430\u0437\u043d\u043e, AdGuard Home \u0449\u0435 \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430 <a href='https:\/\/1.1.1.1\/' target='_blank'>Cloudflare DNS<\/a> \u0437\u0430 \u0433\u043b\u0430\u0432\u0435\u043d. \u0418\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u0439 tls:\/\/ \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043a\u0430 \u0437\u0430 DNS \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u0449\u0438 TLS \u0432\u0440\u044a\u0437\u043a\u0430.",
"test_upstream_btn": "\u0422\u0435\u0441\u0442\u0432\u0430\u0439 \u0433\u043b\u0430\u0432\u043d\u0438\u044f DNS",
"apply_btn": "\u041f\u0440\u0438\u043b\u043e\u0436\u0438",
"disabled_filtering_toast": "\u0417\u0430\u0431\u0440\u0430\u043d\u0438 \u0444\u0438\u043b\u0442\u0440\u0438\u0440\u0430\u043d\u0435\u0442\u043e",
"enabled_filtering_toast": "\u0420\u0430\u0437\u0440\u0435\u0448\u0438 \u0444\u0438\u0442\u0440\u0438\u0440\u0430\u043d\u0435\u0442\u043e",
"disabled_safe_browsing_toast": "\u0417\u0430\u0431\u0440\u0430\u043d\u0438 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e-\u0441\u044a\u0440\u0444\u0438\u0440\u0430\u043d\u0435",
"enabled_safe_browsing_toast": "\u0420\u0437\u0440\u0435\u0448\u0438 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e-\u0441\u044a\u0440\u0444\u0438\u0440\u0430\u043d\u0435",
"disabled_parental_toast": "\u0417\u0430\u0431\u0440\u0430\u043d\u0438 \u0420\u043e\u0434\u0438\u0442\u0435\u043b\u0441\u043a\u0438 \u041d\u0430\u0434\u0437\u043e\u0440",
"enabled_parental_toast": "\u0420\u0430\u0437\u0440\u0435\u0448\u0438 \u0420\u043e\u0434\u0438\u0442\u0435\u043b\u0441\u043a\u0438 \u041d\u0430\u0434\u0437\u043e\u0440",
"disabled_safe_search_toast": "\u0417\u0430\u0431\u0440\u0430\u043d\u0438 \u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e \u0422\u044a\u0440\u0441\u0435\u043d\u0435",
"enabled_save_search_toast": "\u0420\u0430\u0437\u0440\u0435\u0448\u0438 \u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e \u0422\u044a\u0440\u0441\u0435\u043d\u0435",
"enabled_table_header": "\u0420\u0430\u0437\u0440\u0435\u0448\u0438",
"name_table_header": "\u0418\u043c\u0435",
"filter_url_table_header": "URL \u0444\u0438\u043b\u0442\u044a\u0440",
"rules_count_table_header": "\u041f\u0440\u0430\u0432\u0438\u043b\u0430 \u043e\u0431\u0449\u043e",
"last_time_updated_table_header": "\u041f\u043e\u0441\u043b\u0435\u0434\u043d\u043e \u043e\u0431\u043d\u043e\u0432\u0435\u043d",
"actions_table_header": "\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u044f",
"delete_table_action": "\u0418\u0437\u0442\u0440\u0438\u0439",
"filters_and_hosts": "\u0427\u0435\u0440\u043d\u0438 \u0441\u043f\u0438\u0441\u044a\u0446\u0438 \u0441 \u043e\u0431\u0449\u0438 \u0438 \u043c\u0435\u0441\u0442\u043d\u0438 \u0444\u0438\u043b\u0442\u0440\u0438",
"filters_and_hosts_hint": "AdGuard Home \u0440\u0430\u0437\u0431\u0438\u0440\u0430 adblock \u0438 host \u0441\u0438\u043d\u0442\u0430\u043a\u0441\u0438\u0441.",
"no_filters_added": "\u041d\u044f\u043c\u0430 \u0434\u043e\u0431\u0430\u0432\u0435\u043d\u0438 \u0444\u0438\u043b\u0442\u0440\u0438",
"add_filter_btn": "\u0414\u043e\u0431\u0430\u0432\u0438 \u0444\u0438\u043b\u0442\u044a\u0440",
"cancel_btn": "\u041e\u0442\u043a\u0430\u0436\u0438",
"enter_name_hint": "\u0412\u044a\u0432\u0435\u0434\u0438 \u0438\u043c\u0435",
"enter_url_hint": "\u0412\u044a\u0432\u0435\u0434\u0438 URL",
"check_updates_btn": "\u041f\u0440\u043e\u0432\u0435\u0440\u0438 \u0437\u0430 \u0430\u043a\u0442\u0443\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f",
"new_filter_btn": "\u0412\u044a\u0432\u0435\u0434\u0438 \u043d\u043e\u0432 \u0444\u0438\u043b\u0442\u044a\u0440",
"enter_valid_filter_url": "\u041c\u043e\u043b\u044f \u0432\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u0432\u0430\u043b\u0438\u0434\u0435\u043d URL \u0437\u0430 \u0444\u0438\u043b\u0442\u044a\u0440\u0430 \u0438\u043b\u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u0442\u0435 host \u043f\u0440\u0430\u0432\u043e\u043f\u0438\u0441\u0430.",
"custom_filter_rules": "\u041c\u0435\u0441\u0442\u043d\u0438 \u043f\u0440\u0430\u0432\u0438\u043b\u0430 \u0437\u0430 \u0444\u0438\u043b\u0442\u0440\u0438\u0440\u0430\u043d\u0435",
"custom_filter_rules_hint": "\u0412\u044a\u0432\u0435\u0436\u0434\u0430\u0439\u0442\u0435 \u0432\u0441\u044f\u043a\u043e \u043f\u0440\u0430\u0432\u0438\u043b\u043e \u043d\u0430 \u043d\u043e\u0432 \u0440\u0435\u0434. \u041c\u043e\u0436\u0435 \u0434\u0430 \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u0442\u0435 adblock \u0438\u043b\u0438 hosts \u0444\u0430\u0439\u043b\u043e\u0432 \u0441\u0438\u043d\u0442\u0430\u043a\u0441\u0438\u0441.",
"examples_title": "\u041f\u0440\u0438\u043c\u0435\u0440\u0438",
"example_meaning_filter_block": "\u0411\u043b\u043e\u043a\u0438\u0440\u0430\u0439 \u0434\u043e\u0441\u0442\u044a\u043f \u0434\u043e \u0434\u043e\u043c\u0435\u0439\u043d example.org \u0438 \u0432\u0441\u0438\u0447\u043a\u0438 \u043f\u043e\u0434 \u0434\u043e\u043c\u0435\u0439\u043d\u0438.",
"example_meaning_filter_whitelist": "\u0420\u0430\u0437\u0440\u0435\u0448\u0438 \u0434\u043e\u0441\u0442\u044a\u043f \u0434\u043e \u0434\u043e\u043c\u0435\u0439\u043d example.org \u0438 \u0432\u0441\u0438\u0447\u043a\u0438\u0442\u0435 \u043c\u0443 \u043f\u043e\u0434 \u0434\u043e\u043c\u0435\u0439\u043d\u0438.",
"example_meaning_host_block": "AdGuard Home \u0449\u0435 \u043e\u0442\u0433\u043e\u0432\u043e\u0440\u0438 \u0441 127.0.0.1 = \u043f\u0440\u0430\u0437\u0435\u043d \u0430\u0434\u0440\u0435\u0441 \u0437\u0430 \u0434\u043e\u043c\u0435\u0439\u043d example.org (\u043d\u043e \u043d\u0435 \u0438 \u0437\u0430 \u043f\u043e\u0434 \u0434\u043e\u043c\u0435\u0439\u043d\u0438).",
"example_comment": "! \u0422\u043e\u0432\u0430 \u0435 \u043a\u043e\u043c\u0435\u043d\u0442\u0430\u0440",
"example_comment_meaning": "\u043f\u0440\u0438\u043c\u0435\u0440 \u0437\u0430 \u043a\u043e\u043c\u0435\u043d\u0442\u0430\u0440",
"example_comment_hash": "# \u0422\u043e\u0432\u0430 \u0435 \u0441\u044a\u0449\u043e \u043a\u043e\u043c\u0435\u043d\u0442\u0430\u0440",
"example_regex_meaning": "\u0411\u043b\u043e\u043a\u0438\u0440\u0430\u0439 \u0434\u043e\u0441\u0442\u044a\u043f \u0434\u043e \u0434\u043e\u043c\u0435\u0439\u043d\u0438 \u043a\u043e\u0439\u0442\u043e \u0441\u044a\u0432\u043f\u0430\u0434\u0430\u0442 \u0441\u044a\u0441 \u0441\u043b\u0435\u0434\u043d\u043e\u0442\u043e",
"example_upstream_regular": "\u043a\u043b\u0430\u0441\u0438\u0447\u0435\u0441\u043a\u0438 DNS (UDP \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b)",
"example_upstream_dot": "\u043a\u0440\u0438\u043f\u0442\u0438\u0440\u0430\u043d <0>DNS-\u0432\u044a\u0440\u0445\u0443-TLS<\/0>",
"example_upstream_doh": "\u043a\u0440\u0438\u043f\u0442\u0438\u0440\u0430\u043d <0>DNS-\u0432\u044a\u0440\u0445\u0443-HTTPS<\/0>",
"example_upstream_sdns": "\u043c\u043e\u0436\u0435 \u0434\u0430 \u043f\u043e\u043b\u0437\u0432\u0430\u0442\u0435 <0>DNS \u041f\u043e\u0434\u043f\u0438\u0441\u0432\u0430\u043d\u0435<\/0> \u0437\u0430 <1>DNSCrypt<\/1> \u0438\u043b\u0438 <2>DNS-\u0432\u044a\u0440\u0445\u0443-HTTPS<\/2> \u0441\u044a\u0440\u0432\u044a\u0440\u0438",
"example_upstream_tcp": "\u043a\u043b\u0430\u0441\u0438\u0447\u0435\u0441\u043a\u0438 DNS (TCP \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b)",
"all_filters_up_to_date_toast": "\u0412\u0441\u0438\u0447\u043a\u0438 \u0444\u0438\u043b\u0442\u0438 \u0441\u0430 \u0430\u043a\u0442\u0443\u0430\u043b\u0438\u0437\u0438\u0440\u0430\u043d\u0438",
"updated_upstream_dns_toast": "\u0413\u043b\u043e\u0431\u0430\u043b\u043d\u0438\u0442\u0435 DNS \u0441\u044a\u0440\u0432\u044a\u0440\u0438 \u0441\u0430 \u043e\u0431\u043d\u043e\u0432\u0435\u043d\u0438",
"dns_test_ok_toast": "\u0412\u044a\u0432\u0435\u0434\u0435\u043d\u0438\u0442\u0435 DNS \u0441\u044a\u0440\u0432\u044a\u0440\u0438 \u0440\u0430\u0431\u043e\u0442\u044f\u0442 \u043a\u043e\u0440\u0435\u043a\u0442\u043d\u043e",
"dns_test_not_ok_toast": "\u0421\u044a\u0440\u0432\u044a\u0440 \"{{key}}\": \u043d\u0435 \u0440\u0430\u0431\u043e\u0442\u0438, \u043c\u043e\u043b\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u0442\u0435 \u0434\u0430\u043b\u0438 \u0435 \u0432\u044a\u0432\u0435\u0434\u0435\u043d \u043a\u043e\u0440\u0435\u043a\u0442\u043d\u043e",
"unblock_btn": "\u041e\u0442\u0431\u043b\u043e\u043a\u0438\u0440\u0430\u0439",
"block_btn": "\u0411\u043b\u043e\u043a\u0438\u0440\u0430\u0439",
"time_table_header": "\u0412\u0440\u0435\u043c\u0435",
"domain_name_table_header": "\u0418\u043c\u0435 \u043d\u0430 \u0434\u043e\u043c\u0435\u0439\u043d",
"type_table_header": "\u0422\u0438\u043f",
"response_table_header": "\u041e\u0442\u0433\u043e\u0432\u043e\u0440",
"client_table_header": "\u041a\u043b\u0438\u0435\u043d\u0442",
"empty_response_status": "\u041f\u0440\u0430\u0437\u0435\u043d",
"show_all_filter_type": "\u041f\u043e\u043a\u0430\u0436\u0438 \u0432\u0441\u0438\u0447\u043a\u0438",
"show_filtered_type": "\u041f\u043e\u043a\u0430\u0436\u0438 \u0444\u0438\u043b\u0442\u0440\u0438\u0440\u0430\u043d\u0438",
"no_logs_found": "\u041d\u044f\u043c\u0430 \u0438\u0441\u0442\u043e\u0440\u0438\u044f",
"disabled_log_btn": "\u0417\u0430\u0431\u0440\u0430\u043d\u0438 \u0438\u0441\u0442\u043e\u0440\u0438\u044f\u0442\u0430",
"download_log_file_btn": "\u0421\u043c\u044a\u043a\u043d\u0438 \u0438\u0441\u0442\u043e\u0440\u0438\u044f\u0442\u0430",
"refresh_btn": "\u041e\u0431\u043d\u043e\u0432\u0438",
"enabled_log_btn": "\u0420\u0430\u0437\u0440\u0435\u0448\u0438 \u0438\u0441\u0442\u043e\u0440\u0438\u044f\u0442\u0430",
"last_dns_queries": "\u041f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0442\u0435 5000 DNS \u0437\u0430\u044f\u0432\u043a\u0438",
"previous_btn": "\u041f\u0440\u0435\u0434\u0445\u043e\u0434\u0435\u043d",
"next_btn": "\u0421\u043b\u0435\u0434\u0432\u0430\u0449",
"loading_table_status": "\u0417\u0430\u0440\u0435\u0436\u0434\u0430\u043d\u0435...",
"page_table_footer_text": "\u0421\u0442\u0440\u0430\u043d\u0438\u0446\u0430",
"of_table_footer_text": "\u043e\u0442",
"rows_table_footer_text": "\u0440\u0435\u0434\u043e\u0432\u0435",
"updated_custom_filtering_toast": "\u041e\u0431\u043d\u043e\u0432\u0435\u043d\u0438 \u043c\u0435\u0441\u0442\u043d\u0438 \u043f\u0440\u0430\u0432\u0438\u043b\u0430 \u0437\u0430 \u0444\u0438\u043b\u0442\u0440\u0438\u0440\u0430\u043d\u0435",
"rule_removed_from_custom_filtering_toast": "\u041f\u0440\u0435\u043c\u0430\u0445\u043d\u0430\u0442\u043e \u043e\u0442 \u043c\u0435\u0441\u0442\u043d\u0438 \u043f\u0440\u0430\u0432\u0438\u043b\u0430 \u0437\u0430 \u0444\u0438\u043b\u0442\u0440\u0438\u0440\u0430\u043d\u0435",
"rule_added_to_custom_filtering_toast": "\u0414\u043e\u0431\u0430\u0432\u0435\u043d\u043e \u0434\u043e \u043c\u0435\u0441\u0442\u043d\u0438 \u043f\u0440\u0430\u0432\u0438\u043b\u0430 \u0437\u0430 \u0444\u0438\u043b\u0442\u0440\u0438\u0440\u0430\u043d\u0435",
"query_log_disabled_toast": "\u0418\u0441\u0442\u043e\u0440\u0438\u044f\u0442\u0430 \u0435 \u0437\u0430\u0431\u0440\u0430\u043d\u0435\u043d\u0430",
"query_log_enabled_toast": "\u0418\u0441\u0442\u043e\u0440\u0438\u044f\u0442\u0430 \u0435 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0430",
"source_label": "\u0418\u0437\u0442\u043e\u0447\u043d\u0438\u043a",
"found_in_known_domain_db": "\u041d\u0430\u043c\u0435\u0440\u0435\u043d \u0432 \u0441\u043f\u0438\u0441\u044a\u0446\u0438\u0442\u0435 \u0441 \u0434\u043e\u043c\u0435\u0439\u043d\u0438.",
"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\u0442\u044a\u0440",
"unknown_filter": "\u041d\u0435\u043f\u043e\u0437\u043d\u0430\u0442 \u0444\u0438\u043b\u0442\u044a\u0440 {{filterId}}",
"install_welcome_title": "\u0414\u043e\u0431\u0440\u0435 \u0434\u043e\u0448\u043b\u0438 \u0432 AdGuard Home!",
"install_welcome_desc": "AdGuard Home e \u043c\u0440\u0435\u0436\u043e\u0432\u043e \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u0437\u0430 \u0431\u043b\u043e\u043a\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u0440\u0435\u043a\u043b\u0430\u043c\u0438 \u0438 \u0442\u0440\u0430\u043a\u0435\u0440\u0438 \u043d\u0430 DNS \u043d\u0438\u0432\u043e. \u0421\u044a\u0437\u0434\u0430\u0434\u0435\u043d\u043e \u0435 \u0437\u0430 \u0434\u0430 \u0432\u0438 \u0434\u0430\u0434\u0435 \u043f\u044a\u043b\u0435\u043d \u043a\u043e\u043d\u0442\u0440\u043e\u043b \u043d\u0430\u0434 \u043c\u0440\u0435\u0436\u0430\u0442\u0430 \u0438 \u0432\u0441\u0438\u0447\u043a\u0438\u0442\u0435 \u0432\u0438 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430, \u0431\u0435\u0437 \u0434\u0430 \u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0434\u043e\u043f\u044a\u043b\u043d\u0438\u0442\u0435\u043b\u043d\u043e \u0438\u043d\u0441\u0442\u0430\u043b\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u0434\u0440\u0443\u0433 \u0441\u043e\u0444\u0442\u0443\u0435\u0440.",
"install_settings_title": "\u0410\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f",
"install_settings_listen": "\u0410\u043a\u0442\u0438\u0432\u043d\u0438 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0438",
"install_settings_port": "\u041f\u043e\u0440\u0442",
"install_settings_interface_link": "\u0412\u0430\u0448\u0430\u0442\u0430 AdGuard Home \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u0437\u0430 \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u0449\u0435 \u0435 \u0434\u043e\u0441\u0442\u044a\u043f\u043d\u0430 \u043d\u0430 \u0442\u043e\u0437\u0438 \u0430\u0434\u0440\u0435\u0441:",
"form_error_port": "\u041c\u043e\u043b\u044f \u0432\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u0432\u0430\u043b\u0438\u0434\u0435\u043d \u043f\u043e\u0440\u0442",
"install_settings_dns": "DNS \u0441\u044a\u0440\u0432\u044a\u0440",
"install_settings_dns_desc": "\u0417\u0430 \u0434\u0430 \u0440\u0430\u0431\u043e\u0442\u0438, \u0449\u0435 \u0442\u0440\u044f\u0431\u0432\u0430 \u0434\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u0435 \u0432\u0430\u0448\u0438\u044f\u0442 \u0440\u0443\u0442\u0435\u0440 \u0438\u043b\u0438 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0434\u0430 \u043f\u043e\u043b\u0437\u0432\u0430\u0442 DNS \u0441\u044a\u0440\u0432\u044a\u0440 \u0441 \u0430\u0434\u0440\u0435\u0441:",
"install_settings_all_interfaces": "\u0412\u0441\u0438\u0447\u043a\u0438 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0438",
"install_auth_title": "\u0423\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u0432\u0430\u043d\u0435",
"install_auth_desc": "\u041c\u043d\u043e\u0433\u043e \u0435 \u0432\u0430\u0436\u043d\u043e \u0434\u0430 \u0437\u0430\u0434\u0430\u0434\u0435\u0442\u0435 \u0438\u043c\u0435 \u0438 \u043f\u0430\u0440\u043e\u043b\u0430 \u0437\u0430 \u0434\u043e\u0441\u0442\u044a\u043f \u0434\u043e \u0432\u0430\u0448\u0438\u044f \u043f\u0430\u043d\u0435\u043b \u0437\u0430 \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u043d\u0430 AdGuard Home. \u041f\u0440\u0435\u043f\u043e\u0440\u044a\u0447\u0432\u0430\u043c\u0435 \u0432\u0438 \u0434\u0430 \u0437\u0430\u0434\u0430\u0434\u0435\u0442\u0435 \u0438\u043c\u0435 \u0438 \u043f\u0430\u0440\u043e\u043b\u0430 \u043d\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e \u0447\u0435 \u0433\u043e \u043f\u043e\u043b\u0437\u0432\u0430\u0442\u0435 \u0441\u0430\u043c\u043e \u0432 \u043a\u044a\u0449\u0438.",
"install_auth_username": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b",
"install_auth_password": "\u041f\u0430\u0440\u043e\u043b\u0430",
"install_auth_confirm": "\u041f\u043e\u0442\u0432\u044a\u0440\u0434\u0435\u0442\u0435 \u043f\u0430\u0440\u043e\u043b\u0430\u0442\u0430",
"install_auth_username_enter": "\u0412\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u043f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b",
"install_auth_password_enter": "\u0412\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u043f\u0430\u0440\u043e\u043b\u0430",
"install_step": "\u0421\u0442\u044a\u043f\u043a\u0430",
"install_devices_title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 \u0432\u0430\u0448\u0435\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e",
"install_devices_desc": "\u0414\u0430 \u0437\u0430\u043f\u043e\u0447\u043d\u0435\u0442\u0435 \u0434\u0430 \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u0442\u0435 AdGuard Home, \u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0434\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u0435 \u0432\u0430\u0448\u0438\u0442\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430.",
"install_submit_title": "\u041f\u043e\u0437\u0434\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f!",
"install_submit_desc": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u0442\u0430 \u0435 \u0437\u0430\u0432\u044a\u0440\u0448\u0435\u043d\u0430, \u043c\u043e\u0436\u0435 \u0434\u0430 \u0437\u0430\u043f\u043e\u0447\u043d\u0435\u0442\u0435 \u0434\u0430 \u043f\u043e\u043b\u0437\u0432\u0430\u0442\u0435 AdGuard Home.",
"install_devices_router": "\u0420\u0443\u0442\u0435\u0440",
"install_devices_router_desc": "\u0410\u043a\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u0435 \u0432\u0430\u0448\u0438\u044f\u0442 \u0440\u0443\u0442\u0435\u0440 \u043d\u044f\u043c\u0430 \u043d\u0443\u0436\u0434\u0430 \u0440\u044a\u0447\u043d\u043e \u0434\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u0432\u0430\u0442\u0435 \u0432\u0441\u044f\u043a\u043e \u0435\u0434\u043d\u043e \u043e\u0442 \u0443\u0441\u0442\u0440\u0439\u0441\u0442\u0432\u0430\u0442\u0430 \u0432 \u043c\u0440\u0435\u0436\u0430\u0442\u0430.",
"install_devices_address": "AdGuard Home DNS \u0441\u044a\u0440\u0432\u044a\u0440\u044a\u0442 \u0435 \u043d\u0430 \u0441\u043b\u0435\u0434\u043d\u0438\u044f \u0430\u0434\u0440\u0435\u0441",
"install_devices_router_list_1": "\u041e\u0442\u0432\u043e\u0440\u0435\u0442\u0435 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0442\u0430 \u0437\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043d\u0430 \u0432\u0430\u0448\u0438\u044f \u0440\u0443\u0442\u0435\u0440. \u041e\u0431\u0438\u043a\u043d\u043e\u0432\u0435\u043d\u043e \u0442\u044f \u0441\u0435 \u043d\u0430\u043c\u0438\u0440\u0430 \u043d\u0430 URL (\u0442\u0443\u043a http:\/\/192.168.0.1\/ \u0438\u043b\u0438 \u0442\u0443\u043a http:\/\/192.168.1.1\/). \u0417\u0430 \u0434\u043e\u0441\u0442\u044a\u043f \u043c\u043e\u0436\u0435 \u0434\u0430 \u0432\u0438 \u0442\u0440\u044f\u0431\u0432\u0430 \u043f\u0430\u0440\u043e\u043b\u0430. \u0410\u043a\u043e \u0441\u0442\u0435 \u0437\u0430\u0431\u0440\u0430\u0432\u0438\u043b\u0438 \u043f\u0430\u0440\u043e\u043b\u0430\u0442\u0430 \u043c\u043e\u0436\u0435 \u0434\u0430 \u044f \u0440\u0435\u0441\u0435\u0442\u043d\u0435\u0442\u0435 \u043a\u0430\u0442\u043e \u043d\u0430\u0442\u0438\u0441\u043d\u0435\u0442\u0430 \u0441\u043a\u0440\u0438\u0442\u0438\u044f \u0440\u0435\u0441\u0435\u0442 \u0431\u0443\u0442\u043e\u043d - \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u0442\u043e\u0432\u0430 \u0449\u0435 \u0440\u0435\u0441\u0435\u0442\u043d\u0435 \u0432\u0441\u0438\u0447\u043a\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043d\u0430 \u0440\u0443\u0442\u0435\u0440\u0430 \u0434\u043e \u0444\u0430\u0431\u0440\u0438\u0447\u043d\u0438! \u041d\u044f\u043a\u043e\u0439 \u0440\u0443\u0442\u0435\u0440\u0438 \u043c\u043e\u0433\u0430\u0442 \u0434\u0430 \u0431\u044a\u0434\u0430\u0442\u0435 \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0438\u0440\u0430\u043d\u0438 \u043e\u0442 \u0441\u043e\u0444\u0442\u0443\u0435\u0440 \u0438\u043b\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435, \u043a\u043e\u0439\u0442\u043e \u0431\u0438 \u0442\u0440\u044f\u0431\u0432\u0430\u043b\u043e \u0434\u0430 \u0435 \u0432\u0435\u0447\u0435 \u0438\u043d\u0441\u0442\u0430\u043b\u0438\u0440\u0430\u043d \u043d\u0430 \u043a\u043e\u043c\u043f\u044e\u0442\u044a\u0440\u0430\/\u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0430 \u0432\u0438.",
"install_devices_router_list_2": "\u041d\u0430\u043c\u0435\u0440\u0435\u0442\u0430 DHCP\/DNS \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438. \u0412 \u043f\u043e\u0434 \u0440\u0430\u0437\u0434\u0435\u043b DHCP \u0440\u0437\u0433\u043b\u0435\u0434\u0430\u0439\u0442\u0435 \u0438 \u043d\u0430\u043c\u0435\u0440\u0435\u0442\u0435 \u043a\u044a\u0434\u0435 \u0435 \u043f\u043e\u043b\u0435\u0442\u043e \u0437\u0430 DNS \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0432 \u043a\u043e\u0435\u0442\u043e \u043c\u043e\u0436\u0435 \u0434\u0430 \u0432\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u043b\u0438\u0437\u0438\u0440\u0430\u043d\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0437\u0430 DNS \u0441\u044a\u0440\u0432\u044a\u0440\u0438.",
"install_devices_router_list_3": "\u0412\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u0430\u0434\u0440\u0435\u0441\u044a\u0442 \u043d\u0430 AdGuard Home \u0441\u044a\u0440\u0432\u044a\u0440\u0430.",
"install_devices_windows_list_1": "\u041e\u0442\u0432\u043e\u0440\u0435\u0442\u0435 \u041a\u043e\u043d\u0442\u0440\u043e\u043b\u043d\u0438\u044f \u041f\u0430\u043d\u0435\u043b \u043f\u0440\u0435\u0437 \u0421\u0442\u0430\u0440\u0442 \u043c\u0435\u043d\u044e \u0438\u043b\u0438 \u0447\u0440\u0435\u0437 \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0442\u044a\u0440\u0441\u0435\u043d\u0435 \u043d\u0430 Windows.",
"install_devices_windows_list_2": "\u0412\u044a\u0440\u0432\u0435\u0442\u0435 \u0434\u043e \u041d\u0430\u0441\u0442\u0440\u0439\u043a\u0438 \u043d\u0430 \u041c\u0440\u0435\u0436\u0438 \u0438 \u0418\u043d\u0442\u0435\u0440\u043d\u0435\u0442 \u0438 \u043e\u0442 \u0442\u0430\u043c \u0438\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u041c\u0440\u0435\u0436\u0438 \u0438 \u0426\u0435\u043d\u0442\u044a\u0440 \u0437\u0430 \u0421\u043f\u043e\u0434\u0435\u043b\u044f\u043d\u0435.",
"install_devices_windows_list_3": "\u041e\u0442 \u043b\u044f\u0432\u043e \u043d\u0430 \u0435\u043a\u0440\u0430\u043d\u0430 \u043d\u0430\u043c\u0435\u0440\u0435\u0442\u0435 \u0421\u043c\u0435\u043d\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u043a\u0438 \u043d\u0430 \u043c\u0440\u0435\u0436\u043e\u0432\u0438\u044f \u0430\u0434\u0430\u043f\u0442\u0435\u0440 \u0438 \u043a\u043b\u0438\u043a\u043d\u0435\u0442\u0435 \u043d\u0430 \u043d\u0435\u0433\u043e.",
"install_devices_windows_list_4": "\u0418\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u0442\u043e\u0437\u0438 \u043a\u043e\u0439\u0442\u043e \u0435 \u0430\u043a\u0442\u0438\u0432\u0435\u043d, \u0434\u044f\u0441\u043d\u043e-\u043a\u043b\u0438\u043a\u0432\u0430\u043d\u0435 \u0438 \u0438\u0437\u0431\u0435\u0440\u0435\u0442\u0430 \u0421\u0432\u043e\u0439\u0441\u0442\u0432\u0430.",
"install_devices_windows_list_5": "\u041d\u0430\u043c\u0435\u0440\u0435\u0442\u0435 \u0418\u043d\u0442\u0435\u0440\u043d\u0435\u0442 \u041f\u0440\u043e\u0442\u043e\u043a\u043e\u043b \u0412\u0435\u0440\u0441\u0438\u044f 4 (TCP\/IP) \u0432 \u0441\u043f\u0438\u0441\u044a\u043a\u0430, \u0438\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u0438 \u043a\u043b\u0438\u043a\u043d\u0435\u0442\u0435 \u043e\u0442\u043d\u043e\u0432\u043e \u043d\u0430 \u0421\u0432\u043e\u0439\u0441\u0442\u0432\u0430.",
"install_devices_windows_list_6": "\u0418\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u0418\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u0439 \u0441\u043b\u0435\u0434\u043d\u0438\u0442\u0435 \u0430\u0434\u0440\u0435\u0441\u0438 \u0437\u0430 DNS \u0441\u044a\u0440\u0441\u044a\u0440\u0438 \u0438 \u0432\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u0430\u0434\u0440\u0435\u0441\u0430 \u043d\u0430 AdGuard Home \u0441\u044a\u0440\u0432\u044a\u0440\u0430 \u0432\u0438.",
"install_devices_macos_list_1": "\u0426\u044a\u043a\u043d\u0435\u0442\u0435 \u043d\u0430 Apple \u0438\u043a\u043e\u043d\u043a\u0430\u0442\u0430 \u0438 \u0438\u0437\u0431\u0435\u0440\u0435\u0442\u0435 System Preferences...",
"install_devices_macos_list_2": "\u0426\u044a\u043a\u043d\u0435\u0442\u0435 \u043d\u0430 Network.",
"install_devices_macos_list_3": "\u0418\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u0437\u0435\u043b\u0435\u043d\u0430\u0442\u0430-\u0430\u043a\u0442\u0438\u0432\u043d\u0430 \u0432\u0440\u044a\u0437\u043a\u0430 \u0432 \u0441\u043f\u0438\u0441\u044a\u043a\u0430 \u0438 \u043a\u043b\u0438\u043a\u043d\u0435\u0442\u0435 \u043d\u0430 Advanced.",
"install_devices_macos_list_4": "\u0418\u0437\u0431\u0435\u0440\u0435\u0442\u0435 DNS \u0442\u0430\u0431 \u0438 \u043a\u043b\u0438\u043a\u043d\u0435\u0442\u0435 \u043d\u0430 + \u0437\u0430 \u0434\u0430 \u0432\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u0430\u0434\u0440\u0435\u0441\u0430 \u043d\u0430 AdGuard Home \u0441\u044a\u0440\u0432\u044a\u0440\u0430.",
"install_devices_android_list_1": "\u0418\u0437\u0431\u0435\u0440\u0435\u0442\u0435 Android \u041c\u0435\u043d\u044e \u043e\u0442 \u0434\u043e\u043c\u0430\u0448\u043d\u0438\u044f \u0435\u043a\u0440\u0430\u043d, \u0438 \u0446\u044a\u043a\u043d\u0435\u0442\u0435 \u043d\u0430 \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438.",
"install_devices_android_list_2": "\u0426\u044a\u043a\u043d\u0435\u0442\u0435 \u043d\u0430 Wi-Fi \u043c\u0435\u043d\u044e. \u041d\u0430 \u0435\u043a\u0440\u0430\u043d\u0430 \u0449\u0435 \u0441\u0435 \u043f\u043e\u044f\u0432\u0430\u0442 \u0432\u0441\u0438\u0447\u043a\u0438 \u0431\u0435\u0437\u0436\u0438\u0447\u043d\u0438 \u043f\u0440\u0435\u0436\u0438 (\u0442\u0430\u043c \u043d\u044f\u043c\u0430 \u0432\u044a\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442 \u0437\u0430 \u0432\u044a\u0432\u0435\u0436\u0434\u0430\u043d\u0435 \u043d\u0430 DNS \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438).",
"install_devices_android_list_3": "\u0426\u044a\u043a\u043d\u0435\u0442\u0435 \u0438 \u0437\u0430\u0434\u0440\u044a\u0436\u0434\u0435 \u0432\u044a\u0440\u0445\u0443 \u0412\u0438\u0435 \u0441\u0442\u0435 \u0441\u0432\u044a\u0440\u0437\u0430\u043d\u0438 \u0441.., \u0438 \u043a\u043b\u0438\u043a\u043d\u0435\u0442\u0435 \u043d\u0430 \u041c\u043e\u0434\u0438\u0444\u0438\u0446\u0438\u0440\u0430\u0439 \u043c\u0440\u0435\u0436\u0430.",
"install_devices_android_list_4": "\u041d\u0430 \u043d\u044f\u043a\u043e\u0439 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u043c\u043e\u0436\u0435 \u0434\u0430 \u0435 \u043d\u0435\u043e\u0445\u043e\u0434\u0438\u043c\u043e \u0434\u0430 \u043c\u0430\u0440\u043a\u0438\u0440\u0430\u0442\u0435 \u043f\u043e\u043a\u0430\u0436\u0438 \u0420\u0430\u0437\u0448\u0438\u0440\u0435\u043d\u0438, \u0437\u0430 \u0434\u0430 \u0432\u0438\u0434\u0438\u0442\u0435 \u0432\u0441\u0438\u0447\u043a\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438. \u0417\u0430 \u0434\u0430 \u043f\u0440\u043e\u043c\u0435\u043d\u0438\u0442\u0435 Android DNS \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u0442\u0435, \u043c\u043e\u0436\u0435 \u0434\u0430 \u0441\u0435 \u043d\u0430\u043b\u043e\u0436\u0438 \u0434\u0430 \u043f\u0440\u043e\u043c\u0435\u043d\u0438\u0442\u0435 IP \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u0442\u0435 \u043e\u0442 DHCP \u043d\u0430 \u0421\u0442\u0430\u0442\u0438\u0447\u043d\u0438.",
"install_devices_android_list_5": "\u041f\u0440\u043e\u043c\u0435\u043d\u0435\u0442\u0435 \u0441\u0442\u043e\u0439\u043d\u043e\u0441\u0442\u0438\u0442\u0435 \u043d\u0430 DNS 1 \u0438 DNS 2 \u0434\u0430 \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u0442 AdGuard Home \u0441\u044a\u0440\u0432\u044a\u0440\u0430.",
"install_devices_ios_list_1": "\u041e\u0442 \u043d\u0430\u0447\u0430\u043b\u0435\u043d \u0435\u043a\u0440\u0430\u043d, \u0446\u044a\u043a\u043d\u0435\u0442\u0435 \u043d\u0430 Settings.",
"install_devices_ios_list_2": "\u0418\u0437\u0431\u0435\u0440\u0435\u0442\u0435 Wi-Fi \u043e\u0442 \u043b\u044f\u0432\u043e\u0442\u043e \u043c\u0435\u043d\u044e (\u0442\u0430\u043c \u043d\u044f\u043c\u0430 \u0432\u044a\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442 \u0437\u0430 \u0432\u044a\u0432\u0435\u0436\u0434\u0430\u043d\u0435 \u043d\u0430 DNS \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438).",
"install_devices_ios_list_3": "\u041a\u043b\u0438\u043d\u0435\u0442\u0435 \u043d\u0430 \u0438\u043c\u0435\u0442\u043e \u043d\u0430 \u0430\u043a\u0442\u0438\u0432\u043d\u0430\u0442\u0430 \u043c\u0440\u0435\u0436\u0430 \u043a\u044a\u043c \u043a\u043e\u044f\u0442\u043e \u0441\u0442\u0435 \u0441\u0432\u044a\u0440\u0437\u0430\u043d\u0438.",
"install_devices_ios_list_4": "\u0412 \u043f\u043e\u043b\u0435\u0442\u043e \u0437\u0430 DNS \u0438\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u0440\u044a\u0447\u043d\u043e \u0438 \u0432\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u0430\u0434\u0440\u0435\u0441\u0430 \u043d\u0430 AdGuard Home \u0441\u044a\u0440\u0432\u044a\u0440\u0430.",
"get_started": "\u0414\u0430 \u0437\u0430\u043f\u043e\u0447\u0432\u0430\u043c\u0435",
"next": "\u0421\u043b\u0435\u0434\u0432\u0430\u0449",
"open_dashboard": "\u041e\u0442\u0432\u043e\u0440\u0438 \u0442\u0430\u0431\u043b\u043e",
"install_saved": "\u0423\u0441\u043f\u0435\u0448\u043d\u043e \u0437\u0430\u043f\u0438\u0441\u0430\u043d\u043e",
"encryption_title": "\u041a\u0440\u0438\u043f\u0442\u0438\u0440\u0430\u043d\u0435",
"encryption_desc": "\u041f\u043e\u0434\u044a\u0440\u0436\u0430 \u0441\u0435 \u0441\u0438\u0433\u0443\u0440\u043d\u0430 \u0432\u0440\u044a\u0437\u043a\u0430 (HTTPS\/TLS) \u0432\u043a\u043b\u044e\u0447\u0438\u0442\u0435\u043b\u043d\u043e \u0437\u0430 DNS \u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0442\u0430 \u0437\u0430 \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f",
"encryption_config_saved": "\u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f\u0442\u0430 \u0435 \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0437\u0430\u043f\u0438\u0441\u0430\u043d\u0430",
"encryption_server": "\u0418\u043c\u0435 \u043d\u0430 \u0441\u044a\u0440\u0432\u044a\u0440\u0430",
"encryption_server_enter": "\u0412\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u0438\u043c\u0435 \u043d\u0430 \u0434\u043e\u043c\u0435\u0439\u043d\u0430",
"encryption_server_desc": "\u0417\u0430 \u0434\u0430 \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u0442\u0435 HTTPS, \u0442\u0440\u044f\u0431\u0432\u0430 \u0438\u043c\u0435\u0442\u043e \u043d\u0430 \u0441\u044a\u0440\u0432\u044a\u0440\u0430 \u0434\u0430 \u0441\u044a\u0432\u043f\u0430\u0434\u0430 \u0441 \u0442\u043e\u0432\u0430 \u043d\u0430 SSL \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u0430.",
"encryption_redirect": "\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u043e \u043f\u0440\u0435\u043d\u0430\u0441\u043e\u0447\u0432\u0430\u043d\u0435 \u043a\u044a\u043c HTTPS",
"encryption_redirect_desc": "\u0421\u043b\u0443\u0436\u0438 \u0437\u0430 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u043e \u043f\u0440\u0435\u043d\u0430\u0441\u043e\u0447\u0432\u0430\u043d\u0435 \u043e\u0442 HTTP \u043a\u044a\u043c HTTPS \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0442\u0430 \u0437\u0430 \u0410\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u0432 AdGuard Home.",
"encryption_https": "HTTPS \u043f\u043e\u0440\u0442",
"encryption_https_desc": "\u0410\u043a\u043e \u0437\u0430\u0434\u0430\u0434\u0435\u0442\u0435 HTTPS \u043f\u043e\u0440\u0442, \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0442\u0430 \u0437\u0430 \u0410\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u043d\u0430 AdGuard Home \u0449\u0435 \u0431\u044a\u0434\u0435 \u0434\u043e\u0441\u0442\u044a\u043f\u043d\u0430 \u043d\u0430 HTTPS, \u0438 \u0441\u044a\u0449\u043e \u0449\u0435 \u043e\u0442\u0433\u043e\u0432\u0430\u0440\u044f \u043d\u0430 DNS-\u0432\u044a\u0440\u0445\u0443-HTTPS '\/dns-\u0437\u0430\u043f\u0438\u0442\u0432\u0430\u043d\u0438\u044f'.",
"encryption_dot": "DNS-\u0432\u044a\u0440\u0445\u0443-TLS \u043f\u043e\u0440\u0442",
"encryption_dot_desc": "\u0410\u043a\u043e \u043f\u043e\u0440\u0442\u0430 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d, AdGuard Home \u0449\u0435 \u0441\u0442\u0430\u0440\u0442\u0438\u0440\u0430 \u0438 \u0441\u044a\u0440\u0432\u044a\u0440 \u0437\u0430 DNS-\u0432\u044a\u0440\u0445\u0443-TLS.",
"encryption_certificates": "\u0421\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u0438",
"encryption_certificates_desc": "\u0417\u0430 \u0434\u0430 \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u0442\u0435 \u0441\u0438\u0433\u0443\u0440\u043d\u0430 \u0432\u0440\u044a\u0437\u043a\u0430, \u0449\u0435 \u0442\u0440\u044f\u0431\u0432\u0430 \u0434\u0430 \u043e\u0441\u0438\u0433\u0443\u0440\u0438\u0442\u0435 SSL \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u0438 \u0437\u0430 \u0432\u0430\u0448\u0438\u044f \u0434\u043e\u043c\u0435\u0439\u043d. \u041c\u043e\u0436\u0435 \u0434\u0430 \u0437\u0430\u044f\u0432\u0438\u0442\u0435 \u0431\u0435\u0437\u043f\u043b\u0430\u0442\u0435\u043d \u043e\u0442 <0>{{link}}<\/0> \u0438\u043b\u0438 \u0434\u0430 \u0437\u0430\u043a\u0443\u043f\u0438\u0442\u0435 \u043e\u0442 Certificate Authorities.",
"encryption_certificates_input": "\u041a\u043e\u043f\u0438\u0440\u0430\u0439\/\u043f\u043e\u0441\u0442\u0430\u0432\u0438 \u0432\u0430\u0448\u0438\u044f PEM-\u043a\u043e\u0434\u0438\u0440\u0430\u043d \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 \u0442\u0443\u043a.",
"encryption_status": "\u0421\u044a\u0441\u0442\u043e\u044f\u043d\u0438\u0435",
"encryption_expire": "\u0413\u043e\u0434\u0435\u043d \u0434\u043e",
"encryption_key": "\u0427\u0430\u0441\u0442\u0435\u043d \u043a\u043b\u044e\u0447",
"encryption_key_input": "\u041a\u043e\u043f\u0438\u0440\u0430\u0439\/\u043f\u043e\u0441\u0442\u0430\u0432\u0438 \u0432\u0430\u0448\u0438\u044f PEM-\u043a\u043e\u0434\u0438\u0440\u0430\u043d \u0447p\u0430\u0441\u0442\u0435\u043d \u043a\u043b\u044e\u0447 \u0437\u0430 \u0432\u0430\u0448\u0438\u044f \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 \u0442\u0443\u043a.",
"encryption_enable": "\u0420\u0430\u0437p\u0435\u0448\u0438 \u043a\u0440\u0438\u043f\u0442\u0438\u0440\u0430\u043d\u0435 (HTTPS, DNS-\u0432\u044a\u0440\u0445\u0443-HTTPS, \u0438 DNS-\u0432\u044a\u0440\u0445\u0443-TLS)",
"encryption_enable_desc": "\u0410\u043a\u043e \u0441\u0442\u0435 \u0440\u0430\u0437\u0440\u0435\u0448\u0438\u043b\u0438 \u043a\u0440\u0438\u043f\u0442\u0438\u0440\u0430\u043d\u0435, \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0442\u0430 \u0437\u0430 \u0410\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u043d\u0430 AdGuard Home \u0449\u0435 \u0431\u044a\u0434\u0435 \u0434\u043e\u0441\u0442\u044a\u043f\u043d\u0430 \u043f\u0440\u0435\u0437 HTTPS, \u0438 DNS \u0441\u044a\u0440\u0432\u044a\u0440\u0430 \u0449\u0435 \u043e\u0442\u0433\u043e\u0432\u0430\u0440\u044f \u0441\u044a\u0449\u043e \u043d\u0430 \u0437\u0430\u043f\u0438\u0442\u0432\u0430\u043d\u0438\u044f DNS-\u0432\u044a\u0440\u0445\u0443-HTTPS \u0438 DNS-\u0432\u044a\u0440\u0445\u0443-TLS.",
"encryption_chain_valid": "\u0419\u0435\u0440\u0430\u0440\u0445\u0438\u044f\u0442\u0430 \u043e\u0442 \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u0438 \u0435 \u0432\u0430\u043b\u0438\u0434\u043d\u0430",
"encryption_chain_invalid": "\u0419\u0435\u0440\u0430\u0440\u0445\u0438\u044f\u0442\u0430 \u043e\u0442 \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u0438 \u0435 \u043d\u0435\u0432\u0430\u043b\u0438\u0434\u043d\u0430",
"encryption_key_valid": "\u0422\u043e\u0432\u0430 \u0435 \u0432\u0430\u043b\u0438\u0434\u0435\u043d {{type}} \u0447\u0430\u0441\u0442\u0435\u043d \u043a\u043b\u044e\u0447",
"encryption_key_invalid": "\u0422\u043e\u0432\u0430 \u0435 \u043d\u0435\u0432\u0430\u043b\u0438\u0434\u0435\u043d {{type}} \u0447\u0430\u0441\u0442\u0435\u043d \u043a\u043b\u044e\u0447",
"encryption_subject": "\u0422\u0435\u043c\u0430",
"encryption_issuer": "\u0418\u0437\u043f\u044a\u043b\u043d\u0438\u0442\u0435\u043b",
"encryption_hostnames": "\u0418\u043c\u0435\u043d\u0430 \u043d\u0430 \u0445\u043e\u0441\u0442\u0430",
"encryption_reset": "\u0421\u0438\u0433\u0443\u0440\u043d\u0438 \u043b\u0438 \u0441\u0442\u0435 \u0447\u0435 \u0438\u0441\u043a\u0430\u0442\u0435 \u0434\u0430 \u0438\u0437\u0442\u0440\u0438\u0435\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u0442\u0435 \u0437\u0430 \u043a\u0440\u0438\u043f\u0442\u0438\u0440\u0430\u043d\u0435?",
"topline_expiring_certificate": "\u0412\u0430\u0448\u0438\u044f\u0442 SSL \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 \u0438\u0437\u0442\u0438\u0447\u0430. \u041e\u0431\u043d\u043e\u0432\u0438 <0>\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0437\u0430 \u043a\u0440\u0438\u043f\u0442\u0438\u0440\u0430\u043d\u0435<\/0>.",
"topline_expired_certificate": "\u0412\u0430\u0448\u0438\u044f\u0442 SSL \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 \u0435 \u0438\u0437\u0442\u0435\u043a\u044a\u043b. \u041e\u0431\u043d\u043e\u0432\u0438 <0>\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0437\u0430 \u043a\u0440\u0438\u043f\u0442\u0438\u0440\u0430\u043d\u0435<\/0>.",
"form_error_port_range": "\u0412\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u043f\u043e\u0440\u0442 \u0432 \u0434\u0438\u0430\u043f\u0430\u0437\u043e\u043d\u0430 80-65535",
"form_error_port_unsafe": "\u041d\u0435 \u0435 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e \u0434\u0430 \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u0442\u0435 \u0442\u043e\u0437\u0438 \u043f\u043e\u0440\u0442",
"form_error_equal": "\u041d\u0435 \u0442\u0440\u044f\u0431\u0432\u0430 \u0434\u0430 \u0441\u044a\u0432\u043f\u0430\u0434\u0430",
"form_error_password": "\u041f\u0430\u0440\u043e\u043b\u0430\u0442\u0430 \u043d\u0435 \u0441\u044a\u0432\u043f\u0430\u0434\u0430",
"reset_settings": "\u0418\u0437\u0442\u0440\u0438\u0439 \u0432\u0441\u0438\u0447\u043a\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438",
"update_announcement": "\u0418\u043c\u0430 \u043d\u043e\u0432\u0430 AdGuard Home {{version}}! <0>\u0426\u044a\u043a\u043d\u0438 \u0442\u0443\u043a<\/0> \u0437\u0430 \u043f\u043e\u0432\u0435\u0447\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f."
}

View File

@@ -0,0 +1,335 @@
{
"client_settings": "Client settings",
"example_upstream_reserved": "you can specify DNS upstream <0>for a specific domain(s)<\/0>",
"upstream_parallel": "Use parallel queries to speed up resolving by simultaneously querying all upstream servers",
"bootstrap_dns": "Bootstrap DNS servers",
"bootstrap_dns_desc": "Bootstrap DNS servers are used to resolve IP addresses of the DoH\/DoT resolvers you specify as upstreams.",
"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": "It is safe to enable the built-in DHCP server - we didn't find any active DHCP servers on the network. However, we encourage you to re-check it manually as our automatic test currently doesn't give 100% guarantee.",
"dhcp_found": "An active DHCP server is found on the network. It is not safe to enable the built-in DHCP server.",
"dhcp_leases": "DHCP leases",
"dhcp_static_leases": "DHCP static 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_mac_format": "Invalid MAC 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 DHCP server anyway, make sure that there is no other active DHCP server in your network. Otherwise, it can break the Internet for connected devices!",
"dhcp_error": "We could not determine whether there is another DHCP server in the network.",
"dhcp_static_ip_error": "In order to use DHCP server a static IP address must be set. We failed to determine if this network interface is configured using static IP address. Please set a static IP address manually.",
"dhcp_dynamic_ip_found": "Your system uses dynamic IP address configuration for interface <0>{{interfaceName}}<\/0>. In order to use DHCP server a static IP address must be set. Your current IP address is <0>{{ipAddress}}<\/0>. We will automatically set this IP address as static if you press Enable DHCP button.",
"dhcp_lease_added": "Static lease \"{{key}}\" successfully added",
"dhcp_lease_deleted": "Static lease \"{{key}}\" successfully deleted",
"dhcp_new_static_lease": "New static lease",
"dhcp_static_leases_not_found": "No DHCP static leases found",
"dhcp_add_static_lease": "Add static lease",
"delete_confirm": "Are you sure you want to delete \"{{key}}\"?",
"form_enter_hostname": "Enter hostname",
"error_details": "Error details",
"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",
"privacy_policy": "Privacy policy",
"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, DuckDuckGo and Yandex.",
"no_servers_specified": "No servers specified",
"no_settings": "No settings",
"general_settings": "General settings",
"dns_settings": "DNS settings",
"encryption_settings": "Encryption settings",
"dhcp_settings": "DHCP 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.",
"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",
"edit_table_action": "Edit",
"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 <0>specified regular expression</0>",
"example_upstream_regular": "regular DNS (over UDP)",
"example_upstream_dot": "encrypted <0>DNS-over-TLS<\/0>",
"example_upstream_doh": "encrypted <0>DNS-over-HTTPS<\/0>",
"example_upstream_sdns": "you can use <0>DNS Stamps<\/0> for <1>DNSCrypt<\/1> or <2>DNS-over-HTTPS<\/2> 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": "To start using AdGuard Home, 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 on 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 to 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 certificates here.",
"encryption_status": "Status",
"encryption_expire": "Expires",
"encryption_key": "Private key",
"encryption_key_input": "Copy\/paste your PEM-encoded private key for your certificate 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.",
"setup_guide": "Setup guide",
"dns_addresses": "DNS addresses",
"down": "Down",
"fix": "Fix",
"dns_providers": "Here is a <0>list of known DNS providers<\/0> to choose from.",
"update_now": "Update now",
"update_failed": "Auto-update failed. Please <a href='https:\/\/github.com\/AdguardTeam\/AdGuardHome\/wiki\/Getting-Started#update'>follow the steps<\/a> to update manually.",
"processing_update": "Please wait, AdGuard Home is being updated",
"clients_title": "Clients",
"clients_desc": "Configure devices connected to AdGuard Home",
"settings_global": "Global",
"settings_custom": "Custom",
"table_client": "Client",
"table_name": "Name",
"save_btn": "Save",
"client_add": "Add Client",
"client_new": "New Client",
"client_edit": "Edit Client",
"client_identifier": "Identifier",
"ip_address": "IP address",
"client_identifier_desc": "Clients can be identified by the IP address or MAC address. Please note, that using MAC as identifier is possible only if AdGuard Home is also a <0>DHCP server<\/0>",
"form_enter_ip": "Enter IP",
"form_enter_mac": "Enter MAC",
"form_client_name": "Enter client name",
"client_global_settings": "Use global settings",
"client_deleted": "Client \"{{key}}\" successfully deleted",
"client_added": "Client \"{{key}}\" successfully added",
"client_updated": "Client \"{{key}}\" successfully updated",
"table_statistics": "Requests count (last 24 hours)",
"clients_not_found": "No clients found",
"client_confirm_delete": "Are you sure you want to delete client \"{{key}}\"?",
"filter_confirm_delete": "Are you sure you want to delete filter?",
"auto_clients_title": "Clients (runtime)",
"auto_clients_desc": "Data on the clients that use AdGuard Home, but not stored in the configuration",
"access_title": "Access settings",
"access_desc": "Here you can configure access rules for the AdGuard Home DNS server.",
"access_allowed_title": "Allowed clients",
"access_allowed_desc": "A list of CIDR or IP addresses. If configured, AdGuard Home will accept requests from these IP addresses only.",
"access_disallowed_title": "Disallowed clients",
"access_disallowed_desc": "A list of CIDR or IP addresses. If configured, AdGuard Home will drop requests from these IP addresses.",
"access_blocked_title": "Blocked domains",
"access_blocked_desc": "Don't confuse this with filters. AdGuard Home will drop DNS queries with these domains in query's question.",
"access_settings_saved": "Access settings successfully saved",
"updates_checked": "Updates successfully checked",
"updates_version_equal": "AdGuard Home is up-to-date",
"check_updates_now": "Check for updates now",
"dns_privacy": "DNS Privacy",
"setup_dns_privacy_1": "<0>DNS-over-TLS:</0> Use <1>{{address}}</1> string.",
"setup_dns_privacy_2": "<0>DNS-over-HTTPS:</0> Use <1>{{address}}</1> string.",
"setup_dns_privacy_3": "<0>Please note that encrypted DNS protocols are supported only on Android 9. So you need to install additional software for other operating systems.</0><0>Here's a list of software you can use.</0>",
"setup_dns_privacy_android_1": "Android 9 supports DNS-over-TLS natively. To configure it, go to Settings → Network & internet → Advanced → Private DNS and enter your domain name there.",
"setup_dns_privacy_android_2": "<0>AdGuard for Android</0> supports <1>DNS-over-HTTPS</1> and <1>DNS-over-TLS</1>.",
"setup_dns_privacy_android_3": "<0>Intra</0> adds <1>DNS-over-HTTPS</1> support to Android.",
"setup_dns_privacy_ios_1": "<0>DNSCloak</0> supports <1>DNS-over-HTTPS</1>, but in order to configure it to use your own server, you'll need to generate a <2>DNS Stamp</2> for it.",
"setup_dns_privacy_ios_2": "<0>AdGuard for iOS</0> supports <1>DNS-over-HTTPS</1> and <1>DNS-over-TLS</1> setup.",
"setup_dns_privacy_other_title": "Other implementations",
"setup_dns_privacy_other_1": "AdGuard Home itself can be a secure DNS client on any platform.",
"setup_dns_privacy_other_2": "<0>dnsproxy</0> supports all known secure DNS protocols.",
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> supports <1>DNS-over-HTTPS</1>.",
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> supports <1>DNS-over-HTTPS</1>.",
"setup_dns_privacy_other_5": "You will find more implementations <0>here</0> and <1>here</1>.",
"setup_dns_notice": "In order to use <1>DNS-over-HTTPS</1> or <1>DNS-over-TLS</1>, you need to <0>configure Encryption</0> in AdGuard Home settings."
}

View File

@@ -0,0 +1,316 @@
{
"example_upstream_reserved": "puede especificar el DNS de subida <0>para un dominio espec\u00edfico<\/0>",
"upstream_parallel": "Usar consultas paralelas para acelerar la resoluci\u00f3n al consultar simult\u00e1neamente a todos los servidores de subida",
"bootstrap_dns": "Servidores DNS de arranque",
"bootstrap_dns_desc": "Los servidores DNS de arranque se utilizan para resolver las direcciones IP de los resolutores DoH\/DoT que usted especifique como DNS de subida.",
"url_added_successfully": "URL a\u00f1adida correctamente",
"check_dhcp_servers": "Comprobar si hay servidores DHCP",
"save_config": "Guardar configuraci\u00f3n",
"enabled_dhcp": "Servidor DHCP habilitado",
"disabled_dhcp": "Servidor DHCP deshabilitado",
"dhcp_title": "Servidor DHCP (experimental)",
"dhcp_description": "Si su router no proporciona la configuraci\u00f3n DHCP, puede utilizar el propio servidor DHCP incorporado de AdGuard.",
"dhcp_enable": "Habilitar servidor DHCP",
"dhcp_disable": "Deshabilitar servidor DHCP",
"dhcp_not_found": "Es seguro habilitar el servidor DHCP incorporado. No se ha encontrado ning\u00fan servidor DHCP activo en la red, sin embargo le recomendamos que lo vuelva a comprobar manualmente, ya que nuestra prueba autom\u00e1tica no ofrece actualmente una garant\u00eda del 100%.",
"dhcp_found": "Un servidor DHCP activo se encuentra en la red. No es seguro habilitar el servidor DHCP incorporado.",
"dhcp_leases": "Asignaciones DHCP",
"dhcp_static_leases": "DHCP static leases",
"dhcp_leases_not_found": "No se han encontrado asignaciones 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_mac_format": "Formato MAC no v\u00e1lido",
"form_error_positive": "Debe ser mayor que 0",
"dhcp_form_gateway_input": "IP de puerta de enlace",
"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 asignaci\u00f3n DHCP (en segundos)",
"dhcp_form_lease_input": "Duraci\u00f3n de asignaci\u00f3n",
"dhcp_interface_select": "Seleccione la interfaz DHCP",
"dhcp_hardware_address": "Direcci\u00f3n MAC",
"dhcp_ip_addresses": "Direcciones IP",
"dhcp_table_hostname": "Nombre del host",
"dhcp_table_expires": "Expira",
"dhcp_warning": "Si de todos modos desea habilitar el servidor DHCP, aseg\u00farese de que no hay otro servidor DHCP activo en su red. \u00a1De lo contrario, puede dejar sin Internet a los dispositivos conectados!",
"dhcp_error": "No pudimos determinar si hay otro servidor DHCP en la red.",
"dhcp_static_ip_error": "Para poder utilizar el servidor DHCP se debe establecer una direcci\u00f3n IP est\u00e1tica. No hemos podido determinar si esta interfaz de red est\u00e1 configurada utilizando una direcci\u00f3n IP est\u00e1tica. Por favor establezca una direcci\u00f3n IP est\u00e1tica manualmente.",
"dhcp_dynamic_ip_found": "Su sistema utiliza la configuraci\u00f3n de direcci\u00f3n IP din\u00e1mica para la interfaz <0>{{interfaceName}}<\/0>. Para poder utilizar el servidor DHCP se debe establecer una direcci\u00f3n IP est\u00e1tica. Su direcci\u00f3n IP actual es <0>{{ipAddress}}<\/0>. Si presiona el bot\u00f3n Habilitar servidor DHCP, estableceremos autom\u00e1ticamente esta direcci\u00f3n IP como est\u00e1tica.",
"dhcp_lease_added": "Static lease \"{{key}}\" successfully added",
"dhcp_lease_deleted": "Static lease \"{{key}}\" successfully deleted",
"dhcp_new_static_lease": "New static lease",
"dhcp_static_leases_not_found": "No DHCP static leases found",
"dhcp_add_static_lease": "Add static lease",
"delete_confirm": "Are you sure you want to delete \"{{key}}\"?",
"form_enter_hostname": "Enter hostname",
"error_details": "Detalles del error",
"back": "Atr\u00e1s",
"dashboard": "Panel de control",
"settings": "Configuraci\u00f3n",
"filters": "Filtros",
"query_log": "Registro de consultas",
"faq": "Preguntas frecuentes",
"version": "versi\u00f3n",
"address": "direcci\u00f3n",
"on": "Activado",
"off": "Desactivado",
"copyright": "Copyright",
"homepage": "P\u00e1gina de inicio",
"report_an_issue": "Reportar un error",
"privacy_policy": "Pol\u00edtica de privacidad",
"enable_protection": "Habilitar protecci\u00f3n",
"enabled_protection": "Protecci\u00f3n habilitada",
"disable_protection": "Deshabilitar protecci\u00f3n",
"disabled_protection": "Protecci\u00f3n deshabilitada",
"refresh_statics": "Restablecer estad\u00edsticas",
"dns_query": "Consultas DNS",
"blocked_by": "Bloqueado por filtros",
"stats_malware_phishing": "Malware\/phishing bloqueado",
"stats_adult": "Sitios web para adultos bloqueado",
"stats_query_domain": "Dominios m\u00e1s consultados",
"for_last_24_hours": "en las \u00faltimas 24 horas",
"no_domains_found": "No se han encontrado dominios",
"requests_count": "N\u00famero de peticiones",
"top_blocked_domains": "Dominios m\u00e1s bloqueados",
"top_clients": "Clientes m\u00e1s frecuentes",
"no_clients_found": "No se han encontrado clientes",
"general_statistics": "Estad\u00edsticas generales",
"number_of_dns_query_24_hours": "N\u00famero de consultas DNS procesadas durante las \u00faltimas 24 horas",
"number_of_dns_query_blocked_24_hours": "N\u00famero de peticiones DNS bloqueadas por los filtros y listas de bloqueo de hosts",
"number_of_dns_query_blocked_24_hours_by_sec": "N\u00famero de peticiones DNS bloqueadas por el m\u00f3dulo de seguridad de navegaci\u00f3n de AdGuard",
"number_of_dns_query_blocked_24_hours_adult": "N\u00famero de sitios web para adultos bloqueado",
"enforced_save_search": "B\u00fasquedas seguras forzadas",
"number_of_dns_query_to_safe_search": "N\u00famero de peticiones DNS a los motores de b\u00fasqueda para los que se aplic\u00f3 la b\u00fasqueda segura forzada",
"average_processing_time": "Tiempo promedio de procesamiento",
"average_processing_time_hint": "Tiempo promedio en milisegundos al procesar una petici\u00f3n 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 la configuraci\u00f3n de <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 la API de b\u00fasqueda amigable con la privacidad para realizar la comprobaci\u00f3n: solo se env\u00eda al servidor un prefijo corto del nombre de dominio con hash SHA256.",
"use_adguard_parental": "Usar el 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 del 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, DuckDuckGo y Yandex.",
"no_servers_specified": "No hay servidores especificados",
"no_settings": "Sin configuraci\u00f3n",
"general_settings": "Configuraci\u00f3n general",
"dns_settings": "DNS settings",
"encryption_settings": "Encryption settings",
"dhcp_settings": "DHCP settings",
"client_settings": "Client settings",
"upstream_dns": "Servidores DNS de subida",
"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 DNS de subida. Utilice el prefijo tls:\/\/ para los servidores DNS mediante TLS.",
"test_upstream_btn": "Probar DNS de subida",
"apply_btn": "Aplicar",
"disabled_filtering_toast": "Filtrado deshabilitado",
"enabled_filtering_toast": "Filtrado habilitado",
"disabled_safe_browsing_toast": "B\u00fasqueda segura deshabilitada",
"enabled_safe_browsing_toast": "B\u00fasqueda segura habilitada",
"disabled_parental_toast": "Control parental deshabilitado",
"enabled_parental_toast": "Control parental habilitado",
"disabled_safe_search_toast": "B\u00fasqueda segura deshabilitada",
"enabled_save_search_toast": "B\u00fasqueda segura habilitada",
"enabled_table_header": "Habilitado",
"name_table_header": "Nombre",
"filter_url_table_header": "URL del filtro",
"rules_count_table_header": "N\u00famero de reglas",
"last_time_updated_table_header": "\u00daltima actualizaci\u00f3n",
"actions_table_header": "Acciones",
"edit_table_action": "Editar",
"delete_table_action": "Eliminar",
"filters_and_hosts": "Filtros y listas de bloqueo de hosts",
"filters_and_hosts_hint": "AdGuard Home entiende las reglas b\u00e1sicas de bloqueo y la sintaxis de los archivos hosts.",
"no_filters_added": "No hay filtros a\u00f1adidos",
"add_filter_btn": "A\u00f1adir filtro",
"cancel_btn": "Cancelar",
"enter_name_hint": "Ingrese el nombre",
"enter_url_hint": "Ingrese la URL",
"check_updates_btn": "Buscar actualizaciones",
"new_filter_btn": "Nueva suscripci\u00f3n a filtro",
"enter_valid_filter_url": "Ingrese una URL v\u00e1lida para suscribirse a un filtro o archivo hosts.",
"custom_filter_rules": "Reglas de filtrado personalizado",
"custom_filter_rules_hint": "Ingrese una regla por l\u00ednea. Puede utilizar reglas de bloqueo o la sintaxis de los archivos hosts.",
"examples_title": "Ejemplos",
"example_meaning_filter_block": "bloquea el acceso al dominio ejemplo.org\ny a todos sus subdominios",
"example_meaning_filter_whitelist": "desbloquea el acceso al dominio ejemplo.org y a todos 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_regex_meaning": "bloquear el acceso a los dominios que coincidan con la expresi\u00f3n regular especificada",
"example_upstream_regular": "DNS regular (mediante UDP)",
"example_upstream_dot": "cifrado <0>DNS mediante TLS<\/0>",
"example_upstream_doh": "cifrado <0>DNS mediante HTTPS<\/0>",
"example_upstream_sdns": "puedes usar <0>DNS Stamps<\/0> para <1>DNSCrypt<\/1> o resolutores <2>DNS mediante HTTPS<\/2>",
"example_upstream_tcp": "DNS regular (mediante TCP)",
"all_filters_up_to_date_toast": "Todos los filtros ya est\u00e1n actualizados",
"updated_upstream_dns_toast": "Servidores DNS de subida actualizados",
"dns_test_ok_toast": "Los servidores DNS especificados funcionan correctamente",
"dns_test_not_ok_toast": "Servidor \"{{key}}\": no se puede utilizar, por favor revise si lo ha escrito correctamente",
"unblock_btn": "Desbloquear",
"block_btn": "Bloquear",
"time_table_header": "Hora",
"domain_name_table_header": "Nombre del 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": "Deshabilitar registro",
"download_log_file_btn": "Descargar archivo de registro",
"refresh_btn": "Actualizar",
"enabled_log_btn": "Habilitar registro",
"last_dns_queries": "\u00daltimas 5000 consultas DNS",
"previous_btn": "Atr\u00e1s",
"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": "Reglas de filtrado personalizado actualizadas",
"rule_removed_from_custom_filtering_toast": "Regla eliminada de las reglas de filtrado personalizado",
"rule_added_to_custom_filtering_toast": "Regla a\u00f1adida a las reglas de filtrado personalizado",
"query_log_disabled_toast": "Registro de consultas deshabilitado",
"query_log_enabled_toast": "Registro de consultas habilitado",
"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}}",
"install_welcome_title": "\u00a1Bienvenido a AdGuard Home!",
"install_welcome_desc": "AdGuard Home es un servidor DNS para bloqueo de anuncios y rastreadores a nivel de red. Su prop\u00f3sito es permitirle controlar toda su red y todos sus dispositivos, y no requiere el uso de un programa del lado del cliente.",
"install_settings_title": "Interfaz web de administraci\u00f3n",
"install_settings_listen": "Interfaz de escucha",
"install_settings_port": "Puerto",
"install_settings_interface_link": "Su interfaz web de administraci\u00f3n de AdGuard Home estar\u00e1 disponible en las siguientes direcciones:",
"form_error_port": "Ingrese un valor de puerto v\u00e1lido",
"install_settings_dns": "Servidor DNS",
"install_settings_dns_desc": "Deber\u00e1 configurar sus dispositivos o router para usar el servidor DNS en las siguientes direcciones:",
"install_settings_all_interfaces": "Todas las interfaces",
"install_auth_title": "Autenticaci\u00f3n",
"install_auth_desc": "Se recomienda encarecidamente configurar la autenticaci\u00f3n por contrase\u00f1a para la interfaz web de administraci\u00f3n de AdGuard Home. Incluso si solo es accesible en su red local, es importante que est\u00e9 protegido contra el acceso no autorizado.",
"install_auth_username": "Usuario",
"install_auth_password": "Contrase\u00f1a",
"install_auth_confirm": "Confirmar contrase\u00f1a",
"install_auth_username_enter": "Ingrese su nombre de usuario",
"install_auth_password_enter": "Ingrese su contrase\u00f1a",
"install_step": "Paso",
"install_devices_title": "Configure sus dispositivos",
"install_devices_desc": "Para que AdGuard Home comience a funcionar, necesita configurar sus dispositivos para usarlo.",
"install_submit_title": "\u00a1Felicitaciones!",
"install_submit_desc": "El proceso de configuraci\u00f3n ha finalizado y est\u00e1 listo para comenzar a usar AdGuard Home.",
"install_devices_router": "Router",
"install_devices_router_desc": "Esta configuraci\u00f3n cubrir\u00e1 autom\u00e1ticamente todos los dispositivos conectados a su router dom\u00e9stico y no necesitar\u00e1 configurar cada uno de ellos manualmente.",
"install_devices_address": "El servidor DNS de AdGuard Home est\u00e1 escuchando en las siguientes direcciones",
"install_devices_router_list_1": "Abra las preferencias de su router. Por lo general, puede acceder a \u00e9l desde su navegador a trav\u00e9s de una URL (como http:\/\/192.168.0.1\/ o http:\/\/192.168.1.1\/). Se le puede pedir que ingrese la contrase\u00f1a. Si no lo recuerda, a menudo puede restablecer la contrase\u00f1a presionando un bot\u00f3n en el router. Algunos routers requieren una aplicaci\u00f3n espec\u00edfica, que en ese caso ya deber\u00eda estar instalada en su computadora\/tel\u00e9fono.",
"install_devices_router_list_2": "Busque la configuraci\u00f3n de DHCP\/DNS. Busque las letras DNS junto a un campo que permita ingresar dos o tres grupos de n\u00fameros, cada uno dividido en cuatro grupos de uno a tres d\u00edgitos.",
"install_devices_router_list_3": "Ingrese las direcciones de su servidor AdGuard Home all\u00ed.",
"install_devices_windows_list_1": "Abra el Panel de control a trav\u00e9s del men\u00fa Inicio o en el buscador de Windows.",
"install_devices_windows_list_2": "Vaya a la categor\u00eda Red e Internet, luego al Centro de redes y recursos compartidos.",
"install_devices_windows_list_3": "En el lado izquierdo de la pantalla, busque Cambiar la configuraci\u00f3n del adaptador y haga clic en \u00e9l.",
"install_devices_windows_list_4": "Seleccione su conexi\u00f3n activa, luego haga clic derecho sobre ella y elija Propiedades.",
"install_devices_windows_list_5": "Busque el Protocolo de Internet versi\u00f3n 4 (TCP\/IP) en la lista, selecci\u00f3nelo y luego haga clic en Propiedades nuevamente.",
"install_devices_windows_list_6": "Elija Usar las siguientes direcciones de servidor DNS e ingrese las direcciones de su servidor AdGuard Home.",
"install_devices_macos_list_1": "Haga clic en el icono de Apple y vaya a Preferencias del sistema.",
"install_devices_macos_list_2": "Haga clic en Red.",
"install_devices_macos_list_3": "Seleccione la primera conexi\u00f3n de la lista y haga clic en Avanzado.",
"install_devices_macos_list_4": "Seleccione la pesta\u00f1a DNS e ingrese las direcciones de su servidor AdGuard Home.",
"install_devices_android_list_1": "En la pantalla de inicio del men\u00fa Android, pulse en Configuraci\u00f3n.",
"install_devices_android_list_2": "Pulse Wi-Fi en el men\u00fa. Aparecer\u00e1 la pantalla que lista todas las redes disponibles (es imposible configurar DNS personalizados para la conexi\u00f3n m\u00f3vil).",
"install_devices_android_list_3": "Mantenga presionada la red a la que est\u00e1 conectado y pulse Modificar red.",
"install_devices_android_list_4": "En algunos dispositivos, es posible que deba marcar la casilla Avanzado para ver m\u00e1s configuraciones. Para ajustar la configuraci\u00f3n DNS de su Android, deber\u00e1 cambiar la configuraci\u00f3n de IP de DHCP a Est\u00e1tica.",
"install_devices_android_list_5": "Cambie los valores de DNS 1 y DNS 2 a las direcciones de su servidor AdGuard Home.",
"install_devices_ios_list_1": "En la pantalla de inicio, pulse en Configuraci\u00f3n.",
"install_devices_ios_list_2": "Elija Wi-Fi en el men\u00fa de la izquierda (es imposible configurar DNS para redes m\u00f3viles).",
"install_devices_ios_list_3": "Pulse sobre el nombre de la red activa en ese momento.",
"install_devices_ios_list_4": "En el campo DNS ingrese las direcciones de su servidor AdGuard Home.",
"get_started": "Comenzar",
"next": "Siguiente",
"open_dashboard": "Abrir panel de control",
"install_saved": "Guardado correctamente",
"encryption_title": "Cifrado",
"encryption_desc": "Soporte de cifrado (HTTPS\/TLS) tanto para DNS como para la interfaz web de administraci\u00f3n",
"encryption_config_saved": "Configuraci\u00f3n de cifrado guardado",
"encryption_server": "Nombre del servidor",
"encryption_server_enter": "Ingrese su nombre de dominio",
"encryption_server_desc": "Para utilizar HTTPS, debe ingresar el nombre del servidor que coincida con su certificado SSL.",
"encryption_redirect": "Redireccionar a HTTPS autom\u00e1ticamente",
"encryption_redirect_desc": "Si est\u00e1 marcado, AdGuard Home redireccionar\u00e1 autom\u00e1ticamente de HTTP a las direcciones HTTPS.",
"encryption_https": "Puerto HTTPS",
"encryption_https_desc": "Si el puerto HTTPS est\u00e1 configurado, la interfaz de administraci\u00f3n de AdGuard Home ser\u00e1 accesible a trav\u00e9s de HTTPS, y tambi\u00e9n proporcionar\u00e1 DNS mediante HTTPS en la ubicaci\u00f3n '\/dns-query'.",
"encryption_dot": "Puerto DNS mediante TLS",
"encryption_dot_desc": "Si este puerto est\u00e1 configurado, AdGuard Home ejecutar\u00e1 un servidor DNS mediante TLS en este puerto.",
"encryption_certificates": "Certificados",
"encryption_certificates_desc": "Para utilizar el cifrado, debe proporcionar una cadena de certificados SSL v\u00e1lida para su dominio. Puede obtener un certificado gratuito en <0>{{link}}<\/0> o puede comprarlo en una de las autoridades de certificaci\u00f3n de confianza.",
"encryption_certificates_input": "Copie\/pegue aqu\u00ed sus certificados codificados PEM.",
"encryption_status": "Estado",
"encryption_expire": "Expira",
"encryption_key": "Clave privada",
"encryption_key_input": "Copie\/pegue aqu\u00ed su clave privada codificada PEM para su certificado.",
"encryption_enable": "Habilitar cifrado (HTTPS, DNS mediante HTTPS y DNS mediante TLS)",
"encryption_enable_desc": "Si el cifrado est\u00e1 habilitado, la interfaz de administraci\u00f3n de AdGuard Home funcionar\u00e1 a trav\u00e9s de HTTPS, y el servidor DNS escuchar\u00e1 las peticiones DNS mediante HTTPS y DNS mediante TLS.",
"encryption_chain_valid": "La cadena de certificado es v\u00e1lida",
"encryption_chain_invalid": "La cadena de certificado no es v\u00e1lida",
"encryption_key_valid": "Esta es una clave privada {{type}} v\u00e1lida",
"encryption_key_invalid": "Esta es una clave privada {{type}} no v\u00e1lida",
"encryption_subject": "Asunto",
"encryption_issuer": "Emisor",
"encryption_hostnames": "Nombres de hosts",
"encryption_reset": "\u00bfEst\u00e1 seguro de que desea restablecer la configuraci\u00f3n de cifrado?",
"topline_expiring_certificate": "Su certificado SSL est\u00e1 a punto de expirar. Actualice la <0>configuraci\u00f3n del cifrado<\/0>.",
"topline_expired_certificate": "Su certificado SSL ha expirado. Actualice la <0>configuraci\u00f3n del cifrado<\/0>.",
"form_error_port_range": "Ingrese el valor del puerto en el rango de 80 a 65535",
"form_error_port_unsafe": "Este es un puerto inseguro",
"form_error_equal": "No deber\u00eda ser igual",
"form_error_password": "La contrase\u00f1a no coincide",
"reset_settings": "Restablecer configuraci\u00f3n",
"update_announcement": "\u00a1AdGuard Home {{version}} ya est\u00e1 disponible! <0>Haga clic aqu\u00ed<\/0> para m\u00e1s informaci\u00f3n.",
"setup_guide": "Gu\u00eda de configuraci\u00f3n",
"dns_addresses": "Direcciones DNS",
"down": "Abajo",
"fix": "Corregir",
"dns_providers": "Aqu\u00ed hay una <0>lista de proveedores DNS<\/0> conocidos para elegir.",
"update_now": "Actualizar ahora",
"update_failed": "Error en la actualizaci\u00f3n autom\u00e1tica. Por favor <a href='https:\/\/github.com\/AdguardTeam\/AdGuardHome\/wiki\/Getting-Started#update'>siga los pasos<\/a> para actualizar manualmente.",
"processing_update": "Por favor espere, AdGuard Home se est\u00e1 actualizando",
"clients_title": "Clientes",
"clients_desc": "Configurar dispositivos conectados con AdGuard Home",
"settings_global": "Global",
"settings_custom": "Personalizado",
"table_client": "Cliente",
"table_name": "Nombre",
"save_btn": "Guardar",
"client_add": "A\u00f1adir cliente",
"client_new": "Cliente nuevo",
"client_edit": "Editar cliente",
"client_identifier": "Identificador",
"ip_address": "Direcci\u00f3n IP",
"client_identifier_desc": "Los clientes pueden ser identificados por la direcci\u00f3n IP o MAC. Tenga en cuenta que el uso de MAC como identificador solo es posible si AdGuard Home tambi\u00e9n es un <0>servidor DHCP<\/0>.",
"form_enter_ip": "Ingresar IP",
"form_enter_mac": "Ingresar MAC",
"form_client_name": "Ingrese el nombre del cliente",
"client_global_settings": "Usar configuraci\u00f3n global",
"client_deleted": "Cliente \"{{key}}\" eliminado correctamente",
"client_added": "Cliente \"{{key}}\" a\u00f1adido correctamente",
"client_updated": "Cliente \"{{key}}\" actualizado correctamente",
"table_statistics": "N\u00famero de peticiones (\u00faltimas 24 horas)",
"clients_not_found": "No se han encontrado clientes",
"client_confirm_delete": "\u00bfEst\u00e1 seguro de que desea eliminar el cliente \"{{key}}\"?",
"filter_confirm_delete": "Are you sure you want to delete filter?",
"auto_clients_title": "Clientes (activos)",
"auto_clients_desc": "Datos de los clientes que utilizan AdGuard Home pero que no est\u00e1n almacenados en la configuraci\u00f3n",
"access_title": "Access settings",
"access_desc": "Here you can configure access rules for the AdGuard Home DNS server.",
"access_allowed_title": "Allowed clients",
"access_allowed_desc": "A list of CIDR or IP addresses. If configured, AdGuard Home will accept requests from these IP addresses only.",
"access_disallowed_title": "Disallowed clients",
"access_disallowed_desc": "A list of CIDR or IP addresses. If configured, AdGuard Home will drop requests from these IP addresses.",
"access_blocked_title": "Blocked domains",
"access_blocked_desc": "Don't confuse this with filters. AdGuard Home will drop DNS queries with these domains in query's question.",
"access_settings_saved": "Access settings successfully saved"
}

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": "<0>DNS-au-dessus-de-TLS<\/0> chiffr\u00e9",
"example_upstream_doh": "<0>DNS-au-dessus-de-HTTPS<\/0> chiffr\u00e9",
"example_upstream_sdns": "vous pouvez utiliser <0>DNS Stamps<\/0> pour <1>DNSCrypt<\/1> ou les resolveurs <2>DNS-au-dessus-de-HTTPS<\/2>",
"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 <0>DNS-over-TLS<\/0>",
"example_upstream_doh": "\u6697\u53f7\u5316\u3055\u308c\u3066\u3044\u308b <0>DNS-over-HTTPS<\/0>",
"example_upstream_sdns": "<0>DNSCrypt<\/0> \u307e\u305f\u306f <1>DNS-over-HTTPS<\/1> \u30ea\u30be\u30eb\u30d0\u306e\u305f\u3081\u306b <2>DNS Stamps<\/2> \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,316 @@
{
"example_upstream_reserved": "Voc\u00ea pode especificar um DNS upstream <0>para um dom\u00ednio(s) especifico<\/0>",
"upstream_parallel": "Usar consultas paralelas para acelerar a resolu\u00e7\u00e3o consultando simultaneamente todos os servidores upstream",
"bootstrap_dns": "Servidores DNS de inicializa\u00e7\u00e3o",
"bootstrap_dns_desc": "Servidores DNS de inicializa\u00e7\u00e3o s\u00e3o usados para resolver endere\u00e7os IP dos resolvedores DoH\/DoT que voc\u00ea especifica como upstreams.",
"url_added_successfully": "URL adicionada com sucesso",
"check_dhcp_servers": "Verificar por 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_static_leases": "DHCP static leases",
"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_mac_format": "Invalid MAC format",
"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",
"dhcp_warning": "Se voc\u00ea quiser ativar o servidor DHCP, verifique se n\u00e3o h\u00e1 outro servidor DHCP ativo na sua rede. Caso contr\u00e1rio, a internet pode parar de funcionar para outros dispositivos conectados!",
"dhcp_error": "N\u00e3o foi poss\u00edvel determinar se existe outro servidor DHCP na rede.",
"dhcp_static_ip_error": "Para usar o servidor DHCP, voc\u00ea deve definir um endere\u00e7o IP est\u00e1tico. N\u00e3o conseguimos determinar se essa interface de rede est\u00e1 configurada usando o endere\u00e7o de IP est\u00e1tico. Por favor, defina um endere\u00e7o IP est\u00e1tico manualmente.",
"dhcp_dynamic_ip_found": "Seu sistema usa a configura\u00e7\u00e3o de endere\u00e7o IP din\u00e2mico para a interface <0>{{interfaceName}}<\/0>. Para usar o servidor DHCP, voc\u00ea deve definir um endere\u00e7o de IP est\u00e1tico. Seu endere\u00e7o IP atual \u00e9 <0> {{ipAddress}} <\/ 0>. Vamos definir automaticamente este endere\u00e7o IP como est\u00e1tico se voc\u00ea pressionar o bot\u00e3o Ativar DHCP.",
"dhcp_lease_added": "Static lease \"{{key}}\" successfully added",
"dhcp_lease_deleted": "Static lease \"{{key}}\" successfully deleted",
"dhcp_new_static_lease": "New static lease",
"dhcp_static_leases_not_found": "No DHCP static leases found",
"dhcp_add_static_lease": "Add static lease",
"delete_confirm": "Are you sure you want to delete \"{{key}}\"?",
"form_enter_hostname": "Enter hostname",
"error_details": "Detalhes do erro",
"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",
"privacy_policy": "Pol\u00edtica de privacidade",
"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",
"dns_settings": "DNS settings",
"encryption_settings": "Encryption settings",
"dhcp_settings": "DHCP settings",
"client_settings": "Client settings",
"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.",
"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",
"edit_table_action": "Edit",
"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_regex_meaning": "bloqueia o acesso aos dom\u00ednios correspondentes \u00e0 express\u00e3o regular especificada",
"example_upstream_regular": "DNS regular (atrav\u00e9s do UDP)",
"example_upstream_dot": "<0>DNS-sobre-TLS<\/0> criptografado",
"example_upstream_doh": "<0>DNS-sobre-HTTPS<\/0> criptografado",
"example_upstream_sdns": "Voc\u00ea pode usar <0>DNS Stamps<\/0>para o <1>DNSCrypt<\/1>ou usar os resolvedores <2>DNS-sobre-HTTPS<\/2>",
"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}}",
"install_welcome_title": "Bem-vindo(a) ao AdGuard Home!",
"install_welcome_desc": "O AdGuard Home \u00e9 um servidor de DNS para bloqueio de an\u00fancios e rastreamento em toda a rede. Sua finalidade \u00e9 permitir que voc\u00ea controle toda a sua rede e seus dispositivos sem precisar ter um programa instalado.",
"install_settings_title": "Interface web de administrador",
"install_settings_listen": "Interface de escuta",
"install_settings_port": "Porta",
"install_settings_interface_link": "A interface web de administrador do AdGuard estar\u00e1 dispon\u00edvel nos seguintes endere\u00e7os:",
"form_error_port": "Digite uma porta v\u00e1lida",
"install_settings_dns": "Servidor DNS",
"install_settings_dns_desc": "Voc\u00ea precisa configurar seu dispositivo ou roteador para usar o servidor DNS nos seguintes endere\u00e7os:",
"install_settings_all_interfaces": "Todas interfaces",
"install_auth_title": "Autentica\u00e7\u00e3o",
"install_auth_desc": "\u00c9 altamente recomend\u00e1vel configurar uma senha de autentica\u00e7\u00e3o na interface web de administrador do AdGuard Home. Mesmo que seja acess\u00edvel apenas em sua rede local, ainda \u00e9 importante proteg\u00ea-lo contra o acesso irrestrito.",
"install_auth_username": "Nome de usu\u00e1rio",
"install_auth_password": "Senha",
"install_auth_confirm": "Confirmar senha",
"install_auth_username_enter": "Digite o nome de usu\u00e1rio",
"install_auth_password_enter": "Digite a senha",
"install_step": "Passo",
"install_devices_title": "Configure seus dispositivos",
"install_devices_desc": "Para que o AdGuard Home comece a funcionar, voc\u00ea precisa configurar seus dispositivos para us\u00e1-lo.",
"install_submit_title": "Parab\u00e9ns!",
"install_submit_desc": "O procedimento de configura\u00e7\u00e3o est\u00e1 conclu\u00eddo e voc\u00ea est\u00e1 pronto para come\u00e7ar a usar o AdGuard Home.",
"install_devices_router": "Roteador",
"install_devices_router_desc": "Esta configura\u00e7\u00e3o cobrir\u00e1 automaticamente todos os dispositivos conectados ao seu roteador dom\u00e9stico e voc\u00ea n\u00e3o ir\u00e1 precisar configurar cada um deles manualmente.",
"install_devices_address": "O servidor de DNS do AdGuard Home est\u00e1 capturando os seguintes endere\u00e7os",
"install_devices_router_list_1": "Abra as configura\u00e7\u00f5es do seu roteador\nNo navegador digite o IP do roteador, o padr\u00e3o \u00e9 (http:\/\/192.168.0.1\/ ou http:\/\/192.168.1.1\/), e o login e senha \u00e9 admin\/admin; Se voc\u00ea n\u00e3o se lembra da senha, voc\u00ea pode redefinir a senha rapidamente pressionando um bot\u00e3o no pr\u00f3prio roteador. Alguns roteadores t\u00eam um aplicativo espec\u00edfico que j\u00e1 deve estar instalado em seu computador\/telefone.",
"install_devices_router_list_2": "Encontre as Configura\u00e7\u00f5es de DNS. Procure as letras DNS ao lado de um campo que permite dois ou tr\u00eas conjuntos de n\u00fameros, cada um dividido em quatro grupos de um a tr\u00eas n\u00fameros.",
"install_devices_router_list_3": "Digite aqui seu servidor do AdGuard Home.",
"install_devices_windows_list_1": "Abra o Painel de Controle pelo Menu Iniciar ou pela Pesquisa do Windows.",
"install_devices_windows_list_2": "Entre na categoria Rede e Internet e depois clique em Central de Rede e Compartilhamento.",
"install_devices_windows_list_3": "No lado esquerdo da janela clique em Alterar as configura\u00e7\u00f5es do adaptador.",
"install_devices_windows_list_4": "Selecione sua atual conex\u00e3o, clique nela com o bot\u00e3o direito do mouse e depois clique em Propriedades.",
"install_devices_windows_list_5": "Procure na lista por Internet Protocol Version 4 (TCP\/IP), selecione e clique em Propriedades novamente.",
"install_devices_windows_list_6": "Marque usar os seguintes endere\u00e7os de servidor DNS e digite os endere\u00e7os do servidores do AdGuard Home.",
"install_devices_macos_list_1": "Clique na \u00edcone da Apple e depois em Prefer\u00eancias do Sistema.",
"install_devices_macos_list_2": "Clique em Rede.",
"install_devices_macos_list_3": "Selecione a primeira conex\u00e3o da lista e clique em Avan\u00e7ado.",
"install_devices_macos_list_4": "Selecione a guia DNS e digite os endere\u00e7os dos servidores do AdGuard Home.",
"install_devices_android_list_1": "Na tela inicial do menu Android, toque em Configura\u00e7\u00f5es.",
"install_devices_android_list_2": "Toque em Wi-Fi. A tela listando todas as redes ser\u00e1 exibida (n\u00e3o \u00e9 poss\u00edvel configurar DNS personalizado para uma conex\u00e3o de dados m\u00f3veis)",
"install_devices_android_list_3": "Pressione prolongadamente a rede para a qual voc\u00ea est\u00e1 conectado e toque em Modificar rede",
"install_devices_android_list_4": "Em alguns dispositivos, talvez seja necess\u00e1rio marcar a caixa Avan\u00e7ado para ver as outras configura\u00e7\u00f5es. Para ajustar suas configura\u00e7\u00f5es de DNS do Android, voc\u00ea precisar\u00e1 alternar as configura\u00e7\u00f5es de IP de DHCP para Est\u00e1tico.",
"install_devices_android_list_5": "Altere o conjunto dos valores DNS 1 e DNS 2 para os endere\u00e7os de servidores do AdGuard Home.",
"install_devices_ios_list_1": "Na tela incial, toque em Ajustes.",
"install_devices_ios_list_2": "Selecione Wi-Fi no menu esquerdo (n\u00e3o \u00e9 poss\u00edvel configurar o DNS em conex\u00f5es de dados m\u00f3veis).",
"install_devices_ios_list_3": "Toque no nome da rede atualmente ativa.",
"install_devices_ios_list_4": "No campo DNS, digite os endere\u00e7os dos servidores do AdGuard Home.",
"get_started": "Come\u00e7ar",
"next": "Pr\u00f3ximo",
"open_dashboard": "Abrir painel",
"install_saved": "Salvo com sucesso",
"encryption_title": "Criptografia",
"encryption_desc": "Suporte a criptografia (HTTPS\/TLS) para DNS e interface de administra\u00e7\u00e3o web",
"encryption_config_saved": "Configura\u00e7\u00e3o de criptografia salva",
"encryption_server": "Nome do servidor",
"encryption_server_enter": "Digite seu nome de dom\u00ednio",
"encryption_server_desc": "Para usar o protocolo HTTPS, voc\u00ea precisa digitar o nome do servidor que corresponde ao seu certificado SSL.",
"encryption_redirect": "Redirecionar automaticamente para HTTPS",
"encryption_redirect_desc": "Se marcado, o AdGuard Home ir\u00e1 redirecionar automaticamente os endere\u00e7os HTTP para HTTPS.",
"encryption_https": "Porta HTTPS",
"encryption_https_desc": "Se a porta HTTPS estiver configurada, a interface administrativa do AdGuard Home ser\u00e1 acess\u00edvel via HTTPS e tamb\u00e9m fornecer\u00e1 o DNS-sobre-HTTPS no local '\/dns-query'.",
"encryption_dot": "Porta DNS-sobre-TLS",
"encryption_dot_desc": "Se essa porta estiver configurada, o AdGuard Home ir\u00e1 executar o servidor DNS-sobre- TSL nesta porta.",
"encryption_certificates": "Certificados",
"encryption_certificates_desc": "Para usar criptografia, voc\u00ea precisa fornecer uma cadeia de certificados SSL v\u00e1lida para seu dom\u00ednio. Voc\u00ea pode obter um certificado gratuito em <0> {{link}}<\/0> ou pode compr\u00e1-lo de uma das autoridades de certifica\u00e7\u00e3o confi\u00e1veis.",
"encryption_certificates_input": "Copie\/cole aqui seu certificado codificado em PEM.",
"encryption_status": "Status",
"encryption_expire": "Expira",
"encryption_key": "Chave privada",
"encryption_key_input": "Copie\/cole aqui a chave privada codificada em PEM para seu certificado.",
"encryption_enable": "Ativar criptografia (HTTPS, DNS-sobre-HTTPS e DNS-sobre-TLS)",
"encryption_enable_desc": "Se a criptografia estiver ativada, a interface administrativa do AdGuard Home funcionar\u00e1 em HTTPS, o servidor DNS ir\u00e1 capturar as solicita\u00e7\u00f5es por meio do DNS-sobre-HTTPS e DNS-sobre-TLS.",
"encryption_chain_valid": "Cadeia de chave v\u00e1lida.",
"encryption_chain_invalid": "A cadeia de certificado \u00e9 inv\u00e1lida",
"encryption_key_valid": "Esta \u00e9 uma chave privada {{type}} v\u00e1lida",
"encryption_key_invalid": "Esta \u00e9 uma chave privada {{type}} inv\u00e1lida",
"encryption_subject": "Assunto",
"encryption_issuer": "Emissor",
"encryption_hostnames": "Hostnames",
"encryption_reset": "Voc\u00ea tem certeza de que deseja redefinir a configura\u00e7\u00e3o de criptografia?",
"topline_expiring_certificate": "Seu certificado SSL est\u00e1 prestes a expirar. Atualize suas <0>configura\u00e7\u00f5es de criptografia<\/]0>",
"topline_expired_certificate": "Seu certificado SSL est\u00e1 expirado. Atualize suas <0>configura\u00e7\u00f5es de criptografia<\/0>",
"form_error_port_range": "Digite um porta entre 80 e 65535",
"form_error_port_unsafe": "Esta porta n\u00e3o \u00e9 segura",
"form_error_equal": "N\u00e3o deve ser igual",
"form_error_password": "Senhas n\u00e3o coincidem",
"reset_settings": "Redefinir configura\u00e7\u00f5es",
"update_announcement": "AdGuard Home {{version}} est\u00e1 dispon\u00edvel!<0>Clique aqui<\/0> para mais informa\u00e7\u00f5es.",
"setup_guide": "Guia de configura\u00e7\u00e3o",
"dns_addresses": "Endere\u00e7os DNS",
"down": "Caiu",
"fix": "Corrigido",
"dns_providers": "Aqui est\u00e1 uma <0>lista de provedores de DNS conhecidos<\/0> para escolher.",
"update_now": "Update now",
"update_failed": "Auto-update failed. Please <a href='https:\/\/github.com\/AdguardTeam\/AdGuardHome\/wiki\/Getting-Started#update'>follow the steps<\/a> to update manually.",
"processing_update": "Please wait, AdGuard Home is being updated",
"clients_title": "Clients",
"clients_desc": "Configure devices connected to AdGuard Home",
"settings_global": "Global",
"settings_custom": "Custom",
"table_client": "Client",
"table_name": "Name",
"save_btn": "Save",
"client_add": "Add Client",
"client_new": "New Client",
"client_edit": "Edit Client",
"client_identifier": "Identifier",
"ip_address": "IP address",
"client_identifier_desc": "Clients can be identified by the IP address or MAC address. Please note, that using MAC as identifier is possible only if AdGuard Home is also a <0>DHCP server<\/0>",
"form_enter_ip": "Enter IP",
"form_enter_mac": "Enter MAC",
"form_client_name": "Enter client name",
"client_global_settings": "Use global settings",
"client_deleted": "Client \"{{key}}\" successfully deleted",
"client_added": "Client \"{{key}}\" successfully added",
"client_updated": "Client \"{{key}}\" successfully updated",
"table_statistics": "Requests count (last 24 hours)",
"clients_not_found": "No clients found",
"client_confirm_delete": "Are you sure you want to delete client \"{{key}}\"?",
"filter_confirm_delete": "Are you sure you want to delete filter?",
"auto_clients_title": "Clients (runtime)",
"auto_clients_desc": "Data on the clients that use AdGuard Home, but not stored in the configuration",
"access_title": "Access settings",
"access_desc": "Here you can configure access rules for the AdGuard Home DNS server.",
"access_allowed_title": "Allowed clients",
"access_allowed_desc": "A list of CIDR or IP addresses. If configured, AdGuard Home will accept requests from these IP addresses only.",
"access_disallowed_title": "Disallowed clients",
"access_disallowed_desc": "A list of CIDR or IP addresses. If configured, AdGuard Home will drop requests from these IP addresses.",
"access_blocked_title": "Blocked domains",
"access_blocked_desc": "Don't confuse this with filters. AdGuard Home will drop DNS queries with these domains in query's question.",
"access_settings_saved": "Access settings successfully saved"
}

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 <0>DNS-\u043f\u043e\u0432\u0435\u0440\u0445-TLS<\/0>",
"example_upstream_doh": "\u0437\u0430\u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 <0>DNS-\u043f\u043e\u0432\u0435\u0440\u0445-HTTPS<\/0>",
"example_upstream_sdns": "\u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c <0>DNS Stamps<\/0> \u0434\u043b\u044f <1>DNSCrypt<\/1> \u0438\u043b\u0438 <2>DNS-over-HTTPS<\/2> \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,250 @@
{
"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",
"dhcp_warning": "Om du vill koppla in den inbyggda DHCP-servern m\u00e5ste du f\u00f6rs\u00e4kra dig om att det finns n\u00e5gra andra DHCP-servar som i s\u00e5 fall riskerar att orsaka avbrott p\u00e5 internet!",
"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_regex_meaning": "blockera \u00e5tkomst till en dom\u00e4n som matchar det angivna uttrycket",
"example_upstream_regular": "vanlig DNS (\u00f6ver UDP)",
"example_upstream_dot": "krypterat <0>DNS-over-TLS<\/0>",
"example_upstream_doh": "krypterat <0>DNS-over-HTTPS<\/0>",
"example_upstream_sdns": "Du kan anv\u00e4nda <0>DNS-stamps<\/0> f\u00f6r <1>DNSCrypt<\/1> eller <2>DNS-\u00f6ver-HTTPS<\/2>\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}}",
"install_welcome_title": "V\u00e4lkommen till AdGuard Home!",
"install_welcome_desc": "AdGuard Home \u00e4r en DNS-server f\u00f6r n\u00e4tverkst\u00e4ckande annons- och sp\u00e5rningsblockering. Dess syfte \u00e4r att de dig kontroll \u00f6ver hela n\u00e4tverket och alla dina enheter, utan behov av att anv\u00e4nda klientbaserade program.",
"install_settings_title": "Administrat\u00f6rens webbgr\u00e4nssnitt",
"install_settings_listen": "\u00d6vervakningsgr\u00e4nssnitt",
"install_settings_port": "Port",
"install_settings_interface_link": "Din administrat\u00f6rssida f\u00f6r AdGuard Home finns p\u00e5 f\u00f6ljande adresser:",
"form_error_port": "Skriv in ett giltigt portnummer",
"install_settings_dns": "DNS-server",
"install_settings_dns_desc": "Du beh\u00f6ver st\u00e4lla in dina enheter eller din router f\u00f6r att anv\u00e4nda DNS-server p\u00e5 f\u00f6ljande adresser.",
"install_settings_all_interfaces": "Alla gr\u00e4nssnitt",
"install_auth_title": "Autentisering",
"install_auth_desc": "Det rekommenderas starkt att st\u00e4lla in l\u00f6senordsskydd till webbgr\u00e4nssnittets administrativa del i ditt AdGuard Home. \u00c4ven om den endast \u00e4r \u00e5tkomlig p\u00e5 ditt lokala n\u00e4tverk rekommenderas det \u00e4nd\u00e5 att skydda det mot o\u00f6nskad \u00e5tkomst.",
"install_auth_username": "Anv\u00e4ndarnamn",
"install_auth_password": "L\u00f6senord",
"install_auth_confirm": "Bekr\u00e4fta l\u00f6senord",
"install_auth_username_enter": "Skriv in anv\u00e4ndarnamn",
"install_auth_password_enter": "Skriv in l\u00f6senord",
"install_step": "Steg",
"install_devices_title": "St\u00e4ll in dina enheter",
"install_devices_desc": "F\u00f6r att kunna anv\u00e4nda AdGuard Home m\u00e5ste du s\u00e4lla in dina enheter f\u00f6r att utnyttja den.",
"install_submit_title": "Grattis!",
"install_submit_desc": "Inst\u00e4llningsproceduren \u00e4r klar och du kan b\u00f6rja anv\u00e4nda AdGuard Home.",
"install_devices_router": "Router",
"install_devices_router_desc": "Den h\u00e4r anpassningen kommer att automatiskt t\u00e4cka in alla de enheter som \u00e4r anslutna till din hemmarouter och du beh\u00f6ver d\u00e4rf\u00f6r inte konfigurera var och en individuellt.",
"install_devices_address": "AdGuard Home DNS-server t\u00e4cker f\u00f6ljande adresser",
"install_devices_router_list_1": "\u00d6ppna routern Inst\u00e4llningar. Vanligtvis f\u00e5r man \u00e5tkomst via en URL (http:\/\/192.168.0.1 eller 192.168.1.1)- Du kommer att bli ombes att ange ett l\u00f6senord. L\u00f6senordet kan st\u00e5 angivet p\u00e5 routerns bak- eller undersida. Om l\u00f6senordet \u00e4ndrats och du inte k\u00e4nner till det kan du \u00e5terst\u00e4lla med Reset-knappen. En del routrar kr\u00e4ver en s\u00e4rskild applikation som skall finnas p\u00e5 antingen din dator eller i din mobil.",
"install_devices_router_list_2": "Leta upp DHCP\/DNS-inst\u00e4llningarna. Titta efter DNS-tecken intill ett f\u00e4lt med tv\u00e5 eller tre upps\u00e4ttningar siffror, var och en uppdelade i grupper om fyra med en eller tre siffror.",
"install_devices_router_list_3": "Ange serveradressen till ditt AdGuard Home.",
"install_devices_windows_list_1": "\u00d6ppna Kontrollpanelen via Start eller Windows S\u00f6k.",
"install_devices_windows_list_2": "V\u00e4lj N\u00e4tverks och delningscenter, N\u00e4tverk och Internet.",
"install_devices_windows_list_3": "Leta upp \u00c4ndra n\u00e4tverkskortsalternativ",
"install_devices_windows_list_4": "Markera din aktiva anslutning. H\u00f6gerklicka p\u00e5 den och v\u00e4lj Egenskaper.",
"install_devices_windows_list_5": "Markera Internet Protocol Version 4 (TCP\/IP) och klicka p\u00e5 knappen Egenskaper.",
"install_devices_windows_list_6": "Markera Anv\u00e4nd f\u00f6ljande DNS-serveradresser och skriv in adresserna till ditt AdGuard Home.",
"install_devices_macos_list_1": "Klicka p\u00e5 Apple-ikonen och v\u00e4lj Systemalternativ.",
"install_devices_macos_list_2": "Klicka p\u00e5 N\u00e4tverk.",
"install_devices_macos_list_3": "V\u00e4lj den f\u00f6rsta anslutningen i listan och klicka p\u00e5 Avancerat.",
"install_devices_macos_list_4": "Klicka p\u00e5 DNS-fliken och skriv in AdGuard Homes serveradresser",
"install_devices_android_list_1": "V\u00e4lj Inst\u00e4llningar fr\u00e5n Androids hemknapp",
"install_devices_android_list_2": "Tryck p\u00e5 N\u00e4tverk och Internet, Wi-Fi. Alla tillg\u00e4ngliga n\u00e4tverk visas i en lista (det g\u00e5r inte all v\u00e4lja egen DNS p\u00e5 mobiln\u00e4tverk.",
"install_devices_android_list_3": "H\u00e5ll ner p\u00e5 n\u00e4tverksnamnet som du \u00e4r ansluten till och v\u00e4lj \u00c4ndra n\u00e4tverk.",
"install_devices_android_list_4": "P\u00e5 en del enheter kan du beh\u00f6va v\u00e4lja Avancerat f\u00f6r att komma \u00e5t ytterligare inst\u00e4llningar. F\u00f6r att \u00e4ndra p\u00e5 DNS-inst\u00e4llningar m\u00e5ste du byta IP-inst\u00e4llning fr\u00e5n DHCP till Statisk. P\u00e5 Android Pie v\u00e4ljs Privat DNS p\u00e5 N\u00e4tverk och internet.",
"install_devices_android_list_5": "\u00c4ndra DNS 1 och DNS 2 till serveradresserna f\u00f6r AdGuard Home.",
"install_devices_ios_list_1": "Tryck Inst\u00e4llningar fr\u00e5n hemsk\u00e4rmen.",
"install_devices_ios_list_2": "V\u00e4lj Wi_Fi p\u00e5 den v\u00e4nstra menyn (det g\u00e5r inte att st\u00e4lla in egen DNS f\u00f6r mobila n\u00e4tverk).",
"install_devices_ios_list_3": "Tryck p\u00e5 namnet p\u00e5 den aktiva anslutningen.",
"install_devices_ios_list_4": "Skriv in AdGuard Homes serveradresser i DNS-f\u00e4lten.",
"get_started": "Kom ig\u00e5ng",
"next": "N\u00e4sta",
"open_dashboard": "\u00d6ppna Kontrollbordet",
"install_saved": "Sparat utan fel",
"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 to 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 certificates here.",
"encryption_status": "Status",
"encryption_expire": "Expires",
"encryption_key": "Private key",
"encryption_key_input": "Copy\/paste your PEM-encoded private key for your certificate 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": "L\u00f6senorden \u00f6verensst\u00e4mmer inte",
"reset_settings": "Reset settings",
"update_announcement": "AdGuard Home {{version}} is now available! <0>Click here<\/0> for more info."
}

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 <0>DNS-over-TLS<\/0>",
"example_upstream_doh": "\u0111\u01b0\u1ee3c m\u00e3 ho\u00e1 <0>DNS-over-HTTPS<\/0>",
"example_upstream_sdns": "b\u1ea1n c\u00f3 th\u1ec3 s\u1eed d\u1ee5ng <0>DNS Stamps<\/0> for <1>DNSCrypt<\/1> ho\u1eb7c <2>DNS-over-HTTPS<\/2> ",
"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,253 @@
{
"upstream_parallel": "\u901a\u8fc7\u540c\u65f6\u67e5\u8be2\u6240\u6709\u4e0a\u6d41\u670d\u52a1\u5668\u4ee5\u4f7f\u7528\u5e76\u884c\u67e5\u8be2\u52a0\u901f\u89e3\u6790",
"bootstrap_dns": "Bootstrap DNS \u670d\u52a1\u5668",
"bootstrap_dns_desc": "Bootstrap DNS servers are used to resolve IP addresses of the DoH\/DoT resolvers you specify as upstreams.",
"url_added_successfully": "\u7f51\u5740\u6dfb\u52a0\u6210\u529f",
"check_dhcp_servers": "\u68c0\u67e5 DHCP \u670d\u52a1\u5668",
"save_config": "\u4fdd\u5b58\u914d\u7f6e",
"enabled_dhcp": "DHCP \u670d\u52a1\u5668\u5df2\u542f\u7528",
"disabled_dhcp": "DHCP \u670d\u52a1\u5668\u5df2\u7981\u7528",
"dhcp_title": "DHCP \u670d\u52a1\u5668\uff08\u5b9e\u9a8c\u6027\uff09",
"dhcp_description": "\u5982\u679c\u4f60\u7684\u8def\u7531\u5668\u6ca1\u6709\u63d0\u4f9b\u52a8\u6001\u4e3b\u673a\u8bbe\u7f6e\u534f\u8bae\uff08DHCP\uff09\u8bbe\u7f6e\uff0c\u4f60\u53ef\u4ee5\u4f7f\u7528 AdGuard \u5185\u7f6e\u7684 DHCP \u670d\u52a1\u5668\u3002",
"dhcp_enable": "\u542f\u7528 DHCP \u670d\u52a1\u5668",
"dhcp_disable": "\u7981\u7528 DHCP \u670d\u52a1\u5668",
"dhcp_not_found": "\u5728\u5f53\u524d\u7f51\u7edc\u4e2d\u672a\u68c0\u6d4b\u5230 DHCP \u670d\u52a1\u5668\u3002\u60a8\u53ef\u4ee5\u5b89\u5168\u5730\u542f\u7528\u5185\u7f6e DHCP \u670d\u52a1\u5668\u3002",
"dhcp_found": "\u5728\u5f53\u524d\u7f51\u7edc\u4e2d\u68c0\u6d4b\u5230 DHCP \u670d\u52a1\u5668\u3002\u5982\u679c\u542f\u7528\u5185\u7f6e\u7684 DHCP \u670d\u52a1\u5668\u53ef\u80fd\u4e0d\u5b89\u5168\u3002",
"dhcp_leases": "DHCP \u79df\u7ea6",
"dhcp_leases_not_found": "\u672a\u68c0\u6d4b\u5230 DHCP \u79df\u7ea6",
"dhcp_config_saved": "\u4fdd\u5b58 DHCP \u670d\u52a1\u5668\u914d\u7f6e",
"form_error_required": "\u5fc5\u586b\u5b57\u6bb5",
"form_error_ip_format": "\u65e0\u6548\u7684 IPv4 \u683c\u5f0f",
"form_error_positive": "\u5fc5\u987b\u5927\u4e8e 0",
"dhcp_form_gateway_input": "\u7f51\u5173 IP",
"dhcp_form_subnet_input": "\u5b50\u7f51\u63a9\u7801",
"dhcp_form_range_title": "IP \u5730\u5740\u8303\u56f4",
"dhcp_form_range_start": "\u8d77\u59cb IP \u5730\u5740",
"dhcp_form_range_end": "\u672b\u5c3e IP \u5730\u5740",
"dhcp_form_lease_title": "DHCP \u79df\u7ea6\u65f6\u95f4\uff08\u79d2\uff09",
"dhcp_form_lease_input": "\u79df\u671f",
"dhcp_interface_select": "\u9009\u62e9 DHCP \u63a5\u53e3",
"dhcp_hardware_address": "\u786c\u4ef6\u5730\u5740",
"dhcp_ip_addresses": "IP \u5730\u5740",
"dhcp_table_hostname": "\u4e3b\u673a\u540d",
"dhcp_table_expires": "\u5230\u671f",
"dhcp_warning": "\u5982\u679c\u4f60\u60f3\u8981\u542f\u7528\u5185\u7f6e\u7684 DHCP \u670d\u52a1\u5668\uff0c\u8bf7\u786e\u4fdd\u5728\u5f53\u524d\u7f51\u7edc\u4e2d\u6ca1\u6709\u5176\u5b83\u6d3b\u52a8\u7684 DHCP \u670d\u52a1\u5668\u3002\u5426\u5219\uff0c\u6b64\u64cd\u4f5c\u53ef\u80fd\u4f1a\u7834\u574f\u5df2\u8fde\u63a5\u8bbe\u5907\u7684\u7f51\u7edc\u8fde\u63a5\uff01",
"back": "\u8fd4\u56de",
"dashboard": "\u4eea\u8868\u76d8",
"settings": "\u8bbe\u7f6e",
"filters": "\u8fc7\u6ee4\u5668",
"query_log": "\u67e5\u8be2\u65e5\u5fd7",
"faq": "\u5e38\u89c1\u95ee\u9898",
"version": "\u7248\u672c",
"address": "\u5730\u5740",
"on": "\u542f\u7528\u4e2d",
"off": "\u7981\u7528\u4e2d",
"copyright": "\u7248\u6743",
"homepage": "\u4e3b\u9875",
"report_an_issue": "\u95ee\u9898\u53cd\u9988",
"enable_protection": "\u542f\u7528\u4fdd\u62a4",
"enabled_protection": "\u4fdd\u62a4\u5df2\u542f\u7528",
"disable_protection": "\u7981\u7528\u4fdd\u62a4",
"disabled_protection": "\u4fdd\u62a4\u5df2\u7981\u7528",
"refresh_statics": "\u5237\u65b0\u72b6\u6001",
"dns_query": "DNS\u67e5\u8be2",
"blocked_by": "\u5df2\u88ab\u8fc7\u6ee4\u5668\u62e6\u622a",
"stats_malware_phishing": "\u88ab\u62e6\u622a\u7684\u6076\u610f\/\u9493\u9c7c\u7f51\u7ad9",
"stats_adult": "\u88ab\u62e6\u622a\u7684\u6210\u4eba\u7f51\u7ad9",
"stats_query_domain": "\u8bf7\u6c42\u57df\u540d\u6392\u884c",
"for_last_24_hours": "\u5728\u8fc7\u53bb 24 \u5c0f\u65f6",
"no_domains_found": "\u672a\u627e\u5230\u57df\u540d",
"requests_count": "\u8bf7\u6c42\u6570",
"top_blocked_domains": "\u88ab\u62e6\u622a\u57df\u540d\u6392\u884c",
"top_clients": "\u5ba2\u6237\u7aef\u6392\u884c",
"no_clients_found": "\u672a\u627e\u5230\u5ba2\u6237\u7aef",
"general_statistics": "\u6982\u51b5\u7edf\u8ba1",
"number_of_dns_query_24_hours": "\u8fc7\u53bb 24 \u5c0f\u65f6\u5185\u5904\u7406\u7684 DNS \u8bf7\u6c42\u603b\u6570",
"number_of_dns_query_blocked_24_hours": "\u88ab\u5e7f\u544a\u8fc7\u6ee4\u5668\u548c Hosts \u62e6\u622a\u6e05\u5355\u62e6\u622a\u7684 DNS \u8bf7\u6c42\u603b\u6570",
"number_of_dns_query_blocked_24_hours_by_sec": "\u88ab AdGuard \u5b89\u5168\u6d4f\u89c8\u6a21\u5757\u62e6\u622a\u7684 DNS \u8bf7\u6c42\u603b\u6570",
"number_of_dns_query_blocked_24_hours_adult": "\u88ab\u62e6\u622a\u7684\u6210\u4eba\u7f51\u7ad9\u603b\u6570",
"enforced_save_search": "\u5f3a\u5236\u5b89\u5168\u641c\u7d22",
"number_of_dns_query_to_safe_search": "\u542f\u7528\u5f3a\u5236\u5b89\u5168\u641c\u7d22\u540e\u5bf9\u641c\u7d22\u5f15\u64ce\u7684 DNS \u8bf7\u6c42\u603b\u6570",
"average_processing_time": "\u5e73\u5747\u5904\u7406\u65f6\u95f4",
"average_processing_time_hint": "\u5904\u7406 DNS \u8bf7\u6c42\u7684\u5e73\u5747\u65f6\u95f4\uff08\u6beb\u79d2\uff09",
"block_domain_use_filters_and_hosts": "\u4f7f\u7528\u8fc7\u6ee4\u5668\u548c Hosts \u6587\u4ef6\u4ee5\u62e6\u622a\u6307\u5b9a\u57df\u540d",
"filters_block_toggle_hint": "\u4f60\u53ef\u4ee5\u5728 <a href='#filters'>\u8fc7\u6ee4\u5668<\/a> \u8bbe\u7f6e\u4e2d\u6dfb\u52a0\u8fc7\u6ee4\u89c4\u5219\u3002",
"use_adguard_browsing_sec": "\u4f7f\u7528 AdGuard\u3010\u6d4f\u89c8\u5b89\u5168\u3011\u7f51\u9875\u670d\u52a1",
"use_adguard_browsing_sec_hint": "AdGuard Home \u5c06\u68c0\u67e5\u57df\u540d\u662f\u5426\u88ab\u6d4f\u89c8\u5b89\u5168\u670d\u52a1\u5217\u5165\u9ed1\u540d\u5355\u3002\u5b83\u5c06\u4f7f\u7528\u9690\u79c1\u6027\u5f3a\u7684\u68c0\u7d22 API \u6765\u6267\u884c\u68c0\u67e5\uff0c\u53ea\u6709\u57df\u540d\u7684 SHA256 \u7684\u77ed\u524d\u7f00\u4f1a\u88ab\u53d1\u9001\u5230\u670d\u52a1\u5668\u3002",
"use_adguard_parental": "\u4f7f\u7528 AdGuard \u3010\u5bb6\u957f\u63a7\u5236\u3011\u670d\u52a1",
"use_adguard_parental_hint": "AdGuard Home \u5c06\u4f7f\u7528\u4e0e\u6d4f\u89c8\u5b89\u5168\u670d\u52a1\u76f8\u540c\u7684\u9690\u79c1\u6027\u5f3a\u7684 API \u6765\u68c0\u67e5\u57df\u540d\u6307\u5411\u7684\u7f51\u7ad9\u662f\u5426\u5305\u542b\u6210\u4eba\u5185\u5bb9\u3002",
"enforce_safe_search": "\u5f3a\u5236\u5b89\u5168\u641c\u7d22",
"enforce_save_search_hint": "AdGuard Home \u5c06\u5bf9\u4ee5\u4e0b\u641c\u7d22\u5f15\u64ce\u5f3a\u5236\u542f\u7528\u5b89\u5168\u641c\u7d22\uff1aGoogle\u3001YouTube\u3001Bing \u548c Yandex\u3002",
"no_servers_specified": "\u672a\u627e\u5230\u6307\u5b9a\u7684\u670d\u52a1\u5668",
"no_settings": "\u672a\u627e\u5230\u8bbe\u7f6e",
"general_settings": "\u5e38\u89c4\u8bbe\u7f6e",
"upstream_dns": "\u4e0a\u6e38 DNS \u670d\u52a1\u5668",
"upstream_dns_hint": "\u5982\u679c\u6b64\u5904\u7559\u7a7a\uff0cAdGuard Home \u5c06\u4f1a\u4f7f\u7528 <a href='https:\/\/1.1.1.1\/' target='_blank'>Cloudflare DNS<\/a> \u4f5c\u4e3a\u4e0a\u6e38 DNS\u3002\u5982\u679c\u60f3\u8981\u4f7f\u7528\u4f7f\u7528 DNS over TLS\uff0c\u8bf7\u4ee5 tls:\/\/ \u4e3a\u5f00\u5934\u3002",
"test_upstream_btn": "\u6d4b\u8bd5\u4e0a\u6e38 DNS",
"apply_btn": "\u5e94\u7528",
"disabled_filtering_toast": "\u8fc7\u6ee4\u5668\u5df2\u7981\u7528",
"enabled_filtering_toast": "\u8fc7\u6ee4\u5668\u5df2\u542f\u7528",
"disabled_safe_browsing_toast": "\u5b89\u5168\u6d4f\u89c8\u5df2\u7981\u7528",
"enabled_safe_browsing_toast": "\u5b89\u5168\u6d4f\u89c8\u5df2\u542f\u7528",
"disabled_parental_toast": "\u5bb6\u957f\u63a7\u5236\u5df2\u7981\u7528",
"enabled_parental_toast": "\u5bb6\u957f\u63a7\u5236\u5df2\u542f\u7528",
"disabled_safe_search_toast": "\u5b89\u5168\u641c\u7d22\u5df2\u7981\u7528",
"enabled_save_search_toast": "\u5b89\u5168\u641c\u7d22\u5df2\u542f\u7528",
"enabled_table_header": "\u5df2\u542f\u7528",
"name_table_header": "\u540d\u79f0",
"filter_url_table_header": "\u8fc7\u6ee4\u5668\u5730\u5740",
"rules_count_table_header": "\u89c4\u5219\u6570",
"last_time_updated_table_header": "\u4e0a\u6b21\u66f4\u65b0\u65f6\u95f4",
"actions_table_header": "\u6d3b\u8dc3\u72b6\u6001",
"delete_table_action": "\u5220\u9664",
"filters_and_hosts": "\u8fc7\u6ee4\u5668\u548c Hosts \u62e6\u622a\u6e05\u5355",
"filters_and_hosts_hint": "AdGuard Home \u53ef\u4ee5\u89e3\u6790\u57fa\u7840\u7684 adblock \u89c4\u5219\u548c Hosts \u8bed\u6cd5\u3002",
"no_filters_added": "\u672a\u6dfb\u52a0\u4efb\u4f55\u8fc7\u6ee4\u5668",
"add_filter_btn": "\u6dfb\u52a0\u8fc7\u6ee4\u5668",
"cancel_btn": "\u53d6\u6d88",
"enter_name_hint": "\u8f93\u5165\u540d\u79f0",
"enter_url_hint": "\u8f93\u5165 URL",
"check_updates_btn": "\u68c0\u67e5\u66f4\u65b0",
"new_filter_btn": "\u8ba2\u9605\u65b0\u7684\u8fc7\u6ee4\u5668",
"enter_valid_filter_url": "\u8f93\u5165\u4e00\u4e2a\u8fc7\u6ee4\u5668\u8ba2\u9605\u6216 Hosts \u6587\u4ef6\u7684\u6709\u6548 URL",
"custom_filter_rules": "\u81ea\u5b9a\u4e49\u8fc7\u6ee4\u5668\u89c4\u5219",
"custom_filter_rules_hint": "\u8bf7\u786e\u4fdd\u6bcf\u884c\u53ea\u8f93\u5165\u4e00\u6761\u89c4\u5219\u3002\u4f60\u53ef\u4ee5\u8f93\u5165\u7b26\u5408 adblock \u8bed\u6cd5\u6216 Hosts \u8bed\u6cd5\u7684\u89c4\u5219\u3002",
"examples_title": "\u8303\u4f8b",
"example_meaning_filter_block": "\u62e6\u622a example.org \u57df\u540d\u53ca\u5176\u6240\u6709\u5b50\u57df\u540d",
"example_meaning_filter_whitelist": "\u653e\u884c example.org \u53ca\u5176\u6240\u6709\u5b50\u57df\u540d",
"example_meaning_host_block": "AdGuard Home \u73b0\u5728\u5c06\u4f1a\u628a example.org\uff08\u4f46\u4e0d\u5305\u62ec\u5b83\u7684\u5b50\u57df\u540d\uff09\u89e3\u6790\u5230 127.0.0.1\u3002",
"example_comment": "! \u8fd9\u662f\u4e00\u884c\u6ce8\u91ca",
"example_comment_meaning": "\u53ea\u662f\u4e00\u6761\u6ce8\u91ca",
"example_comment_hash": "# \u8fd9\u4e5f\u662f\u4e00\u884c\u6ce8\u91ca",
"example_regex_meaning": "\u963b\u6b62\u8bbf\u95ee\u4e0e\u6307\u5b9a\u7684\u6b63\u5219\u8868\u8fbe\u5f0f\u5339\u914d\u7684\u57df\u540d",
"example_upstream_regular": "\u5e38\u89c4 DNS\uff08\u57fa\u4e8e UDP\uff09",
"example_upstream_dot": "\u52a0\u5bc6 <0>DNS-over-TLS<\/0>",
"example_upstream_doh": "\u52a0\u5bc6 <0>DNS-over-HTTPS<\/0>",
"example_upstream_sdns": "\u4f60\u53ef\u4ee5\u4f7f\u7528 <0>DNSCrypt<\/0> \u7684 <1>DNS Stamps<\/1> \u6216\u8005 <2>DNS-over-HTTPS<\/2> \u89e3\u6790\u5668",
"example_upstream_tcp": "\u5e38\u89c4 DNS\uff08\u57fa\u4e8e TCP \uff09",
"all_filters_up_to_date_toast": "\u6240\u6709\u8fc7\u6ee4\u5668\u5df2\u66f4\u65b0\u81f3\u6700\u65b0",
"updated_upstream_dns_toast": "\u4e0a\u6e38 DNS \u5df2\u66f4\u65b0",
"dns_test_ok_toast": "\u6307\u5b9a\u7684 DNS \u670d\u52a1\u5668\u73b0\u5df2\u6b63\u5e38\u8fd0\u884c",
"dns_test_not_ok_toast": "\u670d\u52a1\u5668 \"{{key}}\"\uff1a\u65e0\u6cd5\u4f7f\u7528\uff0c\u8bf7\u68c0\u67e5\u4f60\u8f93\u5165\u7684\u662f\u5426\u6b63\u786e",
"unblock_btn": "\u653e\u884c",
"block_btn": "\u62e6\u622a",
"time_table_header": "\u65f6\u95f4",
"domain_name_table_header": "\u57df\u540d",
"type_table_header": "\u7c7b\u578b",
"response_table_header": "\u54cd\u5e94",
"client_table_header": "\u5ba2\u6237\u7aef",
"empty_response_status": "\u7a7a",
"show_all_filter_type": "\u663e\u793a\u6240\u6709",
"show_filtered_type": "\u663e\u793a\u88ab\u62e6\u622a\u7684",
"no_logs_found": "\u672a\u627e\u5230\u65e5\u5fd7",
"disabled_log_btn": "\u7981\u7528\u65e5\u5fd7",
"download_log_file_btn": "\u4e0b\u8f7d\u65e5\u5fd7\u6587\u4ef6",
"refresh_btn": "\u5237\u65b0",
"enabled_log_btn": "\u542f\u7528\u65e5\u5fd7",
"last_dns_queries": "\u6700\u8fd1\u7684 5000 \u4e2a DNS \u8bf7\u6c42",
"previous_btn": "\u4e0a\u4e00\u9875",
"next_btn": "\u4e0b\u4e00\u9875",
"loading_table_status": "\u52a0\u8f7d\u4e2d\u2026\u2026",
"page_table_footer_text": "\u9875",
"of_table_footer_text": "\u5728",
"rows_table_footer_text": "\u884c",
"updated_custom_filtering_toast": "\u81ea\u5b9a\u4e49\u8fc7\u6ee4\u89c4\u5219\u5df2\u66f4\u65b0",
"rule_removed_from_custom_filtering_toast": "\u89c4\u5219\u5df2\u4ece\u81ea\u5b9a\u4e49\u8fc7\u6ee4\u89c4\u5219\u5217\u8868\u4e2d\u79fb\u9664",
"rule_added_to_custom_filtering_toast": "\u89c4\u5219\u5df2\u6dfb\u52a0\u5230\u81ea\u5b9a\u4e49\u8fc7\u6ee4\u89c4\u5219\u5217\u8868\u4e2d",
"query_log_disabled_toast": "\u67e5\u8be2\u65e5\u5fd7\u5df2\u7981\u7528",
"query_log_enabled_toast": "\u67e5\u8be2\u65e5\u5fd7\u5df2\u542f\u7528",
"source_label": "\u6e90",
"found_in_known_domain_db": "\u6210\u529f\u5728\u5df2\u77e5\u57df\u540d\u6570\u636e\u5e93\u4e2d\u67e5\u8be2\u5230",
"category_label": "\u7c7b\u522b",
"rule_label": "\u89c4\u5219",
"filter_label": "\u8fc7\u6ee4\u5668",
"unknown_filter": "\u672a\u77e5\u8fc7\u6ee4\u5668 {{filterId}}",
"install_welcome_title": "\u6b22\u8fce\u4f7f\u7528 AdGuard Home\uff01",
"install_welcome_desc": "AdGuard Home \u662f\u4e00\u4e2a\u53ef\u5728\u7279\u5b9a\u7f51\u7edc\u8303\u56f4\u5185\u62e6\u622a\u6240\u6709\u5e7f\u544a\u548c\u8ddf\u8e2a\u5668\u7684 DNS \u670d\u52a1\u5668\u3002\u5b83\u7684\u76ee\u7684\u662f\u8ba9\u60a8\u63a7\u5236\u6574\u4e2a\u7f51\u7edc\u548c\u60a8\u7684\u6240\u6709\u8bbe\u5907\uff0c\u4e14\u4e0d\u9700\u8981\u4f7f\u7528\u4efb\u4f55\u5ba2\u6237\u7aef\u7a0b\u5e8f\u3002",
"install_settings_title": "\u7f51\u9875\u7ba1\u7406\u754c\u9762",
"install_settings_listen": "\u76d1\u542c\u63a5\u53e3",
"install_settings_port": "\u7aef\u53e3",
"install_settings_interface_link": "\u60a8\u53ef\u4ee5\u901a\u8fc7\u4ee5\u4e0b\u5730\u5740\u8bbf\u95ee\u60a8\u7684 AdGuard Home \u7f51\u9875\u7ba1\u7406\u754c\u9762\uff1a",
"form_error_port": "\u8f93\u5165\u6709\u6548\u7684\u7aef\u53e3\u503c",
"install_settings_dns": "DNS \u670d\u52a1\u5668",
"install_settings_dns_desc": "\u60a8\u5c06\u9700\u8981\u4f7f\u7528\u4ee5\u4e0b\u5730\u5740\u6765\u8bbe\u7f6e\u60a8\u7684\u8bbe\u5907\u6216\u8def\u7531\u5668\u7684 DNS \u670d\u52a1\u5668\uff1a",
"install_settings_all_interfaces": "\u6240\u6709\u63a5\u53e3",
"install_auth_title": "\u8eab\u4efd\u8ba4\u8bc1",
"install_auth_desc": "\u6211\u4eec\u5f3a\u70c8\u5efa\u8bae\u60a8\u4e3a AdGuard Home \u7684\u7f51\u9875\u7ba1\u7406\u754c\u9762\u914d\u7f6e\u5bc6\u7801\u3002\u5c3d\u7ba1\u8be5\u9875\u9762\u53ea\u80fd\u901a\u8fc7\u60a8\u7684\u672c\u5730\u7f51\u7edc\u8bbf\u95ee\uff0c\u4f46\u907f\u514d\u5b83\u88ab\u4e0d\u52a0\u9650\u5236\u5730\u8bbf\u95ee\u4ecd\u5341\u5206\u91cd\u8981\u3002",
"install_auth_username": "\u7528\u6237\u540d",
"install_auth_password": "\u5bc6\u7801",
"install_auth_confirm": "\u786e\u8ba4\u5bc6\u7801",
"install_auth_username_enter": "\u8f93\u5165\u7528\u6237\u540d",
"install_auth_password_enter": "\u8f93\u5165\u5bc6\u7801",
"install_step": "\u6b65\u9aa4",
"install_devices_title": "\u914d\u7f6e\u60a8\u7684\u8bbe\u5907",
"install_devices_desc": "\u4e3a\u4fdd\u8bc1 AdGuard Home \u53ef\u4ee5\u5f00\u59cb\u6b63\u5e38\u5de5\u4f5c\uff0c\u60a8\u9700\u8981\u5728\u8bbe\u5907\u4e0a\u5bf9\u5176\u8fdb\u884c\u914d\u7f6e\u3002",
"install_submit_title": "\u606d\u559c\u60a8\uff01",
"install_submit_desc": "\u5b89\u88c5\u8fc7\u7a0b\u5df2\u7ecf\u5b8c\u6210\uff0c\u60a8\u53ef\u4ee5\u5f00\u59cb\u4f7f\u7528 AdGuard Home \u4e86\u3002",
"install_devices_router": "\u8def\u7531\u5668",
"install_devices_router_desc": "\u6b64\u8bbe\u7f6e\u5c06\u81ea\u52a8\u8986\u76d6\u8fde\u63a5\u5230\u60a8\u7684\u5bb6\u5ead\u8def\u7531\u5668\u7684\u6240\u6709\u8bbe\u5907\uff0c\u60a8\u4e0d\u9700\u8981\u624b\u52a8\u914d\u7f6e\u5b83\u4eec\u3002",
"install_devices_address": "AdGuard Home DNS \u670d\u52a1\u5668\u6b63\u5728\u76d1\u542c\u4ee5\u4e0b\u5730\u5740",
"install_devices_router_list_1": "\u6253\u5f00\u60a8\u7684\u8def\u7531\u5668\u914d\u7f6e\u754c\u9762\u3002\u901a\u5e38\u60c5\u51b5\u4e0b\uff0c\u60a8\u53ef\u4ee5\u901a\u8fc7\u6d4f\u89c8\u5668\u8bbf\u95ee\u5730\u5740\uff08\u5982 http:\/\/192.168.0.1\/ \u6216 http:\/\/192.168.1.1 \uff09\u3002\u6253\u5f00\u540e\u60a8\u53ef\u80fd\u9700\u8981\u8f93\u5165\u5bc6\u7801\u4ee5\u8fdb\u5165\u914d\u7f6e\u754c\u9762\u3002\u5982\u679c\u60a8\u4e0d\u8bb0\u5f97\u5bc6\u7801\uff0c\u901a\u5e38\u53ef\u4ee5\u901a\u8fc7\u6309\u4e0b\u8def\u7531\u5668\u4e0a\u7684\u91cd\u7f6e\u6309\u94ae\u6765\u91cd\u8bbe\u5bc6\u7801\u3002\u4e00\u4e9b\u8def\u7531\u5668\u53ef\u80fd\u9700\u8981\u901a\u8fc7\u7279\u5b9a\u7684\u5e94\u7528\u6765\u8fdb\u884c\u8fd9\u4e00\u64cd\u4f5c\uff0c\u8bf7\u786e\u4fdd\u60a8\u5df2\u7ecf\u5728\u8ba1\u7b97\u673a\u6216\u624b\u673a\u4e0a\u5b89\u88c5\u4e86\u76f8\u5173\u5e94\u7528\u3002",
"install_devices_router_list_2": "\u627e\u5230\u8def\u7531\u5668\u7684 DHCP\/DNS \u8bbe\u7f6e\u9875\u9762\u3002\u60a8\u4f1a\u5728 DNS \u8fd9\u4e00\u5355\u8bcd\u65c1\u8fb9\u627e\u5230\u4e24\u5230\u4e09\u884c\u5141\u8bb8\u8f93\u5165\u7684\u8f93\u5165\u6846\uff0c\u6bcf\u4e00\u884c\u8f93\u5165\u6846\u5206\u4e3a\u56db\u7ec4\uff0c\u6bcf\u7ec4\u5141\u8bb8\u8f93\u5165\u4e00\u5230\u4e09\u4e2a\u6570\u5b57\u3002",
"install_devices_router_list_3": "\u8bf7\u5728\u6b64\u5904\u8f93\u5165\u60a8\u7684 AdGuard Home \u670d\u52a1\u5668\u5730\u5740\u3002",
"install_devices_windows_list_1": "\u901a\u8fc7\u5f00\u59cb\u83dc\u5355\u6216 Windows \u641c\u7d22\u529f\u80fd\u6253\u5f00\u63a7\u5236\u9762\u677f\u3002",
"install_devices_windows_list_2": "\u70b9\u51fb\u8fdb\u5165 \u201d\u7f51\u7edc\u548c Internet\u201c \u540e\uff0c\u518d\u6b21\u70b9\u51fb\u8fdb\u5165 \u201c\u7f51\u7edc\u548c\u5171\u4eab\u4e2d\u5fc3\u201d",
"install_devices_windows_list_3": "\u5728\u7a97\u53e3\u7684\u5de6\u4fa7\u627e\u5230 \u201d\u66f4\u6539\u9002\u914d\u5668\u8bbe\u7f6e\u201c \u5e76\u70b9\u51fb\u8fdb\u5165\u3002",
"install_devices_windows_list_4": "\u9009\u62e9\u60a8\u6b63\u5728\u8fde\u63a5\u7684\u7f51\u7edc\u8bbe\u5907\uff0c\u53f3\u51fb\u5b83\u5e76\u9009\u62e9 \u201d\u5c5e\u6027\u201c \u3002",
"install_devices_windows_list_5": "\u5728\u5217\u8868\u4e2d\u627e\u5230 \u201dInternet \u534f\u8bae\u7248\u672c 4 (TCP\/IPv4)\u201c \uff0c\u9009\u62e9\u5e76\u518d\u6b21\u70b9\u51fb \u201d\u5c5e\u6027\u201c \u3002",
"install_devices_windows_list_6": "\u9009\u62e9 \u201d\u4f7f\u7528\u4e0b\u9762\u7684 DNS \u670d\u52a1\u5668\u5730\u5740\u201c \uff0c\u5e76\u8f93\u5165\u60a8\u7684 AdGuard Home \u670d\u52a1\u5668\u5730\u5740\u3002",
"install_devices_macos_list_1": "\u70b9\u51fb\u82f9\u679c\u56fe\u6807\uff0c\u8fdb\u5165 \u201d\u7cfb\u7edf\u9996\u9009\u9879\u201c\u3002",
"install_devices_macos_list_2": "\u70b9\u51fb \u201d\u7f51\u7edc\u201c \u3002",
"install_devices_macos_list_3": "\u9009\u62e9\u5728\u5217\u8868\u4e2d\u7684\u7b2c\u4e00\u4e2a\u8fde\u63a5\uff0c\u5e76\u70b9\u51fb \u201d\u9ad8\u7ea7\u201c \u3002",
"install_devices_macos_list_4": "\u9009\u62e9 \u201dDNS\u201c \u9009\u9879\u5361\uff0c\u5e76\u8f93\u5165\u60a8\u7684 AdGuard Home \u670d\u52a1\u5668\u5730\u5740\u3002",
"install_devices_android_list_1": "\u5728\u5b89\u5353\u4e3b\u5c4f\u5e55\u83dc\u5355\u4e2d\u70b9\u51fb\u8bbe\u7f6e\u3002",
"install_devices_android_list_2": "\u70b9\u51fb\u83dc\u5355\u4e0a\u7684 \u201d\u65e0\u7ebf\u5c40\u57df\u7f51\u201c \u9009\u9879\u3002\u5728\u5c4f\u5e55\u4e0a\u5c06\u5217\u51fa\u6240\u6709\u53ef\u7528\u7684\u7f51\u7edc\uff08\u8702\u7a9d\u79fb\u52a8\u7f51\u7edc\u4e0d\u652f\u6301\u4fee\u6539 DNS \uff09\u3002",
"install_devices_android_list_3": "\u957f\u6309\u5f53\u524d\u5df2\u8fde\u63a5\u7684\u7f51\u7edc\uff0c\u7136\u540e\u70b9\u51fb \u201d\u4fee\u6539\u7f51\u7edc\u8bbe\u7f6e\u201c \u3002",
"install_devices_android_list_4": "\u5728\u67d0\u4e9b\u8bbe\u5907\u4e0a\uff0c\u60a8\u53ef\u80fd\u9700\u8981\u9009\u4e2d \u201d\u9ad8\u7ea7\u201c \u590d\u9009\u6846\u4ee5\u67e5\u770b\u8fdb\u4e00\u6b65\u7684\u8bbe\u7f6e\u3002\u60a8\u53ef\u80fd\u9700\u8981\u8c03\u6574\u60a8\u5b89\u5353\u8bbe\u5907\u7684 DNS \u8bbe\u7f6e\uff0c\u6216\u662f\u9700\u8981\u5c06 IP \u8bbe\u7f6e\u4ece DHCP \u5207\u6362\u5230\u9759\u6001\u3002",
"install_devices_android_list_5": "\u5c06 \"DNS 1 \/ \u4e3b DNS\" \u548c \u201dDNS 2 \/ \u526f DNS\u201c \u7684\u503c\u6539\u4e3a\u60a8\u7684 AdGuard Home \u670d\u52a1\u5668\u5730\u5740\u3002",
"install_devices_ios_list_1": "\u4ece\u4e3b\u5c4f\u5e55\u4e2d\u70b9\u51fb \u201d\u8bbe\u7f6e\u201c \u3002",
"install_devices_ios_list_2": "\u4ece\u5de6\u4fa7\u76ee\u5f55\u4e2d\u9009\u62e9 \u201d\u65e0\u7ebf\u5c40\u57df\u7f51\u201c\uff08\u79fb\u52a8\u6570\u636e\u7f51\u7edc\u73af\u5883\u4e0b\u4e0d\u652f\u6301\u4fee\u6539 DNS \uff09\u3002",
"install_devices_ios_list_3": "\u70b9\u51fb\u5f53\u524d\u5df2\u8fde\u63a5\u7f51\u7edc\u7684\u540d\u79f0\u3002",
"install_devices_ios_list_4": "\u5728 DNS \u5b57\u6bb5\u4e2d\u8f93\u5165\u60a8\u7684 AdGuard Home \u670d\u52a1\u5668\u5730\u5740\u3002",
"get_started": "\u5f00\u59cb\u914d\u7f6e",
"next": "\u4e0b\u4e00\u6b65",
"open_dashboard": "\u6253\u5f00\u4eea\u8868\u76d8",
"install_saved": "\u4fdd\u5b58\u6210\u529f",
"encryption_title": "\u52a0\u5bc6",
"encryption_desc": "\u4e3a DNS \u4e0e\u7f51\u9875\u7ba1\u7406\u754c\u9762\u542f\u7528\u52a0\u5bc6\uff08HTTPS\/TLS\uff09",
"encryption_config_saved": "\u52a0\u5bc6\u914d\u7f6e\u5df2\u4fdd\u5b58",
"encryption_server": "\u670d\u52a1\u5668\u540d\u79f0",
"encryption_server_enter": "\u8f93\u5165\u60a8\u7684\u57df\u540d",
"encryption_server_desc": "\u82e5\u8981\u4f7f\u7528 HTTPS \uff0c\u60a8\u9700\u8981\u8f93\u5165\u4e0e SSL \u8bc1\u4e66\u76f8\u5339\u914d\u7684\u670d\u52a1\u5668\u540d\u79f0\u3002",
"encryption_redirect": "HTTPS \u81ea\u52a8\u91cd\u5b9a\u5411",
"encryption_redirect_desc": "\u5982\u679c\u52fe\u9009\u6b64\u9009\u9879\uff0cAdGuard Home \u5c06\u81ea\u52a8\u5c06\u60a8\u4ece HTTP \u91cd\u5b9a\u5411\u5230 HTTPS \u5730\u5740\u3002",
"encryption_https": "HTTPS \u7aef\u53e3",
"encryption_https_desc": "\u5982\u679c\u914d\u7f6e\u4e86 HTTPS \u7aef\u53e3\uff0cAdGuard Home \u7ba1\u7406\u754c\u9762\u5c06\u53ef\u4ee5\u901a\u8fc7 HTTPS \u8bbf\u95ee\uff0c\u5b83\u8fd8\u5c06\u5728\u5728 '\/dns-query' \u4f4d\u7f6e\u63d0\u4f9b DNS-over-HTTPS \u3002",
"encryption_dot": "DNS-over-TLS \u7aef\u53e3",
"encryption_dot_desc": "\u5982\u679c\u914d\u7f6e\u4e86\u6b64\u7aef\u53e3\uff0cAdGuard Home \u5c06\u5728\u6b64\u7aef\u53e3\u4e0a\u8fd0\u884c\u4e00\u4e2a DNS-over-TLS \u670d\u52a1\u5668\u3002",
"encryption_certificates": "\u8bc1\u4e66",
"encryption_certificates_desc": "\u4e3a\u4e86\u4f7f\u7528\u52a0\u5bc6\uff0c\u60a8\u9700\u8981\u4e3a\u57df\u63d0\u4f9b\u6709\u6548\u7684 SSL \u8bc1\u4e66\u94fe\u3002\u60a8\u53ef\u4ee5\u5728 <0>{{link}}<\/0> \u4e0a\u83b7\u5f97\u514d\u8d39\u8bc1\u4e66\uff0c\u4e5f\u53ef\u4ee5\u4ece\u53d7\u4fe1\u4efb\u7684\u8bc1\u4e66\u9881\u53d1\u673a\u6784\u8d2d\u4e70\u8bc1\u4e66\u3002",
"encryption_certificates_input": "\u5c06\u60a8\u4ee5 PEM \u683c\u5f0f\u7f16\u7801\u7684\u8bc1\u4e66\u590d\u5236\u7c98\u8d34\u5230\u6b64\u5904\u3002",
"encryption_status": "\u72b6\u6001",
"encryption_expire": "\u6709\u6548\u671f",
"encryption_key": "\u79c1\u94a5",
"encryption_key_input": "\u5c06\u60a8\u4ee5 PEM \u683c\u5f0f\u7f16\u7801\u7684\u8bc1\u4e66\u79c1\u94a5\u590d\u5236\u7c98\u8d34\u5230\u6b64\u5904\u3002",
"encryption_enable": "\u542f\u7528\u52a0\u5bc6\uff08HTTPS\u3001DNS-over-HTTPS\u3001DNS-over-TLS\uff09",
"encryption_enable_desc": "\u5982\u679c\u542f\u7528\u52a0\u5bc6\u9009\u9879\uff0cAdGuard Home \u7684\u7f51\u9875\u7ba1\u7406\u754c\u9762\u5c06\u901a\u8fc7 HTTPS \u8fde\u63a5\u8bbf\u95ee\uff0c\u540c\u65f6 DNS \u670d\u52a1\u5668\u5c06\u76d1\u542c\u901a\u8fc7 DNS-over-HTTPS \u4e0e DNS-over-TLS \u53d1\u9001\u7684\u8bf7\u6c42\u3002",
"encryption_chain_valid": "\u8bc1\u4e66\u94fe\u9a8c\u8bc1\u6709\u6548",
"encryption_chain_invalid": "\u8bc1\u4e66\u94fe\u9a8c\u8bc1\u65e0\u6548",
"encryption_key_valid": "\u8be5 {{type}} \u79c1\u94a5\u9a8c\u8bc1\u6709\u6548",
"encryption_key_invalid": "\u8be5 {{type}} \u79c1\u94a5\u9a8c\u8bc1\u65e0\u6548",
"encryption_subject": "\u4f7f\u7528\u8005",
"encryption_issuer": "\u9881\u53d1\u8005",
"encryption_hostnames": "\u4e3b\u673a\u540d",
"encryption_reset": "\u60a8\u786e\u5b9a\u60f3\u8981\u91cd\u7f6e\u52a0\u5bc6\u8bbe\u7f6e\uff1f",
"topline_expiring_certificate": "\u60a8\u7684 SSL \u8bc1\u4e66\u5373\u5c06\u8fc7\u671f\u3002\u8bf7\u66f4\u65b0 <0>\u52a0\u5bc6\u8bbe\u7f6e<\/0> \u3002",
"topline_expired_certificate": "\u60a8\u7684 SSL \u8bc1\u4e66\u5df2\u8fc7\u671f\u3002\u8bf7\u66f4\u65b0 <0>\u52a0\u5bc6\u8bbe\u7f6e<\/0> \u3002",
"form_error_port_range": "\u8f93\u5165 80 - 65535 \u8303\u56f4\u5185\u7684\u7aef\u53e3\u503c",
"form_error_port_unsafe": "\u8fd9\u662f\u4e00\u4e2a\u4e0d\u5b89\u5168\u7684\u7aef\u53e3",
"form_error_equal": "\u4e0d\u5e94\u8be5\u76f8\u540c",
"form_error_password": "\u5bc6\u7801\u4e0d\u5339\u914d",
"reset_settings": "\u91cd\u7f6e\u8bbe\u7f6e",
"update_announcement": "AdGuard Home {{version}} \u73b0\u5df2\u53d1\u5e03\uff01 <0>\u70b9\u51fb\u6b64\u5904<\/0> \u4ee5\u83b7\u53d6\u8be6\u7ec6\u4fe1\u606f\u3002"
}

View File

@@ -0,0 +1,316 @@
{
"example_upstream_reserved": "\u60a8\u53ef\u660e\u78ba\u6307\u5b9a<0>\u7528\u65bc\u7279\u5b9a\u7684\u7db2\u57df<\/0>\u4e4b DNS \u4e0a\u6e38",
"upstream_parallel": "\u900f\u904e\u540c\u6642\u5730\u67e5\u8a62\u6240\u6709\u4e0a\u6e38\u7684\u4f3a\u670d\u5668\uff0c\u4f7f\u7528\u4e26\u884c\u7684\u67e5\u8a62\u4ee5\u52a0\u901f\u89e3\u6790",
"bootstrap_dns": "\u81ea\u6211\u555f\u52d5\uff08Bootstrap\uff09DNS \u4f3a\u670d\u5668",
"bootstrap_dns_desc": "\u81ea\u6211\u555f\u52d5\uff08Bootstrap\uff09DNS \u4f3a\u670d\u5668\u88ab\u7528\u65bc\u89e3\u6790\u60a8\u660e\u78ba\u6307\u5b9a\u4f5c\u70ba\u4e0a\u6e38\u7684 DoH\/DoT \u89e3\u6790\u5668\u4e4b IP \u4f4d\u5740\u3002",
"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\u7528 AdGuard \u81ea\u8eab\u5167\u5efa\u7684 DHCP \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": "\u555f\u7528\u5167\u5efa\u7684\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u4f3a\u670d\u5668\u70ba\u5b89\u5168\u7684 - \u65bc\u8a72\u7db2\u8def\u4e0a\uff0c\u6211\u5011\u672a\u767c\u73fe\u4efb\u4f55\u73fe\u884c\u7684 DHCP \u4f3a\u670d\u5668\u3002\u7136\u800c\uff0c\u6211\u5011\u9f13\u52f5\u60a8\u624b\u52d5\u5730\u91cd\u65b0\u6aa2\u67e5\u5b83\uff0c\u56e0\u70ba\u6211\u5011\u7684\u81ea\u52d5\u4e4b\u6e2c\u8a66\u76ee\u524d\u4e0d\u4e88 100\uff05 \u4fdd\u8b49\u3002",
"dhcp_found": "\u65bc\u8a72\u7db2\u8def\u4e0a\uff0c\u4e00\u500b\u73fe\u884c\u7684\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u4f3a\u670d\u5668\u88ab\u767c\u73fe\u3002\u555f\u7528\u5167\u5efa\u7684 DHCP \u4f3a\u670d\u5668\u70ba\u4e0d\u5b89\u5168\u7684\u3002",
"dhcp_leases": "\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u79df\u8cc3",
"dhcp_static_leases": "DHCP static leases",
"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\u7684 IPv4 \u683c\u5f0f",
"form_error_mac_format": "\u7121\u6548\u7684\u5a92\u9ad4\u5b58\u53d6\u63a7\u5236\uff08MAC\uff09\u683c\u5f0f",
"form_error_positive": "\u5fc5\u9808\u5927\u65bc 0",
"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\u7121\u8ad6\u5982\u4f55\u60f3\u8981\u555f\u7528\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u4f3a\u670d\u5668\uff0c\u78ba\u4fdd\u5728\u60a8\u7684\u7db2\u8def\u7121\u5176\u5b83\u73fe\u884c\u7684 DHCP \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",
"dhcp_error": "\u6211\u5011\u7121\u6cd5\u78ba\u5b9a\u5728\u8a72\u7db2\u8def\u662f\u5426\u6709\u53e6\u5916\u7684\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u4f3a\u670d\u5668\u3002",
"dhcp_static_ip_error": "\u70ba\u4e86\u4f7f\u7528\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u4f3a\u670d\u5668\uff0c\u975c\u614b IP \u4f4d\u5740\u5fc5\u9808\u88ab\u8a2d\u5b9a\u3002\u6211\u5011\u672a\u80fd\u78ba\u5b9a\u8a72\u7db2\u8def\u4ecb\u9762\u662f\u5426\u88ab\u914d\u7f6e\u4f7f\u7528\u975c\u614b IP \u4f4d\u5740\u3002\u8acb\u624b\u52d5\u5730\u8a2d\u5b9a\u975c\u614b IP \u4f4d\u5740\u3002",
"dhcp_dynamic_ip_found": "\u60a8\u7684\u7cfb\u7d71\u4f7f\u7528\u52d5\u614b IP \u4f4d\u5740\u914d\u7f6e\u4f9b\u4ecb\u9762 <0>{{interfaceName}}<\/0>\u3002\u70ba\u4e86\u4f7f\u7528\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u4f3a\u670d\u5668\uff0c\u975c\u614bIP\u4f4d\u5740\u5fc5\u9808\u88ab\u8a2d\u5b9a\u3002\u60a8\u73fe\u884c\u7684 IP \u4f4d\u5740\u70ba <0>{{ipAddress}}<\/0>\u3002\u5982\u679c\u60a8\u6309\u555f\u7528 DHCP \u6309\u9215\uff0c\u6211\u5011\u5c07\u81ea\u52d5\u5730\u8a2d\u5b9a\u6b64 IP \u4f4d\u5740\u4f5c\u70ba\u975c\u614b\u3002",
"dhcp_lease_added": "Static lease \"{{key}}\" successfully added",
"dhcp_lease_deleted": "Static lease \"{{key}}\" successfully deleted",
"dhcp_new_static_lease": "New static lease",
"dhcp_static_leases_not_found": "No DHCP static leases found",
"dhcp_add_static_lease": "Add static lease",
"delete_confirm": "Are you sure you want to delete \"{{key}}\"?",
"form_enter_hostname": "Enter hostname",
"error_details": "\u932f\u8aa4\u7d30\u7bc0",
"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",
"privacy_policy": "\u96b1\u79c1\u653f\u7b56",
"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\u7684 24 \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\u7684 24 \u5c0f\u6642\u5167\u5df2\u8655\u7406\u7684 DNS \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\u7684 DNS \u8acb\u6c42\u4e4b\u6578\u91cf",
"number_of_dns_query_blocked_24_hours_by_sec": "\u5df2\u88ab AdGuard \u700f\u89bd\u5b89\u5168\u6a21\u7d44\u5c01\u9396\u7684 DNS \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\u7684 DNS \u8acb\u6c42\u4e4b\u6578\u91cf",
"average_processing_time": "\u5e73\u5747\u7684\u8655\u7406\u6642\u9593",
"average_processing_time_hint": "\u65bc\u8655\u7406\u4e00\u9805 DNS \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\u7528 AdGuard \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\u540d SHA256 \u96dc\u6e4a\u7684\u77ed\u524d\u7db4\u88ab\u50b3\u9001\u5230\u4f3a\u670d\u5668\u3002",
"use_adguard_parental": "\u4f7f\u7528 AdGuard \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\u3001DuckDuckGo \u548c Yandex \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",
"dns_settings": "DNS settings",
"encryption_settings": "Encryption settings",
"dhcp_settings": "DHCP settings",
"client_settings": "Clients settings",
"upstream_dns": "\u4e0a\u6e38\u7684 DNS \u4f3a\u670d\u5668",
"upstream_dns_hint": "\u5982\u679c\u60a8\u5c07\u8a72\u6b04\u4f4d\u7559\u7a7a\uff0cAdGuard Home \u5c07\u4f7f\u7528 <a href='https:\/\/1.1.1.1\/' target='_blank'>Cloudflare DNS<\/a> \u4f5c\u70ba\u4e0a\u6e38\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",
"edit_table_action": "\u7de8\u8f2f",
"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\u81f3 example.org \u7db2\u57df\u53ca\u5176\u6240\u6709\u7684\u5b50\u7db2\u57df\u4e4b\u5b58\u53d6",
"example_meaning_filter_whitelist": "\u89e3\u9664\u5c01\u9396\u81f3 example.org \u7db2\u57df\u53ca\u5176\u6240\u6709\u7684\u5b50\u7db2\u57df\u4e4b\u5b58\u53d6",
"example_meaning_host_block": "AdGuard Home \u73fe\u5728\u5c07\u5c0d example.org \u7db2\u57df\u8fd4\u56de 127.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\u7b26\u7684\u7db2\u57df\u4e4b\u5b58\u53d6",
"example_upstream_regular": "\u4e00\u822c\u7684 DNS\uff08\u900f\u904e UDP\uff09",
"example_upstream_dot": "\u52a0\u5bc6\u7684 <0>DNS-over-TLS<\/0>",
"example_upstream_doh": "\u52a0\u5bc6\u7684 <0>DNS-over-HTTPS<\/0>",
"example_upstream_sdns": "\u60a8\u53ef\u4f7f\u7528\u95dc\u65bc <1>DNSCrypt<\/1> \u6216 <2>DNS-over-HTTPS<\/2> \u89e3\u6790\u5668\u4e4b <0>DNS \u6233\u8a18<\/0>",
"example_upstream_tcp": "\u4e00\u822c\u7684 DNS\uff08\u900f\u904e TCP\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\u7684 DNS \u4f3a\u670d\u5668",
"dns_test_ok_toast": "\u5df2\u660e\u78ba\u6307\u5b9a\u7684 DNS \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\u7684 5000 \u7b46 DNS \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\u81f3 AdGuard Home\uff01",
"install_welcome_desc": "AdGuard Home \u662f\u5168\u7db2\u8def\u7bc4\u570d\u5ee3\u544a\u548c\u8ffd\u8e64\u5668\u5c01\u9396\u7684 DNS \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\u7684 AdGuard 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\u4e4b DNS \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\u7684 AdGuard 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\u4f7f AdGuard 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\u7528 AdGuard 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\u5230 DHCP\/DNS \u8a2d\u5b9a\u3002\u5c0b\u627e\u7dca\u9130\u8457\u5141\u8a31\u5169\u7d44\u6216\u4e09\u7d44\u6578\u5b57\u96c6\u7684\u6b04\u4f4d\u4e4b DNS \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\u7684 AdGuard Home \u4f3a\u670d\u5668\u4f4d\u5740\u3002",
"install_devices_windows_list_1": "\u901a\u904e\u958b\u59cb\u529f\u80fd\u8868\u6216 Windows \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\u7684 DNS \u4f3a\u670d\u5668\u4f4d\u5740\uff0c\u7136\u5f8c\u8f38\u5165\u60a8\u7684 AdGuard Home \u4f3a\u670d\u5668\u4f4d\u5740\u3002",
"install_devices_macos_list_1": "\u65bc Apple \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\u8a72 DNS \u5206\u9801\uff0c\u7136\u5f8c\u8f38\u5165\u60a8\u7684 AdGuard Home \u4f3a\u670d\u5668\u4f4d\u5740\u3002",
"install_devices_android_list_1": "\u5f9e Android \u9078\u55ae\u4e3b\u756b\u9762\u4e2d\uff0c\u8f15\u89f8\u8a2d\u5b9a\u3002",
"install_devices_android_list_2": "\u65bc\u8a72\u9078\u55ae\u4e0a\u8f15\u89f8 Wi-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\u7684 DNS\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\u7684 Android DNS \u8a2d\u5b9a\uff0c\u60a8\u5c07\u9700\u8981\u628a IP \u8a2d\u5b9a\u5f9e DHCP \u8f49\u63db\u6210\u975c\u614b\u3002",
"install_devices_android_list_5": "\u4f7f\u8a2d\u5b9a DNS 1 \u548c DNS 2 \u503c\u66f4\u6539\u6210\u60a8\u7684 AdGuard 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\u64c7 Wi-Fi\uff08\u4e0d\u53ef\u80fd\u70ba\u884c\u52d5\u7db2\u8def\u914d\u7f6e DNS\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\u8a72 DNS \u6b04\u4f4d\u4e2d\uff0c\u8f38\u5165\u60a8\u7684 AdGuard 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",
"encryption_title": "\u52a0\u5bc6",
"encryption_desc": "\u52a0\u5bc6\uff08HTTPS\/TLS\uff09\u652f\u63f4\u4f9b DNS \u548c\u7ba1\u7406\u54e1\u7db2\u9801\u4ecb\u9762\u5169\u8005",
"encryption_config_saved": "\u52a0\u5bc6\u914d\u7f6e\u5df2\u88ab\u5132\u5b58",
"encryption_server": "\u4f3a\u670d\u5668\u540d\u7a31",
"encryption_server_enter": "\u8f38\u5165\u60a8\u7684\u57df\u540d",
"encryption_server_desc": "\u70ba\u4e86\u4f7f\u7528 HTTPS\uff0c\u60a8\u9700\u8981\u8f38\u5165\u8207\u60a8\u7684\u5b89\u5168\u901a\u8a0a\u7aef\u5c64\uff08SSL\uff09\u6191\u8b49\u76f8\u7b26\u7684\u4f3a\u670d\u5668\u540d\u7a31\u3002",
"encryption_redirect": "\u81ea\u52d5\u5730\u91cd\u65b0\u5c0e\u5411\u5230 HTTPS",
"encryption_redirect_desc": "\u5982\u679c\u88ab\u52fe\u9078\uff0cAdGuard Home \u5c07\u81ea\u52d5\u5730\u91cd\u65b0\u5c0e\u5411\u60a8\u5f9e HTTP \u5230 HTTPS \u4f4d\u5740\u3002",
"encryption_https": "HTTPS \u9023\u63a5\u57e0",
"encryption_https_desc": "\u5982\u679c HTTPS \u9023\u63a5\u57e0\u88ab\u914d\u7f6e\uff0cAdGuard Home \u7ba1\u7406\u54e1\u4ecb\u9762\u900f\u904e HTTPS \u5c07\u70ba\u53ef\u5b58\u53d6\u7684\uff0c\u4e14\u5b83\u4e5f\u5c07\u65bc '\/dns-query' \u4f4d\u7f6e\u4e0a\u63d0\u4f9b DNS-over-HTTPS\u3002",
"encryption_dot": "DNS-over-TLS \u9023\u63a5\u57e0",
"encryption_dot_desc": "\u5982\u679c\u8a72\u9023\u63a5\u57e0\u88ab\u914d\u7f6e\uff0cAdGuard Home \u5c07\u65bc\u6b64\u9023\u63a5\u57e0\u4e0a\u904b\u884c DNS-over-TLS \u4f3a\u670d\u5668\u3002",
"encryption_certificates": "\u6191\u8b49",
"encryption_certificates_desc": "\u70ba\u4e86\u4f7f\u7528\u52a0\u5bc6\uff0c\u60a8\u9700\u8981\u63d0\u4f9b\u6709\u6548\u7684\u5b89\u5168\u901a\u8a0a\u7aef\u5c64\uff08SSL\uff09\u6191\u8b49\u93c8\u7d50\u4f9b\u60a8\u7684\u7db2\u57df\u3002\u65bc <0>{{link}}<\/0> \u4e0a\u60a8\u53ef\u53d6\u5f97\u514d\u8cbb\u7684\u6191\u8b49\u6216\u60a8\u53ef\u5f9e\u53d7\u4fe1\u4efb\u7684\u6191\u8b49\u6388\u6b0a\u55ae\u4f4d\u4e4b\u4e00\u8cfc\u8cb7\u5b83\u3002",
"encryption_certificates_input": "\u65bc\u6b64\u8907\u88fd\/\u8cbc\u4e0a\u60a8\u7684\u96b1\u79c1\u589e\u5f37\u90f5\u4ef6\u7de8\u78bc\u4e4b\uff08PEM-encoded\uff09\u6191\u8b49\u3002",
"encryption_status": "\u72c0\u614b",
"encryption_expire": "\u5230\u671f",
"encryption_key": "\u79c1\u5bc6\u91d1\u9470",
"encryption_key_input": "\u65bc\u6b64\u8907\u88fd\/\u8cbc\u4e0a\u60a8\u7684\u96b1\u79c1\u589e\u5f37\u90f5\u4ef6\u7de8\u78bc\u4e4b\uff08PEM-encoded\uff09\u79c1\u5bc6\u91d1\u9470\u4f9b\u60a8\u7684\u6191\u8b49\u3002",
"encryption_enable": "\u555f\u7528\u52a0\u5bc6\uff08HTTPS\u3001DNS-over-HTTPS \u548c DNS-over-TLS\uff09",
"encryption_enable_desc": "\u5982\u679c\u52a0\u5bc6\u88ab\u555f\u7528\uff0cAdGuard Home \u7ba1\u7406\u54e1\u4ecb\u9762\u900f\u904e HTTPS \u5c07\u904b\u4f5c\uff0c\u4e14\u8a72 DNS \u4f3a\u670d\u5668\u5c07\u7559\u5fc3\u76e3\u807d\u900f\u904e DNS-over-HTTPS \u548c DNS-over-TLS \u4e4b\u8acb\u6c42\u3002",
"encryption_chain_valid": "\u6191\u8b49\u93c8\u7d50\u70ba\u6709\u6548\u7684",
"encryption_chain_invalid": "\u6191\u8b49\u93c8\u7d50\u70ba\u7121\u6548\u7684",
"encryption_key_valid": "\u6b64\u70ba\u6709\u6548\u7684 {{type}} \u79c1\u5bc6\u91d1\u9470",
"encryption_key_invalid": "\u6b64\u70ba\u7121\u6548\u7684 {{type}} \u79c1\u5bc6\u91d1\u9470",
"encryption_subject": "\u7269\u4ef6",
"encryption_issuer": "\u7c3d\u767c\u8005",
"encryption_hostnames": "\u4e3b\u6a5f\u540d\u7a31",
"encryption_reset": "\u60a8\u78ba\u5b9a\u60a8\u60f3\u8981\u91cd\u7f6e\u52a0\u5bc6\u8a2d\u5b9a\u55ce\uff1f",
"topline_expiring_certificate": "\u60a8\u7684\u5b89\u5168\u901a\u8a0a\u7aef\u5c64\uff08SSL\uff09\u6191\u8b49\u5373\u5c07\u5230\u671f\u3002\u66f4\u65b0<0>\u52a0\u5bc6\u8a2d\u5b9a<\/0>\u3002",
"topline_expired_certificate": "\u60a8\u7684\u5b89\u5168\u901a\u8a0a\u7aef\u5c64\uff08SSL\uff09\u6191\u8b49\u70ba\u5df2\u5230\u671f\u7684\u3002\u66f4\u65b0<0>\u52a0\u5bc6\u8a2d\u5b9a<\/0>\u3002",
"form_error_port_range": "\u5728 80-65535 \u4e4b\u7bc4\u570d\u5167\u8f38\u5165\u9023\u63a5\u57e0\u503c",
"form_error_port_unsafe": "\u6b64\u70ba\u4e0d\u5b89\u5168\u7684\u9023\u63a5\u57e0",
"form_error_equal": "\u4e0d\u61c9\u70ba\u76f8\u7b49\u7684",
"form_error_password": "\u4e0d\u76f8\u7b26\u7684\u5bc6\u78bc",
"reset_settings": "\u91cd\u7f6e\u8a2d\u5b9a",
"update_announcement": "AdGuard Home {{version}} \u73fe\u70ba\u53ef\u7528\u7684\uff01\u95dc\u65bc\u66f4\u591a\u7684\u8cc7\u8a0a\uff0c<0>\u9ede\u64ca\u9019\u88e1<\/0>\u3002",
"setup_guide": "\u5b89\u88dd\u6307\u5357",
"dns_addresses": "DNS \u4f4d\u5740",
"down": "\u505c\u6b62\u904b\u4f5c\u7684",
"fix": "\u4fee\u5fa9",
"dns_providers": "\u9019\u88e1\u662f\u4e00\u500b\u5f9e\u4e2d\u9078\u64c7\u4e4b<0>\u5df2\u77e5\u7684 DNS \u4f9b\u61c9\u5546\u4e4b\u6e05\u55ae<\/0>\u3002",
"update_now": "\u7acb\u5373\u66f4\u65b0",
"update_failed": "Auto-update failed. Please <a href='https:\/\/github.com\/AdguardTeam\/AdGuardHome\/wiki\/Getting-Started#update'>follow the steps<\/a> to update manually.",
"processing_update": "Please wait, AdGuard Home is being updated",
"clients_title": "\u7528\u6236\u7aef",
"clients_desc": "Configure devices connected to AdGuard Home",
"settings_global": "Global",
"settings_custom": "Custom",
"table_client": "\u7528\u6236\u7aef",
"table_name": "\u540d\u7a31",
"save_btn": "\u5132\u5b58",
"client_add": "\u589e\u52a0\u7528\u6236\u7aef",
"client_new": "\u65b0\u7684\u7528\u6236\u7aef",
"client_edit": "\u7de8\u8f2f\u7528\u6236\u7aef",
"client_identifier": "Identifier",
"ip_address": "IP \u4f4d\u5740",
"client_identifier_desc": "Clients can be identified by the IP address or MAC address. Please note, that using MAC as identifier is possible only if AdGuard Home is also a <0>DHCP server<\/0>",
"form_enter_ip": "\u8f38\u5165 IP",
"form_enter_mac": "\u8f38\u5165\u5a92\u9ad4\u5b58\u53d6\u63a7\u5236\uff08MAC\uff09",
"form_client_name": "\u8f38\u5165\u7528\u6236\u7aef\u540d\u7a31",
"client_global_settings": "Use global settings",
"client_deleted": "Client \"{{key}}\" successfully deleted",
"client_added": "Client \"{{key}}\" successfully added",
"client_updated": "Client \"{{key}}\" successfully updated",
"table_statistics": "Requests count (last 24 hours)",
"clients_not_found": "No clients found",
"client_confirm_delete": "Are you sure you want to delete client \"{{key}}\"?",
"filter_confirm_delete": "Are you sure you want to delete filter?",
"auto_clients_title": "Clients (runtime)",
"auto_clients_desc": "Data on the clients that use AdGuard Home, but not stored in the configuration",
"access_title": "Access settings",
"access_desc": "Here you can configure access rules for the AdGuard Home DNS server.",
"access_allowed_title": "Allowed clients",
"access_allowed_desc": "A list of CIDR or IP addresses. If configured, AdGuard Home will accept requests from these IP addresses only.",
"access_disallowed_title": "Disallowed clients",
"access_disallowed_desc": "A list of CIDR or IP addresses. If configured, AdGuard Home will drop requests from these IP addresses.",
"access_blocked_title": "Blocked domains",
"access_blocked_desc": "Don't confuse this with filters. AdGuard Home will drop DNS queries with these domains in query's question.",
"access_settings_saved": "Access settings successfully saved"
}

View File

@@ -0,0 +1,45 @@
import { createAction } from 'redux-actions';
import Api from '../api/Api';
import { addErrorToast, addSuccessToast } from './index';
import { normalizeTextarea } from '../helpers/helpers';
const apiClient = new Api();
export const getAccessListRequest = createAction('GET_ACCESS_LIST_REQUEST');
export const getAccessListFailure = createAction('GET_ACCESS_LIST_FAILURE');
export const getAccessListSuccess = createAction('GET_ACCESS_LIST_SUCCESS');
export const getAccessList = () => async (dispatch) => {
dispatch(getAccessListRequest());
try {
const data = await apiClient.getAccessList();
dispatch(getAccessListSuccess(data));
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(getAccessListFailure());
}
};
export const setAccessListRequest = createAction('SET_ACCESS_LIST_REQUEST');
export const setAccessListFailure = createAction('SET_ACCESS_LIST_FAILURE');
export const setAccessListSuccess = createAction('SET_ACCESS_LIST_SUCCESS');
export const setAccessList = config => async (dispatch) => {
dispatch(setAccessListRequest());
try {
const { allowed_clients, disallowed_clients, blocked_hosts } = config;
const values = {
allowed_clients: (allowed_clients && normalizeTextarea(allowed_clients)) || [],
disallowed_clients: (disallowed_clients && normalizeTextarea(disallowed_clients)) || [],
blocked_hosts: (blocked_hosts && normalizeTextarea(blocked_hosts)) || [],
};
await apiClient.setAccessList(values);
dispatch(setAccessListSuccess());
dispatch(addSuccessToast('access_settings_saved'));
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(setAccessListFailure());
}
};

View File

@@ -0,0 +1,84 @@
import { createAction } from 'redux-actions';
import { t } from 'i18next';
import Api from '../api/Api';
import { addErrorToast, addSuccessToast, getClients } from './index';
import { CLIENT_ID } from '../helpers/constants';
const apiClient = new Api();
export const toggleClientModal = createAction('TOGGLE_CLIENT_MODAL');
export const addClientRequest = createAction('ADD_CLIENT_REQUEST');
export const addClientFailure = createAction('ADD_CLIENT_FAILURE');
export const addClientSuccess = createAction('ADD_CLIENT_SUCCESS');
export const addClient = config => async (dispatch) => {
dispatch(addClientRequest());
try {
let data;
if (config.identifier === CLIENT_ID.MAC) {
const { ip, identifier, ...values } = config;
data = { ...values };
} else {
const { mac, identifier, ...values } = config;
data = { ...values };
}
await apiClient.addClient(data);
dispatch(addClientSuccess());
dispatch(toggleClientModal());
dispatch(addSuccessToast(t('client_added', { key: config.name })));
dispatch(getClients());
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(addClientFailure());
}
};
export const deleteClientRequest = createAction('DELETE_CLIENT_REQUEST');
export const deleteClientFailure = createAction('DELETE_CLIENT_FAILURE');
export const deleteClientSuccess = createAction('DELETE_CLIENT_SUCCESS');
export const deleteClient = config => async (dispatch) => {
dispatch(deleteClientRequest());
try {
await apiClient.deleteClient(config);
dispatch(deleteClientSuccess());
dispatch(addSuccessToast(t('client_deleted', { key: config.name })));
dispatch(getClients());
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(deleteClientFailure());
}
};
export const updateClientRequest = createAction('UPDATE_CLIENT_REQUEST');
export const updateClientFailure = createAction('UPDATE_CLIENT_FAILURE');
export const updateClientSuccess = createAction('UPDATE_CLIENT_SUCCESS');
export const updateClient = (config, name) => async (dispatch) => {
dispatch(updateClientRequest());
try {
let data;
if (config.identifier === CLIENT_ID.MAC) {
const { ip, identifier, ...values } = config;
data = { name, data: { ...values } };
} else {
const { mac, identifier, ...values } = config;
data = { name, data: { ...values } };
}
await apiClient.updateClient(data);
dispatch(updateClientSuccess());
dispatch(toggleClientModal());
dispatch(addSuccessToast(t('client_updated', { key: name })));
dispatch(getClients());
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(updateClientFailure());
}
};

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,50 +1,75 @@
import { createAction } from 'redux-actions';
import round from 'lodash/round';
import { t } from 'i18next';
import { showLoading, hideLoading } from 'react-redux-loading-bar';
import axios from 'axios';
import { normalizeHistory, normalizeFilteringStatus } from '../helpers/helpers';
import versionCompare from '../helpers/versionCompare';
import { normalizeHistory, normalizeFilteringStatus, normalizeLogs, normalizeTextarea, sortClients } from '../helpers/helpers';
import { SETTINGS_NAMES, CHECK_TIMEOUT } from '../helpers/constants';
import { getTlsStatus } from './encryption';
import Api from '../api/Api';
const apiClient = new Api();
export const addErrorToast = createAction('ADD_ERROR_TOAST');
export const addSuccessToast = createAction('ADD_SUCCESS_TOAST');
export const addNoticeToast = createAction('ADD_NOTICE_TOAST');
export const removeToast = createAction('REMOVE_TOAST');
export const toggleSettingStatus = createAction('SETTING_STATUS_TOGGLE');
export const showSettingsFailure = createAction('SETTINGS_FAILURE_SHOW');
export const toggleSetting = (settingKey, status) => async (dispatch) => {
switch (settingKey) {
case 'filtering':
if (status) {
await apiClient.disableFiltering();
} else {
await apiClient.enableFiltering();
}
dispatch(toggleSettingStatus({ settingKey }));
break;
case 'safebrowsing':
if (status) {
await apiClient.disableSafebrowsing();
} else {
await apiClient.enableSafebrowsing();
}
dispatch(toggleSettingStatus({ settingKey }));
break;
case 'parental':
if (status) {
await apiClient.disableParentalControl();
} else {
await apiClient.enableParentalControl();
}
dispatch(toggleSettingStatus({ settingKey }));
break;
case 'safesearch':
if (status) {
await apiClient.disableSafesearch();
} else {
await apiClient.enableSafesearch();
}
dispatch(toggleSettingStatus({ settingKey }));
break;
default:
break;
let successMessage = '';
try {
switch (settingKey) {
case SETTINGS_NAMES.filtering:
if (status) {
successMessage = 'disabled_filtering_toast';
await apiClient.disableFiltering();
} else {
successMessage = 'enabled_filtering_toast';
await apiClient.enableFiltering();
}
dispatch(toggleSettingStatus({ settingKey }));
break;
case SETTINGS_NAMES.safebrowsing:
if (status) {
successMessage = 'disabled_safe_browsing_toast';
await apiClient.disableSafebrowsing();
} else {
successMessage = 'enabled_safe_browsing_toast';
await apiClient.enableSafebrowsing();
}
dispatch(toggleSettingStatus({ settingKey }));
break;
case SETTINGS_NAMES.parental:
if (status) {
successMessage = 'disabled_parental_toast';
await apiClient.disableParentalControl();
} else {
successMessage = 'enabled_parental_toast';
await apiClient.enableParentalControl();
}
dispatch(toggleSettingStatus({ settingKey }));
break;
case SETTINGS_NAMES.safesearch:
if (status) {
successMessage = 'disabled_safe_search_toast';
await apiClient.disableSafesearch();
} else {
successMessage = 'enabled_save_search_toast';
await apiClient.enableSafesearch();
}
dispatch(toggleSettingStatus({ settingKey }));
break;
default:
break;
}
dispatch(addSuccessToast(successMessage));
} catch (error) {
dispatch(addErrorToast({ error }));
}
};
@@ -73,11 +98,175 @@ export const initSettings = settingsList => async (dispatch) => {
};
dispatch(initSettingsSuccess({ settingsList: newSettingsList }));
} catch (error) {
console.error(error);
dispatch(addErrorToast({ error }));
dispatch(initSettingsFailure());
}
};
export const getFilteringRequest = createAction('GET_FILTERING_REQUEST');
export const getFilteringFailure = createAction('GET_FILTERING_FAILURE');
export const getFilteringSuccess = createAction('GET_FILTERING_SUCCESS');
export const getFiltering = () => async (dispatch) => {
dispatch(getFilteringRequest());
try {
const filteringStatus = await apiClient.getFilteringStatus();
dispatch(getFilteringSuccess(filteringStatus.enabled));
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(getFilteringFailure());
}
};
export const toggleProtectionRequest = createAction('TOGGLE_PROTECTION_REQUEST');
export const toggleProtectionFailure = createAction('TOGGLE_PROTECTION_FAILURE');
export const toggleProtectionSuccess = createAction('TOGGLE_PROTECTION_SUCCESS');
export const toggleProtection = status => async (dispatch) => {
dispatch(toggleProtectionRequest());
let successMessage = '';
try {
if (status) {
successMessage = 'disabled_protection';
await apiClient.disableGlobalProtection();
} else {
successMessage = 'enabled_protection';
await apiClient.enableGlobalProtection();
}
dispatch(addSuccessToast(successMessage));
dispatch(toggleProtectionSuccess());
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(toggleProtectionFailure());
}
};
export const getVersionRequest = createAction('GET_VERSION_REQUEST');
export const getVersionFailure = createAction('GET_VERSION_FAILURE');
export const getVersionSuccess = createAction('GET_VERSION_SUCCESS');
export const getVersion = (recheck = false) => async (dispatch, getState) => {
dispatch(getVersionRequest());
try {
const data = await apiClient.getGlobalVersion({ recheck_now: recheck });
dispatch(getVersionSuccess(data));
if (recheck) {
const { dnsVersion } = getState().dashboard;
const currentVersion = dnsVersion === 'undefined' ? 0 : dnsVersion;
if (data && versionCompare(currentVersion, data.new_version) === -1) {
dispatch(addSuccessToast('updates_checked'));
} else {
dispatch(addSuccessToast('updates_version_equal'));
}
}
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(getVersionFailure());
}
};
export const getUpdateRequest = createAction('GET_UPDATE_REQUEST');
export const getUpdateFailure = createAction('GET_UPDATE_FAILURE');
export const getUpdateSuccess = createAction('GET_UPDATE_SUCCESS');
export const getUpdate = () => async (dispatch, getState) => {
const { dnsVersion } = getState().dashboard;
dispatch(getUpdateRequest());
try {
await apiClient.getUpdate();
const checkUpdate = async (attempts) => {
let count = attempts || 1;
let timeout;
if (count > 60) {
dispatch(addNoticeToast({ error: 'update_failed' }));
dispatch(getUpdateFailure());
return false;
}
const rmTimeout = t => t && clearTimeout(t);
const setRecursiveTimeout = (time, ...args) => setTimeout(
checkUpdate,
time,
...args,
);
axios.get('control/status')
.then((response) => {
rmTimeout(timeout);
if (response && response.status === 200) {
const responseVersion = response.data && response.data.version;
if (dnsVersion !== responseVersion) {
dispatch(getUpdateSuccess());
window.location.reload(true);
}
}
timeout = setRecursiveTimeout(CHECK_TIMEOUT, count += 1);
})
.catch(() => {
rmTimeout(timeout);
timeout = setRecursiveTimeout(CHECK_TIMEOUT, count += 1);
});
return false;
};
checkUpdate();
} catch (error) {
dispatch(addNoticeToast({ error: 'update_failed' }));
dispatch(getUpdateFailure());
}
};
export const getClientsRequest = createAction('GET_CLIENTS_REQUEST');
export const getClientsFailure = createAction('GET_CLIENTS_FAILURE');
export const getClientsSuccess = createAction('GET_CLIENTS_SUCCESS');
export const getClients = () => async (dispatch) => {
dispatch(getClientsRequest());
try {
const data = await apiClient.getClients();
const sortedClients = data.clients && sortClients(data.clients);
const sortedAutoClients = data.auto_clients && sortClients(data.auto_clients);
dispatch(getClientsSuccess({
clients: sortedClients || [],
autoClients: sortedAutoClients || [],
}));
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(getClientsFailure());
}
};
export const getTopStatsRequest = createAction('GET_TOP_STATS_REQUEST');
export const getTopStatsFailure = createAction('GET_TOP_STATS_FAILURE');
export const getTopStatsSuccess = createAction('GET_TOP_STATS_SUCCESS');
export const getTopStats = () => async (dispatch, getState) => {
dispatch(getTopStatsRequest());
const timer = setInterval(async () => {
const state = getState();
if (state.dashboard.isCoreRunning) {
clearInterval(timer);
try {
const stats = await apiClient.getGlobalStatsTop();
dispatch(getTopStatsSuccess(stats));
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(getTopStatsFailure(error));
}
}
}, 100);
};
export const dnsStatusRequest = createAction('DNS_STATUS_REQUEST');
export const dnsStatusFailure = createAction('DNS_STATUS_FAILURE');
export const dnsStatusSuccess = createAction('DNS_STATUS_SUCCESS');
@@ -87,8 +276,10 @@ export const getDnsStatus = () => async (dispatch) => {
try {
const dnsStatus = await apiClient.getGlobalStatus();
dispatch(dnsStatusSuccess(dnsStatus));
dispatch(getVersion());
dispatch(getTlsStatus());
} catch (error) {
console.error(error);
dispatch(addErrorToast({ error }));
dispatch(initSettingsFailure());
}
};
@@ -103,7 +294,7 @@ export const enableDns = () => async (dispatch) => {
await apiClient.startGlobalFiltering();
dispatch(enableDnsSuccess());
} catch (error) {
console.error(error);
dispatch(addErrorToast({ error }));
dispatch(enableDnsFailure());
}
};
@@ -118,8 +309,8 @@ export const disableDns = () => async (dispatch) => {
await apiClient.stopGlobalFiltering();
dispatch(disableDnsSuccess());
} catch (error) {
console.error(error);
dispatch(disableDnsFailure());
dispatch(disableDnsFailure(error));
dispatch(addErrorToast({ error }));
}
};
@@ -139,51 +330,30 @@ export const getStats = () => async (dispatch) => {
dispatch(getStatsSuccess(processedStats));
} catch (error) {
console.error(error);
dispatch(addErrorToast({ error }));
dispatch(getStatsFailure());
}
};
export const getTopStatsRequest = createAction('GET_TOP_STATS_REQUEST');
export const getTopStatsFailure = createAction('GET_TOP_STATS_FAILURE');
export const getTopStatsSuccess = createAction('GET_TOP_STATS_SUCCESS');
export const getTopStats = () => async (dispatch, getState) => {
dispatch(getTopStatsRequest());
try {
const state = getState();
const timer = setInterval(async () => {
if (state.dashboard.isCoreRunning) {
const stats = await apiClient.getGlobalStatsTop();
dispatch(getTopStatsSuccess(stats));
clearInterval(timer);
}
}, 100);
} catch (error) {
console.error(error);
dispatch(getTopStatsFailure());
}
};
export const getLogsRequest = createAction('GET_LOGS_REQUEST');
export const getLogsFailure = createAction('GET_LOGS_FAILURE');
export const getLogsSuccess = createAction('GET_LOGS_SUCCESS');
export const getLogs = () => async (dispatch, getState) => {
dispatch(getLogsRequest());
try {
const timer = setInterval(async () => {
const state = getState();
const timer = setInterval(async () => {
if (state.dashboard.isCoreRunning) {
const logs = await apiClient.getQueryLog();
if (state.dashboard.isCoreRunning) {
clearInterval(timer);
try {
const logs = normalizeLogs(await apiClient.getQueryLog());
dispatch(getLogsSuccess(logs));
clearInterval(timer);
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(getLogsFailure(error));
}
}, 100);
} catch (error) {
console.error(error);
dispatch(getLogsFailure());
}
}
}, 100);
};
export const toggleLogStatusRequest = createAction('TOGGLE_LOGS_REQUEST');
@@ -193,16 +363,20 @@ export const toggleLogStatusSuccess = createAction('TOGGLE_LOGS_SUCCESS');
export const toggleLogStatus = queryLogEnabled => async (dispatch) => {
dispatch(toggleLogStatusRequest());
let toggleMethod;
let successMessage;
if (queryLogEnabled) {
toggleMethod = apiClient.disableQueryLog.bind(apiClient);
successMessage = 'query_log_disabled_toast';
} else {
toggleMethod = apiClient.enableQueryLog.bind(apiClient);
successMessage = 'query_log_enabled_toast';
}
try {
await toggleMethod();
dispatch(addSuccessToast(successMessage));
dispatch(toggleLogStatusSuccess());
} catch (error) {
console.error(error);
dispatch(addErrorToast({ error }));
dispatch(toggleLogStatusFailure());
}
};
@@ -214,10 +388,14 @@ export const setRulesSuccess = createAction('SET_RULES_SUCCESS');
export const setRules = rules => async (dispatch) => {
dispatch(setRulesRequest());
try {
await apiClient.setRules(rules);
const replacedLineEndings = rules
.replace(/^\n/g, '')
.replace(/\n\s*\n/g, '\n');
await apiClient.setRules(replacedLineEndings);
dispatch(addSuccessToast('updated_custom_filtering_toast'));
dispatch(setRulesSuccess());
} catch (error) {
console.error(error);
dispatch(addErrorToast({ error }));
dispatch(setRulesFailure());
}
};
@@ -232,7 +410,7 @@ export const getFilteringStatus = () => async (dispatch) => {
const status = await apiClient.getFilteringStatus();
dispatch(getFilteringStatusSuccess({ status: normalizeFilteringStatus(status) }));
} catch (error) {
console.error(error);
dispatch(addErrorToast({ error }));
dispatch(getFilteringStatusFailure());
}
};
@@ -258,7 +436,7 @@ export const toggleFilterStatus = url => async (dispatch, getState) => {
dispatch(toggleFilterSuccess(url));
dispatch(getFilteringStatus());
} catch (error) {
console.error(error);
dispatch(addErrorToast({ error }));
dispatch(toggleFilterFailure());
}
};
@@ -268,14 +446,28 @@ 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 {
await apiClient.refreshFilters();
dispatch(refreshFiltersSuccess);
const refreshText = await apiClient.refreshFilters();
dispatch(refreshFiltersSuccess());
if (refreshText.includes('OK')) {
if (refreshText.includes('OK 0')) {
dispatch(addSuccessToast('all_filters_up_to_date_toast'));
} else {
dispatch(addSuccessToast(refreshText.replace(/OK /g, '')));
}
} else {
dispatch(addErrorToast({ error: refreshText }));
}
dispatch(getFilteringStatus());
dispatch(hideLoading());
} catch (error) {
console.error(error);
dispatch(addErrorToast({ error }));
dispatch(refreshFiltersFailure());
dispatch(hideLoading());
}
};
@@ -292,7 +484,7 @@ export const getStatsHistory = () => async (dispatch) => {
const normalizedHistory = normalizeHistory(statsHistory);
dispatch(getStatsHistorySuccess(normalizedHistory));
} catch (error) {
console.error(error);
dispatch(addErrorToast({ error }));
dispatch(getStatsHistoryFailure());
}
};
@@ -301,14 +493,14 @@ export const addFilterRequest = createAction('ADD_FILTER_REQUEST');
export const addFilterFailure = createAction('ADD_FILTER_FAILURE');
export const addFilterSuccess = createAction('ADD_FILTER_SUCCESS');
export const addFilter = url => async (dispatch) => {
export const addFilter = (url, name) => async (dispatch) => {
dispatch(addFilterRequest());
try {
await apiClient.addFilter(url);
await apiClient.addFilter(url, name);
dispatch(addFilterSuccess(url));
dispatch(getFilteringStatus());
} catch (error) {
console.error(error);
dispatch(addErrorToast({ error }));
dispatch(addFilterFailure());
}
};
@@ -325,7 +517,7 @@ export const removeFilter = url => async (dispatch) => {
dispatch(removeFilterSuccess(url));
dispatch(getFilteringStatus());
} catch (error) {
console.error(error);
dispatch(addErrorToast({ error }));
dispatch(removeFilterFailure());
}
};
@@ -336,7 +528,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());
@@ -344,7 +535,7 @@ export const downloadQueryLog = () => async (dispatch) => {
data = await apiClient.downloadQueryLog();
dispatch(downloadQueryLogSuccess());
} catch (error) {
console.error(error);
dispatch(addErrorToast({ error }));
dispatch(downloadQueryLogFailure());
}
return data;
@@ -355,13 +546,213 @@ export const setUpstreamRequest = createAction('SET_UPSTREAM_REQUEST');
export const setUpstreamFailure = createAction('SET_UPSTREAM_FAILURE');
export const setUpstreamSuccess = createAction('SET_UPSTREAM_SUCCESS');
export const setUpstream = url => async (dispatch) => {
export const setUpstream = config => async (dispatch) => {
dispatch(setUpstreamRequest());
try {
await apiClient.setUpstream(url);
const values = { ...config };
values.bootstrap_dns = (
values.bootstrap_dns && normalizeTextarea(values.bootstrap_dns)
) || [];
values.upstream_dns = (
values.upstream_dns && normalizeTextarea(values.upstream_dns)
) || [];
await apiClient.setUpstream(values);
dispatch(addSuccessToast('updated_upstream_dns_toast'));
dispatch(setUpstreamSuccess());
} catch (error) {
console.error(error);
dispatch(addErrorToast({ error }));
dispatch(setUpstreamFailure());
}
};
export const testUpstreamRequest = createAction('TEST_UPSTREAM_REQUEST');
export const testUpstreamFailure = createAction('TEST_UPSTREAM_FAILURE');
export const testUpstreamSuccess = createAction('TEST_UPSTREAM_SUCCESS');
export const testUpstream = config => async (dispatch) => {
dispatch(testUpstreamRequest());
try {
const values = { ...config };
values.bootstrap_dns = (
values.bootstrap_dns && normalizeTextarea(values.bootstrap_dns)
) || [];
values.upstream_dns = (
values.upstream_dns && normalizeTextarea(values.upstream_dns)
) || [];
const upstreamResponse = await apiClient.testUpstream(values);
const testMessages = Object.keys(upstreamResponse).map((key) => {
const message = upstreamResponse[key];
if (message !== 'OK') {
dispatch(addErrorToast({ error: t('dns_test_not_ok_toast', { key }) }));
}
return message;
});
if (testMessages.every(message => message === 'OK')) {
dispatch(addSuccessToast('dns_test_ok_toast'));
}
dispatch(testUpstreamSuccess());
} catch (error) {
dispatch(addErrorToast({ error }));
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');
export const setDhcpConfig = values => async (dispatch, getState) => {
const { config } = getState().dhcp;
const updatedConfig = { ...config, ...values };
dispatch(setDhcpConfigRequest());
dispatch(findActiveDhcp(values.interface_name));
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');
export const toggleDhcp = values => async (dispatch) => {
dispatch(toggleDhcpRequest());
let config = { ...values, enabled: false };
let successMessage = 'disabled_dhcp';
if (!values.enabled) {
config = { ...values, enabled: true };
successMessage = 'enabled_dhcp';
dispatch(findActiveDhcp(values.interface_name));
}
try {
await apiClient.setDhcpConfig(config);
dispatch(toggleDhcpSuccess());
dispatch(addSuccessToast(successMessage));
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(toggleDhcpFailure());
}
};
export const toggleLeaseModal = createAction('TOGGLE_LEASE_MODAL');
export const addStaticLeaseRequest = createAction('ADD_STATIC_LEASE_REQUEST');
export const addStaticLeaseFailure = createAction('ADD_STATIC_LEASE_FAILURE');
export const addStaticLeaseSuccess = createAction('ADD_STATIC_LEASE_SUCCESS');
export const addStaticLease = config => async (dispatch) => {
dispatch(addStaticLeaseRequest());
try {
const name = config.hostname || config.ip;
await apiClient.addStaticLease(config);
dispatch(addStaticLeaseSuccess(config));
dispatch(addSuccessToast(t('dhcp_lease_added', { key: name })));
dispatch(toggleLeaseModal());
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(addStaticLeaseFailure());
}
};
export const removeStaticLeaseRequest = createAction('REMOVE_STATIC_LEASE_REQUEST');
export const removeStaticLeaseFailure = createAction('REMOVE_STATIC_LEASE_FAILURE');
export const removeStaticLeaseSuccess = createAction('REMOVE_STATIC_LEASE_SUCCESS');
export const removeStaticLease = config => async (dispatch) => {
dispatch(removeStaticLeaseRequest());
try {
const name = config.hostname || config.ip;
await apiClient.removeStaticLease(config);
dispatch(removeStaticLeaseSuccess(config));
dispatch(addSuccessToast(t('dhcp_lease_deleted', { key: name })));
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(removeStaticLeaseFailure());
}
};

View File

@@ -0,0 +1,61 @@
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());
}
};
export const checkConfigRequest = createAction('CHECK_CONFIG_REQUEST');
export const checkConfigFailure = createAction('CHECK_CONFIG_FAILURE');
export const checkConfigSuccess = createAction('CHECK_CONFIG_SUCCESS');
export const checkConfig = values => async (dispatch) => {
dispatch(checkConfigRequest());
try {
const check = await apiClient.checkConfig(values);
dispatch(checkConfigSuccess(check));
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(checkConfigFailure());
}
};

View File

@@ -1,18 +1,26 @@
import axios from 'axios';
import startOfToday from 'date-fns/start_of_today';
import endOfToday from 'date-fns/end_of_today';
import subHours from 'date-fns/sub_hours';
import dateFormat from 'date-fns/format';
export default class Api {
baseUrl = 'control';
async makeRequest(path, method = 'POST', config) {
const response = await axios({
url: `${this.baseUrl}/${path}`,
method,
...config,
});
return response.data;
try {
const response = await axios({
url: `${this.baseUrl}/${path}`,
method,
...config,
});
return response.data;
} catch (error) {
console.error(error);
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}`);
}
}
// Global methods
@@ -26,7 +34,12 @@ export default class Api {
GLOBAL_QUERY_LOG = { path: 'querylog', method: 'GET' };
GLOBAL_QUERY_LOG_ENABLE = { path: 'querylog_enable', method: 'POST' };
GLOBAL_QUERY_LOG_DISABLE = { path: 'querylog_disable', method: 'POST' };
GLOBAL_SET_UPSTREAM_DNS = { path: 'set_upstream_dns', method: 'POST' };
GLOBAL_SET_UPSTREAM_DNS = { path: 'set_upstreams_config', method: 'POST' };
GLOBAL_TEST_UPSTREAM_DNS = { path: 'test_upstream_dns', method: 'POST' };
GLOBAL_VERSION = { path: 'version.json', method: 'POST' };
GLOBAL_ENABLE_PROTECTION = { path: 'enable_protection', method: 'POST' };
GLOBAL_DISABLE_PROTECTION = { path: 'disable_protection', method: 'POST' };
GLOBAL_UPDATE = { path: 'update', method: 'POST' };
restartGlobalFiltering() {
const { path, method } = this.GLOBAL_RESTART;
@@ -51,13 +64,12 @@ export default class Api {
getGlobalStatsHistory() {
const { path, method } = this.GLOBAL_STATS_HISTORY;
const format = 'YYYY-MM-DDTHH:mm:ssZ';
const todayStart = dateFormat(startOfToday(), format);
const todayEnd = dateFormat(endOfToday(), format);
const dateNow = Date.now();
const config = {
params: {
start_time: todayStart,
end_time: todayEnd,
start_time: dateFormat(subHours(dateNow, 24), format),
end_time: dateFormat(dateNow, format),
time_unit: 'hours',
},
};
@@ -99,18 +111,51 @@ export default class Api {
const { path, method } = this.GLOBAL_SET_UPSTREAM_DNS;
const config = {
data: url,
header: { 'Content-Type': 'text/plain' },
headers: { 'Content-Type': 'application/json' },
};
return this.makeRequest(path, method, config);
}
testUpstream(servers) {
const { path, method } = this.GLOBAL_TEST_UPSTREAM_DNS;
const config = {
data: servers,
headers: { 'Content-Type': 'application/json' },
};
return this.makeRequest(path, method, config);
}
getGlobalVersion(data) {
const { path, method } = this.GLOBAL_VERSION;
const config = {
data,
headers: { 'Content-Type': 'application/json' },
};
return this.makeRequest(path, method, config);
}
enableGlobalProtection() {
const { path, method } = this.GLOBAL_ENABLE_PROTECTION;
return this.makeRequest(path, method);
}
disableGlobalProtection() {
const { path, method } = this.GLOBAL_DISABLE_PROTECTION;
return this.makeRequest(path, method);
}
getUpdate() {
const { path, method } = this.GLOBAL_UPDATE;
return this.makeRequest(path, method);
}
// Filtering
FILTERING_STATUS = { path: 'filtering/status', method: 'GET' };
FILTERING_ENABLE = { path: 'filtering/enable', method: 'POST' };
FILTERING_DISABLE = { path: 'filtering/disable', method: 'POST' };
FILTERING_ADD_FILTER = { path: 'filtering/add_url', method: 'PUT' };
FILTERING_REMOVE_FILTER = { path: 'filtering/remove_url', method: 'DELETE' };
FILTERING_SET_RULES = { path: 'filtering/set_rules', method: 'PUT' };
FILTERING_ADD_FILTER = { path: 'filtering/add_url', method: 'POST' };
FILTERING_REMOVE_FILTER = { path: 'filtering/remove_url', method: 'POST' };
FILTERING_SET_RULES = { path: 'filtering/set_rules', method: 'POST' };
FILTERING_ENABLE_FILTER = { path: 'filtering/enable_url', method: 'POST' };
FILTERING_DISABLE_FILTER = { path: 'filtering/disable_url', method: 'POST' };
FILTERING_REFRESH = { path: 'filtering/refresh', method: 'POST' };
@@ -136,26 +181,25 @@ export default class Api {
return this.makeRequest(path, method);
}
addFilter(url) {
addFilter(url, name) {
const { path, method } = this.FILTERING_ADD_FILTER;
const parameter = 'url';
const requestBody = `${parameter}=${url}`;
const config = {
data: requestBody,
header: { 'Content-Type': 'text/plain' },
data: {
name,
url,
},
};
return this.makeRequest(path, method, config);
}
removeFilter(url) {
removeFilter(config) {
const { path, method } = this.FILTERING_REMOVE_FILTER;
const parameter = 'url';
const requestBody = `${parameter}=${url}`;
const config = {
data: requestBody,
header: { 'Content-Type': 'text/plain' },
const parameters = {
data: config,
headers: { 'Content-Type': 'application/json' },
};
return this.makeRequest(path, method, config);
return this.makeRequest(path, method, parameters);
}
setRules(rules) {
@@ -253,4 +297,188 @@ 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' };
DHCP_ADD_STATIC_LEASE = { path: 'dhcp/add_static_lease', method: 'POST' };
DHCP_REMOVE_STATIC_LEASE = { path: 'dhcp/remove_static_lease', method: 'POST' };
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);
}
addStaticLease(config) {
const { path, method } = this.DHCP_ADD_STATIC_LEASE;
const parameters = {
data: config,
headers: { 'Content-Type': 'application/json' },
};
return this.makeRequest(path, method, parameters);
}
removeStaticLease(config) {
const { path, method } = this.DHCP_REMOVE_STATIC_LEASE;
const parameters = {
data: config,
headers: { 'Content-Type': 'application/json' },
};
return this.makeRequest(path, method, parameters);
}
// Installation
INSTALL_GET_ADDRESSES = { path: 'install/get_addresses', method: 'GET' };
INSTALL_CONFIGURE = { path: 'install/configure', method: 'POST' };
INSTALL_CHECK_CONFIG = { path: 'install/check_config', 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);
}
checkConfig(config) {
const { path, method } = this.INSTALL_CHECK_CONFIG;
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);
}
// Per-client settings
GET_CLIENTS = { path: 'clients', method: 'GET' };
ADD_CLIENT = { path: 'clients/add', method: 'POST' };
DELETE_CLIENT = { path: 'clients/delete', method: 'POST' };
UPDATE_CLIENT = { path: 'clients/update', method: 'POST' };
getClients() {
const { path, method } = this.GET_CLIENTS;
return this.makeRequest(path, method);
}
addClient(config) {
const { path, method } = this.ADD_CLIENT;
const parameters = {
data: config,
headers: { 'Content-Type': 'application/json' },
};
return this.makeRequest(path, method, parameters);
}
deleteClient(config) {
const { path, method } = this.DELETE_CLIENT;
const parameters = {
data: config,
headers: { 'Content-Type': 'application/json' },
};
return this.makeRequest(path, method, parameters);
}
updateClient(config) {
const { path, method } = this.UPDATE_CLIENT;
const parameters = {
data: config,
headers: { 'Content-Type': 'application/json' },
};
return this.makeRequest(path, method, parameters);
}
// DNS access settings
ACCESS_LIST = { path: 'access/list', method: 'GET' };
ACCESS_SET = { path: 'access/set', method: 'POST' };
getAccessList() {
const { path, method } = this.ACCESS_LIST;
return this.makeRequest(path, method);
}
setAccessList(config) {
const { path, method } = this.ACCESS_SET;
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 {
@@ -17,3 +17,16 @@ body {
min-height: calc(100vh - 117px);
}
}
.loading-bar {
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,8 @@
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';
import '../ui/Tabler.css';
@@ -11,44 +13,105 @@ import Header from '../../containers/Header';
import Dashboard from '../../containers/Dashboard';
import Settings from '../../containers/Settings';
import Filters from '../../containers/Filters';
import Logs from '../../containers/Logs';
import Footer from '../ui/Footer';
import Dns from '../../containers/Dns';
import Encryption from '../../containers/Encryption';
import Dhcp from '../../containers/Dhcp';
import Clients from '../../containers/Clients';
import Logs from '../../containers/Logs';
import SetupGuide from '../../containers/SetupGuide';
import Toasts from '../Toasts';
import Footer from '../ui/Footer';
import Status from '../ui/Status';
import UpdateTopline from '../ui/UpdateTopline';
import UpdateOverlay from '../ui/UpdateOverlay';
import EncryptionTopline from '../ui/EncryptionTopline';
import Icons from '../ui/Icons';
import i18n from '../../i18n';
class App extends Component {
componentDidMount() {
this.props.getDnsStatus();
}
componentDidUpdate(prevProps) {
if (this.props.dashboard.language !== prevProps.dashboard.language) {
this.setLanguage();
}
}
handleStatusChange = () => {
this.props.enableDns();
};
handleUpdate = () => {
this.props.getUpdate();
};
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.isCoreRunning && dashboard.isUpdateAvailable;
return (
<HashRouter hashType='noslash'>
<HashRouter hashType="noslash">
<Fragment>
{updateAvailable && (
<Fragment>
<UpdateTopline
url={dashboard.announcementUrl}
version={dashboard.newVersion}
canAutoUpdate={dashboard.canAutoUpdate}
getUpdate={this.handleUpdate}
processingUpdate={dashboard.processingUpdate}
/>
<UpdateOverlay processingUpdate={dashboard.processingUpdate} />
</Fragment>
)}
{!encryption.processing && (
<EncryptionTopline notAfter={encryption.not_after} />
)}
<LoadingBar className="loading-bar" updateTime={1000} />
<Route component={Header} />
<div className="container container--wrap">
{!dashboard.processing && !dashboard.isCoreRunning &&
{!dashboard.processing && !dashboard.isCoreRunning && (
<div className="row row-cards">
<div className="col-lg-12">
<Status handleStatusChange={this.handleStatusChange} />
</div>
</div>
}
{!dashboard.processing && dashboard.isCoreRunning &&
)}
{!dashboard.processing && dashboard.isCoreRunning && (
<Fragment>
<Route path="/" exact component={Dashboard} />
<Route path="/settings" component={Settings} />
<Route path="/dns" component={Dns} />
<Route path="/encryption" component={Encryption} />
<Route path="/dhcp" component={Dhcp} />
<Route path="/clients" component={Clients} />
<Route path="/filters" component={Filters} />
<Route path="/logs" component={Logs} />
<Route path="/guide" component={SetupGuide} />
</Fragment>
}
)}
</div>
<Footer />
<Toasts />
<Icons />
</Fragment>
</HashRouter>
);
@@ -57,9 +120,13 @@ class App extends Component {
App.propTypes = {
getDnsStatus: PropTypes.func,
getUpdate: PropTypes.func,
enableDns: PropTypes.func,
dashboard: PropTypes.object,
isCoreRunning: PropTypes.bool,
error: PropTypes.string,
changeLanguage: PropTypes.func,
encryption: PropTypes.object,
};
export default App;
export default withNamespaces()(App);

View File

@@ -1,34 +1,79 @@
import React from 'react';
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';
import Popover from '../ui/Popover';
const Clients = props => (
<Card title="Top blocked domains" subtitle="in the last 3 minutes" bodyType="card-table" refresh={props.refreshButton}>
<ReactTable
data={map(props.topBlockedDomains, (value, prop) => (
{ ip: prop, domain: value }
))}
columns={[{
Header: 'IP',
accessor: 'ip',
}, {
Header: 'Domain name',
accessor: 'domain',
}]}
showPagination={false}
noDataText="No domains found"
minRows={6}
className="-striped -highlight card-table-overflow"
/>
</Card>
);
import { getTrackerData } from '../../helpers/trackers/trackers';
import { getPercent } from '../../helpers/helpers';
import { STATUS_COLORS } from '../../helpers/constants';
Clients.propTypes = {
class BlockedDomains extends Component {
columns = [{
Header: 'IP',
accessor: 'ip',
Cell: (row) => {
const { value } = row;
const trackerData = getTrackerData(value);
return (
<div className="logs__row">
<div className="logs__text" title={value}>
{value}
</div>
{trackerData && <Popover data={trackerData} />}
</div>
);
},
}, {
Header: <Trans>requests_count</Trans>,
accessor: 'domain',
maxWidth: 190,
Cell: ({ value }) => {
const {
blockedFiltering,
replacedSafebrowsing,
replacedParental,
} = this.props;
const blocked = blockedFiltering + replacedSafebrowsing + replacedParental;
const percent = getPercent(blocked, value);
return (
<Cell value={value} percent={percent} color={STATUS_COLORS.red} />
);
},
}];
render() {
const { t } = this.props;
return (
<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={ t('no_domains_found') }
minRows={6}
className="-striped -highlight card-table-overflow stats__table"
/>
</Card>
);
}
}
BlockedDomains.propTypes = {
topBlockedDomains: PropTypes.object.isRequired,
refreshButton: PropTypes.node,
blockedFiltering: PropTypes.number.isRequired,
replacedSafebrowsing: PropTypes.number.isRequired,
replacedParental: PropTypes.number.isRequired,
refreshButton: PropTypes.node.isRequired,
t: PropTypes.func,
};
export default Clients;
export default withNamespaces()(BlockedDomains);

View File

@@ -1,34 +1,87 @@
import React from 'react';
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';
const Clients = props => (
<Card title="Top clients" subtitle="in the last 3 minutes" bodyType="card-table" refresh={props.refreshButton}>
<ReactTable
data={map(props.topClients, (value, prop) => (
{ ip: prop, count: value }
))}
columns={[{
Header: 'IP',
accessor: 'ip',
}, {
Header: 'Request count',
accessor: 'count',
}]}
showPagination={false}
noDataText="No clients found"
minRows={6}
className="-striped -highlight card-table-overflow"
/>
</Card>
);
import { getPercent, getClientName } from '../../helpers/helpers';
import { STATUS_COLORS } from '../../helpers/constants';
class Clients extends Component {
getPercentColor = (percent) => {
if (percent > 50) {
return STATUS_COLORS.green;
} else if (percent > 10) {
return STATUS_COLORS.yellow;
}
return STATUS_COLORS.red;
}
columns = [{
Header: 'IP',
accessor: 'ip',
Cell: ({ value }) => {
const clientName = getClientName(this.props.clients, value)
|| getClientName(this.props.autoClients, value);
let client;
if (clientName) {
client = <span>{clientName} <small>({value})</small></span>;
} else {
client = value;
}
return (
<div className="logs__row logs__row--overflow">
<span className="logs__text" title={value}>
{client}
</span>
</div>
);
},
sortMethod: (a, b) => parseInt(a.replace(/\./g, ''), 10) - parseInt(b.replace(/\./g, ''), 10),
}, {
Header: <Trans>requests_count</Trans>,
accessor: 'count',
Cell: ({ value }) => {
const percent = getPercent(this.props.dnsQueries, value);
const percentColor = this.getPercentColor(percent);
return (
<Cell value={value} percent={percent} color={percentColor} />
);
},
}];
render() {
const { t } = this.props;
return (
<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={ t('no_clients_found') }
minRows={6}
className="-striped -highlight card-table-overflow"
/>
</Card>
);
}
}
Clients.propTypes = {
topClients: PropTypes.object.isRequired,
refreshButton: PropTypes.node,
dnsQueries: PropTypes.number.isRequired,
refreshButton: PropTypes.node.isRequired,
clients: PropTypes.array.isRequired,
autoClients: PropTypes.array.isRequired,
t: PropTypes.func,
};
export default Clients;
export default withNamespaces()(Clients);

View File

@@ -1,17 +1,20 @@
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';
const tooltipType = 'tooltip-custom--narrow';
const Counters = props => (
<Card title="General counters" subtitle="in the last 3 minutes" 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 in the last 3 minutes" />
<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">
@@ -21,8 +24,10 @@ const Counters = props => (
</tr>
<tr>
<td>
Blocked by filters
<Tooltip text="A number of DNS requests blocked by filters" />
<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">
@@ -32,8 +37,8 @@ const Counters = props => (
</tr>
<tr>
<td>
Blocked malware/phishing
<Tooltip text="A number of DNS requests blocked" />
<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">
@@ -43,8 +48,8 @@ const Counters = props => (
</tr>
<tr>
<td>
Blocked adult websites
<Tooltip text="A number of adult websites blocked" />
<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">
@@ -54,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" />
<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">
@@ -65,8 +70,8 @@ const Counters = props => (
</tr>
<tr>
<td>
Average processing time
<Tooltip text="Average time in milliseconds on processing a DNS request" />
<Trans>average_processing_time</Trans>
<Tooltip text={ props.t('average_processing_time_hint') } type={tooltipType} />
</td>
<td className="text-right">
<span className="text-muted">
@@ -86,7 +91,8 @@ Counters.propTypes = {
replacedParental: PropTypes.number.isRequired,
replacedSafesearch: PropTypes.number.isRequired,
avgProcessingTime: PropTypes.number.isRequired,
refreshButton: PropTypes.node,
refreshButton: PropTypes.node.isRequired,
t: PropTypes.func.isRequired,
};
export default Counters;
export default withNamespaces()(Counters);

View File

@@ -0,0 +1,22 @@
.stats__table .popover__body {
left: 0;
transform: none;
}
.stats__table .popover__body:after {
left: 13px;
}
.stats__table .rt-tr-group:first-child .popover__body,
.stats__table .rt-tr-group:nth-child(2) .popover__body {
top: calc(100% + 5px);
bottom: initial;
z-index: 1;
}
.stats__table .rt-tr-group:first-child .popover__body:after,
.stats__table .rt-tr-group:nth-child(2) .popover__body:after {
top: -11px;
border-top: 6px solid transparent;
border-bottom: 6px solid #585965;
}

View File

@@ -1,34 +1,81 @@
import React from 'react';
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';
import Popover from '../ui/Popover';
const QueriedDomains = props => (
<Card title="Top queried domains" subtitle="in the last 3 minutes" bodyType="card-table" refresh={props.refreshButton}>
<ReactTable
data={map(props.topQueriedDomains, (value, prop) => (
{ ip: prop, count: value }
))}
columns={[{
Header: 'IP',
accessor: 'ip',
}, {
Header: 'Request count',
accessor: 'count',
}]}
showPagination={false}
noDataText="No domains found"
minRows={6}
className="-striped -highlight card-table-overflow"
/>
</Card>
);
import { getTrackerData } from '../../helpers/trackers/trackers';
import { getPercent } from '../../helpers/helpers';
import { STATUS_COLORS } from '../../helpers/constants';
class QueriedDomains extends Component {
getPercentColor = (percent) => {
if (percent > 10) {
return STATUS_COLORS.red;
} else if (percent > 5) {
return STATUS_COLORS.yellow;
}
return STATUS_COLORS.green;
}
columns = [{
Header: 'IP',
accessor: 'ip',
Cell: (row) => {
const { value } = row;
const trackerData = getTrackerData(value);
return (
<div className="logs__row">
<div className="logs__text" title={value}>
{value}
</div>
{trackerData && <Popover data={trackerData} />}
</div>
);
},
}, {
Header: <Trans>requests_count</Trans>,
accessor: 'count',
maxWidth: 190,
Cell: ({ value }) => {
const percent = getPercent(this.props.dnsQueries, value);
const percentColor = this.getPercentColor(percent);
return (
<Cell value={value} percent={percent} color={percentColor} />
);
},
}];
render() {
const { t } = this.props;
return (
<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={ t('no_domains_found') }
minRows={6}
className="-striped -highlight card-table-overflow stats__table"
/>
</Card>
);
}
}
QueriedDomains.propTypes = {
topQueriedDomains: PropTypes.object.isRequired,
refreshButton: PropTypes.node,
dnsQueries: PropTypes.number.isRequired,
refreshButton: PropTypes.node.isRequired,
t: PropTypes.func,
};
export default QueriedDomains;
export default withNamespaces()(QueriedDomains);

View File

@@ -1,61 +1,112 @@
import React from 'react';
import { ResponsiveLine } from '@nivo/line';
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';
const Statistics = props => (
<Card title="Statistics" subtitle="Today" bodyType="card-graph" refresh={props.refreshButton}>
{props.history ?
<ResponsiveLine
data={props.history}
margin={{
top: 50,
right: 40,
bottom: 80,
left: 80,
}}
minY="auto"
stacked={false}
curve='monotoneX'
axisBottom={{
orient: 'bottom',
tickSize: 5,
tickPadding: 5,
tickRotation: -45,
legend: 'time',
legendOffset: 50,
legendPosition: 'center',
}}
axisLeft={{
orient: 'left',
tickSize: 5,
tickPadding: 5,
tickRotation: 0,
legend: 'count',
legendOffset: -40,
legendPosition: 'center',
}}
enableArea={true}
dotSize={10}
dotColor="inherit:darker(0.3)"
dotBorderWidth={2}
dotBorderColor="#ffffff"
dotLabel="y"
dotLabelYOffset={-12}
animate={true}
motionStiffness={90}
motionDamping={15}
/>
:
<h2 className="text-muted">Empty data</h2>
}
</Card>
);
import { getPercent } from '../../helpers/helpers';
import { STATUS_COLORS } from '../../helpers/constants';
class Statistics extends Component {
render() {
const {
dnsQueries,
blockedFiltering,
replacedSafebrowsing,
replacedParental,
} = this.props;
const filteringData = [this.props.history[1]];
const queriesData = [this.props.history[2]];
const parentalData = [this.props.history[3]];
const safebrowsingData = [this.props.history[4]];
return (
<div className="row">
<div className="col-sm-6 col-lg-3">
<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">
<Trans>dns_query</Trans>
</div>
</div>
<div className="card-chart-bg">
<Line data={queriesData} color={STATUS_COLORS.blue}/>
</div>
</Card>
</div>
<div className="col-sm-6 col-lg-3">
<Card type="card--full" bodyType="card-wrap">
<div className="card-body-stats">
<div className="card-value card-value-stats text-red">
{blockedFiltering}
</div>
<div className="card-value card-value-percent text-red">
{getPercent(dnsQueries, blockedFiltering)}
</div>
<div className="card-title-stats">
<a href="#filters">
<Trans>blocked_by</Trans>
</a>
</div>
</div>
<div className="card-chart-bg">
<Line data={filteringData} color={STATUS_COLORS.red}/>
</div>
</Card>
</div>
<div className="col-sm-6 col-lg-3">
<Card type="card--full" bodyType="card-wrap">
<div className="card-body-stats">
<div className="card-value card-value-stats text-green">
{replacedSafebrowsing}
</div>
<div className="card-value card-value-percent text-green">
{getPercent(dnsQueries, replacedSafebrowsing)}
</div>
<div className="card-title-stats">
<Trans>stats_malware_phishing</Trans>
</div>
</div>
<div className="card-chart-bg">
<Line data={safebrowsingData} color={STATUS_COLORS.green}/>
</div>
</Card>
</div>
<div className="col-sm-6 col-lg-3">
<Card type="card--full" bodyType="card-wrap">
<div className="card-body-stats">
<div className="card-value card-value-stats text-yellow">
{replacedParental}
</div>
<div className="card-value card-value-percent text-yellow">
{getPercent(dnsQueries, replacedParental)}
</div>
<div className="card-title-stats">
<Trans>stats_adult</Trans>
</div>
</div>
<div className="card-chart-bg">
<Line data={parentalData} color={STATUS_COLORS.yellow}/>
</div>
</Card>
</div>
</div>
);
}
}
Statistics.propTypes = {
history: PropTypes.array.isRequired,
refreshButton: PropTypes.node,
dnsQueries: PropTypes.number.isRequired,
blockedFiltering: PropTypes.number.isRequired,
replacedSafebrowsing: PropTypes.number.isRequired,
replacedParental: PropTypes.number.isRequired,
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';
@@ -10,31 +10,72 @@ import BlockedDomains from './BlockedDomains';
import PageTitle from '../ui/PageTitle';
import Loading from '../ui/Loading';
import './Dashboard.css';
class Dashboard extends Component {
componentDidMount() {
this.getAllStats();
}
getAllStats = () => {
this.props.getStats();
this.props.getStatsHistory();
this.props.getTopStats();
this.props.getClients();
}
getToggleFilteringButton = () => {
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)}
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.processingClients ||
dashboard.processingTopStats;
const disableButton = <button type="button" className="btn btn-outline-secondary btn-sm mr-2" onClick={() => this.props.disableDns()}>Disable DNS</button>;
const refreshFullButton = <button type="button" className="btn btn-outline-primary btn-sm" onClick={() => this.props.getStats()}>Refresh statistics</button>;
const refreshButton = <button type="button" className="btn btn-outline-primary btn-sm card-refresh" onClick={() => this.props.getStats()}></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-icon btn-outline-primary btn-sm"
onClick={() => this.getAllStats()}
>
<svg className="icons">
<use xlinkHref="#refresh" />
</svg>
</button>
);
return (
<Fragment>
<PageTitle title="Dashboard">
<PageTitle title={ t('dashboard') }>
<div className="page-title__actions">
{disableButton}
{this.getToggleFilteringButton()}
{refreshFullButton}
</div>
</PageTitle>
@@ -46,6 +87,10 @@ class Dashboard extends Component {
<Statistics
history={dashboard.statsHistory}
refreshButton={refreshButton}
dnsQueries={dashboard.stats.dns_queries}
blockedFiltering={dashboard.stats.blocked_filtering}
replacedSafebrowsing={dashboard.stats.replaced_safebrowsing}
replacedParental={dashboard.stats.replaced_parental}
/>
</div>
}
@@ -66,12 +111,16 @@ class Dashboard extends Component {
<Fragment>
<div className="col-lg-6">
<Clients
dnsQueries={dashboard.stats.dns_queries}
refreshButton={refreshButton}
topClients={dashboard.topStats.top_clients}
clients={dashboard.clients}
autoClients={dashboard.autoClients}
/>
</div>
<div className="col-lg-6">
<QueriedDomains
dnsQueries={dashboard.stats.dns_queries}
refreshButton={refreshButton}
topQueriedDomains={dashboard.topStats.top_queried_domains}
/>
@@ -80,6 +129,9 @@ class Dashboard extends Component {
<BlockedDomains
refreshButton={refreshButton}
topBlockedDomains={dashboard.topStats.top_blocked_domains}
blockedFiltering={dashboard.stats.blocked_filtering}
replacedSafebrowsing={dashboard.stats.replaced_safebrowsing}
replacedParental={dashboard.stats.replaced_parental}
/>
</div>
</Fragment>
@@ -95,9 +147,13 @@ Dashboard.propTypes = {
getStats: PropTypes.func,
getStatsHistory: PropTypes.func,
getTopStats: PropTypes.func,
disableDns: PropTypes.func,
dashboard: PropTypes.object,
isCoreRunning: PropTypes.bool,
getFiltering: PropTypes.func,
toggleProtection: PropTypes.func,
getClients: PropTypes.func,
processingProtection: PropTypes.bool,
t: PropTypes.func,
};
export default Dashboard;
export default withNamespaces()(Dashboard);

View File

@@ -1,13 +0,0 @@
.remove-icon {
position: relative;
top: 2px;
display: inline-block;
width: 20px;
height: 18px;
opacity: 0.6;
}
.remove-icon:hover {
cursor: pointer;
opacity: 1;
}

View File

@@ -2,21 +2,22 @@ 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';
import '../ui/Modal.css';
ReactModal.setAppElement('#root');
export default class Modal extends Component {
state = {
url: '',
isUrlValid: false,
};
const initialState = {
url: '',
name: '',
isUrlValid: false,
};
// eslint-disable-next-line
isUrlValid = url => {
return R_URL_REQUIRES_PROTOCOL.test(url);
};
class Modal extends Component {
state = initialState;
isUrlValid = url => R_URL_REQUIRES_PROTOCOL.test(url);
handleUrlChange = async (e) => {
const { value: url } = e.currentTarget;
@@ -27,33 +28,49 @@ export default class Modal extends Component {
}
};
handleNameChange = (e) => {
const { value: name } = e.currentTarget;
this.setState({ ...this.state, name });
};
handleNext = () => {
this.props.addFilter(this.state.url);
this.props.addFilter(this.state.url, this.state.name);
setTimeout(() => {
if (this.props.isFilterAdded) {
this.props.toggleModal();
this.closeModal();
}
}, 2000);
};
closeModal = () => {
this.props.toggleModal();
this.setState({ ...this.state, ...initialState });
}
render() {
const {
isOpen,
toggleModal,
title,
inputDescription,
processingAddFilter,
} = this.props;
const { isUrlValid, url } = this.state;
const inputClass = classnames({
const { isUrlValid, url, name } = this.state;
const inputUrlClass = classnames({
'form-control mb-2': true,
'is-invalid': url.length > 0 && !isUrlValid,
'is-valid': url.length > 0 && isUrlValid,
});
const inputNameClass = classnames({
'form-control mb-2': true,
'is-valid': name.length > 0,
});
const renderBody = () => {
if (!this.props.isFilterAdded) {
return (
<React.Fragment>
<input type="text" className={inputClass} placeholder="Enter URL or path" 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}
@@ -63,38 +80,50 @@ export default class Modal extends Component {
}
return (
<div className="description">
Url added successfully
<Trans>url_added_successfully</Trans>
</div>
);
};
const isValidForSubmit = !(url.length > 0 && isUrlValid);
const isValidForSubmit = !(url.length > 0 && isUrlValid && name.length > 0);
return (
<ReactModal
className="Modal__Bootstrap modal-dialog modal-dialog-centered"
closeTimeoutMS={0}
isOpen={ isOpen }
onRequestClose={toggleModal}
isOpen={isOpen}
onRequestClose={this.closeModal}
>
<div className="modal-content">
<div className="modal-header">
<h4 className="modal-title">
{title}
</h4>
<button type="button" className="close" onClick={toggleModal}>
<button type="button" className="close" onClick={this.closeModal}>
<span className="sr-only">Close</span>
</button>
</div>
<div className="modal-body">
{ renderBody()}
{renderBody()}
</div>
{
!this.props.isFilterAdded &&
<div className="modal-footer">
<button type="button" className="btn btn-secondary" onClick={toggleModal}>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>
@@ -109,4 +138,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,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,61 @@ 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."
>
<Card title={t('custom_filter_rules')} subtitle={t('custom_filter_rules_hint')}>
<form onSubmit={this.handleSubmit}>
<textarea className="form-control" value={this.props.userRules} onChange={this.handleChange} />
<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/>
<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>example.org 127.0.0.1</code> - AdGuard DNS 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> &nbsp;
{t('example_comment_meaning')}
</li>
<li>
<code>/REGEX/</code> &nbsp;
<Trans
components={[
<a
href="https://kb.adguard.com/general/dns-filtering-syntax"
target="_blank"
rel="noopener noreferrer"
key="0"
>
link
</a>,
]}
>
example_regex_meaning
</Trans>
</li>
</ol>
</div>
@@ -64,4 +82,7 @@ UserRules.propTypes = {
userRules: PropTypes.string,
handleRulesChange: PropTypes.func,
handleRulesSubmit: PropTypes.func,
t: PropTypes.func,
};
export default withNamespaces()(UserRules);

View File

@@ -1,11 +1,11 @@
import React, { Component } from 'react';
import ReactTable from 'react-table';
import PropTypes from 'prop-types';
import Modal from '../ui/Modal';
import { Trans, withNamespaces } from 'react-i18next';
import Modal from './Modal';
import PageTitle from '../ui/PageTitle';
import Card from '../ui/Card';
import UserRules from './UserRules';
import './Filters.css';
class Filters extends Component {
componentDidMount() {
@@ -32,58 +32,101 @@ class Filters extends Component {
);
};
handleDelete = (url) => {
// eslint-disable-next-line no-alert
if (window.confirm(this.props.t('filter_confirm_delete'))) {
this.props.removeFilter({ url });
}
}
columns = [{
Header: 'Enabled',
Header: <Trans>enabled_table_header</Trans>,
accessor: 'enabled',
Cell: this.renderCheckbox,
width: 90,
className: 'text-center',
}, {
Header: 'Filter 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: 'Host file 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 update',
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 }) => (
<button
type="button"
className="btn btn-icon btn-outline-secondary btn-sm"
onClick={() => this.handleDelete(value)}
title={this.props.t('delete_table_action')}
>
<svg className="icons">
<use xlinkHref="#delete" />
</svg>
</button>
),
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="Blocking filters and hosts files"
subtitle="AdGuard DNS 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>
@@ -101,8 +144,9 @@ class Filters extends Component {
toggleModal={this.props.toggleFilteringModal}
addFilter={this.props.addFilter}
isFilterAdded={this.props.filtering.isFilterAdded}
title="New filter subscription"
inputDescription="Enter valid URL or file path of the filter into field above. You will be subscribed to that filter."
processingAddFilter={this.props.filtering.processingAddFilter}
title={ t('new_filter_btn') }
inputDescription={ t('enter_valid_filter_url') }
/>
</div>
);
@@ -117,6 +161,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,
@@ -124,7 +170,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

@@ -16,11 +16,13 @@
stroke: #9aa0ac;
}
.nav-tabs .nav-link.active .nav-icon {
.nav-tabs .nav-link.active .nav-icon,
.nav-tabs .nav-item.show .nav-icon {
stroke: #66b574;
}
.nav-tabs .nav-link.active:hover .nav-icon {
.nav-tabs .nav-link.active:hover .nav-icon,
.nav-tabs .nav-item.show:hover .nav-icon {
stroke: #58a273;
}
@@ -67,13 +69,47 @@
}
.nav-version {
padding: 16px 0;
font-size: 0.85rem;
padding: 7px 0;
font-size: 0.80rem;
text-align: right;
}
.nav-version__value {
max-width: 110px;
font-weight: 600;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
@media screen and (min-width: 992px) {
.nav-version__value {
max-width: 100%;
overflow: visible;
}
}
.nav-version__link {
position: relative;
display: inline-block;
border-bottom: 1px dashed #495057;
cursor: pointer;
}
.nav-version__text {
display: flex;
align-items: center;
justify-content: flex-end;
}
.header-brand-img {
height: 26px;
height: 32px;
}
.nav-tabs .nav-item.show .nav-link {
color: #66b574;
background-color: #fff;
border-bottom-color: #66b574;
}
@media screen and (min-width: 992px) {
@@ -84,6 +120,8 @@
.nav-tabs .nav-link {
width: auto;
border-bottom: 1px solid transparent;
font-size: 13px;
white-space: nowrap;
}
.mobile-menu {
@@ -103,7 +141,24 @@
.nav-version {
padding: 0;
font-size: 0.9rem;
}
.nav-icon {
display: none;
}
}
@media screen and (min-width: 1280px) {
.nav-tabs .nav-link {
font-size: 14px;
}
.nav-version {
font-size: 0.85rem;
}
.nav-icon {
display: block;
}
}

View File

@@ -3,6 +3,10 @@ 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 { SETTINGS_URLS } from '../../helpers/constants';
import Dropdown from '../ui/Dropdown';
class Menu extends Component {
handleClickOutside = () => {
@@ -13,44 +17,87 @@ class Menu extends Component {
this.props.toggleMenuOpen();
};
getActiveClassForSettings = () => {
const { pathname } = this.props.location;
const isSettingsPage = SETTINGS_URLS.some(item => item === pathname);
return isSettingsPage ? 'active' : '';
};
render() {
const menuClass = classnames({
'col-lg mobile-menu': true,
'col-lg-6 mobile-menu': true,
'mobile-menu--active': this.props.isMenuOpen,
});
const dropdownControlClass = `nav-link ${this.getActiveClassForSettings()}`;
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
<svg className="nav-icon">
<use xlinkHref="#back" />
</svg>
<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
</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
<svg className="nav-icon">
<use xlinkHref="#dashboard" />
</svg>
<Trans>dashboard</Trans>
</NavLink>
</li>
<Dropdown
label={this.props.t('settings')}
baseClassName="dropdown nav-item"
controlClassName={dropdownControlClass}
icon="settings"
>
<Fragment>
<NavLink to="/settings" className="dropdown-item">
<Trans>general_settings</Trans>
</NavLink>
<NavLink to="/dns" className="dropdown-item">
<Trans>dns_settings</Trans>
</NavLink>
<NavLink to="/encryption" className="dropdown-item">
<Trans>encryption_settings</Trans>
</NavLink>
<NavLink to="/clients" className="dropdown-item">
<Trans>client_settings</Trans>
</NavLink>
<NavLink to="/dhcp" className="dropdown-item">
<Trans>dhcp_settings</Trans>
</NavLink>
</Fragment>
</Dropdown>
<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
<svg className="nav-icon">
<use xlinkHref="#filters" />
</svg>
<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
<svg className="nav-icon">
<use xlinkHref="#log" />
</svg>
<Trans>query_log</Trans>
</NavLink>
</li>
<li className="nav-item">
<NavLink to="/guide" className="nav-link">
<svg className="nav-icon">
<use xlinkHref="#setup" />
</svg>
<Trans>setup_guide</Trans>
</NavLink>
</li>
</ul>
@@ -64,6 +111,8 @@ Menu.propTypes = {
isMenuOpen: PropTypes.bool,
closeMenu: PropTypes.func,
toggleMenuOpen: PropTypes.func,
location: PropTypes.object,
t: PropTypes.func,
};
export default enhanceWithClickOutside(Menu);
export default withNamespaces()(enhanceWithClickOutside(Menu));

View File

@@ -1,17 +1,49 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Trans, withNamespaces } from 'react-i18next';
const Version = (props) => {
const {
dnsVersion, dnsAddresses, processingVersion, t,
} = props;
export default function Version(props) {
const { dnsVersion, dnsAddress, dnsPort } = props;
return (
<div className="nav-version">
v.{dnsVersion} / address: {dnsAddress}:{dnsPort}
<div className="nav-version__text">
<Trans>version</Trans>:&nbsp;<span className="nav-version__value" title={dnsVersion}>{dnsVersion}</span>
<button
type="button"
className="btn btn-icon btn-icon-sm btn-outline-primary btn-sm ml-2"
onClick={() => props.getVersion(true)}
disabled={processingVersion}
title={t('check_updates_now')}
>
<svg className="icons">
<use xlinkHref="#refresh" />
</svg>
</button>
</div>
<div className="nav-version__link">
<div className="popover__trigger popover__trigger--address">
<Trans>dns_addresses</Trans>
</div>
<div className="popover__body popover__body--address">
<div className="popover__list">
{dnsAddresses.map(ip => <li key={ip}>{ip}</li>)}
</div>
</div>
</div>
</div>
);
}
};
Version.propTypes = {
dnsVersion: PropTypes.string,
dnsAddress: PropTypes.string,
dnsPort: PropTypes.number,
dnsVersion: PropTypes.string.isRequired,
dnsAddresses: PropTypes.array.isRequired,
dnsPort: PropTypes.number.isRequired,
getVersion: PropTypes.func.isRequired,
processingVersion: PropTypes.bool.isRequired,
t: PropTypes.func.isRequired,
};
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 = () => {
@@ -23,11 +23,12 @@ class Header extends Component {
};
render() {
const { dashboard } = this.props;
const { dashboard, getVersion, location } = this.props;
const { isMenuOpen } = this.state;
const badgeClass = classnames({
'badge dns-status': true,
'badge-success': dashboard.isCoreRunning,
'badge-danger': !dashboard.isCoreRunning,
'badge-success': dashboard.protectionEnabled,
'badge-danger': !dashboard.protectionEnabled,
});
return (
@@ -42,24 +43,27 @@ class Header extends Component {
<Link to="/" className="nav-link pl-0 pr-1">
<img src={logo} alt="" className="header-brand-img" />
</Link>
{!dashboard.proccessing &&
{!dashboard.proccessing && dashboard.isCoreRunning &&
<span className={badgeClass}>
{dashboard.isCoreRunning ? 'ON' : 'OFF'}
<Trans>{dashboard.protectionEnabled ? 'on' : 'off'}</Trans>
</span>
}
</div>
</div>
<Menu
location={this.props.location}
isMenuOpen={this.state.isMenuOpen}
location={location}
isMenuOpen={isMenuOpen}
toggleMenuOpen={this.toggleMenuOpen}
closeMenu={this.closeMenu}
/>
<div className="col col-sm-6 col-lg-3">
<Version
{ ...this.props.dashboard }
/>
</div>
{!dashboard.processing &&
<div className="col col-sm-6 col-lg-3">
<Version
{ ...dashboard }
getVersion={getVersion}
/>
</div>
}
</div>
</div>
</div>
@@ -68,8 +72,9 @@ class Header extends Component {
}
Header.propTypes = {
dashboard: PropTypes.object,
location: PropTypes.object,
dashboard: PropTypes.object.isRequired,
location: PropTypes.object.isRequired,
getVersion: PropTypes.func.isRequired,
};
export default Header;
export default withNamespaces()(Header);

View File

@@ -1 +0,0 @@
<svg viewBox="0 0 118 26" xmlns="http://www.w3.org/2000/svg"><path fill="#232323" d="M92.535 18.314l-.897-2.259h-4.47l-.849 2.259h-3.034L88.13 6.809h2.708l4.796 11.505h-3.1zm-3.1-8.434l-1.468 3.949h2.904L89.435 9.88zm-6.607 4.095c0 .693-.117 1.324-.35 1.893a4.115 4.115 0 0 1-1.004 1.463 4.63 4.63 0 0 1-1.574.95c-.614.228-1.297.341-2.047.341-.761 0-1.447-.113-2.056-.34a4.468 4.468 0 0 1-1.55-.951 4.126 4.126 0 0 1-.978-1.463 5.038 5.038 0 0 1-.343-1.893V6.809H75.7v6.939c0 .314.041.612.123.893.081.282.206.534.375.756.169.222.392.398.669.528s.612.195 1.003.195c.392 0 .726-.065 1.003-.195a1.83 1.83 0 0 0 .677-.528 2.1 2.1 0 0 0 .376-.756c.076-.281.114-.58.114-.893v-6.94h2.79v7.167zm-11.446 3.64a8.898 8.898 0 0 1-1.982.715 10.43 10.43 0 0 1-2.472.276c-.924 0-1.775-.146-2.553-.439a5.895 5.895 0 0 1-2.006-1.235 5.63 5.63 0 0 1-1.314-1.909c-.315-.742-.473-1.568-.473-2.478 0-.92.16-1.755.482-2.502a5.567 5.567 0 0 1 1.33-1.91 5.893 5.893 0 0 1 1.99-1.21 7.044 7.044 0 0 1 2.463-.423c.913 0 1.762.138 2.545.414.783.277 1.419.648 1.908 1.114l-1.762 1.998a3.05 3.05 0 0 0-1.076-.772c-.446-.2-.952-.3-1.517-.3-.49 0-.941.09-1.354.268a3.256 3.256 0 0 0-1.077.747 3.39 3.39 0 0 0-.71 1.138 3.977 3.977 0 0 0-.253 1.438c0 .53.077 1.018.229 1.463.152.444.378.826.677 1.145.299.32.669.569 1.11.748.44.178.943.268 1.508.268.326 0 .636-.025.93-.073.294-.05.566-.128.816-.236v-2.096h-2.203V11.52h4.764v6.094zm46.107-5.086c0 1.007-.188 1.877-.563 2.608a5.262 5.262 0 0 1-1.484 1.804 6.199 6.199 0 0 1-2.08 1.04 8.459 8.459 0 0 1-2.35.333h-4.306V6.809h4.176c.816 0 1.62.095 2.414.284.794.19 1.501.504 2.121.943.62.438 1.12 1.026 1.5 1.763.382.736.572 1.646.572 2.73zm-2.904 0c0-.65-.106-1.19-.318-1.617a2.724 2.724 0 0 0-.848-1.024 3.4 3.4 0 0 0-1.208-.544 5.955 5.955 0 0 0-1.394-.163h-1.387v6.728h1.321c.5 0 .982-.057 1.444-.17.462-.115.87-.301 1.224-.562a2.78 2.78 0 0 0 .848-1.04c.212-.433.318-.97.318-1.608zm-55.226 0c0 1.007-.188 1.877-.563 2.608a5.262 5.262 0 0 1-1.484 1.804 6.199 6.199 0 0 1-2.08 1.04 8.459 8.459 0 0 1-2.35.333h-4.306V6.809h4.176c.816 0 1.62.095 2.414.284.794.19 1.501.504 2.121.943.62.438 1.12 1.026 1.5 1.763.382.736.572 1.646.572 2.73zm-2.904 0c0-.65-.106-1.19-.318-1.617a2.724 2.724 0 0 0-.848-1.024 3.4 3.4 0 0 0-1.207-.544 5.955 5.955 0 0 0-1.395-.163H51.3v6.728h1.321c.5 0 .982-.057 1.444-.17.462-.115.87-.301 1.224-.562a2.78 2.78 0 0 0 .848-1.04c.212-.433.318-.97.318-1.608zm-11.86 5.785l-.897-2.259h-4.47l-.848 2.259h-3.034L40.19 6.809h2.708l4.796 11.505h-3.1zm-3.1-8.434l-1.467 3.949h2.903L41.496 9.88zm61.203 8.434l-2.496-4.566h-.946v4.566h-2.74V6.809h4.404c.555 0 1.096.057 1.623.17.528.114 1 .306 1.42.577.418.271.752.629 1.003 1.073.25.444.375.996.375 1.657 0 .78-.212 1.436-.636 1.966-.425.531-1.012.91-1.762 1.138l3.018 4.924h-3.263zm-.114-7.979c0-.27-.057-.49-.171-.658a1.172 1.172 0 0 0-.44-.39 1.919 1.919 0 0 0-.604-.187 4.469 4.469 0 0 0-.645-.049H99.24v2.681h1.321c.228 0 .462-.018.701-.056.24-.038.457-.106.653-.204.196-.097.356-.238.481-.422s.188-.422.188-.715z"/><path fill="#68bc71" d="M12.651 0C8.697 0 3.927.93 0 2.977c0 4.42-.054 15.433 12.651 22.958C25.357 18.41 25.303 7.397 25.303 2.977 21.376.93 16.606 0 12.651 0z"/><path fill="#67b279" d="M12.638 25.927C-.054 18.403 0 7.396 0 2.977 3.923.932 8.687.002 12.638 0v25.927z"/><path fill="#fff" d="M12.19 17.305l7.65-10.311c-.56-.45-1.052-.133-1.323.113h-.01l-6.379 6.636-2.403-2.892c-1.147-1.325-2.705-.314-3.07-.047l5.535 6.5"/></svg>

Before

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -0,0 +1,93 @@
.logs__row {
position: relative;
display: flex;
align-items: center;
min-height: 26px;
}
.logs__row--center {
justify-content: center;
}
.logs__row--overflow {
overflow: hidden;
}
.logs__row .list-unstyled {
margin-bottom: 0;
overflow: hidden;
}
.logs__text,
.logs__row .list-unstyled li {
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.logs__row .tooltip-custom {
top: 0;
margin-left: 0;
margin-right: 5px;
}
.logs__action {
position: absolute;
top: 10px;
right: 15px;
background-color: #fff;
border-radius: 4px;
transition: opacity 0.2s ease, visibility 0.2s ease;
visibility: hidden;
opacity: 0;
}
.logs__table .rt-td {
position: relative;
}
.logs__table .rt-tr:hover .logs__action {
visibility: visible;
opacity: 1;
}
.logs__table .rt-tr-group:first-child .tooltip-custom:before {
top: calc(100% + 12px);
bottom: initial;
z-index: 1;
}
.logs__table .rt-tr-group:first-child .tooltip-custom:after {
top: initial;
bottom: -4px;
border-top: 6px solid transparent;
border-bottom: 6px solid #585965;
}
.logs__table .rt-tr-group:first-child .popover__body {
top: calc(100% + 5px);
bottom: initial;
z-index: 1;
}
.logs__table .rt-tr-group:first-child .popover__body:after {
top: -11px;
border-top: 6px solid transparent;
border-bottom: 6px solid #585965;
}
.logs__table .rt-thead.-filters input,
.logs__table .rt-thead.-filters select {
padding: 6px 7px;
border-radius: 3px;
font-size: 0.9375rem;
line-height: 1.6;
color: #495057;
border: 1px solid rgba(0, 40, 100, 0.12);
}
.logs__table .rt-thead.-filters input:focus,
.logs__table .rt-thead.-filters select:focus {
border-color: #1991eb;
box-shadow: 0 0 0 2px rgba(70, 127, 207, 0.25);
}

View File

@@ -1,21 +1,27 @@
import React, { Component } from 'react';
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
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, getClientName } 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 { normalizeLogs } from '../../helpers/helpers';
import PopoverFiltered from '../ui/PopoverFilter';
import Popover from '../ui/Popover';
import './Logs.css';
const DOWNLOAD_LOG_FILENAME = 'dns-logs.txt';
class Logs extends Component {
componentDidMount() {
// get logs on initialization if queryLogIsEnabled
if (this.props.dashboard.queryLogEnabled) {
this.props.getLogs();
}
this.getLogs();
this.props.getFilteringStatus();
this.props.getClients();
}
componentDidUpdate(prevProps) {
@@ -25,46 +31,242 @@ class Logs extends Component {
}
}
getLogs = () => {
// get logs on initialization if queryLogIsEnabled
if (this.props.dashboard.queryLogEnabled) {
this.props.getLogs();
}
}
renderTooltip(isFiltered, rule, filter) {
if (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}`;
const blockingRule = type === 'block' ? baseUnblocking : baseRule;
const unblockingRule = type === 'block' ? baseRule : baseUnblocking;
const preparedBlockingRule = new RegExp(`(^|\n)${escapeRegExp(blockingRule)}($|\n)`);
const preparedUnblockingRule = new RegExp(`(^|\n)${escapeRegExp(unblockingRule)}($|\n)`);
if (userRules.match(preparedBlockingRule)) {
this.props.setRules(userRules.replace(`${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(`${t('rule_added_to_custom_filtering_toast')}: ${unblockingRule}`);
}
this.props.getFilteringStatus();
}
renderBlockingButton(isFiltered, domain) {
const buttonClass = isFiltered ? 'btn-outline-secondary' : 'btn-outline-danger';
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(buttonType, domain)}
disabled={this.props.filtering.processingRules}
>
<Trans>{buttonText}</Trans>
</button>
</div>
);
}
renderLogs(logs) {
const { t, dashboard } = this.props;
const columns = [{
Header: 'Time',
Header: t('time_table_header'),
accessor: 'time',
maxWidth: 150,
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;
const trackerData = getTrackerData(response);
return (
<div className="logs__row" title={response}>
<div className="logs__text">
{response}
</div>
{trackerData && <Popover data={trackerData}/>}
</div>
);
},
}, {
Header: 'Type',
Header: t('type_table_header'),
accessor: 'type',
maxWidth: 100,
maxWidth: 60,
}, {
Header: 'Response',
Header: t('response_table_header'),
accessor: 'response',
Cell: (row) => {
const responses = row.value;
const { reason } = row.original;
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">
<span className="logs__text" title={parsedFilteredReason}>
{parsedFilteredReason}
</span>
{this.renderTooltip(isFiltered, rule, filterName)}
</div>
);
}
if (responses.length > 0) {
const liNodes = responses.map((response, index) =>
(<li key={index}>{response}</li>));
return (<ul className="list-unstyled">{liNodes}</ul>);
(<li key={index} title={response}>{response}</li>));
const isRenderTooltip = reason === 'NotFilteredWhiteList';
return (
<div className="logs__row">
<ul className="list-unstyled">{liNodes}</ul>
{this.renderTooltip(isRenderTooltip, rule, filterName)}
</div>
);
}
return 'Empty';
return (
<div className="logs__row">
<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 || row._original.reason === 'NotFilteredWhiteList';
}
return true;
},
Filter: ({ filter, onChange }) =>
<select
onChange={event => onChange(event.target.value)}
className="form-control"
value={filter ? filter.value : 'all'}
>
<option value="all">{ t('show_all_filter_type') }</option>
<option value="filtered">{ t('show_filtered_type') }</option>
</select>,
}, {
Header: t('client_table_header'),
accessor: 'client',
maxWidth: 250,
Cell: (row) => {
const { reason } = row.original;
const isFiltered = row ? reason.indexOf('Filtered') === 0 : false;
const clientName = getClientName(dashboard.clients, row.value)
|| getClientName(dashboard.autoClients, row.value);
let client;
if (clientName) {
client = <span>{clientName} <small>({row.value})</small></span>;
} else {
client = row.value;
}
return (
<Fragment>
<div className="logs__row">
{client}
</div>
{this.renderBlockingButton(isFiltered, row.original.domain)}
</Fragment>
);
},
},
];
if (logs) {
const normalizedLogs = normalizeLogs(logs);
return (<ReactTable
data={normalizedLogs}
className='logs__table'
filterable
data={logs}
columns={columns}
showPagination={false}
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 ?
String(row[id]).indexOf(filter.value) !== -1 : true;
}}
defaultSorted={[
{
id: 'time',
desc: true,
},
]}
getTrProps={(_state, rowInfo) => {
// highlight filtered requests
if (!rowInfo) {
return {};
}
if (rowInfo.original.reason.indexOf('Filtered') === 0) {
return {
className: 'red',
};
} else if (rowInfo.original.reason === 'NotFilteredWhiteList') {
return {
className: 'green',
};
}
return {
className: '',
};
}}
/>);
}
return undefined;
@@ -78,35 +280,65 @@ class Logs extends Component {
saveAs(dataBlob, DOWNLOAD_LOG_FILENAME);
};
renderButtons(queryLogEnabled) {
return (<div className="card-actions-top">
<button
className="btn btn-success btn-standart mr-2"
type="submit"
onClick={() => this.props.toggleLogStatus(queryLogEnabled)}
>{queryLogEnabled ? 'Disable log' : 'Enable log'}</button>
{queryLogEnabled &&
<button
className="btn btn-primary btn-standart"
type="submit"
onClick={this.handleDownloadButton}
>Download log file</button> }
</div>);
renderButtons(queryLogEnabled, logStatusProcessing) {
if (queryLogEnabled) {
return (
<Fragment>
<button
className="btn btn-gray btn-sm mr-2"
type="submit"
onClick={() => this.props.toggleLogStatus(queryLogEnabled)}
disabled={logStatusProcessing}
><Trans>disabled_log_btn</Trans></button>
<button
className="btn btn-primary btn-sm mr-2"
type="submit"
onClick={this.handleDownloadButton}
><Trans>download_log_file_btn</Trans></button>
<button
className="btn btn-outline-primary btn-sm"
type="submit"
onClick={this.getLogs}
><Trans>refresh_btn</Trans></button>
</Fragment>
);
}
return (
<button
className="btn btn-success btn-sm mr-2"
type="submit"
onClick={() => this.props.toggleLogStatus(queryLogEnabled)}
disabled={logStatusProcessing}
><Trans>enabled_log_btn</Trans></button>
);
}
render() {
const { queryLogs, dashboard } = this.props;
const { queryLogs, dashboard, t } = this.props;
const { queryLogEnabled } = dashboard;
return (
<div>
<PageTitle title="Query Log" subtitle="DNS queries log" />
<Fragment>
<PageTitle title={ t('query_log') } subtitle={ t('last_dns_queries') }>
<div className="page-title__actions">
{this.renderButtons(queryLogEnabled, dashboard.logStatusProcessing)}
</div>
</PageTitle>
<Card>
{this.renderButtons(queryLogEnabled)}
{queryLogEnabled && queryLogs.processing && <Loading />}
{queryLogEnabled && !queryLogs.processing &&
this.renderLogs(queryLogs.logs)}
{
queryLogEnabled
&& queryLogs.getLogsProcessing
&& dashboard.processingClients
&& <Loading />
}
{
queryLogEnabled
&& !queryLogs.getLogsProcessing
&& !dashboard.processingClients
&& this.renderLogs(queryLogs.logs)
}
</Card>
</div>
</Fragment>
);
}
}
@@ -117,6 +349,15 @@ Logs.propTypes = {
dashboard: PropTypes.object,
toggleLogStatus: PropTypes.func,
downloadQueryLog: PropTypes.func,
getFilteringStatus: PropTypes.func,
filtering: PropTypes.object,
userRules: PropTypes.string,
setRules: PropTypes.func,
addSuccessToast: PropTypes.func,
processingRules: PropTypes.bool,
logStatusProcessing: PropTypes.bool,
t: PropTypes.func,
getClients: PropTypes.func.isRequired,
};
export default Logs;
export default withNamespaces()(Logs);

View File

@@ -0,0 +1,118 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { withNamespaces } from 'react-i18next';
import ReactTable from 'react-table';
import { CLIENT_ID } from '../../../helpers/constants';
import Card from '../../ui/Card';
class AutoClients extends Component {
getClient = (name, clients) => {
const client = clients.find(item => name === item.name);
if (client) {
const identifier = client.mac ? CLIENT_ID.MAC : CLIENT_ID.IP;
return {
identifier,
use_global_settings: true,
...client,
};
}
return {
identifier: 'ip',
use_global_settings: true,
};
};
getStats = (ip, stats) => {
if (stats && stats.top_clients) {
return stats.top_clients[ip];
}
return '';
};
cellWrap = ({ value }) => (
<div className="logs__row logs__row--overflow">
<span className="logs__text" title={value}>
{value}
</span>
</div>
);
columns = [
{
Header: this.props.t('table_client'),
accessor: 'ip',
Cell: this.cellWrap,
},
{
Header: this.props.t('table_name'),
accessor: 'name',
Cell: this.cellWrap,
},
{
Header: this.props.t('source_label'),
accessor: 'source',
Cell: this.cellWrap,
},
{
Header: this.props.t('table_statistics'),
accessor: 'statistics',
Cell: (row) => {
const clientIP = row.original.ip;
const clientStats = clientIP && this.getStats(clientIP, this.props.topStats);
if (clientStats) {
return (
<div className="logs__row">
<div className="logs__text" title={clientStats}>
{clientStats}
</div>
</div>
);
}
return '';
},
},
];
render() {
const { t, autoClients } = this.props;
return (
<Card
title={t('auto_clients_title')}
subtitle={t('auto_clients_desc')}
bodyType="card-body box-body--settings"
>
<ReactTable
data={autoClients || []}
columns={this.columns}
className="-striped -highlight card-table-overflow"
showPagination={true}
defaultPageSize={10}
minRows={5}
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('clients_not_found')}
/>
</Card>
);
}
}
AutoClients.propTypes = {
t: PropTypes.func.isRequired,
autoClients: PropTypes.array.isRequired,
topStats: PropTypes.object.isRequired,
};
export default withNamespaces()(AutoClients);

View File

@@ -0,0 +1,260 @@
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import { Trans, withNamespaces } from 'react-i18next';
import ReactTable from 'react-table';
import { MODAL_TYPE, CLIENT_ID } from '../../../helpers/constants';
import Card from '../../ui/Card';
import Modal from './Modal';
class ClientsTable extends Component {
handleFormAdd = (values) => {
this.props.addClient(values);
};
handleFormUpdate = (values, name) => {
this.props.updateClient(values, name);
};
handleSubmit = (values) => {
if (this.props.modalType === MODAL_TYPE.EDIT) {
this.handleFormUpdate(values, this.props.modalClientName);
} else {
this.handleFormAdd(values);
}
};
cellWrap = ({ value }) => (
<div className="logs__row logs__row--overflow">
<span className="logs__text" title={value}>
{value}
</span>
</div>
);
getClient = (name, clients) => {
const client = clients.find(item => name === item.name);
if (client) {
const identifier = client.mac ? CLIENT_ID.MAC : CLIENT_ID.IP;
return {
identifier,
use_global_settings: true,
...client,
};
}
return {
identifier: CLIENT_ID.IP,
use_global_settings: true,
};
};
getStats = (ip, stats) => {
if (stats && stats.top_clients) {
return stats.top_clients[ip];
}
return '';
};
handleDelete = (data) => {
// eslint-disable-next-line no-alert
if (window.confirm(this.props.t('client_confirm_delete', { key: data.name }))) {
this.props.deleteClient(data);
}
};
columns = [
{
Header: this.props.t('table_client'),
accessor: 'ip',
Cell: (row) => {
if (row.original && row.original.mac) {
return (
<div className="logs__row logs__row--overflow">
<span className="logs__text" title={row.original.mac}>
{row.original.mac} <em>(MAC)</em>
</span>
</div>
);
} else if (row.value) {
return (
<div className="logs__row logs__row--overflow">
<span className="logs__text" title={row.value}>
{row.value} <em>(IP)</em>
</span>
</div>
);
}
return '';
},
},
{
Header: this.props.t('table_name'),
accessor: 'name',
Cell: this.cellWrap,
},
{
Header: this.props.t('settings'),
accessor: 'use_global_settings',
Cell: ({ value }) => {
const title = value ? (
<Trans>settings_global</Trans>
) : (
<Trans>settings_custom</Trans>
);
return (
<div className="logs__row logs__row--overflow">
<div className="logs__text" title={title}>
{title}
</div>
</div>
);
},
},
{
Header: this.props.t('table_statistics'),
accessor: 'statistics',
Cell: (row) => {
const clientIP = row.original.ip;
const clientStats = clientIP && this.getStats(clientIP, this.props.topStats);
if (clientStats) {
return (
<div className="logs__row">
<div className="logs__text" title={clientStats}>
{clientStats}
</div>
</div>
);
}
return '';
},
},
{
Header: this.props.t('actions_table_header'),
accessor: 'actions',
maxWidth: 150,
Cell: (row) => {
const clientName = row.original.name;
const {
toggleClientModal, processingDeleting, processingUpdating, t,
} = this.props;
return (
<div className="logs__row logs__row--center">
<button
type="button"
className="btn btn-icon btn-outline-primary btn-sm mr-2"
onClick={() =>
toggleClientModal({
type: MODAL_TYPE.EDIT,
name: clientName,
})
}
disabled={processingUpdating}
title={t('edit_table_action')}
>
<svg className="icons">
<use xlinkHref="#edit" />
</svg>
</button>
<button
type="button"
className="btn btn-icon btn-outline-secondary btn-sm"
onClick={() => this.handleDelete({ name: clientName })}
disabled={processingDeleting}
title={t('delete_table_action')}
>
<svg className="icons">
<use xlinkHref="#delete" />
</svg>
</button>
</div>
);
},
},
];
render() {
const {
t,
clients,
isModalOpen,
modalType,
modalClientName,
toggleClientModal,
processingAdding,
processingUpdating,
} = this.props;
const currentClientData = this.getClient(modalClientName, clients);
return (
<Card
title={t('clients_title')}
subtitle={t('clients_desc')}
bodyType="card-body box-body--settings"
>
<Fragment>
<ReactTable
data={clients || []}
columns={this.columns}
className="-striped -highlight card-table-overflow"
showPagination={true}
defaultPageSize={10}
minRows={5}
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('clients_not_found')}
/>
<button
type="button"
className="btn btn-success btn-standard mt-3"
onClick={() => toggleClientModal(MODAL_TYPE.ADD)}
disabled={processingAdding}
>
<Trans>client_add</Trans>
</button>
<Modal
isModalOpen={isModalOpen}
modalType={modalType}
toggleClientModal={toggleClientModal}
currentClientData={currentClientData}
handleSubmit={this.handleSubmit}
processingAdding={processingAdding}
processingUpdating={processingUpdating}
/>
</Fragment>
</Card>
);
}
}
ClientsTable.propTypes = {
t: PropTypes.func.isRequired,
clients: PropTypes.array.isRequired,
topStats: PropTypes.object.isRequired,
toggleClientModal: PropTypes.func.isRequired,
deleteClient: PropTypes.func.isRequired,
addClient: PropTypes.func.isRequired,
updateClient: PropTypes.func.isRequired,
isModalOpen: PropTypes.bool.isRequired,
modalType: PropTypes.string.isRequired,
modalClientName: PropTypes.string.isRequired,
processingAdding: PropTypes.bool.isRequired,
processingDeleting: PropTypes.bool.isRequired,
processingUpdating: PropTypes.bool.isRequired,
};
export default withNamespaces()(ClientsTable);

View File

@@ -0,0 +1,217 @@
import React 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 { renderField, renderSelectField, ipv4, mac, required } from '../../../helpers/form';
import { CLIENT_ID } from '../../../helpers/constants';
let Form = (props) => {
const {
t,
handleSubmit,
reset,
pristine,
submitting,
clientIdentifier,
useGlobalSettings,
toggleClientModal,
processingAdding,
processingUpdating,
} = props;
return (
<form onSubmit={handleSubmit}>
<div className="modal-body">
<div className="form__group">
<div className="form-inline mb-3">
<strong className="mr-3">
<Trans>client_identifier</Trans>
</strong>
<label className="mr-3">
<Field
name="identifier"
component={renderField}
type="radio"
className="form-control mr-2"
value="ip"
/>{' '}
<Trans>ip_address</Trans>
</label>
<label>
<Field
name="identifier"
component={renderField}
type="radio"
className="form-control mr-2"
value="mac"
/>{' '}
MAC
</label>
</div>
{clientIdentifier === CLIENT_ID.IP && (
<div className="form__group">
<Field
id="ip"
name="ip"
component={renderField}
type="text"
className="form-control"
placeholder={t('form_enter_ip')}
validate={[ipv4, required]}
/>
</div>
)}
{clientIdentifier === CLIENT_ID.MAC && (
<div className="form__group">
<Field
id="mac"
name="mac"
component={renderField}
type="text"
className="form-control"
placeholder={t('form_enter_mac')}
validate={[mac, required]}
/>
</div>
)}
<div className="form__desc">
<Trans
components={[
<a href="#dhcp" key="0">
link
</a>,
]}
>
client_identifier_desc
</Trans>
</div>
</div>
<div className="form__group">
<Field
id="name"
name="name"
component={renderField}
type="text"
className="form-control"
placeholder={t('form_client_name')}
validate={[required]}
/>
</div>
<div className="mb-4">
<strong>
<Trans>settings</Trans>
</strong>
</div>
<div className="form__group">
<Field
name="use_global_settings"
type="checkbox"
component={renderSelectField}
placeholder={t('client_global_settings')}
/>
</div>
<div className="form__group">
<Field
name="filtering_enabled"
type="checkbox"
component={renderSelectField}
placeholder={t('block_domain_use_filters_and_hosts')}
disabled={useGlobalSettings}
/>
</div>
<div className="form__group">
<Field
name="safebrowsing_enabled"
type="checkbox"
component={renderSelectField}
placeholder={t('use_adguard_browsing_sec')}
disabled={useGlobalSettings}
/>
</div>
<div className="form__group">
<Field
name="parental_enabled"
type="checkbox"
component={renderSelectField}
placeholder={t('use_adguard_parental')}
disabled={useGlobalSettings}
/>
</div>
<div className="form__group">
<Field
name="safesearch_enabled"
type="checkbox"
component={renderSelectField}
placeholder={t('enforce_safe_search')}
disabled={useGlobalSettings}
/>
</div>
</div>
<div className="modal-footer">
<div className="btn-list">
<button
type="button"
className="btn btn-secondary btn-standard"
disabled={submitting}
onClick={() => {
reset();
toggleClientModal();
}}
>
<Trans>cancel_btn</Trans>
</button>
<button
type="submit"
className="btn btn-success btn-standard"
disabled={submitting || pristine || processingAdding || processingUpdating}
>
<Trans>save_btn</Trans>
</button>
</div>
</div>
</form>
);
};
Form.propTypes = {
pristine: PropTypes.bool.isRequired,
handleSubmit: PropTypes.func.isRequired,
reset: PropTypes.func.isRequired,
submitting: PropTypes.bool.isRequired,
toggleClientModal: PropTypes.func.isRequired,
clientIdentifier: PropTypes.string,
useGlobalSettings: PropTypes.bool,
t: PropTypes.func.isRequired,
processingAdding: PropTypes.bool.isRequired,
processingUpdating: PropTypes.bool.isRequired,
};
const selector = formValueSelector('clientForm');
Form = connect((state) => {
const clientIdentifier = selector(state, 'identifier');
const useGlobalSettings = selector(state, 'use_global_settings');
return {
clientIdentifier,
useGlobalSettings,
};
})(Form);
export default flow([
withNamespaces(),
reduxForm({
form: 'clientForm',
enableReinitialize: true,
}),
])(Form);

View File

@@ -0,0 +1,64 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Trans, withNamespaces } from 'react-i18next';
import ReactModal from 'react-modal';
import { MODAL_TYPE } from '../../../helpers/constants';
import Form from './Form';
const Modal = (props) => {
const {
isModalOpen,
modalType,
currentClientData,
handleSubmit,
toggleClientModal,
processingAdding,
processingUpdating,
} = props;
return (
<ReactModal
className="Modal__Bootstrap modal-dialog modal-dialog-centered modal-dialog--clients"
closeTimeoutMS={0}
isOpen={isModalOpen}
onRequestClose={() => toggleClientModal()}
>
<div className="modal-content">
<div className="modal-header">
<h4 className="modal-title">
{modalType === MODAL_TYPE.EDIT ? (
<Trans>client_edit</Trans>
) : (
<Trans>client_new</Trans>
)}
</h4>
<button type="button" className="close" onClick={() => toggleClientModal()}>
<span className="sr-only">Close</span>
</button>
</div>
<Form
initialValues={{
...currentClientData,
}}
onSubmit={handleSubmit}
toggleClientModal={toggleClientModal}
processingAdding={processingAdding}
processingUpdating={processingUpdating}
/>
</div>
</ReactModal>
);
};
Modal.propTypes = {
isModalOpen: PropTypes.bool.isRequired,
modalType: PropTypes.string.isRequired,
currentClientData: PropTypes.object.isRequired,
handleSubmit: PropTypes.func.isRequired,
toggleClientModal: PropTypes.func.isRequired,
processingAdding: PropTypes.bool.isRequired,
processingUpdating: PropTypes.bool.isRequired,
};
export default withNamespaces()(Modal);

View File

@@ -0,0 +1,71 @@
import React, { Component, Fragment } from 'react';
import { withNamespaces } from 'react-i18next';
import PropTypes from 'prop-types';
import ClientsTable from './ClientsTable';
import AutoClients from './AutoClients';
import PageTitle from '../../ui/PageTitle';
import Loading from '../../ui/Loading';
class Clients extends Component {
componentDidMount() {
this.props.getClients();
this.props.getTopStats();
}
render() {
const {
t,
dashboard,
clients,
addClient,
updateClient,
deleteClient,
toggleClientModal,
} = this.props;
return (
<Fragment>
<PageTitle title={t('client_settings')} />
{(dashboard.processingTopStats || dashboard.processingClients) && <Loading />}
{!dashboard.processingTopStats && !dashboard.processingClients && (
<Fragment>
<ClientsTable
clients={dashboard.clients}
topStats={dashboard.topStats}
isModalOpen={clients.isModalOpen}
modalClientName={clients.modalClientName}
modalType={clients.modalType}
addClient={addClient}
updateClient={updateClient}
deleteClient={deleteClient}
toggleClientModal={toggleClientModal}
processingAdding={clients.processingAdding}
processingDeleting={clients.processingDeleting}
processingUpdating={clients.processingUpdating}
/>
<AutoClients
autoClients={dashboard.autoClients}
topStats={dashboard.topStats}
/>
</Fragment>
)}
</Fragment>
);
}
}
Clients.propTypes = {
t: PropTypes.func.isRequired,
dashboard: PropTypes.object.isRequired,
clients: PropTypes.object.isRequired,
toggleClientModal: PropTypes.func.isRequired,
deleteClient: PropTypes.func.isRequired,
addClient: PropTypes.func.isRequired,
updateClient: PropTypes.func.isRequired,
getClients: PropTypes.func.isRequired,
getTopStats: PropTypes.func.isRequired,
topStats: PropTypes.object,
};
export default withNamespaces()(Clients);

View File

@@ -0,0 +1,200 @@
import React 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 { renderField, required, ipv4, isPositive, toNumber } from '../../../helpers/form';
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 Form = (props) => {
const {
t,
handleSubmit,
submitting,
invalid,
enabled,
interfaces,
interfaceValue,
processingConfig,
processingInterfaces,
} = props;
return (
<form onSubmit={handleSubmit}>
{!processingInterfaces && 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"
validate={[required]}
>
<option value="" disabled={enabled}>
{t('dhcp_interface_select')}
</option>
{renderInterfaces(interfaces)}
</Field>
</div>
</div>
{interfaceValue &&
<div className="col-sm-12 col-md-6">
{interfaces[interfaceValue] &&
renderInterfaceValues(interfaces[interfaceValue])}
</div>
}
</div>
}
<hr/>
<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,
interfaceValue: PropTypes.string,
initialValues: PropTypes.object,
processingConfig: PropTypes.bool,
processingInterfaces: PropTypes.bool,
enabled: PropTypes.bool,
t: PropTypes.func,
};
const selector = formValueSelector('dhcpForm');
Form = connect((state) => {
const interfaceValue = selector(state, 'interface_name');
return {
interfaceValue,
};
})(Form);
export default flow([
withNamespaces(),
reduxForm({ form: 'dhcpForm' }),
])(Form);

View File

@@ -0,0 +1,53 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import ReactTable from 'react-table';
import { Trans, withNamespaces } from 'react-i18next';
class Leases extends Component {
cellWrap = ({ value }) => (
<div className="logs__row logs__row--overflow">
<span className="logs__text" title={value}>
{value}
</span>
</div>
);
render() {
const { leases, t } = this.props;
return (
<ReactTable
data={leases || []}
columns={[
{
Header: 'MAC',
accessor: 'mac',
Cell: this.cellWrap,
}, {
Header: 'IP',
accessor: 'ip',
Cell: this.cellWrap,
}, {
Header: <Trans>dhcp_table_hostname</Trans>,
accessor: 'hostname',
Cell: this.cellWrap,
}, {
Header: <Trans>dhcp_table_expires</Trans>,
accessor: 'expires',
Cell: this.cellWrap,
},
]}
showPagination={false}
noDataText={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,96 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Field, reduxForm } from 'redux-form';
import { Trans, withNamespaces } from 'react-i18next';
import flow from 'lodash/flow';
import { renderField, ipv4, mac, required } from '../../../../helpers/form';
const Form = (props) => {
const {
t,
handleSubmit,
reset,
pristine,
submitting,
toggleLeaseModal,
processingAdding,
} = props;
return (
<form onSubmit={handleSubmit}>
<div className="modal-body">
<div className="form__group">
<Field
id="mac"
name="mac"
component={renderField}
type="text"
className="form-control"
placeholder={t('form_enter_mac')}
validate={[required, mac]}
/>
</div>
<div className="form__group">
<Field
id="ip"
name="ip"
component={renderField}
type="text"
className="form-control"
placeholder={t('form_enter_ip')}
validate={[required, ipv4]}
/>
</div>
<div className="form__group">
<Field
id="hostname"
name="hostname"
component={renderField}
type="text"
className="form-control"
placeholder={t('form_enter_hostname')}
/>
</div>
</div>
<div className="modal-footer">
<div className="btn-list">
<button
type="button"
className="btn btn-secondary btn-standard"
disabled={submitting}
onClick={() => {
reset();
toggleLeaseModal();
}}
>
<Trans>cancel_btn</Trans>
</button>
<button
type="submit"
className="btn btn-success btn-standard"
disabled={submitting || pristine || processingAdding}
>
<Trans>save_btn</Trans>
</button>
</div>
</div>
</form>
);
};
Form.propTypes = {
pristine: PropTypes.bool.isRequired,
handleSubmit: PropTypes.func.isRequired,
reset: PropTypes.func.isRequired,
submitting: PropTypes.bool.isRequired,
toggleLeaseModal: PropTypes.func.isRequired,
processingAdding: PropTypes.bool.isRequired,
t: PropTypes.func.isRequired,
};
export default flow([
withNamespaces(),
reduxForm({ form: 'leaseForm' }),
])(Form);

View File

@@ -0,0 +1,49 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Trans, withNamespaces } from 'react-i18next';
import ReactModal from 'react-modal';
import Form from './Form';
const Modal = (props) => {
const {
isModalOpen,
handleSubmit,
toggleLeaseModal,
processingAdding,
} = props;
return (
<ReactModal
className="Modal__Bootstrap modal-dialog modal-dialog-centered modal-dialog--clients"
closeTimeoutMS={0}
isOpen={isModalOpen}
onRequestClose={() => toggleLeaseModal()}
>
<div className="modal-content">
<div className="modal-header">
<h4 className="modal-title">
<Trans>dhcp_new_static_lease</Trans>
</h4>
<button type="button" className="close" onClick={() => toggleLeaseModal()}>
<span className="sr-only">Close</span>
</button>
</div>
<Form
onSubmit={handleSubmit}
toggleLeaseModal={toggleLeaseModal}
processingAdding={processingAdding}
/>
</div>
</ReactModal>
);
};
Modal.propTypes = {
isModalOpen: PropTypes.bool.isRequired,
handleSubmit: PropTypes.func.isRequired,
toggleLeaseModal: PropTypes.func.isRequired,
processingAdding: PropTypes.bool.isRequired,
};
export default withNamespaces()(Modal);

View File

@@ -0,0 +1,112 @@
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import ReactTable from 'react-table';
import { Trans, withNamespaces } from 'react-i18next';
import Modal from './Modal';
class StaticLeases extends Component {
cellWrap = ({ value }) => (
<div className="logs__row logs__row--overflow">
<span className="logs__text" title={value}>
{value}
</span>
</div>
);
handleSubmit = (data) => {
this.props.addStaticLease(data);
}
handleDelete = (ip, mac, hostname = '') => {
const name = hostname || ip;
// eslint-disable-next-line no-alert
if (window.confirm(this.props.t('delete_confirm', { key: name }))) {
this.props.removeStaticLease({ ip, mac, hostname });
}
}
render() {
const {
isModalOpen,
toggleLeaseModal,
processingAdding,
processingDeleting,
staticLeases,
t,
} = this.props;
return (
<Fragment>
<ReactTable
data={staticLeases || []}
columns={[
{
Header: 'MAC',
accessor: 'mac',
Cell: this.cellWrap,
},
{
Header: 'IP',
accessor: 'ip',
Cell: this.cellWrap,
},
{
Header: <Trans>dhcp_table_hostname</Trans>,
accessor: 'hostname',
Cell: this.cellWrap,
},
{
Header: <Trans>actions_table_header</Trans>,
accessor: 'actions',
maxWidth: 150,
Cell: (row) => {
const { ip, mac, hostname } = row.original;
return (
<div className="logs__row logs__row--center">
<button
type="button"
className="btn btn-icon btn-outline-secondary btn-sm"
title={t('delete_table_action')}
disabled={processingDeleting}
onClick={() =>
this.handleDelete(ip, mac, hostname)
}
>
<svg className="icons">
<use xlinkHref="#delete" />
</svg>
</button>
</div>
);
},
},
]}
showPagination={false}
noDataText={t('dhcp_static_leases_not_found')}
className="-striped -highlight card-table-overflow"
minRows={6}
/>
<Modal
isModalOpen={isModalOpen}
toggleLeaseModal={toggleLeaseModal}
handleSubmit={this.handleSubmit}
processingAdding={processingAdding}
/>
</Fragment>
);
}
}
StaticLeases.propTypes = {
staticLeases: PropTypes.array.isRequired,
isModalOpen: PropTypes.bool.isRequired,
toggleLeaseModal: PropTypes.func.isRequired,
removeStaticLease: PropTypes.func.isRequired,
addStaticLease: PropTypes.func.isRequired,
processingAdding: PropTypes.bool.isRequired,
processingDeleting: PropTypes.bool.isRequired,
t: PropTypes.func.isRequired,
};
export default withNamespaces()(StaticLeases);

View File

@@ -0,0 +1,275 @@
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { Trans, withNamespaces } from 'react-i18next';
import { DHCP_STATUS_RESPONSE } from '../../../helpers/constants';
import Form from './Form';
import Leases from './Leases';
import StaticLeases from './StaticLeases/index';
import Card from '../../ui/Card';
import Accordion from '../../ui/Accordion';
import PageTitle from '../../ui/PageTitle';
import Loading from '../../ui/Loading';
class Dhcp extends Component {
componentDidMount() {
this.props.getDhcpStatus();
this.props.getDhcpInterfaces();
}
handleFormSubmit = (values) => {
if (values.interface_name) {
this.props.setDhcpConfig(values);
}
};
handleToggle = (config) => {
this.props.toggleDhcp(config);
};
getToggleDhcpButton = () => {
const {
config, check, processingDhcp, processingConfig,
} = this.props.dhcp;
const otherDhcpFound =
check && check.otherServer && check.otherServer.found === DHCP_STATUS_RESPONSE.YES;
const filledConfig = Object.keys(config).every((key) => {
if (key === 'enabled' || key === 'icmp_timeout_msec') {
return true;
}
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 || !check || otherDhcpFound || processingDhcp || processingConfig
}
>
<Trans>dhcp_enable</Trans>
</button>
);
};
getActiveDhcpMessage = (t, check) => {
const { found } = check.otherServer;
if (found === DHCP_STATUS_RESPONSE.ERROR) {
return (
<div className="text-danger mb-2">
<Trans>dhcp_error</Trans>
<div className="mt-2 mb-2">
<Accordion label={t('error_details')}>
<span>{check.otherServer.error}</span>
</Accordion>
</div>
</div>
);
}
return (
<div className="mb-2">
{found === DHCP_STATUS_RESPONSE.YES ? (
<div className="text-danger">
<Trans>dhcp_found</Trans>
</div>
) : (
<div className="text-secondary">
<Trans>dhcp_not_found</Trans>
</div>
)}
</div>
);
};
getDhcpWarning = (check) => {
if (check.otherServer.found === DHCP_STATUS_RESPONSE.NO) {
return '';
}
return (
<div className="text-danger">
<Trans>dhcp_warning</Trans>
</div>
);
};
getStaticIpWarning = (t, check, interfaceName) => {
if (check.staticIP.static === DHCP_STATUS_RESPONSE.ERROR) {
return (
<Fragment>
<div className="text-danger mb-2">
<Trans>dhcp_static_ip_error</Trans>
<div className="mt-2 mb-2">
<Accordion label={t('error_details')}>
<span>{check.staticIP.error}</span>
</Accordion>
</div>
</div>
<hr className="mt-4 mb-4" />
</Fragment>
);
} else if (
check.staticIP.static === DHCP_STATUS_RESPONSE.NO &&
check.staticIP.ip &&
interfaceName
) {
return (
<Fragment>
<div className="text-secondary mb-2">
<Trans
components={[<strong key="0">example</strong>]}
values={{
interfaceName,
ipAddress: check.staticIP.ip,
}}
>
dhcp_dynamic_ip_found
</Trans>
</div>
<hr className="mt-4 mb-4" />
</Fragment>
);
}
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>
<PageTitle title={t('dhcp_settings')} />
{(dhcp.processing || dhcp.processingInterfaces) && <Loading />}
{!dhcp.processing && !dhcp.processingInterfaces && (
<Fragment>
<Card
title={t('dhcp_title')}
subtitle={t('dhcp_description')}
bodyType="card-body box-body--settings"
>
<div className="dhcp">
<Fragment>
<Form
onSubmit={this.handleFormSubmit}
initialValues={{
interface_name,
...values,
}}
interfaces={dhcp.interfaces}
processingConfig={dhcp.processingConfig}
processingInterfaces={dhcp.processingInterfaces}
enabled={enabled}
/>
<hr />
<div className="card-actions mb-3">
{this.getToggleDhcpButton()}
<button
type="button"
className={statusButtonClass}
onClick={() =>
this.props.findActiveDhcp(interface_name)
}
disabled={
enabled || !interface_name || dhcp.processingConfig
}
>
<Trans>check_dhcp_servers</Trans>
</button>
</div>
{!enabled && dhcp.check && (
<Fragment>
{this.getStaticIpWarning(t, dhcp.check, interface_name)}
{this.getActiveDhcpMessage(t, dhcp.check)}
{this.getDhcpWarning(dhcp.check)}
</Fragment>
)}
</Fragment>
</div>
</Card>
{dhcp.config.enabled && (
<Fragment>
<Card
title={t('dhcp_leases')}
bodyType="card-body box-body--settings"
>
<div className="row">
<div className="col">
<Leases leases={dhcp.leases} />
</div>
</div>
</Card>
<Card
title={t('dhcp_static_leases')}
bodyType="card-body box-body--settings"
>
<div className="row">
<div className="col-12">
<StaticLeases
staticLeases={dhcp.staticLeases}
isModalOpen={dhcp.isModalOpen}
addStaticLease={this.props.addStaticLease}
removeStaticLease={this.props.removeStaticLease}
toggleLeaseModal={this.props.toggleLeaseModal}
processingAdding={dhcp.processingAdding}
processingDeleting={dhcp.processingDeleting}
/>
</div>
<div className="col-12">
<button
type="button"
className="btn btn-success btn-standard mt-3"
onClick={() => this.props.toggleLeaseModal()}
>
<Trans>dhcp_add_static_lease</Trans>
</button>
</div>
</div>
</Card>
</Fragment>
)}
</Fragment>
)}
</Fragment>
);
}
}
Dhcp.propTypes = {
dhcp: PropTypes.object,
toggleDhcp: PropTypes.func,
getDhcpStatus: PropTypes.func,
setDhcpConfig: PropTypes.func,
findActiveDhcp: PropTypes.func,
addStaticLease: PropTypes.func,
removeStaticLease: PropTypes.func,
toggleLeaseModal: PropTypes.func,
getDhcpInterfaces: PropTypes.func,
t: PropTypes.func,
};
export default withNamespaces()(Dhcp);

View File

@@ -0,0 +1,80 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Field, reduxForm } from 'redux-form';
import { Trans, withNamespaces } from 'react-i18next';
import flow from 'lodash/flow';
const Form = (props) => {
const { handleSubmit, submitting, invalid } = props;
return (
<form onSubmit={handleSubmit}>
<div className="form__group mb-5">
<label className="form__label form__label--with-desc" htmlFor="allowed_clients">
<Trans>access_allowed_title</Trans>
</label>
<div className="form__desc form__desc--top">
<Trans>access_allowed_desc</Trans>
</div>
<Field
id="allowed_clients"
name="allowed_clients"
component="textarea"
type="text"
className="form-control form-control--textarea"
/>
</div>
<div className="form__group mb-5">
<label className="form__label form__label--with-desc" htmlFor="disallowed_clients">
<Trans>access_disallowed_title</Trans>
</label>
<div className="form__desc form__desc--top">
<Trans>access_disallowed_desc</Trans>
</div>
<Field
id="disallowed_clients"
name="disallowed_clients"
component="textarea"
type="text"
className="form-control form-control--textarea"
/>
</div>
<div className="form__group mb-5">
<label className="form__label form__label--with-desc" htmlFor="blocked_hosts">
<Trans>access_blocked_title</Trans>
</label>
<div className="form__desc form__desc--top">
<Trans>access_blocked_desc</Trans>
</div>
<Field
id="blocked_hosts"
name="blocked_hosts"
component="textarea"
type="text"
className="form-control form-control--textarea"
/>
</div>
<div className="card-actions">
<div className="btn-list">
<button
type="submit"
className="btn btn-success btn-standard"
disabled={submitting || invalid}
>
<Trans>save_config</Trans>
</button>
</div>
</div>
</form>
);
};
Form.propTypes = {
handleSubmit: PropTypes.func,
submitting: PropTypes.bool,
invalid: PropTypes.bool,
initialValues: PropTypes.object,
t: PropTypes.func,
};
export default flow([withNamespaces(), reduxForm({ form: 'accessForm' })])(Form);

View File

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

View File

@@ -0,0 +1,131 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Trans, withNamespaces } from 'react-i18next';
const Examples = props => (
<div className="list leading-loose">
<p>
<Trans
components={[
<a
href="https://kb.adguard.com/general/dns-providers"
target="_blank"
rel="noopener noreferrer"
key="0"
>
DNS providers
</a>,
]}
>
dns_providers
</Trans>
</p>
<Trans>examples_title</Trans>:
<ol className="leading-loose">
<li>
<code>1.1.1.1</code> - {props.t('example_upstream_regular')}
</li>
<li>
<code>tls://1dot1dot1dot1.cloudflare-dns.com</code> &nbsp;
<span>
<Trans
components={[
<a
href="https://en.wikipedia.org/wiki/DNS_over_TLS"
target="_blank"
rel="noopener noreferrer"
key="0"
>
DNS-over-TLS
</a>,
]}
>
example_upstream_dot
</Trans>
</span>
</li>
<li>
<code>https://cloudflare-dns.com/dns-query</code> &nbsp;
<span>
<Trans
components={[
<a
href="https://en.wikipedia.org/wiki/DNS_over_HTTPS"
target="_blank"
rel="noopener noreferrer"
key="0"
>
DNS-over-HTTPS
</a>,
]}
>
example_upstream_doh
</Trans>
</span>
</li>
<li>
<code>tcp://1.1.1.1</code> <Trans>example_upstream_tcp</Trans>
</li>
<li>
<code>sdns://...</code> &nbsp;
<span>
<Trans
components={[
<a
href="https://dnscrypt.info/stamps/"
target="_blank"
rel="noopener noreferrer"
key="0"
>
DNS Stamps
</a>,
<a
href="https://dnscrypt.info/"
target="_blank"
rel="noopener noreferrer"
key="1"
>
DNSCrypt
</a>,
<a
href="https://en.wikipedia.org/wiki/DNS_over_HTTPS"
target="_blank"
rel="noopener noreferrer"
key="2"
>
DNS-over-HTTPS
</a>,
]}
>
example_upstream_sdns
</Trans>
</span>
</li>
<li>
<code>[/example.local/]1.1.1.1</code> &nbsp;
<span>
<Trans
components={[
<a
href="https://github.com/AdguardTeam/AdGuardHome/wiki/Configuration#upstreams-for-domains"
target="_blank"
rel="noopener noreferrer"
key="0"
>
Link
</a>,
]}
>
example_upstream_reserved
</Trans>
</span>
</li>
</ol>
</div>
);
Examples.propTypes = {
t: PropTypes.func.isRequired,
};
export default withNamespaces()(Examples);

View File

@@ -0,0 +1,145 @@
import React 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 classnames from 'classnames';
import { renderSelectField } from '../../../../helpers/form';
import Examples from './Examples';
let Form = (props) => {
const {
t,
handleSubmit,
testUpstream,
upstreamDns,
bootstrapDns,
allServers,
submitting,
invalid,
processingSetUpstream,
processingTestUpstream,
} = props;
const testButtonClass = classnames({
'btn btn-primary btn-standard mr-2': true,
'btn btn-primary btn-standard mr-2 btn-loading': processingTestUpstream,
});
return (
<form onSubmit={handleSubmit}>
<div className="row">
<div className="col-12">
<div className="form__group form__group--settings">
<label className="form__label" htmlFor="upstream_dns">
<Trans>upstream_dns</Trans>
</label>
<Field
id="upstream_dns"
name="upstream_dns"
component="textarea"
type="text"
className="form-control form-control--textarea"
placeholder={t('upstream_dns')}
/>
</div>
</div>
<div className="col-12">
<div className="form__group form__group--settings">
<Field
name="all_servers"
type="checkbox"
component={renderSelectField}
placeholder={t('upstream_parallel')}
/>
</div>
</div>
<div className="col-12">
<Examples />
<hr />
</div>
<div className="col-12">
<div className="form__group">
<label className="form__label form__label--with-desc" htmlFor="bootstrap_dns">
<Trans>bootstrap_dns</Trans>
</label>
<div className="form__desc form__desc--top">
<Trans>bootstrap_dns_desc</Trans>
</div>
<Field
id="bootstrap_dns"
name="bootstrap_dns"
component="textarea"
type="text"
className="form-control"
placeholder={t('bootstrap_dns')}
/>
</div>
</div>
</div>
<div className="card-actions">
<div className="btn-list">
<button
type="button"
className={testButtonClass}
onClick={() =>
testUpstream({
upstream_dns: upstreamDns,
bootstrap_dns: bootstrapDns,
all_servers: allServers,
})
}
disabled={!upstreamDns || processingTestUpstream}
>
<Trans>test_upstream_btn</Trans>
</button>
<button
type="submit"
className="btn btn-success btn-standard"
disabled={
submitting || invalid || processingSetUpstream || processingTestUpstream
}
>
<Trans>apply_btn</Trans>
</button>
</div>
</div>
</form>
);
};
Form.propTypes = {
handleSubmit: PropTypes.func,
testUpstream: PropTypes.func,
submitting: PropTypes.bool,
invalid: PropTypes.bool,
initialValues: PropTypes.object,
upstreamDns: PropTypes.string,
bootstrapDns: PropTypes.string,
allServers: PropTypes.bool,
processingTestUpstream: PropTypes.bool,
processingSetUpstream: PropTypes.bool,
t: PropTypes.func,
};
const selector = formValueSelector('upstreamForm');
Form = connect((state) => {
const upstreamDns = selector(state, 'upstream_dns');
const bootstrapDns = selector(state, 'bootstrap_dns');
const allServers = selector(state, 'all_servers');
return {
upstreamDns,
bootstrapDns,
allServers,
};
})(Form);
export default flow([
withNamespaces(),
reduxForm({
form: 'upstreamForm',
}),
])(Form);

View File

@@ -0,0 +1,64 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { withNamespaces } from 'react-i18next';
import Form from './Form';
import Card from '../../../ui/Card';
class Upstream extends Component {
handleSubmit = (values) => {
this.props.setUpstream(values);
};
handleTest = (values) => {
this.props.testUpstream(values);
};
render() {
const {
t,
upstreamDns: upstream_dns,
bootstrapDns: bootstrap_dns,
allServers: all_servers,
processingSetUpstream,
processingTestUpstream,
} = this.props;
return (
<Card
title={t('upstream_dns')}
subtitle={t('upstream_dns_hint')}
bodyType="card-body box-body--settings"
>
<div className="row">
<div className="col">
<Form
initialValues={{
upstream_dns,
bootstrap_dns,
all_servers,
}}
testUpstream={this.handleTest}
onSubmit={this.handleSubmit}
processingTestUpstream={processingTestUpstream}
processingSetUpstream={processingSetUpstream}
/>
</div>
</div>
</Card>
);
}
}
Upstream.propTypes = {
upstreamDns: PropTypes.string,
bootstrapDns: PropTypes.string,
allServers: PropTypes.bool,
setUpstream: PropTypes.func.isRequired,
testUpstream: PropTypes.func.isRequired,
processingSetUpstream: PropTypes.bool.isRequired,
processingTestUpstream: PropTypes.bool.isRequired,
t: PropTypes.func.isRequired,
};
export default withNamespaces()(Upstream);

View File

@@ -0,0 +1,60 @@
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import { withNamespaces } from 'react-i18next';
import Upstream from './Upstream';
import Access from './Access';
import PageTitle from '../../ui/PageTitle';
import Loading from '../../ui/Loading';
class Dns extends Component {
componentDidMount() {
this.props.getAccessList();
}
render() {
const {
t,
dashboard,
settings,
access,
setAccessList,
testUpstream,
setUpstream,
} = this.props;
return (
<Fragment>
<PageTitle title={t('dns_settings')} />
{(dashboard.processing || access.processing) && <Loading />}
{!dashboard.processing && !access.processing && (
<Fragment>
<Upstream
upstreamDns={dashboard.upstreamDns}
bootstrapDns={dashboard.bootstrapDns}
allServers={dashboard.allServers}
processingTestUpstream={settings.processingTestUpstream}
processingSetUpstream={settings.processingSetUpstream}
setUpstream={setUpstream}
testUpstream={testUpstream}
/>
<Access access={access} setAccessList={setAccessList} />
</Fragment>
)}
</Fragment>
);
}
}
Dns.propTypes = {
dashboard: PropTypes.object.isRequired,
settings: PropTypes.object.isRequired,
setUpstream: PropTypes.func.isRequired,
testUpstream: PropTypes.func.isRequired,
getAccessList: PropTypes.func.isRequired,
setAccessList: PropTypes.func.isRequired,
access: PropTypes.object.isRequired,
t: PropTypes.func.isRequired,
};
export default withNamespaces()(Dns);

View File

@@ -0,0 +1,377 @@
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, portTLS, 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,
valid_pair,
dns_names,
key_type,
issuer,
subject,
warning_validation,
setTlsConfig,
} = props;
const isSavingDisabled =
invalid ||
submitting ||
processingConfig ||
processingValidate ||
(isEnabled && (!privateKey || !certificateChain)) ||
(privateKey && !valid_key) ||
(certificateChain && !valid_cert) ||
(privateKey && certificateChain && !valid_pair);
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={[portTLS]}
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={t('encryption_key_input')}
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={isSavingDisabled}
>
<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,
valid_pair: 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,80 @@
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';
import PageTitle from '../../ui/PageTitle';
import Loading from '../../ui/Loading';
class Encryption extends Component {
componentDidMount() {
const { validateTlsConfig, encryption } = this.props;
if (encryption.enabled) {
validateTlsConfig(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">
<PageTitle title={t('encryption_settings')} />
{encryption.processing && <Loading />}
{!encryption.processing && (
<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,91 @@
margin-bottom: 0;
}
.btn-standart {
.form__group--settings:last-child {
margin-bottom: 20px;
}
.btn-standard {
padding-left: 20px;
padding-right: 20px;
}
.form-control--textarea {
min-height: 110px;
}
.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__label--with-desc {
margin-bottom: 0;
}
.form__status {
margin-top: 10px;
font-size: 14px;
line-height: 1.7;
}
.encryption__list {
padding-left: 0;
}
.encryption__list li {
list-style: inside;
}
.btn-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 30px;
height: 30px;
}
.btn-icon-sm {
width: 23px;
height: 23px;
min-width: 23px;
padding: 5px;
}

View File

@@ -1,52 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Card from '../ui/Card';
export default class Upstream extends Component {
handleChange = (e) => {
const { value } = e.currentTarget;
this.props.handleUpstreamChange(value);
};
handleSubmit = (e) => {
e.preventDefault();
this.props.handleUpstreamSubmit();
};
render() {
return (
<Card
title="Upstream DNS servers"
subtitle="If you keep this field empty, AdGuard will use <a href='https://1.1.1.1/' target='_blank'>Cloudflare DNS</a> as an upstream."
bodyType="card-body box-body--settings"
>
<div className="row">
<div className="col">
<form>
<textarea
className="form-control"
value={this.props.upstream}
onChange={this.handleChange}
/>
<div className="card-actions">
<button
className="btn btn-success btn-standart"
type="submit"
onClick={this.handleSubmit}
>
Apply
</button>
</div>
</form>
</div>
</div>
</Card>
);
}
}
Upstream.propTypes = {
upstream: PropTypes.string,
handleUpstreamChange: PropTypes.func,
handleUpstreamSubmit: PropTypes.func,
};

View File

@@ -1,33 +1,35 @@
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import Upstream from './Upstream';
import { withNamespaces, Trans } from 'react-i18next';
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 DNS will check if domain is blacklisted by the browsing security web service (sb.adtidy.org). It will use privacy-safe lookup API to do the check.',
title: 'use_adguard_browsing_sec',
subtitle: 'use_adguard_browsing_sec_hint',
},
parental: {
enabled: false,
title: 'Use AdGuard parental control web service',
subtitle: 'AdGuard DNS 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 DNS can enforce safe search in the major search engines: Google, Bing, Yandex.',
title: 'enforce_safe_search',
subtitle: 'enforce_save_search_hint',
},
};
@@ -35,55 +37,46 @@ export default class Settings extends Component {
this.props.initSettings(this.settings);
}
handleUpstreamChange = (value) => {
this.props.handleUpstreamChange({ upstream: value });
};
handleUpstreamSubmit = () => {
this.props.setUpstream(this.props.settings.upstream);
};
renderSettings = (settings) => {
if (Object.keys(settings).length > 0) {
return Object.keys(settings).map((key) => {
const setting = settings[key];
const { enabled } = setting;
return (<Checkbox
key={key}
{...settings[key]}
handleChange={() => this.props.toggleSetting(key, enabled)}
/>);
return (
<Checkbox
key={key}
{...settings[key]}
handleChange={() => this.props.toggleSetting(key, enabled)}
/>
);
});
}
return (
<div>No settings</div>
<div>
<Trans>no_settings</Trans>
</div>
);
}
};
render() {
const { settings, upstream } = this.props;
const { settings, t } = this.props;
return (
<Fragment>
<PageTitle title="Settings" />
<PageTitle title={t('general_settings')} />
{settings.processing && <Loading />}
{!settings.processing &&
{!settings.processing && (
<div className="content">
<div className="row">
<div className="col-md-12">
<Card title="General settings" bodyType="card-body box-body--settings">
<Card bodyType="card-body box-body--settings">
<div className="form">
{this.renderSettings(settings.settingsList)}
</div>
</Card>
<Upstream
upstream={upstream}
handleUpstreamChange={this.handleUpstreamChange}
handleUpstreamSubmit={this.handleUpstreamSubmit}
/>
</div>
</div>
</div>
}
)}
</Fragment>
);
}
@@ -94,7 +87,7 @@ Settings.propTypes = {
settings: PropTypes.object,
settingsList: PropTypes.object,
toggleSetting: PropTypes.func,
handleUpstreamChange: PropTypes.func,
setUpstream: PropTypes.func,
upstream: PropTypes.string,
t: PropTypes.func,
};
export default withNamespaces()(Settings);

View File

@@ -0,0 +1,15 @@
.guide {
max-width: 768px;
margin: 0 auto;
}
.guide__title {
margin-bottom: 10px;
font-size: 17px;
font-weight: 700;
}
.guide__desc {
margin-bottom: 20px;
font-size: 15px;
}

View File

@@ -0,0 +1,41 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Trans, withNamespaces } from 'react-i18next';
import Guide from '../ui/Guide';
import Card from '../ui/Card';
import PageTitle from '../ui/PageTitle';
import './Guide.css';
const SetupGuide = ({
t,
dashboard: {
dnsAddresses,
},
}) => (
<div className="guide">
<PageTitle title={t('setup_guide')} />
<Card>
<div className="guide__title">
<Trans>install_devices_title</Trans>
</div>
<div className="guide__desc">
<Trans>install_devices_desc</Trans>
<div className="mt-1">
<Trans>install_devices_address</Trans>:
</div>
<div className="mt-2 font-weight-bold">
{dnsAddresses.map(ip => <li key={ip}>{ip}</li>)}
</div>
</div>
<Guide dnsAddresses={dnsAddresses} />
</Card>
</div>
);
SetupGuide.propTypes = {
dashboard: PropTypes.object.isRequired,
t: PropTypes.func.isRequired,
};
export default withNamespaces()(SetupGuide);

View File

@@ -0,0 +1,66 @@
.toasts {
position: fixed;
right: 24px;
bottom: 24px;
z-index: 105;
width: 345px;
}
.toast {
display: flex;
align-items: flex-start;
margin-bottom: 12px;
padding: 16px;
font-weight: 600;
color: #ffffff;
border-radius: 4px;
background-color: rgba(236, 53, 53, 0.75);
}
.toast--success {
background-color: rgba(90, 173, 99, 0.75);
}
.toast:last-child {
margin-bottom: 0;
}
.toast__content {
flex: 1 1 auto;
margin: 0 12px 0 0;
text-overflow: ellipsis;
overflow: hidden;
}
.toast__content a {
font-weight: 600;
color: #fff;
text-decoration: underline;
}
.toast__dismiss {
display: block;
flex: 0 0 auto;
padding: 0;
background: transparent;
border: 0;
cursor: pointer;
}
.toast-enter {
opacity: 0.01;
}
.toast-enter-active {
opacity: 1;
transition: all 0.3s ease-out;
}
.toast-exit {
opacity: 1;
}
.toast-exit-active {
opacity: 0.01;
transition: all 0.3s ease-out;
}

View File

@@ -0,0 +1,52 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Trans, withNamespaces } from 'react-i18next';
class Toast extends Component {
componentDidMount() {
const timeout = this.props.type === 'success' ? 5000 : 30000;
setTimeout(() => {
this.props.removeToast(this.props.id);
}, timeout);
}
shouldComponentUpdate() {
return false;
}
showMessage(t, type, message) {
if (type === 'notice') {
return <span dangerouslySetInnerHTML={{ __html: t(message) }} />;
}
return <Trans>{message}</Trans>;
}
render() {
const {
type, id, t, message,
} = this.props;
return (
<div className={`toast toast--${type}`}>
<p className="toast__content">
{this.showMessage(t, type, message)}
</p>
<button className="toast__dismiss" onClick={() => this.props.removeToast(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>
</button>
</div>
);
}
}
Toast.propTypes = {
t: PropTypes.func.isRequired,
id: PropTypes.string.isRequired,
message: PropTypes.string.isRequired,
type: PropTypes.string.isRequired,
removeToast: PropTypes.func.isRequired,
};
export default withNamespaces()(Toast);

View File

@@ -0,0 +1,42 @@
import { connect } from 'react-redux';
import React from 'react';
import PropTypes from 'prop-types';
import { CSSTransition, TransitionGroup } from 'react-transition-group';
import * as actionCreators from '../../actions';
import Toast from './Toast';
import './Toast.css';
const Toasts = props => (
<TransitionGroup className="toasts">
{props.toasts.notices && props.toasts.notices.map((toast) => {
const { id } = toast;
return (
<CSSTransition
key={id}
timeout={500}
classNames="toast"
>
<Toast removeToast={props.removeToast} {...toast} />
</CSSTransition>
);
})}
</TransitionGroup>
);
Toasts.propTypes = {
toasts: PropTypes.object,
removeToast: PropTypes.func,
};
const mapStateToProps = (state) => {
const { toasts } = state;
const props = { toasts };
return props;
};
export default connect(
mapStateToProps,
actionCreators,
)(Toasts);

View File

@@ -0,0 +1,32 @@
.accordion {
color: #495057;
}
.accordion__label {
position: relative;
display: inline-block;
padding-left: 25px;
cursor: pointer;
user-select: none;
}
.accordion__label:after {
content: "";
position: absolute;
top: 7px;
left: 0;
width: 17px;
height: 10px;
background-image: url("./svg/chevron-down.svg");
background-repeat: no-repeat;
background-position: center;
background-size: 100%;
}
.accordion__label--open:after {
transform: rotate(180deg);
}
.accordion__content {
padding-top: 5px;
}

View File

@@ -0,0 +1,43 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import './Accordion.css';
class Accordion extends Component {
state = {
isOpen: false,
}
handleClick = () => {
this.setState(prevState => ({ isOpen: !prevState.isOpen }));
};
render() {
const accordionClass = this.state.isOpen
? 'accordion__label accordion__label--open'
: 'accordion__label';
return (
<div className="accordion">
<div
className={accordionClass}
onClick={this.handleClick}
>
{this.props.label}
</div>
{this.state.isOpen && (
<div className="accordion__content">
{this.props.children}
</div>
)}
</div>
);
}
}
Accordion.propTypes = {
children: PropTypes.node.isRequired,
label: PropTypes.string.isRequired,
};
export default Accordion;

View File

@@ -26,7 +26,6 @@
display: flex;
align-items: center;
justify-content: center;
height: 400px;
}
.card-body--status {
@@ -34,17 +33,52 @@
text-align: center;
}
.card-refresh {
height: 26px;
width: 26px;
background-size: 14px;
background-position: center;
background-repeat: no-repeat;
background-image: url('data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiBoZWlnaHQ9IjI0IiBzdHJva2U9IiM0NjdmY2YiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJtMjMgNHY2aC02Ii8+PHBhdGggZD0ibTEgMjB2LTZoNiIvPjxwYXRoIGQ9Im0zLjUxIDlhOSA5IDAgMCAxIDE0Ljg1LTMuMzZsNC42NCA0LjM2bS0yMiA0IDQuNjQgNC4zNmE5IDkgMCAwIDAgMTQuODUtMy4zNiIvPjwvc3ZnPg==');
.card-title-stats {
font-size: 13px;
color: #9aa0ac;
}
.card-refresh:hover,
.card-refresh:not(:disabled):not(.disabled):active,
.card-refresh:focus:active {
background-image: url('data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiBoZWlnaHQ9IjI0IiBzdHJva2U9IiNmZmYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyIiB2aWV3Qm94PSIwIDAgMjQgMjQiIHdpZHRoPSIyNCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJtMjMgNHY2aC02Ii8+PHBhdGggZD0ibTEgMjB2LTZoNiIvPjxwYXRoIGQ9Im0zLjUxIDlhOSA5IDAgMCAxIDE0Ljg1LTMuMzZsNC42NCA0LjM2bS0yMiA0IDQuNjQgNC4zNmE5IDkgMCAwIDAgMTQuODUtMy4zNiIvPjwvc3ZnPg==');
.card-body-stats {
position: relative;
flex: 1 1 auto;
height: calc(100% - 3rem);
margin: 0;
padding: 1rem 1.5rem;
}
.card-value-stats {
display: block;
font-size: 2.1rem;
line-height: 2.7rem;
height: 2.7rem;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
.card-value-percent {
position: absolute;
top: 15px;
right: 15px;
font-size: 0.9rem;
line-height: 1;
height: auto;
}
.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

@@ -0,0 +1,30 @@
import React from 'react';
import PropTypes from 'prop-types';
const Cell = props => (
<div className="stats__row">
<div className="stats__row-value mb-1">
<strong>{props.value}</strong>
<small className="ml-3 text-muted">
{props.percent}%
</small>
</div>
<div className="progress progress-xs">
<div
className="progress-bar"
style={{
width: `${props.percent}%`,
backgroundColor: props.color,
}}
/>
</div>
</div>
);
Cell.propTypes = {
value: PropTypes.number.isRequired,
percent: PropTypes.number.isRequired,
color: PropTypes.string.isRequired,
};
export default Cell;

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,19 +73,28 @@
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;
}
.checkbox__label-text--long {
max-width: initial;
}
.checkbox__label-title {
display: block;
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,8 @@
.dropdown-item.active,
.dropdown-item:active {
background-color: #66b574;
}
.dropdown-menu {
cursor: default;
}

View File

@@ -0,0 +1,89 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { withNamespaces } from 'react-i18next';
import enhanceWithClickOutside from 'react-click-outside';
import './Dropdown.css';
class Dropdown extends Component {
state = {
isOpen: false,
};
toggleDropdown = () => {
this.setState(prevState => ({ isOpen: !prevState.isOpen }));
};
hideDropdown = () => {
this.setState({ isOpen: false });
};
handleClickOutside = () => {
if (this.state.isOpen) {
this.hideDropdown();
}
};
render() {
const {
label,
controlClassName,
menuClassName,
baseClassName,
icon,
children,
} = this.props;
const { isOpen } = this.state;
const dropdownClass = classnames({
[baseClassName]: true,
show: isOpen,
});
const dropdownMenuClass = classnames({
[menuClassName]: true,
show: isOpen,
});
const ariaSettings = isOpen ? 'true' : 'false';
return (
<div className={dropdownClass}>
<a
className={controlClassName}
aria-expanded={ariaSettings}
onClick={this.toggleDropdown}
>
{icon && (
<svg className="nav-icon">
<use xlinkHref={`#${icon}`} />
</svg>
)}
{label}
</a>
<div className={dropdownMenuClass} onClick={this.hideDropdown}>
{children}
</div>
</div>
);
}
}
Dropdown.defaultProps = {
baseClassName: 'dropdown',
menuClassName: 'dropdown-menu dropdown-menu-arrow',
controlClassName: '',
};
Dropdown.propTypes = {
label: PropTypes.string.isRequired,
children: PropTypes.node.isRequired,
controlClassName: PropTypes.node.isRequired,
menuClassName: PropTypes.string.isRequired,
baseClassName: PropTypes.string.isRequired,
icon: PropTypes.string,
};
export default withNamespaces()(enhanceWithClickOutside(Dropdown));

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="#encryption" key="0">link</a>]}>
topline_expired_certificate
</Trans>
</Topline>
);
} else if (isAboutExpire) {
return (
<Topline type="warning">
<Trans components={[<a href="#encryption" 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,4 +1,10 @@
import React, { Component } from 'react';
import { Trans, withNamespaces } from 'react-i18next';
import { REPOSITORY, LANGUAGES, PRIVACY_POLICY_LINK } from '../../helpers/constants';
import i18n from '../../i18n';
import './Footer.css';
import './Select.css';
class Footer extends Component {
getYear = () => {
@@ -6,26 +12,38 @@ 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-reverse">
<div className="col-12 col-lg-auto ml-lg-auto">
<ul className="list-inline list-inline-dots text-center mb-0">
<li className="list-inline-item">
<a href="https://adguard.com/welcome.html" target="_blank" rel="noopener noreferrer">Homepage</a>
</li>
<li className="list-inline-item">
<a href="https://github.com/AdguardTeam/" target="_blank" rel="noopener noreferrer">Github</a>
</li>
<li className="list-inline-item">
<a href="https://adguard.com/privacy.html" target="_blank" rel="noopener noreferrer">Privacy Policy</a>
</li>
</ul>
<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="col-12 col-lg-auto mt-3 mt-lg-0 text-center">
© AdGuard {this.getYear()}
<div className="footer__column">
<a href={REPOSITORY.URL} className="footer__link" target="_blank" rel="noopener noreferrer">
<Trans>homepage</Trans>
</a>
<a href={PRIVACY_POLICY_LINK} className="footer__link" target="_blank" rel="noopener noreferrer">
<Trans>privacy_policy</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>
@@ -34,4 +52,4 @@ class Footer extends Component {
}
}
export default Footer;
export default withNamespaces()(Footer);

View File

@@ -0,0 +1,373 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { Trans, withNamespaces } from 'react-i18next';
import Tabs from '../ui/Tabs';
import Icons from '../ui/Icons';
const Guide = (props) => {
const { dnsAddresses } = props;
const tlsAddress = (dnsAddresses && dnsAddresses.filter(item => item.includes('tls://'))) || '';
const httpsAddress =
(dnsAddresses && dnsAddresses.filter(item => item.includes('https://'))) || '';
const showDnsPrivacyNotice = httpsAddress.length < 1 && tlsAddress.length < 1;
return (
<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>
<div label="dns_privacy" title={props.t('dns_privacy')}>
<div className="tab__title">
<Trans>dns_privacy</Trans>
</div>
<div className="tab__text">
{tlsAddress && tlsAddress.length > 0 && (
<div className="tab__paragraph">
<Trans
values={{ address: tlsAddress[0] }}
components={[
<strong key="0">text</strong>,
<code key="1">text</code>,
]}
>
setup_dns_privacy_1
</Trans>
</div>
)}
{httpsAddress && httpsAddress.length > 0 && (
<div className="tab__paragraph">
<Trans
values={{ address: httpsAddress[0] }}
components={[
<strong key="0">text</strong>,
<code key="1">text</code>,
]}
>
setup_dns_privacy_2
</Trans>
</div>
)}
{showDnsPrivacyNotice && (
<div className="tab__paragraph">
<Trans
components={[
<a
href="https://github.com/AdguardTeam/AdguardHome/wiki/Encryption"
target="_blank"
rel="noopener noreferrer"
key="0"
>
link
</a>,
<code key="1">text</code>,
]}
>
setup_dns_notice
</Trans>
</div>
)}
{!showDnsPrivacyNotice && (
<Fragment>
<div className="tab__paragraph">
<Trans components={[<p key="0">text</p>]}>
setup_dns_privacy_3
</Trans>
</div>
<div className="tab__paragraph">
<strong>Android</strong>
<ul>
<li>
<Trans>setup_dns_privacy_android_1</Trans>
</li>
<li>
<Trans
components={[
<a
href="https://adguard.com/adguard-android/overview.html"
target="_blank"
rel="noopener noreferrer"
key="0"
>
link
</a>,
<code key="1">text</code>,
]}
>
setup_dns_privacy_android_2
</Trans>
</li>
<li>
<Trans
components={[
<a
href="https://getintra.org/"
target="_blank"
rel="noopener noreferrer"
key="0"
>
link
</a>,
<code key="1">text</code>,
]}
>
setup_dns_privacy_android_3
</Trans>
</li>
</ul>
</div>
<div className="tab__paragraph">
<strong>iOS</strong>
<ul>
<li>
<Trans
components={[
<a
href="https://itunes.apple.com/app/id1452162351"
target="_blank"
rel="noopener noreferrer"
key="0"
>
link
</a>,
<code key="1">text</code>,
<a
href="https://dnscrypt.info/stamps"
target="_blank"
rel="noopener noreferrer"
key="2"
>
link
</a>,
]}
>
setup_dns_privacy_ios_1
</Trans>
</li>
<li>
<Trans
components={[
<a
href="https://adguard.com/adguard-ios/overview.html"
target="_blank"
rel="noopener noreferrer"
key="0"
>
link
</a>,
<code key="1">text</code>,
]}
>
setup_dns_privacy_ios_2
</Trans>
</li>
</ul>
</div>
<div className="tab__paragraph">
<strong>
<Trans>setup_dns_privacy_other_title</Trans>
</strong>
<ul>
<li>
<Trans>setup_dns_privacy_other_1</Trans>
</li>
<li>
<Trans
components={[
<a
href="https://github.com/AdguardTeam/dnsproxy"
target="_blank"
rel="noopener noreferrer"
key="0"
>
link
</a>,
]}
>
setup_dns_privacy_other_2
</Trans>
</li>
<li>
<Trans
components={[
<a
href="https://github.com/jedisct1/dnscrypt-proxy"
target="_blank"
rel="noopener noreferrer"
key="0"
>
link
</a>,
<code key="1">text</code>,
]}
>
setup_dns_privacy_other_3
</Trans>
</li>
<li>
<Trans
components={[
<a
href="https://www.mozilla.org/firefox/"
target="_blank"
rel="noopener noreferrer"
key="0"
>
link
</a>,
<code key="1">text</code>,
]}
>
setup_dns_privacy_other_4
</Trans>
</li>
<li>
<Trans
components={[
<a
href="https://dnscrypt.info/implementations"
target="_blank"
rel="noopener noreferrer"
key="0"
>
link
</a>,
<a
href="https://dnsprivacy.org/wiki/display/DP/DNS+Privacy+Clients"
target="_blank"
rel="noopener noreferrer"
key="1"
>
link
</a>,
]}
>
setup_dns_privacy_other_5
</Trans>
</li>
</ul>
</div>
</Fragment>
)}
</div>
</div>
</Tabs>
</div>
);
};
Guide.defaultProps = {
dnsAddresses: [],
};
Guide.propTypes = {
dnsAddresses: PropTypes.array,
t: PropTypes.func.isRequired,
};
export default withNamespaces()(Guide);

View File

@@ -0,0 +1,5 @@
.icons {
display: inline-block;
vertical-align: middle;
height: 100%;
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,9 @@
.line__tooltip {
padding: 2px 10px 7px;
line-height: 1.1;
color: #fff;
}
.line__tooltip-text {
font-size: 0.7rem;
}

View File

@@ -0,0 +1,64 @@
import React from 'react';
import PropTypes from 'prop-types';
import { ResponsiveLine } from '@nivo/line';
import './Line.css';
const Line = props => (
props.data &&
<ResponsiveLine
data={props.data}
margin={{
top: 15,
right: 0,
bottom: 1,
left: 0,
}}
minY="auto"
stacked={false}
curve='linear'
axisBottom={{
tickSize: 0,
tickPadding: 10,
}}
axisLeft={{
tickSize: 0,
tickPadding: 10,
}}
enableGridX={false}
enableGridY={false}
enableDots={false}
enableArea={true}
animate={false}
colorBy={() => (props.color)}
tooltip={slice => (
<div>
{slice.data.map(d => (
<div key={d.serie.id} className="line__tooltip">
<span className="line__tooltip-text">
<strong>{d.data.y}</strong>
<br/>
<small>{d.data.x}</small>
</span>
</div>
))}
</div>
)}
theme={{
tooltip: {
container: {
padding: '0',
background: '#333',
borderRadius: '4px',
},
},
}}
/>
);
Line.propTypes = {
data: PropTypes.array.isRequired,
color: PropTypes.string.isRequired,
};
export default Line;

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