Compare commits
165 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
00fabb0ecf | ||
|
|
0736435376 | ||
|
|
f6976f3c7e | ||
|
|
4540a4e94a | ||
|
|
bf410c81ae | ||
|
|
b54bf94697 | ||
|
|
7fade498b9 | ||
|
|
39640d8190 | ||
|
|
242e5e136f | ||
|
|
b105f20837 | ||
|
|
8521635f63 | ||
|
|
04de9d0f7b | ||
|
|
6a2430b799 | ||
|
|
d9ee9b88d6 | ||
|
|
864c91e524 | ||
|
|
b00a789ca3 | ||
|
|
42790bf083 | ||
|
|
4942d0c39f | ||
|
|
af7f51d9b9 | ||
|
|
3d280c5d92 | ||
|
|
c8c76ae12b | ||
|
|
aa202277fb | ||
|
|
9cc09274f1 | ||
|
|
ef57f7e192 | ||
|
|
000e842f7b | ||
|
|
e85fdd7f09 | ||
|
|
8c8deb3d3d | ||
|
|
c9ccc53282 | ||
|
|
daf17f16f2 | ||
|
|
0a66913b4d | ||
|
|
fe357d04f7 | ||
|
|
1e429df3bc | ||
|
|
2b14a043a9 | ||
|
|
97e77cab64 | ||
|
|
19a94bf789 | ||
|
|
197d07f32b | ||
|
|
87bb773d3e | ||
|
|
3b13c031a3 | ||
|
|
1b3122dd35 | ||
|
|
4f4da3397c | ||
|
|
26ccee47b5 | ||
|
|
92141e03c4 | ||
|
|
289c6f8c73 | ||
|
|
40763c903b | ||
|
|
d1c47c0dbb | ||
|
|
4f0975bab5 | ||
|
|
9fd27b9add | ||
|
|
6684a120ac | ||
|
|
72ec4d7d7e | ||
|
|
7313c3bc53 | ||
|
|
b6b26c0681 | ||
|
|
56a0c9684b | ||
|
|
47cc025504 | ||
|
|
89cc1715d9 | ||
|
|
8dbdf49c32 | ||
|
|
906f26d7d2 | ||
|
|
07a9c3bd45 | ||
|
|
8cb4d128f5 | ||
|
|
7c0b2d8ede | ||
|
|
e8885dbf3e | ||
|
|
e7727e9f63 | ||
|
|
4d7984cb8b | ||
|
|
acd8da6f0a | ||
|
|
48c70aefaa | ||
|
|
7e45c2fc24 | ||
|
|
82d0c3047b | ||
|
|
d6d0d53761 | ||
|
|
31ffae7717 | ||
|
|
f579c23bc9 | ||
|
|
9b8cccdfcf | ||
|
|
8bf75b54a4 | ||
|
|
19a1c03d3b | ||
|
|
7bb32eae3d | ||
|
|
73d17ffa81 | ||
|
|
9c2e5c3f51 | ||
|
|
a692616981 | ||
|
|
e9cb8666ce | ||
|
|
90ce70225f | ||
|
|
a4dedacf43 | ||
|
|
df92941ae0 | ||
|
|
477a4bbf54 | ||
|
|
149fcc0f2d | ||
|
|
127a68a39f | ||
|
|
f8202a74bd | ||
|
|
a6d6e9ec9e | ||
|
|
fd26af2677 | ||
|
|
1fc70cbc08 | ||
|
|
317a9cc49e | ||
|
|
71ce0c6da9 | ||
|
|
db703283ba | ||
|
|
49e800727b | ||
|
|
d26fed1901 | ||
|
|
f446186db1 | ||
|
|
4751528d69 | ||
|
|
48c0b487e3 | ||
|
|
7a665ff8f0 | ||
|
|
38c7e732a6 | ||
|
|
bdffd8534c | ||
|
|
757e41ce53 | ||
|
|
f19d8c24c3 | ||
|
|
e0be4e8801 | ||
|
|
713613e3b8 | ||
|
|
af3096e23b | ||
|
|
b3614ba62f | ||
|
|
a0fe1e84c0 | ||
|
|
2e5264652e | ||
|
|
a31116635e | ||
|
|
dc2c68b1e4 | ||
|
|
305df63054 | ||
|
|
a78156a7cb | ||
|
|
9675b3776e | ||
|
|
0a09c7eb4d | ||
|
|
6fdffc1203 | ||
|
|
36636867e1 | ||
|
|
c93cdd106e | ||
|
|
ee41c18d53 | ||
|
|
7c81efcbcb | ||
|
|
81e8bbe63c | ||
|
|
0b7ac93076 | ||
|
|
15f6334748 | ||
|
|
044a457579 | ||
|
|
79ea15dce1 | ||
|
|
ca6048f667 | ||
|
|
fa45f8a256 | ||
|
|
e7cf8f222d | ||
|
|
2b0ababdc9 | ||
|
|
d4a3a01f30 | ||
|
|
1b7ef0e4f4 | ||
|
|
b03b36e47e | ||
|
|
c9a6e4e018 | ||
|
|
b1c7497d05 | ||
|
|
c49f62cfe6 | ||
|
|
d468daac76 | ||
|
|
4a59d5a116 | ||
|
|
7c10cefdf9 | ||
|
|
dbf7a083cb | ||
|
|
0d4dce5c79 | ||
|
|
a7742a3665 | ||
|
|
12f4ebc6a5 | ||
|
|
6ae0a0eb0b | ||
|
|
c7e4ecd85c | ||
|
|
2ac6e48535 | ||
|
|
62c8664fd7 | ||
|
|
e243e69a6e | ||
|
|
6b64d393bd | ||
|
|
941b6c5976 | ||
|
|
33093de6aa | ||
|
|
68cd7976b7 | ||
|
|
0cd6781a9a | ||
|
|
2f5d6593f2 | ||
|
|
a65f983aac | ||
|
|
0d4e95b36d | ||
|
|
359cab5290 | ||
|
|
ec5c5e8109 | ||
|
|
f64868472a | ||
|
|
2a6e9f3c11 | ||
|
|
fab63f167a | ||
|
|
7f69848084 | ||
|
|
2f1e631c66 | ||
|
|
6b76a2c9f7 | ||
|
|
090f549833 | ||
|
|
59720467da | ||
|
|
4d32d42ba2 | ||
|
|
abd251c5c1 | ||
|
|
4ae4cd0723 |
12
.travis.yml
12
.travis.yml
@@ -1,8 +1,9 @@
|
||||
if: repo = AdguardTeam/AdGuardHome
|
||||
language: go
|
||||
sudo: false
|
||||
|
||||
go:
|
||||
- 1.12.x
|
||||
- 1.13.x
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
@@ -33,17 +34,16 @@ notifications:
|
||||
matrix:
|
||||
include:
|
||||
# Release build configuration
|
||||
- if: repo = AdguardTeam/AdGuardHome
|
||||
- name: release
|
||||
go:
|
||||
- 1.12.x
|
||||
- 1.13.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
|
||||
@@ -61,9 +61,9 @@ matrix:
|
||||
skip_cleanup: true
|
||||
|
||||
- name: docker
|
||||
if: type != pull_request AND (branch = master OR tag IS present)
|
||||
if: type != pull_request AND (branch = master OR tag IS present) AND repo = AdguardTeam/AdGuardHome
|
||||
go:
|
||||
- 1.12.x
|
||||
- 1.13.x
|
||||
os:
|
||||
- linux
|
||||
services:
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"da": "Dansk",
|
||||
"de": "Deutsch",
|
||||
"nl": "Dutch",
|
||||
"no": "Norsk",
|
||||
"en": "English",
|
||||
"es": "Español",
|
||||
"fr": "Français",
|
||||
@@ -26,6 +27,9 @@
|
||||
"ja": "日本語",
|
||||
"zh-tw": "正體中文",
|
||||
"zh-cn": "简体中文",
|
||||
"sr-cs": "Srpski",
|
||||
"hr": "Hrvatski",
|
||||
"fa": "فارسی",
|
||||
"ko": "한국어"
|
||||
}
|
||||
}
|
||||
|
||||
163
AGHTechDoc.md
163
AGHTechDoc.md
@@ -21,12 +21,17 @@ Contents:
|
||||
* Add client
|
||||
* Update client
|
||||
* Delete client
|
||||
* API: Find clients by IP
|
||||
* Enable DHCP server
|
||||
* "Show DHCP status" command
|
||||
* "Check DHCP" command
|
||||
* "Enable DHCP" command
|
||||
* Static IP check/set
|
||||
* Add a static lease
|
||||
* API: Reset DHCP configuration
|
||||
* DNS general settings
|
||||
* API: Get DNS general settings
|
||||
* API: Set DNS general settings
|
||||
* DNS access settings
|
||||
* List access settings
|
||||
* Set access settings
|
||||
@@ -59,7 +64,7 @@ Contents:
|
||||
|
||||
## Relations between subsystems
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
|
||||
@@ -542,6 +547,20 @@ Response:
|
||||
200 OK
|
||||
|
||||
|
||||
### API: Reset DHCP configuration
|
||||
|
||||
Clear all DHCP leases and configuration settings.
|
||||
DHCP server will be stopped if it's currently running.
|
||||
|
||||
Request:
|
||||
|
||||
POST /control/dhcp/reset
|
||||
|
||||
Response:
|
||||
|
||||
200 OK
|
||||
|
||||
|
||||
## TLS
|
||||
|
||||
|
||||
@@ -618,8 +637,6 @@ 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.
|
||||
@@ -643,8 +660,7 @@ Response:
|
||||
clients: [
|
||||
{
|
||||
name: "client1"
|
||||
ip: "..."
|
||||
mac: "..."
|
||||
ids: ["...", ...] // IP, CIDR or MAC
|
||||
use_global_settings: true
|
||||
filtering_enabled: false
|
||||
parental_enabled: false
|
||||
@@ -656,6 +672,7 @@ Response:
|
||||
key: "value"
|
||||
...
|
||||
}
|
||||
upstreams: ["upstream1", ...]
|
||||
}
|
||||
]
|
||||
auto_clients: [
|
||||
@@ -682,8 +699,7 @@ Request:
|
||||
|
||||
{
|
||||
name: "client1"
|
||||
ip: "..."
|
||||
mac: "..."
|
||||
ids: ["...", ...] // IP, CIDR or MAC
|
||||
use_global_settings: true
|
||||
filtering_enabled: false
|
||||
parental_enabled: false
|
||||
@@ -691,6 +707,7 @@ Request:
|
||||
safesearch_enabled: false
|
||||
use_global_blocked_services: true
|
||||
blocked_services: [ "name1", ... ]
|
||||
upstreams: ["upstream1", ...]
|
||||
}
|
||||
|
||||
Response:
|
||||
@@ -712,8 +729,7 @@ Request:
|
||||
name: "client1"
|
||||
data: {
|
||||
name: "client1"
|
||||
ip: "..."
|
||||
mac: "..."
|
||||
ids: ["...", ...] // IP, CIDR or MAC
|
||||
use_global_settings: true
|
||||
filtering_enabled: false
|
||||
parental_enabled: false
|
||||
@@ -721,6 +737,7 @@ Request:
|
||||
safesearch_enabled: false
|
||||
use_global_blocked_services: true
|
||||
blocked_services: [ "name1", ... ]
|
||||
upstreams: ["upstream1", ...]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -752,6 +769,85 @@ Error response (Client not found):
|
||||
400
|
||||
|
||||
|
||||
### API: Find clients by IP
|
||||
|
||||
This method returns the list of clients (manual and auto-clients) matching the IP list.
|
||||
For auto-clients only `name`, `ids` and `whois_info` fields are set. Other fields are empty.
|
||||
|
||||
Request:
|
||||
|
||||
GET /control/clients/find?ip0=...&ip1=...&ip2=...
|
||||
|
||||
Response:
|
||||
|
||||
200 OK
|
||||
|
||||
[
|
||||
{
|
||||
"1.2.3.4": {
|
||||
name: "client1"
|
||||
ids: ["...", ...] // IP, CIDR or MAC
|
||||
use_global_settings: true
|
||||
filtering_enabled: false
|
||||
parental_enabled: false
|
||||
safebrowsing_enabled: false
|
||||
safesearch_enabled: false
|
||||
use_global_blocked_services: true
|
||||
blocked_services: [ "name1", ... ]
|
||||
whois_info: {
|
||||
key: "value"
|
||||
...
|
||||
}
|
||||
}
|
||||
}
|
||||
...
|
||||
]
|
||||
|
||||
|
||||
## DNS general settings
|
||||
|
||||
### API: Get DNS general settings
|
||||
|
||||
Request:
|
||||
|
||||
GET /control/dns_info
|
||||
|
||||
Response:
|
||||
|
||||
200 OK
|
||||
|
||||
{
|
||||
"protection_enabled": true | false,
|
||||
"ratelimit": 1234,
|
||||
"blocking_mode": "nxdomain" | "null_ip" | "custom_ip",
|
||||
"blocking_ipv4": "1.2.3.4",
|
||||
"blocking_ipv6": "1:2:3::4",
|
||||
"edns_cs_enabled": true | false,
|
||||
}
|
||||
|
||||
|
||||
### API: Set DNS general settings
|
||||
|
||||
Request:
|
||||
|
||||
POST /control/dns_config
|
||||
|
||||
{
|
||||
"protection_enabled": true | false,
|
||||
"ratelimit": 1234,
|
||||
"blocking_mode": "nxdomain" | "null_ip" | "custom_ip",
|
||||
"blocking_ipv4": "1.2.3.4",
|
||||
"blocking_ipv6": "1:2:3::4",
|
||||
"edns_cs_enabled": true | false,
|
||||
}
|
||||
|
||||
Response:
|
||||
|
||||
200 OK
|
||||
|
||||
`blocking_ipv4` and `blocking_ipv6` values are active when `blocking_mode` is set to `custom_ip`.
|
||||
|
||||
|
||||
## DNS access settings
|
||||
|
||||
There are low-level settings that can block undesired DNS requests. "Blocking" means not responding to request.
|
||||
@@ -1012,17 +1108,21 @@ Response:
|
||||
When a new DNS request is received and processed, we store information about this event in "query log". It is a file on disk in JSON format:
|
||||
|
||||
{
|
||||
"Question":"...","
|
||||
Answer":"...",
|
||||
"IP":"127.0.0.1", // client IP
|
||||
"T":"...", // response time
|
||||
"QH":"...", // target host name without the last dot
|
||||
"QT":"...", // question type
|
||||
"QC":"...", // question class
|
||||
"Answer":"...",
|
||||
"OrigAnswer":"...",
|
||||
"Result":{
|
||||
"IsFiltered":true,
|
||||
"Reason":3,
|
||||
"Rule":"...",
|
||||
"FilterID":1
|
||||
"FilterID":1,
|
||||
},
|
||||
"Time":"...",
|
||||
"Elapsed":12345,
|
||||
"IP":"127.0.0.1"
|
||||
"Upstream":"...",
|
||||
}
|
||||
|
||||
|
||||
@@ -1052,7 +1152,7 @@ Request:
|
||||
&filter_question_type=A | AAAA
|
||||
&filter_response_status= | filtered
|
||||
|
||||
If `older_than` value is set, server returns the next chunk of entries that are older than this time stamp. This setting is used for paging. UI sets the empty value on the first request and gets the latest log entries. To get the older entries, UI sets this value to the timestamp of the last (the oldest) entry from the previous response from Server.
|
||||
`older_than` setting is used for paging. UI uses an empty value for `older_than` on the first request and gets the latest log entries. To get the older entries, UI sets `older_than` to the `oldest` value from the server's response.
|
||||
|
||||
If "filter" settings are set, server returns only entries that match the specified request.
|
||||
|
||||
@@ -1060,7 +1160,9 @@ For `filter.domain` and `filter.client` the server matches substrings by default
|
||||
|
||||
Response:
|
||||
|
||||
[
|
||||
{
|
||||
"oldest":"2006-01-02T15:04:05.999999999Z07:00"
|
||||
"data":[
|
||||
{
|
||||
"answer":[
|
||||
{
|
||||
@@ -1070,6 +1172,13 @@ Response:
|
||||
}
|
||||
...
|
||||
],
|
||||
"original_answer":[ // Answer from upstream server (optional)
|
||||
{
|
||||
"type":"AAAA",
|
||||
"value":"::"
|
||||
}
|
||||
...
|
||||
],
|
||||
"client":"127.0.0.1",
|
||||
"elapsedMs":"0.098403",
|
||||
"filterId":1,
|
||||
@@ -1080,11 +1189,13 @@ Response:
|
||||
},
|
||||
"reason":"FilteredBlackList",
|
||||
"rule":"||doubleclick.net^",
|
||||
"service_name": "...", // set if reason=FilteredBlockedService
|
||||
"status":"NOERROR",
|
||||
"time":"2006-01-02T15:04:05.999999999Z07:00"
|
||||
}
|
||||
...
|
||||
]
|
||||
}
|
||||
|
||||
The most recent entries are at the top of list.
|
||||
|
||||
@@ -1123,6 +1234,26 @@ Response:
|
||||
|
||||
## Filtering
|
||||
|
||||

|
||||
|
||||
This is how DNS requests and responses are filtered by AGH:
|
||||
|
||||
* 'dnsproxy' module receives DNS request from client and passes control to AGH
|
||||
* AGH applies filtering logic to the host name in DNS Question:
|
||||
* process Rewrite rules
|
||||
* match host name against filtering lists
|
||||
* match host name against blocked services rules
|
||||
* process SafeSearch rules
|
||||
* request SafeBrowsing & ParentalControl services and process their response
|
||||
* If the handlers above create a successful result that can be immediately sent to a client, it's passed back to 'dnsproxy' module
|
||||
* Otherwise, AGH passes the DNS request to an upstream server via 'dnsproxy' module
|
||||
* After 'dnsproxy' module has received a response from an upstream server, it passes control back to AGH
|
||||
* If the filtering logic for DNS request returned a 'whitelist' flag, AGH passes the response to a client
|
||||
* Otherwise, AGH applies filtering logic to each DNS record in response:
|
||||
* For CNAME records, the target name is matched against filtering lists (ignoring 'whitelist' rules)
|
||||
* For A and AAAA records, the IP address is matched against filtering lists (ignoring 'whitelist' rules)
|
||||
|
||||
|
||||
### Filters update mechanism
|
||||
|
||||
Filters can be updated either manually by request from UI or automatically.
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
<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 href="https://travis-ci.com/AdguardTeam/AdGuardHome">
|
||||
<img src="https://travis-ci.com/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" />
|
||||
@@ -127,7 +127,7 @@ It depends.
|
||||
|
||||
You will need:
|
||||
|
||||
* [go](https://golang.org/dl/) v1.12 or later.
|
||||
* [go](https://golang.org/dl/) v1.13 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:
|
||||
|
||||
6
ci.sh
6
ci.sh
@@ -16,14 +16,14 @@ golangci-lint --version
|
||||
# Run linter
|
||||
golangci-lint run
|
||||
|
||||
# Run tests
|
||||
go test -race -v -bench=. -coverprofile=coverage.txt -covermode=atomic ./...
|
||||
|
||||
# Make
|
||||
make clean
|
||||
make build/static/index.html
|
||||
make
|
||||
|
||||
# Run tests
|
||||
go test -race -v -bench=. -coverprofile=coverage.txt -covermode=atomic ./...
|
||||
|
||||
# if [[ -z "$(git status --porcelain)" ]]; then
|
||||
# # Working directory clean
|
||||
# echo "Git status is clean"
|
||||
|
||||
41
client/package-lock.json
generated
vendored
41
client/package-lock.json
generated
vendored
@@ -5214,8 +5214,7 @@
|
||||
"ansi-regex": {
|
||||
"version": "2.1.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"aproba": {
|
||||
"version": "1.2.0",
|
||||
@@ -5236,14 +5235,12 @@
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
@@ -5258,20 +5255,17 @@
|
||||
"code-point-at": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"console-control-strings": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
@@ -5388,8 +5382,7 @@
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"ini": {
|
||||
"version": "1.3.5",
|
||||
@@ -5401,7 +5394,6 @@
|
||||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"number-is-nan": "^1.0.0"
|
||||
}
|
||||
@@ -5416,7 +5408,6 @@
|
||||
"version": "3.0.4",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
@@ -5424,14 +5415,12 @@
|
||||
"minimist": {
|
||||
"version": "0.0.8",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"minipass": {
|
||||
"version": "2.3.5",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"safe-buffer": "^5.1.2",
|
||||
"yallist": "^3.0.0"
|
||||
@@ -5450,7 +5439,6 @@
|
||||
"version": "0.5.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"minimist": "0.0.8"
|
||||
}
|
||||
@@ -5531,8 +5519,7 @@
|
||||
"number-is-nan": {
|
||||
"version": "1.0.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
@@ -5544,7 +5531,6 @@
|
||||
"version": "1.4.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
@@ -5630,8 +5616,7 @@
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
@@ -5667,7 +5652,6 @@
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"code-point-at": "^1.0.0",
|
||||
"is-fullwidth-code-point": "^1.0.0",
|
||||
@@ -5687,7 +5671,6 @@
|
||||
"version": "3.0.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^2.0.0"
|
||||
}
|
||||
@@ -5731,14 +5714,12 @@
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"yallist": {
|
||||
"version": "3.0.3",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
<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">
|
||||
<meta http-equiv="x-dns-prefetch-control" content="off">
|
||||
<link rel="icon" type="image/png" href="favicon.png" sizes="48x48">
|
||||
<title>AdGuard Home</title>
|
||||
</head>
|
||||
|
||||
@@ -11,9 +11,7 @@
|
||||
"dhcp_found": "Вашата мрежа вече има активен DHCP сървър. Не е безопасно ползването на втори!",
|
||||
"dhcp_leases": "DHCP раздадени адреси",
|
||||
"dhcp_leases_not_found": "Няма намерени активни DHCP адреси",
|
||||
"dhcp_config_saved": "Запиши конфигурацията на DHCP сървъра",
|
||||
"form_error_required": "Задължително поле",
|
||||
"form_error_ip_format": "Невалиден IPv4 адрес",
|
||||
"form_error_positive": "Проверете дали е положително число",
|
||||
"dhcp_form_gateway_input": "IP шлюз",
|
||||
"dhcp_form_subnet_input": "Мрежова маска",
|
||||
@@ -71,11 +69,9 @@
|
||||
"use_adguard_parental": "Включи AdGuard Родителски Надзор",
|
||||
"use_adguard_parental_hint": "Модул XXX в AdGuard Home ще провери дали страницата има материали за възвъстни. Използва се същия API за анонимност като при модула за Сигурност.",
|
||||
"enforce_safe_search": "Включи Безопасно Търсене",
|
||||
"enforce_save_search_hint": "AdGuard Home прилага Безопасно Търсене в следните търсачки и сайтове: Google, Youtube, Bing, и Yandex.",
|
||||
"no_servers_specified": "Няма избрани услуги",
|
||||
"general_settings": "Общи настройки",
|
||||
"upstream_dns": "Главен DNS сървър",
|
||||
"upstream_dns_hint": "Ако оставите празно, AdGuard Home ще използва <a href='https://1.1.1.1/' target='_blank'>Cloudflare DNS</a> за главен. Използвай tls:// представка за DNS използващи TLS връзка.",
|
||||
"test_upstream_btn": "Тествай главния DNS",
|
||||
"apply_btn": "Приложи",
|
||||
"disabled_filtering_toast": "Забрани филтрирането",
|
||||
|
||||
@@ -19,7 +19,9 @@
|
||||
"dhcp_leases_not_found": "Nebyly nalezeny žádné pronájmy DHCP",
|
||||
"dhcp_config_saved": "Konfigurace DHCP serveru byla uložena",
|
||||
"form_error_required": "Povinné pole",
|
||||
"form_error_ip_format": "Neplatný formát IPv4",
|
||||
"form_error_ip4_format": "Neplatný formát IPv4",
|
||||
"form_error_ip6_format": "Neplatný formát IPv6",
|
||||
"form_error_ip_format": "Neplatný formát IP",
|
||||
"form_error_mac_format": "Neplatný formát MAC",
|
||||
"form_error_positive": "Musí být větší než 0",
|
||||
"dhcp_form_gateway_input": "IP brána",
|
||||
@@ -43,6 +45,7 @@
|
||||
"dhcp_new_static_lease": "Nový statický pronájem",
|
||||
"dhcp_static_leases_not_found": "Nebyly nalezeny žádné statické pronájmy DHCP",
|
||||
"dhcp_add_static_lease": "Přidat statický pronájem",
|
||||
"dhcp_reset": "Opravdu chcete resetovat konfiguraci DHCP?",
|
||||
"delete_confirm": "Opravdu chcete odstranit \"{{key}}\"?",
|
||||
"form_enter_hostname": "Zadejte název hostitele",
|
||||
"error_details": "Podrobnosti chyby",
|
||||
@@ -96,15 +99,16 @@
|
||||
"use_adguard_parental": "Použít službu AdGuard Rodičovská kontrola",
|
||||
"use_adguard_parental_hint": "AdGuard Home zkontroluje, zda doména obsahuje materiály pro dospělé. Používá stejné API přátelské k ochraně osobních údajů jako služba Bezpečnost prohlížení.",
|
||||
"enforce_safe_search": "Vynutit bezpečné vyhledávání",
|
||||
"enforce_save_search_hint": "AdGuard Home může vynutit bezpečné vyhledávání v následujících vyhledávačích: Google, YouTube, Bing DuckDuckGo a Yandex.",
|
||||
"enforce_save_search_hint": "AdGuard Home může vynutit bezpečné vyhledávání v následujících vyhledávačích: Google, YouTube, Bing, DuckDuckGo, Yandex, Pixabay.",
|
||||
"no_servers_specified": "Nebyly specifikovány žádné servery",
|
||||
"general_settings": "Obecná nastavení",
|
||||
"dns_settings": "Nastavení DNS",
|
||||
"encryption_settings": "Nastavení šifrování",
|
||||
"dhcp_settings": "Nastavení DHCP",
|
||||
"upstream_dns": "Upstream DNS servery",
|
||||
"upstream_dns_hint": "Pokud toto pole ponecháte prázdné, AdGuard Home použije <a href='https://1.1.1.1/' target='_blank'>Cloudflare DNS</a> jako upstream.",
|
||||
"upstream_dns_hint": "Pokud toto pole ponecháte prázdné, AdGuard Home použije <a href='https://www.quad9.net/' target='_blank'>Quad9</a> jako upstream.",
|
||||
"test_upstream_btn": "Test upstreamů",
|
||||
"upstreams": "Upstreamy",
|
||||
"apply_btn": "Použít",
|
||||
"disabled_filtering_toast": "Vypnuté filtrování",
|
||||
"enabled_filtering_toast": "Zapnuté filtrování",
|
||||
@@ -279,6 +283,8 @@
|
||||
"update_announcement": "AdGuard Home {{version}} je nyní k dispozici! <0>Klikněte zde<0> pro více informací.",
|
||||
"setup_guide": "Průvodce nastavením",
|
||||
"dns_addresses": "Adresy DNS",
|
||||
"dns_start": "Spustí se server DNS",
|
||||
"dns_status_error": "Chyba při získávání stavu DNS serveru",
|
||||
"down": "Dolů",
|
||||
"fix": "Opravit",
|
||||
"dns_providers": "Zde je <0>seznam známých poskytovatelů DNS</0>, z nichž si můžete vybrat.",
|
||||
@@ -297,9 +303,11 @@
|
||||
"client_edit": "Upravit klienta",
|
||||
"client_identifier": "Identifikátor",
|
||||
"ip_address": "IP adresa",
|
||||
"client_identifier_desc": "Klienti můžou být identifikováni podle IP adresy nebo MAC adresy. Upozorňujeme, že použití MAC jako identifikátoru je možné pouze v případě, že je AdGuard Home také <0>DHCP server</0>",
|
||||
"client_identifier_desc": "Klienti můžou být identifikováni podle IP adresy CIDR nebo MAC adresy. Upozorňujeme, že použití MAC jako identifikátoru je možné pouze v případě, že je AdGuard Home také <0>DHCP server</0>",
|
||||
"form_enter_ip": "Zadejte IP",
|
||||
"form_enter_mac": "Zadejte MAC",
|
||||
"form_enter_id": "Zadejte identifikátor",
|
||||
"form_add_id": "Přidat identifikátor",
|
||||
"form_client_name": "Zadejte název klienta",
|
||||
"client_global_settings": "Použít globální nastavení",
|
||||
"client_deleted": "Klient \"{{key}}\" byl úspěšně odstraněn",
|
||||
@@ -400,5 +408,6 @@
|
||||
"netname": "Název sítě",
|
||||
"descr": "Popis",
|
||||
"whois": "Whois",
|
||||
"filtering_rules_learn_more": "<0>Další informace</0> o vytváření vlastních seznamů zakázaných hostitelů."
|
||||
"filtering_rules_learn_more": "<0>Další informace</0> o vytváření vlastních seznamů zakázaných hostitelů.",
|
||||
"blocked_by_response": "Zakázáno s odpovědí CNAME nebo IP"
|
||||
}
|
||||
@@ -17,9 +17,11 @@
|
||||
"dhcp_leases": "DHCP-leases",
|
||||
"dhcp_static_leases": "DHCP static leases",
|
||||
"dhcp_leases_not_found": "Ingen DHCP-leases fundet",
|
||||
"dhcp_config_saved": "Gemt DHCP-server konfiguration",
|
||||
"dhcp_config_saved": "DHCP-konfiguration gemt",
|
||||
"form_error_required": "Obligatorisk felt",
|
||||
"form_error_ip_format": "Ugyldigt IPv4-format",
|
||||
"form_error_ip4_format": "Ugyldigt IPv4-format",
|
||||
"form_error_ip6_format": "Ugyldigt IPv6-format",
|
||||
"form_error_ip_format": "Ugyldigt IP-format",
|
||||
"form_error_mac_format": "Ugyldigt MAC-format",
|
||||
"form_error_positive": "Skal være større end 0",
|
||||
"dhcp_form_gateway_input": "Gateway IP",
|
||||
@@ -43,6 +45,7 @@
|
||||
"dhcp_new_static_lease": "Ny static lease",
|
||||
"dhcp_static_leases_not_found": "Ingen DHCP static leases fundet",
|
||||
"dhcp_add_static_lease": "Tilføj static lease",
|
||||
"dhcp_reset": "Er du sikker på, at du vil nulstille DHCP-konfigurationen?",
|
||||
"delete_confirm": "Er du sikker på, at du vil slette \"{{key}}\"?",
|
||||
"form_enter_hostname": "Indtast værtsnavn",
|
||||
"error_details": "Fejloplysninger",
|
||||
@@ -96,15 +99,16 @@
|
||||
"use_adguard_parental": "Brug AdGuards forældrekontrol",
|
||||
"use_adguard_parental_hint": "AdGuard Home vil kontrollere, om domænet indeholder voksenindhold. Den bruger den samme privatlivsvenlige API som browsing sikkerhedstjenesten.",
|
||||
"enforce_safe_search": "Håndhæv sikker søgning",
|
||||
"enforce_save_search_hint": "AdGuard Home kan håndhæve sikker søgning i følgende søgemaskiner: Google, Youtube, Bing, DuckDuckGo og Yandex.",
|
||||
"enforce_save_search_hint": "AdGuard Home kan håndhæve sikker søgning i følgende søgemaskiner: Google, Youtube, Bing, DuckDuckGo, Yandex og Pixabay.",
|
||||
"no_servers_specified": "Ingen servere specificeret",
|
||||
"general_settings": "Generelle indstillinger",
|
||||
"dns_settings": "DNS-indstillinger",
|
||||
"encryption_settings": "Krypteringsindstillinger",
|
||||
"dhcp_settings": "DHCP-indstillinger",
|
||||
"upstream_dns": "Upstream DNS-servere",
|
||||
"upstream_dns_hint": "Hvis du lader dette felt være tomt, vil AdGuard Home bruge <a href='https://1.1.1.1/' target='_blank'>Cloudflare DNS</a> til upstream.",
|
||||
"upstream_dns_hint": "Hvis du lader dette felt være tomt, vil AdGuard Home bruge <a href='https://www.quad9.net/' target='_blank'>Quad9</a> som en upstream.",
|
||||
"test_upstream_btn": "Test upstreams",
|
||||
"upstreams": "Upstreams",
|
||||
"apply_btn": "Anvend",
|
||||
"disabled_filtering_toast": "Filtrering deaktiveret",
|
||||
"enabled_filtering_toast": "Filtrering aktiveret",
|
||||
@@ -279,6 +283,8 @@
|
||||
"update_announcement": "AdGuard Home {{version}} er nu tilgængelig! <0>Kik her</0> for mere info.",
|
||||
"setup_guide": "Installationsvejledning",
|
||||
"dns_addresses": "DNS-adresser",
|
||||
"dns_start": "DNS-server starter",
|
||||
"dns_status_error": "Fejl ved at få DNS-serverstatus",
|
||||
"down": "Ned",
|
||||
"fix": "Reparer",
|
||||
"dns_providers": "Her er en <0>liste over kendte DNS-udbydere</ 0> at vælge imellem.",
|
||||
@@ -297,9 +303,11 @@
|
||||
"client_edit": "Rediger Klient",
|
||||
"client_identifier": "Identifikator",
|
||||
"ip_address": "IP-adresse",
|
||||
"client_identifier_desc": "Klienter kan identificeres ud fra IP-adressen eller MAC-adressen. Bemærk venligst, at det kun er muligt at bruge MAC som identifikator, hvis AdGuard Home også er en <0>DHCP-server</0>",
|
||||
"client_identifier_desc": "Klienter kan identificeres ud fra IP-adressen, CIDR eller MAC-adressen. Bemærk venligst, at det kun er muligt at bruge MAC som identifikator, hvis AdGuard Home også er en <0>DHCP-server</0>",
|
||||
"form_enter_ip": "Indtast IP",
|
||||
"form_enter_mac": "Indtast MAC",
|
||||
"form_enter_id": "Indtast identifikator",
|
||||
"form_add_id": "Tilføj identifikator",
|
||||
"form_client_name": "Indtast klientnavn",
|
||||
"client_global_settings": "Brug globale indstillinger",
|
||||
"client_deleted": "Klient \"{{key}}\" succesfuldt slettet",
|
||||
@@ -399,5 +407,7 @@
|
||||
"orgname": "Organisationens navn",
|
||||
"netname": "Netværksnavn",
|
||||
"descr": "Beskrivelse",
|
||||
"whois": "Whois"
|
||||
"whois": "Whois",
|
||||
"filtering_rules_learn_more": "<0>Lær mere</0> om at oprette dine egne værtsblokeringslister.",
|
||||
"blocked_by_response": "Blokeret af CNAME eller IP som svar"
|
||||
}
|
||||
@@ -17,9 +17,7 @@
|
||||
"dhcp_leases": "DHCP-Leasingverträge",
|
||||
"dhcp_static_leases": "DHCP statische Leases",
|
||||
"dhcp_leases_not_found": "Keine DHCP-Leasingverträge gefunden\n",
|
||||
"dhcp_config_saved": "Gespeicherte DHCP-Server-Konfiguration",
|
||||
"form_error_required": "Pflichtfeld",
|
||||
"form_error_ip_format": "Ungültiges IPv4-Format",
|
||||
"form_error_mac_format": "Ungültiges MAC-Format",
|
||||
"form_error_positive": "Muss größer als 0 sein.",
|
||||
"dhcp_form_gateway_input": "Gateway-IP",
|
||||
@@ -96,14 +94,12 @@
|
||||
"use_adguard_parental": "AdGuard Webservice für Kindersicherung verwenden",
|
||||
"use_adguard_parental_hint": "AdGuard Home wird überprüfen, ob die Domain Inhalte hat, die nur für Erwachsene geeignet sind. Zum Schutz Ihrer Privatsphäre wird die gleiche API wie für den Webservice für Internet-Sicherheit verwendet.",
|
||||
"enforce_safe_search": "SafeSearch erzwingen",
|
||||
"enforce_save_search_hint": "AdGuard kann SafeSearch für folgende Suchmaschinen erzwingen: Google, Youtube, Bing und Yandex.",
|
||||
"no_servers_specified": "Keine Server festgelegt",
|
||||
"general_settings": "Allgemeine Einstellungen",
|
||||
"dns_settings": "DNS-Einstellungen",
|
||||
"encryption_settings": "Verschlüsselungseinstellungen",
|
||||
"dhcp_settings": "DHCP-Einstellungen",
|
||||
"upstream_dns": "Upstream-DNS-Server",
|
||||
"upstream_dns_hint": "Wenn Sie dieses Feld leer lassen wird AdGuard Home <a href='https://1.1.1.1/' target='_blank'>Cloudflare DNS</a> als Upstream verwenden. Verwenden Sie das Präfix tls:// für DNS über TLS-Server.",
|
||||
"test_upstream_btn": "Upstreams testen",
|
||||
"apply_btn": "Anwenden",
|
||||
"disabled_filtering_toast": "Filtern deaktiviert",
|
||||
@@ -297,7 +293,6 @@
|
||||
"client_edit": "Client bearbeiten",
|
||||
"client_identifier": "Bezeichner",
|
||||
"ip_address": "IP-Adresse",
|
||||
"client_identifier_desc": "Clients können durch die IP-Adresse oder MAC-Adresse identifiziert werden. Bitte beachten Sie, dass die Verwendung der MAC-Adresse als Identifikator nur möglich ist, wenn AdGuard Home gleichzeitig auch ein <0>DHCP-Server</0> ist.",
|
||||
"form_enter_ip": "IP-Adresse eingeben",
|
||||
"form_enter_mac": "MAC-Adresse eingeben",
|
||||
"form_client_name": "Clientnamen eingeben",
|
||||
|
||||
@@ -17,11 +17,14 @@
|
||||
"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",
|
||||
"dhcp_config_saved": "DHCP config successfully saved",
|
||||
"form_error_required": "Required field",
|
||||
"form_error_ip_format": "Invalid IPv4 format",
|
||||
"form_error_ip4_format": "Invalid IPv4 format",
|
||||
"form_error_ip6_format": "Invalid IPv6 format",
|
||||
"form_error_ip_format": "Invalid IP format",
|
||||
"form_error_mac_format": "Invalid MAC format",
|
||||
"form_error_positive": "Must be greater than 0",
|
||||
"form_error_negative": "Must be equal to 0 or greater",
|
||||
"dhcp_form_gateway_input": "Gateway IP",
|
||||
"dhcp_form_subnet_input": "Subnet mask",
|
||||
"dhcp_form_range_title": "Range of IP addresses",
|
||||
@@ -43,6 +46,7 @@
|
||||
"dhcp_new_static_lease": "New static lease",
|
||||
"dhcp_static_leases_not_found": "No DHCP static leases found",
|
||||
"dhcp_add_static_lease": "Add static lease",
|
||||
"dhcp_reset": "Are you sure you want to reset DHCP config?",
|
||||
"delete_confirm": "Are you sure you want to delete \"{{key}}\"?",
|
||||
"form_enter_hostname": "Enter hostname",
|
||||
"error_details": "Error details",
|
||||
@@ -96,15 +100,16 @@
|
||||
"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.",
|
||||
"enforce_save_search_hint": "AdGuard Home can enforce safe search in the following search engines: Google, Youtube, Bing, DuckDuckGo, Yandex, Pixabay.",
|
||||
"no_servers_specified": "No servers specified",
|
||||
"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.",
|
||||
"upstream_dns_hint": "If you keep this field empty, AdGuard Home will use <a href='https://www.quad9.net/' target='_blank'>Quad9</a> as an upstream.",
|
||||
"test_upstream_btn": "Test upstreams",
|
||||
"upstreams": "Upstreams",
|
||||
"apply_btn": "Apply",
|
||||
"disabled_filtering_toast": "Disabled filtering",
|
||||
"enabled_filtering_toast": "Enabled filtering",
|
||||
@@ -183,6 +188,22 @@
|
||||
"query_log_disabled": "The query log is disabled and can be configured in the <0>settings</0>",
|
||||
"query_log_strict_search": "Use double quotes for strict search",
|
||||
"query_log_retention_confirm": "Are you sure you want to change query log retention? If you decrease the interval value, some data will be lost",
|
||||
"dns_config": "DNS server configuration",
|
||||
"blocking_mode": "Blocking mode",
|
||||
"nxdomain": "NXDOMAIN",
|
||||
"null_ip": "Null IP",
|
||||
"custom_ip": "Custom IP",
|
||||
"blocking_ipv4": "Blocking IPv4",
|
||||
"blocking_ipv6": "Blocking IPv6",
|
||||
"form_enter_rate_limit": "Enter rate limit",
|
||||
"rate_limit": "Rate limit",
|
||||
"edns_enable": "Enable EDNS Client Subnet",
|
||||
"edns_cs_desc": "If enabled, AdGuard Home will be sending clients' subnets to the DNS servers.",
|
||||
"rate_limit_desc": "The number of requests per second that a single client is allowed to make (0: unlimited)",
|
||||
"blocking_ipv4_desc": "IP address to be returned for a blocked A request",
|
||||
"blocking_ipv6_desc": "IP address to be returned for a blocked AAAA request",
|
||||
"blocking_mode_desc": "<0>NXDOMAIN – Respond with NXDOMAIN code;</0> <0>Null IP – Respond with zero IP address (0.0.0.0 for A; :: for AAAA);</0> <0>Custom IP - Respond with a manually set IP address.</0>",
|
||||
"upstream_dns_client_desc": "If you keep this field empty, AdGuard Home will use the servers configured in the <0>DNS settings</0>.",
|
||||
"source_label": "Source",
|
||||
"found_in_known_domain_db": "Found in the known domains database.",
|
||||
"category_label": "Category",
|
||||
@@ -279,6 +300,8 @@
|
||||
"update_announcement": "AdGuard Home {{version}} is now available! <0>Click here</0> for more info.",
|
||||
"setup_guide": "Setup guide",
|
||||
"dns_addresses": "DNS addresses",
|
||||
"dns_start": "DNS server is starting up",
|
||||
"dns_status_error": "Error of getting DNS server status",
|
||||
"down": "Down",
|
||||
"fix": "Fix",
|
||||
"dns_providers": "Here is a <0>list of known DNS providers</0> to choose from.",
|
||||
@@ -297,9 +320,11 @@
|
||||
"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>",
|
||||
"client_identifier_desc": "Clients can be identified by the IP address, CIDR, 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_enter_id": "Enter identifier",
|
||||
"form_add_id": "Add identifier",
|
||||
"form_client_name": "Enter client name",
|
||||
"client_global_settings": "Use global settings",
|
||||
"client_deleted": "Client \"{{key}}\" successfully deleted",
|
||||
@@ -400,5 +425,7 @@
|
||||
"netname": "Network name",
|
||||
"descr": "Description",
|
||||
"whois": "Whois",
|
||||
"filtering_rules_learn_more": "<0>Learn more</0> about creating your own hosts blocklists."
|
||||
"filtering_rules_learn_more": "<0>Learn more</0> about creating your own hosts blocklists.",
|
||||
"blocked_by_response": "Blocked by CNAME or IP in response",
|
||||
"try_again": "Try again"
|
||||
}
|
||||
@@ -17,9 +17,11 @@
|
||||
"dhcp_leases": "Asignaciones DHCP",
|
||||
"dhcp_static_leases": "Asignaciones DHCP estáticas",
|
||||
"dhcp_leases_not_found": "No se han encontrado asignaciones DHCP",
|
||||
"dhcp_config_saved": "Configuración del servidor DHCP guardada",
|
||||
"dhcp_config_saved": "Configuración DHCP guardado correctamente",
|
||||
"form_error_required": "Campo obligatorio",
|
||||
"form_error_ip_format": "Formato IPv4 no válido",
|
||||
"form_error_ip4_format": "Formato IPv4 no válido",
|
||||
"form_error_ip6_format": "Formato IPv6 no válido",
|
||||
"form_error_ip_format": "Formato IP no válido",
|
||||
"form_error_mac_format": "Formato MAC no válido",
|
||||
"form_error_positive": "Debe ser mayor que 0",
|
||||
"dhcp_form_gateway_input": "IP de puerta de enlace",
|
||||
@@ -43,6 +45,7 @@
|
||||
"dhcp_new_static_lease": "Nueva asignación estática",
|
||||
"dhcp_static_leases_not_found": "No se han encontrado asignaciones DHCP estáticas",
|
||||
"dhcp_add_static_lease": "Añadir asignación estática",
|
||||
"dhcp_reset": "¿Está seguro de que desea restablecer la configuración DHCP?",
|
||||
"delete_confirm": "¿Está seguro de que desea eliminar \"{{key}}\"?",
|
||||
"form_enter_hostname": "Ingrese el nombre del host",
|
||||
"error_details": "Detalles del error",
|
||||
@@ -96,15 +99,16 @@
|
||||
"use_adguard_parental": "Usar el control parental de AdGuard",
|
||||
"use_adguard_parental_hint": "AdGuard Home comprobará si el dominio contiene materiales para adultos. Utiliza la misma API amigable con la privacidad del servicio web de seguridad de navegación.",
|
||||
"enforce_safe_search": "Forzar búsqueda segura",
|
||||
"enforce_save_search_hint": "AdGuard Home puede forzar la búsqueda segura en los siguientes motores de búsqueda: Google, YouTube, Bing, DuckDuckGo y Yandex.",
|
||||
"enforce_save_search_hint": "AdGuard Home puede forzar la búsqueda segura en los siguientes motores de búsqueda: Google, YouTube, Bing, DuckDuckGo, Yandex y Pixabay.",
|
||||
"no_servers_specified": "No hay servidores especificados",
|
||||
"general_settings": "Configuración general",
|
||||
"dns_settings": "Configuración del DNS",
|
||||
"encryption_settings": "Configuración de cifrado",
|
||||
"dhcp_settings": "Configuración DHCP",
|
||||
"upstream_dns": "Servidores DNS de subida",
|
||||
"upstream_dns_hint": "Si mantiene este campo vacío, AdGuard Home utilizará <a href='https://1.1.1.1/' target='_blank'>Cloudflare DNS</a> como DNS de subida.",
|
||||
"upstream_dns_hint": "Si mantiene este campo vacío, AdGuard Home utilizará <a href='https://www.quad9.net/' target='_blank'>Quad9</a> como DNS de subida.",
|
||||
"test_upstream_btn": "Probar DNS de subida",
|
||||
"upstreams": "DNS de subida",
|
||||
"apply_btn": "Aplicar",
|
||||
"disabled_filtering_toast": "Filtrado deshabilitado",
|
||||
"enabled_filtering_toast": "Filtrado habilitado",
|
||||
@@ -183,6 +187,12 @@
|
||||
"query_log_disabled": "El registro de consultas está deshabilitado y se puede configurar en la <0>configuración</0>",
|
||||
"query_log_strict_search": "Usar comillas dobles para una búsqueda estricta",
|
||||
"query_log_retention_confirm": "¿Está seguro de que desea cambiar la retención del registro de consultas? Si disminuye el valor del intervalo, se perderán algunos datos",
|
||||
"blocking_mode": "Modo de bloqueo",
|
||||
"nxdomain": "NXDOMAIN",
|
||||
"custom_ip": "IP personalizada",
|
||||
"edns_cs_desc": "Si está habilitado, AdGuard Home enviará las subredes de los clientes a los servidores DNS.",
|
||||
"rate_limit_desc": "Número de peticiones por segundo que un solo cliente puede hacer (0: ilimitado)",
|
||||
"upstream_dns_client_desc": "Si mantiene este campo vacío, AdGuard Home utilizará los servidores configurados en la <0>configuración del DNS</0>.",
|
||||
"source_label": "Fuente",
|
||||
"found_in_known_domain_db": "Encontrado en la base de datos de dominios conocidos.",
|
||||
"category_label": "Categoría",
|
||||
@@ -279,6 +289,8 @@
|
||||
"update_announcement": "¡AdGuard Home {{version}} ya está disponible! <0>Haga clic aquí</0> para más información.",
|
||||
"setup_guide": "Guía de configuración",
|
||||
"dns_addresses": "Direcciones DNS",
|
||||
"dns_start": "El servidor DNS está iniciando",
|
||||
"dns_status_error": "Error al obtener el estado del servidor DNS",
|
||||
"down": "Abajo",
|
||||
"fix": "Corregir",
|
||||
"dns_providers": "Aquí hay una <0>lista de proveedores DNS</0> conocidos para elegir.",
|
||||
@@ -297,9 +309,11 @@
|
||||
"client_edit": "Editar cliente",
|
||||
"client_identifier": "Identificador",
|
||||
"ip_address": "Dirección IP",
|
||||
"client_identifier_desc": "Los clientes pueden ser identificados por la dirección IP o MAC. Tenga en cuenta que el uso de MAC como identificador solo es posible si AdGuard Home también es un <0>servidor DHCP</0>.",
|
||||
"client_identifier_desc": "Los clientes pueden ser identificados por la dirección IP, MAC y CIDR. Tenga en cuenta que el uso de MAC como identificador solo es posible si AdGuard Home también es un <0>servidor DHCP</0>",
|
||||
"form_enter_ip": "Ingresar IP",
|
||||
"form_enter_mac": "Ingresar MAC",
|
||||
"form_enter_id": "Ingrese el identificador",
|
||||
"form_add_id": "Añadir identificador",
|
||||
"form_client_name": "Ingrese el nombre del cliente",
|
||||
"client_global_settings": "Usar configuración global",
|
||||
"client_deleted": "Cliente \"{{key}}\" eliminado correctamente",
|
||||
@@ -400,5 +414,6 @@
|
||||
"netname": "Nombre de la red",
|
||||
"descr": "Descripción",
|
||||
"whois": "Whois",
|
||||
"filtering_rules_learn_more": "<0>Más información</0> sobre cómo crear tus propias listas para bloqueo de hosts."
|
||||
"filtering_rules_learn_more": "<0>Más información</0> sobre cómo crear tus propias listas para bloqueo de hosts.",
|
||||
"blocked_by_response": "Bloqueado por CNAME o IP en respuesta"
|
||||
}
|
||||
366
client/src/__locales/fa.json
Normal file
366
client/src/__locales/fa.json
Normal file
@@ -0,0 +1,366 @@
|
||||
{
|
||||
"client_settings": "تنظیمات کلاینت",
|
||||
"example_upstream_reserved": "میتوانید جریان ارسالی DNS <0> را برای یک دامنه مشخص تعیین کنید </0>",
|
||||
"upstream_parallel": "استفاده از جستار موازی برای سرعت دادن به تفکیک با جستار همزمان همه جریان های ارسالی",
|
||||
"bootstrap_dns": "خودراه انداز سرورهای DNS",
|
||||
"bootstrap_dns_desc": "خودراه انداز سرورهای DNS برای تفکیک آدرس آی پی تفکیک کننده های DoH/DoT که شما بعنوان جریان ارسالی تعیین کردید استفاده میشود.",
|
||||
"check_dhcp_servers": "بررسی برای سرورهای DHCP",
|
||||
"save_config": "ذخیره پیکربندی",
|
||||
"enabled_dhcp": "سرور DHCP فعال شده است",
|
||||
"disabled_dhcp": "سرور DHCP غیرفعال شده است",
|
||||
"dhcp_title": "سرور DHCP",
|
||||
"dhcp_description": "اگر روتر شما تنظیمات DHCP ارائه نمی کند،میتوانید از سرور DHCP تو-کار خود AdGuard استفاده کنید.",
|
||||
"dhcp_enable": "فعالسازی سرور DHCP",
|
||||
"dhcp_disable": "غیرفعالسازی سرور DHCP",
|
||||
"dhcp_not_found": "سرورهای فعال DHCP در شبکه یافت نشد.فعالسازی سرور DHCP تو-کار اَمن است.",
|
||||
"dhcp_found": "تعدادی سرور DHCP فعال در شبکه یافت شد.فعالسازی سرور DHCP توکار اَمن نیست.",
|
||||
"dhcp_leases": "اجاره DHCP",
|
||||
"dhcp_static_leases": "اجاره DHCP ایستا",
|
||||
"dhcp_leases_not_found": "اجاره DHCP یافت نشد",
|
||||
"form_error_required": "فیلد مورد نیاز",
|
||||
"form_error_mac_format": "فرمت مَک نامعتبر است",
|
||||
"form_error_positive": "باید بزرگتر از 0 باشد",
|
||||
"dhcp_form_gateway_input": "آی پی دروازه",
|
||||
"dhcp_form_subnet_input": "ماسک زیر شبکه",
|
||||
"dhcp_form_range_title": "دامنه آدرس های آی پی",
|
||||
"dhcp_form_range_start": "آغاز دامنه",
|
||||
"dhcp_form_range_end": "انتهای دامنه",
|
||||
"dhcp_form_lease_title": "زمان اجاره DHCP (در ثانیه)",
|
||||
"dhcp_form_lease_input": "مدت اجاره",
|
||||
"dhcp_interface_select": "رابط DHCP را انتخاب کنید",
|
||||
"dhcp_hardware_address": "آدرس سخت افزار",
|
||||
"dhcp_ip_addresses": "آدرس آی پی",
|
||||
"dhcp_table_hostname": "نام میزبان",
|
||||
"dhcp_table_expires": "انقضاء",
|
||||
"dhcp_warning": "اگر میخواهید DHCP سرور توکار را فعال کنید،مطمئن شوید DHCP سرور دیگری فعال نباشد.در غیر اینصورت،آن دسترسی به اینترنت را برای دستگاه های وصل شده قطع می کند!",
|
||||
"dhcp_error": "ما نمیتوانیم تشخیص دهیم آیا یک سرور DHCP دیگر در شبکه موجود است یا نه.",
|
||||
"dhcp_static_ip_error": "به منظور استفاده از سرور DHCP یک آدرس آی پی ثابت باید تنظیم شود.ما موفق به تشخیص اینکه آیا رابط این شبکه برای استفاده از آی پی ثابت تنظیم شده است یا نه موفق نشدیم.لطفا آدرس آی پی ثابت را دستی تنظیم کنید.",
|
||||
"dhcp_dynamic_ip_found": "سیستم شما آدرس آی پی متغییر برای این رابط استفاده می کند <0>{{interfaceName}}</0>. به منظور استفاده از سرور DHCP آدرس ثابت باید تعیین شود. آدرس آی پی فعلی شما هست <0>{{ipAddress}}</0>. اگر شما دکمه DHCP را فشار دهید ما این آدرس آی پی را بعنوان ثابت تنظیم می کنیم.",
|
||||
"dhcp_lease_added": "اجاره ایستا \"{{key}}\" با موفقیت اضافه شد",
|
||||
"dhcp_lease_deleted": "اجاره ایستا \"{{key}}\" با موفقیت حذف شد",
|
||||
"dhcp_new_static_lease": "اجاره ایستا جدید",
|
||||
"dhcp_static_leases_not_found": "هیچ اجاره DHCP ایستا یافت نشد",
|
||||
"dhcp_add_static_lease": "افزودن اجاره ایستا",
|
||||
"delete_confirm": "آیا میخواهید \"{{key}}\" را حذف کنید؟",
|
||||
"form_enter_hostname": "نام میزبان را وارد کنید",
|
||||
"error_details": "جزئیات خطا",
|
||||
"back": "قبلی",
|
||||
"dashboard": "داشبورد",
|
||||
"settings": "تنظيمات",
|
||||
"filters": "فيلترها",
|
||||
"query_log": "جستار وقایع",
|
||||
"faq": "پرسش و پاسخ",
|
||||
"version": "نسخه",
|
||||
"address": "آدرس",
|
||||
"on": "روشن",
|
||||
"off": "خاموش",
|
||||
"copyright": "حق مالکیت",
|
||||
"homepage": "صفحه خانگي",
|
||||
"report_an_issue": "گزارش یک مشکل",
|
||||
"privacy_policy": "سیاست حریم خصوصی",
|
||||
"enable_protection": "فعالسازي حفاظت",
|
||||
"enabled_protection": "حفاظت فعال شده",
|
||||
"disable_protection": "غيرفعالسازي حفاظت",
|
||||
"disabled_protection": "حفاظت غير فعال شده",
|
||||
"refresh_statics": "تازه سازی آمار",
|
||||
"dns_query": "جستار DNS",
|
||||
"blocked_by": "مسدود شده با",
|
||||
"stats_malware_phishing": "بدافزار/فیشینگ مسدود شده است",
|
||||
"stats_adult": "وبسایت غیراخلاقی مسدود شده است",
|
||||
"stats_query_domain": "دامنه جستار بالا",
|
||||
"for_last_24_hours": "برای 24 ساعت گذشته",
|
||||
"for_last_days": "برای {{value}} روز آخر",
|
||||
"for_last_days_plural": "برای {{count}} روز گذشته",
|
||||
"no_domains_found": "دامنه یافت نشد",
|
||||
"requests_count": "تعداد درخواست ها",
|
||||
"top_blocked_domains": "دامنه های بیشتر مسدود شده",
|
||||
"top_clients": "بالاترین کلاینت ها",
|
||||
"no_clients_found": "کلاینتی یافت نشد",
|
||||
"general_statistics": "آمار عمومی",
|
||||
"number_of_dns_query_days": "تعداد جستار DNS پردازش شده در {{value}} روز آخر",
|
||||
"number_of_dns_query_days_plural": "تعداد جستار DNS پردازش شده در {{count}} روز گذشته",
|
||||
"number_of_dns_query_24_hours": "تعداد جستار DNS پردازش شده در 24 ساعت گذشته",
|
||||
"number_of_dns_query_blocked_24_hours": "تعداد درخواست DNS مسدود شده با فیلترهای مسدودساز تبلیغ و لیست سیاه میزبان",
|
||||
"number_of_dns_query_blocked_24_hours_by_sec": "تعداد درخواست DNS مسدود شده با مدل امنیت وب گردی AdGuard",
|
||||
"number_of_dns_query_blocked_24_hours_adult": "تعداد وبسایت های غیر اخلاقی مسدود شده",
|
||||
"enforced_save_search": "جستجوی اَمن اجبار شده",
|
||||
"number_of_dns_query_to_safe_search": "تعداد درخواست های DNS برای موتور جستجو که جستجوی اَمن اجبار شده",
|
||||
"average_processing_time": "میانگین زمان پردازش",
|
||||
"average_processing_time_hint": "زمان میانگین بر هزارم ثانیه در پردازش درخواست DNS",
|
||||
"block_domain_use_filters_and_hosts": "مسدودسازی دامنه ها توسط فیلترها و فایل های میزبان",
|
||||
"filters_block_toggle_hint": "میتوانید دستورات مسدودسازی را در تنظیمات <a href='#filters'>فیلترها</a> راه اندازی کنید.",
|
||||
"use_adguard_browsing_sec": "استفاده از سرویس وب امنیت وب گردی AdGuard",
|
||||
"use_adguard_browsing_sec_hint": "AdGuard Home بررسی می کند اگر دامنه در سرویس وب امنیت وب گردی در لیست سیاه است.آن از اِی پی آی دارای حریم خصوصی برای بررسی استفاده می کند:فقط پیشوند کوتاه نام دامنه هش SHA256 به سرور ارسال خواهد شد.",
|
||||
"use_adguard_parental": "از سرویس وب نظارت والدین AdGuard استفاده کن",
|
||||
"use_adguard_parental_hint": "AdGuard Home بررسی می کند اگر دامنه حاوی موارد غیر اخلاقی است.آن از همان اِی پی آی دارای حریم خصوصی سرویس وب امنیت وب گردی استفاده می کند.",
|
||||
"enforce_safe_search": "اجبار جستجوی اَمن",
|
||||
"no_servers_specified": "سروری تعیین نشده است",
|
||||
"general_settings": "تنظیمات عمومی",
|
||||
"dns_settings": "تنظیمات DNS",
|
||||
"encryption_settings": "تنظیمات رمزگُذاری",
|
||||
"dhcp_settings": "تنظیمات DHCP",
|
||||
"upstream_dns": "سرورهای DNS جریان ارسالی",
|
||||
"test_upstream_btn": "تست جریان ارسالی",
|
||||
"apply_btn": "اِعمال",
|
||||
"disabled_filtering_toast": "فیلترینگ غیرفعال شده است",
|
||||
"enabled_filtering_toast": "فیلترینگ فعال شده است",
|
||||
"disabled_safe_browsing_toast": "وب گردی اَمن غیر فعال شده است",
|
||||
"enabled_safe_browsing_toast": "وب گردی اَمن فعال شده است",
|
||||
"disabled_parental_toast": "نظارت والدین غیرفعال شده است",
|
||||
"enabled_parental_toast": "نظارت والدین فعال شده است",
|
||||
"disabled_safe_search_toast": "جستجوی اَمن غیرفعال شده",
|
||||
"enabled_save_search_toast": "جستجوی اَمن فعال شده",
|
||||
"enabled_table_header": "فعال شده",
|
||||
"name_table_header": "نام",
|
||||
"filter_url_table_header": "فیلتر آدرس",
|
||||
"rules_count_table_header": "تعداد دستور",
|
||||
"last_time_updated_table_header": "زمان آخرین بروزرسانی",
|
||||
"actions_table_header": "اقدامات",
|
||||
"edit_table_action": "ويرايش",
|
||||
"delete_table_action": "حذف",
|
||||
"filters_and_hosts": "فیلترها و میزبان های لیست سیاه",
|
||||
"filters_and_hosts_hint": "AdGuard Home دستورات پایه مسدودساز تبلیغ و نحو فایل های میزبان را درک می کند.",
|
||||
"no_filters_added": "فیلتری اضافه نشده است",
|
||||
"add_filter_btn": "افزودن فیلتر",
|
||||
"cancel_btn": "لغو",
|
||||
"enter_name_hint": "نام را وارد کنید",
|
||||
"enter_url_hint": "آدرس را وارد کنید...",
|
||||
"check_updates_btn": "بررسی بروز رسانی",
|
||||
"new_filter_btn": "اشتراک فیلتر جدید",
|
||||
"enter_valid_filter_url": "آدرس معتبر برای اشتراک در فیلتر یا فایل میزبان وارد کنید.",
|
||||
"custom_filter_rules": "دستورات فیلترینگ دستی",
|
||||
"custom_filter_rules_hint": "یک دستور در خط وارد کنید.میتوانید از دستورات مسدودساز تبلیغ یا نحو فایل های میزبان استفاده کنید.",
|
||||
"examples_title": "مثال ها",
|
||||
"example_meaning_filter_block": "مسدودسازی دسترسی به دامنه example.org و همه زیر دامنه ها آن",
|
||||
"example_meaning_filter_whitelist": "بازکردن دسترسی به دامنه example.org و همه زیر دامنه ها آن",
|
||||
"example_meaning_host_block": "AdGuard Home بر می گردد به آدرس 127.0.0.1 برای دامنه example.org (اما نه زیر دامنه های آن)",
|
||||
"example_comment": "! در اینجا نظر قرار می گیرد",
|
||||
"example_comment_meaning": "فقط یک توضیح",
|
||||
"example_comment_hash": "# همچنین یک نظر",
|
||||
"example_upstream_regular": "DNS عادی (بر UDP)",
|
||||
"example_upstream_dot": "کُدگذاری شده <a href='https://en.wikipedia.org/wiki/DNS_over_TLS' target='_blank'>DNS-over-TLS</a>",
|
||||
"example_upstream_doh": "کُدگذاری شده <a href='https://en.wikipedia.org/wiki/DNS_over_HTTPS' target='_blank'>DNS-over-HTTPS</a>",
|
||||
"example_upstream_sdns": "شما میتوانید از <a href='https://dnscrypt.info/stamps/' target='_blank'>DNS Stamps</a> برای <a href='https://dnscrypt.info/' target='_blank'>DNSCrypt</a> یا <a href='https://en.wikipedia.org/wiki/DNS_over_HTTPS' target='_blank'>DNS-over-HTTPS</a> resolvers استفاده کنید",
|
||||
"example_upstream_tcp": "DNS عادی (بر TCP)",
|
||||
"all_filters_up_to_date_toast": "همه فیلترها از قبل بروز رسانی شده است",
|
||||
"updated_upstream_dns_toast": "سرورهای DNS جریان ارسالی بروز رسانی شده است",
|
||||
"dns_test_ok_toast": "سرورهای DNS تعیین شده بدرستی کار می کنند",
|
||||
"dns_test_not_ok_toast": "سرور \"{{key}}\": نمیتواند مورد استفاده قرار گیرد،لطفا بررسی کنید آن را بدرستی نوشته اید",
|
||||
"unblock_btn": "باز کن",
|
||||
"block_btn": "مسدود کن",
|
||||
"time_table_header": "زمان",
|
||||
"domain_name_table_header": "نام دامنه",
|
||||
"type_table_header": "نوع",
|
||||
"response_table_header": "پاسخ",
|
||||
"client_table_header": "کلاینت",
|
||||
"empty_response_status": "خالی",
|
||||
"show_all_filter_type": "نمایش همه",
|
||||
"show_filtered_type": "نمایش فیلتر شده",
|
||||
"no_logs_found": "وقایع یافت نشد",
|
||||
"refresh_btn": "تازه سازی",
|
||||
"previous_btn": "قبلی",
|
||||
"next_btn": "بعدی",
|
||||
"loading_table_status": "بارگیری...",
|
||||
"page_table_footer_text": "صفحه",
|
||||
"of_table_footer_text": "از",
|
||||
"rows_table_footer_text": "سطر",
|
||||
"updated_custom_filtering_toast": "دستورات فیلترینگ دستی بروز رسانی شده است",
|
||||
"rule_removed_from_custom_filtering_toast": "دستور از دستورات فیلترینگ دستی حذف شد",
|
||||
"rule_added_to_custom_filtering_toast": "دستور به دستورات فیلترینگ دستی اضافه شد",
|
||||
"query_log_response_status": "وضعیت: {{value}}",
|
||||
"query_log_filtered": "فیلتر شده با {{filter}}",
|
||||
"source_label": "منبع",
|
||||
"found_in_known_domain_db": "در پایگاه داده دامنه های شناخته شده پیدا شد",
|
||||
"category_label": "دسته بندی",
|
||||
"rule_label": "دستور",
|
||||
"filter_label": "فیلتر",
|
||||
"unknown_filter": "فیلتر ناشناخته {{filterId}}",
|
||||
"install_welcome_title": "به AdGuard Home خوش آمدید!",
|
||||
"install_welcome_desc": "AdGuard Home یک شبکه گسترده و ردیاب و مسدوساز تبلیغ با سرور DNS است.هدف آن این است که به شما اجازه کنترل کل شبکه و همه دستگاه های شما را بدهد و آن نیازی به برنامه سمت-کاربر ندارد.",
|
||||
"install_settings_title": "رابط وب آدمین",
|
||||
"install_settings_listen": "رابط گوش دادن",
|
||||
"install_settings_port": "پورت",
|
||||
"install_settings_interface_link": "رابط صفحه وب آدمین AdGuard Home شما در این آدرس قابل دسترسی خواهد بود:",
|
||||
"form_error_port": "مقدار پورت معتبر وارد کنید",
|
||||
"install_settings_dns": "سرور DNS",
|
||||
"install_settings_dns_desc": "نیاز است شما دستگاه یا روتر خود را برای استفاده از سرور DNS روی آدرس های زیر پیکربندی کنید:",
|
||||
"install_settings_all_interfaces": "همه رابط ها",
|
||||
"install_auth_title": "احراز هویت",
|
||||
"install_auth_username": "نام کاربر",
|
||||
"install_auth_password": "رمزعبور",
|
||||
"install_auth_confirm": "تأیید رمزعبور",
|
||||
"install_auth_username_enter": "نام کاربر را وارد کنید",
|
||||
"install_auth_password_enter": "رمزعبور را وارد کنید",
|
||||
"install_step": "گام",
|
||||
"install_devices_title": "پیکربندی دستگاه شما",
|
||||
"install_devices_desc": "به منظور اینکه AdGuard Home شروع به کار کند،باید دستگاه خود را برای استفاده از آن پیکربندی کنید.",
|
||||
"install_submit_title": "تبریک می گوییم!",
|
||||
"install_submit_desc": "روش راه اندازی به پایان رسیده و شما آماده استفاده از AdGuard Home هستید.",
|
||||
"install_devices_router": "روتر",
|
||||
"install_devices_router_desc": "این راه انداز خودکار همه دستگاه های متصل شده به روتر خانه را پوشش میدهد و نیازی نیست شما هر یک از آنها را دستی پیکربندی کنید.",
|
||||
"install_devices_address": "DNS سرور AdGuard Home به آدرس های زیر گوش میدهد",
|
||||
"install_devices_router_list_1": "اولویت ها را برای روتر خود باز کنید.معمولا میتوانید آن را ز طریق مرورگر از طریق آدرسی مانند ( http://192.168.0.1/ یا http://192.168.1.1/) دسترسی داشته باشید.ممکن است رمزعبور پرسیده شود،اگر آن را بخاطر ندارید،غالبا میتوان رمزعبور را با فشردن دکمه پشت روتر ریست کرد.برخی روترها برنامه خاصی نیاز دارد که باید در رایانه/گوشی نصب شده باشد.",
|
||||
"install_devices_router_list_2": "تنظیمات DHCP/DNS را بیابید.دنبال حروف DNS بگردید در فیلدی که اجازه دو یا سه گروه عدد را میدهد و هر کدام در چهار گروه سه عددی شکسته شده است",
|
||||
"install_devices_router_list_3": "آدرس سرور AdGuard Home خود را آنجا وارد کنید",
|
||||
"install_devices_windows_list_1": "کنترل پنل را از طریق استارت منو یا جستجوی ویندوز باز کنید.",
|
||||
"install_devices_windows_list_2": "بروید به شبکه و دسته اینترنت و سپس به شبکه و مرکز اشتراک گذاری",
|
||||
"install_devices_windows_list_3": "در سمت چپ صفحه تنظیمات آداپتور را تغییر داده و روی آن کلیک کنید",
|
||||
"install_devices_windows_list_4": "ارتباط فعال خود را انتخاب کرده،روی آن راست کلیک کرده و مشخصات را انتخاب کنید.",
|
||||
"install_devices_windows_list_5": "پروتکل اینترنت نسخه 4 (TCP/IP) را در لیست بیابید،آن را انتخاب و سپس روی مشخصات دوباره کلیک کنید.",
|
||||
"install_devices_windows_list_6": "گزینه استفاده از آدرس DNS سرور زیر را انتخاب کرده و آدرس سرور AdGuard Home خود را وارد کنید.",
|
||||
"install_devices_macos_list_1": "روی آیکون اَپل کلیک کرده و بروید به اولویت های سیستم",
|
||||
"install_devices_macos_list_2": "روی شبکه کلیک کنید",
|
||||
"install_devices_macos_list_3": "اولین ارتباط را از لیست خود انتخاب و روی پیشرفته کلیک کنید.",
|
||||
"install_devices_macos_list_4": "تب DNS را انتخاب و آدرس های سرور AdGuard Home خود را وارد کنید.",
|
||||
"install_devices_android_list_1": "از منوی صفحه خانه آندروئید،تنظیمات را فشار دهید",
|
||||
"install_devices_android_list_2": "وای فای را در منو فشار دهید،صفحه لیست کردن همه شبکه های موجود نشان داده میشود (تنظیم DNS دستی برای ارتباط موبایلی غیرممکن است)",
|
||||
"install_devices_android_list_3": "به شبکه ای که متصل شده اید فشار طولانی دهید و ویرایش شبکه را انتخاب کنید.",
|
||||
"install_devices_android_list_4": "در برخی دستگاه ها،شما ممکن است کادر پیشرفته را برای تنظیمات بعدی بررسی کنید.برای تنظیم DNS آندروئید خود،نیاز است شما از تنظیمات IP را از DHCP به Staticتغییر دهید.",
|
||||
"install_devices_android_list_5": "گروه مقادیر DNS 1 و DNS 2 را به آدرس سرور AdGuard Home خود تغییر دهید.",
|
||||
"install_devices_ios_list_1": "از صفحه خانه،تنظیمات را فشار دهید.",
|
||||
"install_devices_ios_list_2": "وای فای را از منوی چپ انتخاب کنید (پیکربندی DNS دستی برای ارتباط موبایلی غیرممکن است).",
|
||||
"install_devices_ios_list_3": "روی نام شبکه فعال فعلی کلیک کنید.",
|
||||
"install_devices_ios_list_4": "در فیلد DNS آدرس سرور AdGuard Home را وارد کنید",
|
||||
"get_started": "شروع به کار",
|
||||
"next": "بعدی",
|
||||
"open_dashboard": "بازکردن داشبورد",
|
||||
"install_saved": "با موفقیت ذخیره نشد",
|
||||
"encryption_title": "رمزگُذاری",
|
||||
"encryption_desc": "پشتیبانی رمزگُذاری (HTTPS/TLS) برای DNS و رابط آدمین وب",
|
||||
"encryption_config_saved": "پیکربندی رمزگذاری ذخیره شد",
|
||||
"encryption_server": "نام سرور",
|
||||
"encryption_server_enter": "نام دامنه خود را وارد کنید",
|
||||
"encryption_server_desc": "به منظور استفاده از HTTPS،شما باید نام سرور مطابق با گواهینامه اِس اِس اِل را وارد کنید.",
|
||||
"encryption_redirect": "تغییر مسیر خودکار به HTTPS",
|
||||
"encryption_redirect_desc": "اگر انتخاب شده باشد،AdGuard Home خودکار شما را از آدرس HTTP به HTTPS منتقل می کند",
|
||||
"encryption_https": "پورت HTTPS",
|
||||
"encryption_https_desc": "اگر پورت HTTPS پیکربندی شده باشد،رابط آدمین AdGuard Home از طریق HTTPS قابل دسترسی خواهد بود و آن همچنین DNS-over-HTTPS را در مکان '/dns-query' ارائه می دهد.",
|
||||
"encryption_dot": "پورت DNS-over-TLS",
|
||||
"encryption_dot_desc": "اگر این پورت پیکربندی شده باشد،AdGuard Home یک DNS-over-TLS سرور روی این پورت اجرا می کند",
|
||||
"encryption_certificates": "گواهینامه ها",
|
||||
"encryption_certificates_desc": "به منظور استفاده از رمزگُذاری، شما باید زنجیره گواهینامه اِس اِس اِل معتبر برای دامنه خود ارائه دهید. میتوانید گواهینامه رایگان از <0>{{link}}</0> بگیرید یا میتوانید آن را از مراجع گواهینامه معتبر بخرید.",
|
||||
"encryption_certificates_input": "کپی/چسباندن گواهینامه پی ای اِم کد گذاری شده در اینجا.",
|
||||
"encryption_status": "وضعیت",
|
||||
"encryption_expire": "تاریخ انقضاء",
|
||||
"encryption_key": "کلید خصوصی",
|
||||
"encryption_key_input": "کپی/چسباندن کلید گواهینامه پی ای اِم کد گذاری شده در اینجا.",
|
||||
"encryption_enable": "فعالسازی رمزگُذاری (HTTPS, DNS-over-HTTPS, و DNS-over-TLS)",
|
||||
"encryption_enable_desc": "اگر رمزگُذاري فعال شده باشد،رابط آدمین AdGuard Home روی HTTPS کار خواهد کرد،و سرور DNS به درخواست های روی DNS-over-HTTPS و DNS-over-TLS گوش خواهد داد.",
|
||||
"encryption_chain_valid": "زنجیره گواهینامه معتبر است",
|
||||
"encryption_chain_invalid": "زنجیره گواهینامه نامعتبر است",
|
||||
"encryption_key_valid": "این یک کلید خصوصی {{type}} معتبر است",
|
||||
"encryption_key_invalid": "این یک کلید خصوصی {{type}} نامعتبر است",
|
||||
"encryption_subject": "موضوع",
|
||||
"encryption_issuer": "صادر کننده",
|
||||
"encryption_hostnames": "نام میزبان",
|
||||
"encryption_reset": "آیا میخواهید تنظیمات رمزگُذاری به پیش فرض بازگردد؟",
|
||||
"topline_expiring_certificate": "گواهینامه اِس اِس اِل شما در صدد انقضاء است. <0>تنظیمات رمزگُذاری</0> را بروز رسانی کنید.",
|
||||
"topline_expired_certificate": "گواهینامه اِس اِس اِل شما منقضی شده است. <0>تنظیمات رمزگُذاری</0> را بروز رسانی کنید.",
|
||||
"form_error_port_range": "مقدار پورت را در محدوده 80-65535 وارد کنید",
|
||||
"form_error_port_unsafe": "این پورت غیر ایمن است",
|
||||
"form_error_equal": "نباید برابر باشد",
|
||||
"form_error_password": "رمزعبور تطبیق ندارد",
|
||||
"reset_settings": "ریست تنظیمات",
|
||||
"update_announcement": "AdGuard Home {{version}} در دسترس است! <0>اینجا کلیک</0> کنید برای اطلاعات بیشتر.",
|
||||
"setup_guide": "راهنمای راه اندازی",
|
||||
"dns_addresses": "آدرس های DNS",
|
||||
"down": "کار نمی کند",
|
||||
"fix": "تعمیر",
|
||||
"dns_providers": "در اینجا یک <0>لیست از سرویس های ارائه دهنده DNS</0> برای انتخاب هست.",
|
||||
"update_now": "حالا بروز رسانی",
|
||||
"update_failed": "بروز رسانی خودکار موفق نشد. لطفا <a href='https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started#update'>مراحل را دنبال کرده</a> تا بطور دستی بروز رسانی کنید.",
|
||||
"processing_update": "منتظر بمانید،AdGuard Home در حال بروز رسانی است",
|
||||
"clients_title": "کلاینت ها",
|
||||
"clients_desc": "پیکربندی دستگاه های متصل شده به AdGuard Home",
|
||||
"settings_global": "سراسری",
|
||||
"settings_custom": "دستي",
|
||||
"table_client": "کلاینت",
|
||||
"table_name": "نام",
|
||||
"save_btn": "ذخیره",
|
||||
"client_add": "افزودن کلاینت",
|
||||
"client_new": "کلاینت جدید",
|
||||
"client_edit": "ویرایش کلاینت",
|
||||
"client_identifier": "احراز با",
|
||||
"ip_address": "آدرس آی پی",
|
||||
"form_enter_ip": "آی پی را وارد کنید",
|
||||
"form_enter_mac": "مَک را وارد کنید",
|
||||
"form_client_name": "نام کلاینت را وارد کنید",
|
||||
"client_global_settings": "استفاده از تنظیمات سراسری",
|
||||
"client_deleted": "کلاینت \"{{key}}\" را با موفقیت حذف کرد",
|
||||
"client_added": "کلاینت \"{{key}}\" را با موفقیت اضافه کرد",
|
||||
"client_updated": "کلاینت \"{{key}}\" با موفقیت بروز رسانی شد",
|
||||
"clients_not_found": "کلاینتی یافت نشد",
|
||||
"client_confirm_delete": "آیا واقعا میخواهید \"{{key}}\" کلاینت را حذف کنید؟",
|
||||
"filter_confirm_delete": "آیا واقعا میخواهید فیلتر را حذف کنید؟",
|
||||
"auto_clients_title": "کلاینت ها (زمان اِجرا)",
|
||||
"auto_clients_desc": "داده در کلاینت ها که از AdGuard Home استفاده می کند،اما در پیکربندی ذخیره نمی شود",
|
||||
"access_title": "تنظیمات دسترسی",
|
||||
"access_desc": "در اینجا میتوانید دستورات دسترسی را برای DNS سرور AdGuard Home وارد کنید.",
|
||||
"access_allowed_title": "کلاینت های مجاز",
|
||||
"access_allowed_desc": "یک لیست از CIDR یا آدرس های IP.اگر پیکربندی شود،AdGuard Home درخواست ها را فقط از این آدرس ها می پذیرد.",
|
||||
"access_disallowed_title": "کلاینت های غیرمجاز",
|
||||
"access_disallowed_desc": "یک لیست از CIDR یا آدرس های IP.اگر پیکربندی شود،AdGuard Home درخواست ها را از این آدرس های IP نمی پذیرد.",
|
||||
"access_blocked_title": "دامنه های مسدود شده",
|
||||
"access_blocked_desc": "این را با فیلتر ها به اشتباه نگیرید.AdGuard Home جستار DNS را با این دامنه ها در جستار سوال ها نمی پذیرد.",
|
||||
"access_settings_saved": "تنظیمات دسترسی با موفقیت ذخیره شد",
|
||||
"updates_checked": "بروز رسانی با موفقیت بررسی شد",
|
||||
"updates_version_equal": "AdGuard Home بروز است",
|
||||
"check_updates_now": "حالا بررسی برای بروز رسانی",
|
||||
"dns_privacy": "حریم خصوصی DNS",
|
||||
"setup_dns_privacy_1": "<0>DNS-over-TLS:</0> استفاده از<1>{{address}}</1> .",
|
||||
"setup_dns_privacy_2": "<0>DNS-over-HTTPS:</0> استفاده از <1>{{address}}</1> .",
|
||||
"setup_dns_privacy_3": "<0>لطفا توجه کنید که پروتکل های رمزگذاری شده DNS فقط در آندروئید 9 پشتیبانی می شود. پس برای سیستم عامل های دیگر نیاز است که برنامه دیگری نصب کنید.</0><0>در اینجا میتوانید لیست نرم افزارهای قابل استفاده را ببینید.</0>",
|
||||
"setup_dns_privacy_android_1": "آندروئید 9 بطور پیش فرض از DNS-over-TLS پشتیبانی می کند. برای پیکربندی آن، بروید به تنظیمات → شبکه & اینترنت → پیشرفته → DNS خصوصی و نام دامنه را آنجا وارد کنید.",
|
||||
"setup_dns_privacy_android_2": "<0>AdGuard for Android</0> پشتیبانی از <1>DNS-over-HTTPS</1> و <1>DNS-over-TLS</1>.",
|
||||
"setup_dns_privacy_android_3": "<0>Intra</0> قابلیت <1>DNS-over-HTTPS</1> را به آندروئید اضافه می کند.",
|
||||
"setup_dns_privacy_ios_1": "<0>DNSCloak</0> پشتیبانی از <1>DNS-over-HTTPS</1>, اما بمنظور پیکربندی آن برای استفاده بعنوان سرور خود،شما نیاز دارید که <2>DNS Stamp</2> برای آن تولید کنید.",
|
||||
"setup_dns_privacy_ios_2": "<0>AdGuard for iOS</0> پشتیبانی از <1>DNS-over-HTTPS</1> و راه اندازی <1>DNS-over-TLS</1> .",
|
||||
"setup_dns_privacy_other_title": "سایر راه کارها",
|
||||
"setup_dns_privacy_other_1": "AdGuard Home خودش میتواند کلاینت DNS اَمن را در هر سیستم عاملی پیاده کند.",
|
||||
"setup_dns_privacy_other_2": "<0>dnsproxy</0> از همه پروتکل های DNS شناخته شده پشتیبانی می کند.",
|
||||
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> supportsپشتیبانی از <1>DNS-over-HTTPS</1>.",
|
||||
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> پشتیبانی از <1>DNS-over-HTTPS</1>.",
|
||||
"setup_dns_privacy_other_5": "شما میتوانید راه کارهای بیشتر را در <0>اینجا</0> و <1>اینجا</1> پیدا کنید.",
|
||||
"setup_dns_notice": "به منظور استفاده از <1>DNS-over-HTTPS</1> یا <1>DNS-over-TLS</1>، شما نیاز به <0>پیکربندی رمزگذاری</0> در تنظیمات AdGuard Home دارید.",
|
||||
"rewrite_added": "بازنویسی DNS برای \"{{key}}\" با موفقیت اضافه شد",
|
||||
"rewrite_deleted": "بازنویسی DNS برای \"{{key}}\" با موفقیت حذف شد",
|
||||
"rewrite_add": "افزودن بازنویسی DNS",
|
||||
"rewrite_not_found": "بازنویسی DNS یافت نشد",
|
||||
"rewrite_confirm_delete": "آیا واقعا میخواهید بازنویسی DNS برای \"{{key}}\" را حذف کنید؟",
|
||||
"rewrite_desc": "به آسانی اجازه پیکربندی پاسخ DNS دستی برای یک نام دامنه خاص را می دهد.",
|
||||
"rewrite_applied": "دستور بازنویسی اِعمال شد",
|
||||
"dns_rewrites": "بازنویسی های DNS",
|
||||
"form_domain": "نام دامنه را وارد کنید",
|
||||
"form_answer": "نام دامنه یا آدرس آی پی را وارد کنید",
|
||||
"form_error_domain_format": "فرمت دامنه اشتباه است",
|
||||
"form_error_answer_format": "فرمت پاسخ اشتباه است",
|
||||
"configure": "پیکربندی",
|
||||
"main_settings": "تنظیمات اصلی",
|
||||
"block_services": "مسدودسازی سرویس های خاص",
|
||||
"blocked_services": "سرویس های مسدود شده",
|
||||
"blocked_services_desc": "مسدودسازی سریع سایت های عمومی و سرویس ها را اجازه می دهد.",
|
||||
"blocked_services_saved": "سرویس های مسدود شده با موفقیت ذخیره شد",
|
||||
"blocked_services_global": "از سرویس های مسدود شده سراسری استفاده کن",
|
||||
"blocked_service": "سرویس مسدود شده",
|
||||
"block_all": "مسدودسازی همه",
|
||||
"unblock_all": "گشودن همه",
|
||||
"encryption_certificate_path": "مسیر گواهینامه",
|
||||
"encryption_private_key_path": "مسیر کلید خصوصی",
|
||||
"encryption_certificates_source_path": "تنظیم مسیر فایل گواهینامه",
|
||||
"encryption_certificates_source_content": "چسباندن محتوای گواهینامه",
|
||||
"encryption_key_source_path": "تنظیم فایل کلید خصوصی",
|
||||
"encryption_key_source_content": "چسباندن محتوای کلید خصوصی",
|
||||
"stats_params": "پیکربندی آمار",
|
||||
"config_successfully_saved": "پیکربندی با موفقیت ذخیره شد",
|
||||
"interval_24_hour": "24 ساعت",
|
||||
"interval_days": "{{value}} روز",
|
||||
"interval_days_plural": "{{count}} روز",
|
||||
"domain": "دامنه",
|
||||
"answer": "پاسخ",
|
||||
"filter_added_successfully": "فیلتر با موفقیت اضافه شد",
|
||||
"statistics_retention": "مدت حفظ آمارها",
|
||||
"statistics_retention_desc": "اگر مقدار فاصله را کاهش دهید،برخی داده ها از بین خواهد رفت",
|
||||
"statistics_clear": " پاکسازی آمار",
|
||||
"statistics_clear_confirm": "آیا واقعا میخواهید آمار را پاک کنید؟",
|
||||
"statistics_cleared": "آمارها با موفقیت حذف شد"
|
||||
}
|
||||
@@ -16,9 +16,7 @@
|
||||
"dhcp_found": "Il y a plusieurs serveurs DHCP actifs sur le réseau. Ce n'est pas prudent d'activer le serveur DHCP intégré en ce moment.",
|
||||
"dhcp_leases": "Locations des serveurs DHCP",
|
||||
"dhcp_leases_not_found": "Aucune location des serveurs DHCP trouvée",
|
||||
"dhcp_config_saved": "La configuration du serveur DHCP est sauvegardée",
|
||||
"form_error_required": "Champ requis",
|
||||
"form_error_ip_format": "Format IPv4 invalide",
|
||||
"form_error_positive": "Doit être supérieur à 0",
|
||||
"dhcp_form_gateway_input": "IP de la passerelle",
|
||||
"dhcp_form_subnet_input": "Masque de sous-réseau",
|
||||
@@ -73,11 +71,9 @@
|
||||
"use_adguard_parental": "Utiliser le contrôle parental d'AdGuard",
|
||||
"use_adguard_parental_hint": "AdGuard Home va vérifier s'il y a du contenu pour adultes sur le domaine. Ce sera fait par aide du même API discret que celui utilisé par le service de Sécurité de navigation.",
|
||||
"enforce_safe_search": "Renforcer la recherche sécurisée",
|
||||
"enforce_save_search_hint": "AdGuard Home peut renforcer la Recherche sécurisée dans les moteurs de recherche suivants : Google, Youtube, Bing et Yandex.",
|
||||
"no_servers_specified": "Pas de serveurs spécifiés",
|
||||
"general_settings": "Paramètres généraux",
|
||||
"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éfixe tls:// pour DNS via les serveurs TLS .",
|
||||
"test_upstream_btn": "Tester les upstreams",
|
||||
"apply_btn": "Appliquer",
|
||||
"disabled_filtering_toast": "Filtrage désactivé",
|
||||
@@ -120,7 +116,7 @@
|
||||
"example_upstream_tcp": "DNS classique (au-dessus de TCP)",
|
||||
"all_filters_up_to_date_toast": "Tous les filtres sont mis à jour",
|
||||
"updated_upstream_dns_toast": "Les serveurs DNS upstream sont mis à jour",
|
||||
"dns_test_ok_toast": "Les serveurs DNS spécifiés fonctionnent de manière incorrecte",
|
||||
"dns_test_ok_toast": "Les serveurs DNS spécifiés fonctionnent de manière correcte",
|
||||
"dns_test_not_ok_toast": "Impossible d'utiliser le serveur \"{{key}}\": veuillez vérifier si le nom saisi est bien correct",
|
||||
"unblock_btn": "Débloquer",
|
||||
"block_btn": "Bloquer",
|
||||
@@ -160,4 +156,4 @@
|
||||
"install_devices_windows_list_4": "Sélectionnez votre connexion active, clic droit dessus et sélectionnez Propriétés.",
|
||||
"install_devices_windows_list_5": "Recherchez la version du protocole Internet 4 (TCP/IP) dans la liste, sélectionnez-la puis cliquez à nouveau sur Propriétés.",
|
||||
"updates_version_equal": "AdGuard Home est à jour"
|
||||
}
|
||||
}
|
||||
|
||||
413
client/src/__locales/hr.json
Normal file
413
client/src/__locales/hr.json
Normal file
@@ -0,0 +1,413 @@
|
||||
{
|
||||
"client_settings": "Postavke klijenta",
|
||||
"example_upstream_reserved": "možete odrediti DNS upstream-ove <0>za određene domene</0>",
|
||||
"upstream_parallel": "Koristi paralelne upite kako bi ubrzali rješavanje istovremenim ispitavanjem svih udaljenih poslužitelja",
|
||||
"bootstrap_dns": "Bootstrap DNS poslužitelji",
|
||||
"bootstrap_dns_desc": "Bootstrap DNS poslužitelji koriste se za rezolvanje IP adresa DoH/DoT rezolvera koje navedete kao upstreams.",
|
||||
"check_dhcp_servers": "Provjera DHCP poslužitelja",
|
||||
"save_config": "Spremi konfiguraciju",
|
||||
"enabled_dhcp": "DHCP poslužitelj je omogućen",
|
||||
"disabled_dhcp": "DHCP poslužitelj je onemogućen",
|
||||
"dhcp_title": "DHCP poslužitelj (eksperimentalno!)",
|
||||
"dhcp_description": "Ukoliko vaš router ne pruža DHCP postavke, možete koristiti AdGuardov ugrađeni DHCP poslužitelj.",
|
||||
"dhcp_enable": "Omogući DHCP poslužitelj",
|
||||
"dhcp_disable": "Onemogući DHCP poslužitelj",
|
||||
"dhcp_not_found": "Sigurno je omogućiti ugrađeni DHCP poslužitelj - nismo pronašli aktivne DHCP poslužitelje na mreži. Međutim, preporučujemo vam da ponovo provjerite ručno, jer naš automatski test trenutno ne daje 100% jamstvo.",
|
||||
"dhcp_found": "Aktivni DHCP poslužitelj je pronađen na mreži. Nije sigurno omogućiti ugrađeni DHCP poslužitelj.",
|
||||
"dhcp_leases": "DHCP leases",
|
||||
"dhcp_static_leases": "DHCP static leases",
|
||||
"dhcp_leases_not_found": "Nisu pronađeni DHCP leases",
|
||||
"dhcp_config_saved": "Postavke DHCP poslužitelja su uspješno spremljene",
|
||||
"form_error_required": "Obavezno polje",
|
||||
"form_error_ip4_format": "Nevažeći IPv4 format",
|
||||
"form_error_ip6_format": "Nevažeći IPv6 format",
|
||||
"form_error_ip_format": "Nevažeći format IP adrese",
|
||||
"form_error_mac_format": "Nevažeći MAC format",
|
||||
"form_error_positive": "Mora biti veće od 0",
|
||||
"dhcp_form_gateway_input": "Gateway IP",
|
||||
"dhcp_form_subnet_input": "Subnet maskiranje",
|
||||
"dhcp_form_range_title": "Raspon IP adresa",
|
||||
"dhcp_form_range_start": "Početak raspona",
|
||||
"dhcp_form_range_end": "Kraj raspona",
|
||||
"dhcp_form_lease_title": "DHCP lease vrijeme (u sekundama)",
|
||||
"dhcp_form_lease_input": "Lease trajanje",
|
||||
"dhcp_interface_select": "Odaberite DHCP sučelje",
|
||||
"dhcp_hardware_address": "Adresa hardvera",
|
||||
"dhcp_ip_addresses": "IP adrese",
|
||||
"dhcp_table_hostname": "Naziv računala",
|
||||
"dhcp_table_expires": "Istječe",
|
||||
"dhcp_warning": "Ako svejedno želite omogućiti DHCP poslužitelj, provjerite da nema drugog aktivnog DHCP poslužitelja na vašoj mreži. Inače može pokvariti Internet za ostale povezane uređaje!",
|
||||
"dhcp_error": "Nismo mogli utvrditi postoji li drugi DHCP poslužitelj na mreži.",
|
||||
"dhcp_static_ip_error": "Za korištenje DHCP poslužitelja mora se postaviti statička IP adresa. Nismo uspjeli utvrditi je li to mrežno sučelje postavljeno pomoću statičke IP adrese. Ručno postavite statičku IP adresu.",
|
||||
"dhcp_dynamic_ip_found": "Vaš sustav koristi postavke dinamičkueIP adrese za sučelje <0>{{interfaceName}}</0>. Za korištenje DHCP poslužitelja mora se postaviti statička IP adresa. Vaša trenutna IP adresa je <0>{{ipAddress}}</0>. Ovu ćemo IP adresu automatski postaviti kao statičku ako pritisnete Omogući DHCP dugme.",
|
||||
"dhcp_lease_added": "Statični lease \"{{key}}\" je uspješno dodan",
|
||||
"dhcp_lease_deleted": "Statični lease \"{{key}}\" je uspješno uklonjen",
|
||||
"dhcp_new_static_lease": "Novi static lease",
|
||||
"dhcp_static_leases_not_found": "Nisu pronađeni statični DHCP leases",
|
||||
"dhcp_add_static_lease": "Dodaj static lease",
|
||||
"dhcp_reset": "Jeste li sigurni da želite poništiti DHCP postavke?",
|
||||
"delete_confirm": "Jeste li sigurni da želite ukloniti \"{{key}}\"?",
|
||||
"form_enter_hostname": "Unesite naziv računala",
|
||||
"error_details": "Detalji o pogrešci",
|
||||
"back": "Natrag",
|
||||
"dashboard": "Upravljačka ploča",
|
||||
"settings": "Postavke",
|
||||
"filters": "Filtri",
|
||||
"query_log": "Zapisnik upita",
|
||||
"faq": "ČPP",
|
||||
"version": "Verzija",
|
||||
"address": "adresa",
|
||||
"on": "UKLJUČENO",
|
||||
"off": "ISKLJUČENO",
|
||||
"copyright": "Autorsko pravo",
|
||||
"homepage": "Početna stranica",
|
||||
"report_an_issue": "Prijavite problem",
|
||||
"privacy_policy": "Politika privatnosti",
|
||||
"enable_protection": "Omogući zaštitu",
|
||||
"enabled_protection": "Omogućena zaštita",
|
||||
"disable_protection": "Onemogućena zaštita",
|
||||
"disabled_protection": "Onemogućena zaštita",
|
||||
"refresh_statics": "Osvježi statistiku",
|
||||
"dns_query": "DNS Upiti",
|
||||
"blocked_by": "<0>Blokirano filtrima</0>",
|
||||
"stats_malware_phishing": "Blokiran zločudni softver/krađe identiteta",
|
||||
"stats_adult": "Blokirane web stranice za odrasle",
|
||||
"stats_query_domain": "Top tražene domene",
|
||||
"for_last_24_hours": "u zadnja 24 sata",
|
||||
"for_last_days": "zadnjih {{count}} dana",
|
||||
"for_last_days_plural": "zadnjih {{count}} dana",
|
||||
"no_domains_found": "Nije pronađena domena",
|
||||
"requests_count": "Broj zahtjeva",
|
||||
"top_blocked_domains": "Top blokirane domene",
|
||||
"top_clients": "Top klijenti",
|
||||
"no_clients_found": "Nema pronađenih klijenata",
|
||||
"general_statistics": "Opća statistika",
|
||||
"number_of_dns_query_days": "Broj DNS upita obrađenih u posljednja {{count}} dana",
|
||||
"number_of_dns_query_days_plural": "Broj DNS upita obrađenih u posljednja {{count}} dana",
|
||||
"number_of_dns_query_24_hours": "Broj DNS upita obrađenih u posljednja 24 sata",
|
||||
"number_of_dns_query_blocked_24_hours": "Broj DNS zahtjeva koji su blokirani od strane filtara za blokiranje oglasa i lista neželjenih poslužitelja",
|
||||
"number_of_dns_query_blocked_24_hours_by_sec": "Broj DNS zahtjeva koje je blokirao modul AdGuard zaštita pregledavanja",
|
||||
"number_of_dns_query_blocked_24_hours_adult": "Broj blokiranih stranica s sadržajem za odrasle",
|
||||
"enforced_save_search": "Omogućeno sigurno pretraživanje",
|
||||
"number_of_dns_query_to_safe_search": "Broj DNS zahtjeva prema pretraživačima za koje je omogućeno Sigurno pretraživanje",
|
||||
"average_processing_time": "Prosječno vrijeme obrade",
|
||||
"average_processing_time_hint": "Prosječno vrijeme u milisekundama za obradu DNS zahtjeva",
|
||||
"block_domain_use_filters_and_hosts": "Blokiraj domene koristeći filtre ili hosts datoteke",
|
||||
"filters_block_toggle_hint": "Pravila blokiranja možete postaviti u postavkama <a href='#filters'>filtara</a>.",
|
||||
"use_adguard_browsing_sec": "Koristi AdGuard uslugu zaštite pregledavanja",
|
||||
"use_adguard_browsing_sec_hint": "AdGuard Home će provjeriti nalazi li se domena na popisu neželjenih domena od usluge zaštite pregledavanja. Za provjeru će se koristiti API za provjeru koji poštuje vašu privatnost. Samo mali dio SHA256 hash-a odnaziva domene se šalje poslužitelju.",
|
||||
"use_adguard_parental": "Koristi web uslugu AdGuard roditeljske zaštite",
|
||||
"use_adguard_parental_hint": "AdGuard Home provjeriti će sadrži li domena sadržaj za odrasle. Koristi isti API za zaštitu privatnosti kao i naša usluga zaštite pregledavanja.",
|
||||
"enforce_safe_search": "Omogući sigurno pretraživanje",
|
||||
"enforce_save_search_hint": "AdGuard Home može nametnuti sigurno pretraživanje na sljedećim tražilicama: Google, Youtube, Bing, DuckDuckGo i Yandex.",
|
||||
"no_servers_specified": "Nije odabran nijedan poslužitelj",
|
||||
"general_settings": "Opće postavke",
|
||||
"dns_settings": "DNS postavke",
|
||||
"encryption_settings": "Postavke šifriranja",
|
||||
"dhcp_settings": "DHCP postavke",
|
||||
"upstream_dns": "Upstream DNS poslužitelji",
|
||||
"upstream_dns_hint": "Ako se ovo polje ostavi prazno, AdGuard Home će koristiti <a href='https://www.quad9.net/' target='_blank'>Quad9</a> kao upstream.",
|
||||
"test_upstream_btn": "Testiraj upstream-ove",
|
||||
"upstreams": "Upstreams",
|
||||
"apply_btn": "Primijeni",
|
||||
"disabled_filtering_toast": "Onemogućeno filtriranje",
|
||||
"enabled_filtering_toast": "Omogućeno filtriranje",
|
||||
"disabled_safe_browsing_toast": "Onemogućeno sigurno pretraživanje",
|
||||
"enabled_safe_browsing_toast": "Omogućeno sigurno pretraživanje",
|
||||
"disabled_parental_toast": "Onemogućen roditeljski nadzor",
|
||||
"enabled_parental_toast": "Omogućen roditeljski nadzor",
|
||||
"disabled_safe_search_toast": "Onemogućeno sigurno pretraživanje",
|
||||
"enabled_save_search_toast": "Omogućeno sigurno pretraživanje",
|
||||
"enabled_table_header": "Omogućeno",
|
||||
"name_table_header": "Naziv",
|
||||
"filter_url_table_header": "URL filtra",
|
||||
"rules_count_table_header": "Broj pravila",
|
||||
"last_time_updated_table_header": "Zadnje ažurirano",
|
||||
"actions_table_header": "Radnje",
|
||||
"edit_table_action": "Uredi",
|
||||
"delete_table_action": "Ukloni",
|
||||
"filters_and_hosts": "Filtri i lista neželjenih poslužitelja",
|
||||
"filters_and_hosts_hint": "AdGuard Home razumije osnovna pravila blokiranja oglasa i sintaksu hosts datoteka.",
|
||||
"no_filters_added": "Nema dodanih filtara",
|
||||
"add_filter_btn": "Dodaj filtar",
|
||||
"cancel_btn": "Poništi",
|
||||
"enter_name_hint": "Unesite naziv",
|
||||
"enter_url_hint": "Unesite URL",
|
||||
"check_updates_btn": "Provjeri ažuriranja",
|
||||
"new_filter_btn": "Pretplata na novi filtar",
|
||||
"enter_valid_filter_url": "Unesite valjani URL za pretplatu filtra ili za hosts datoteku.",
|
||||
"custom_filter_rules": "Prilagođena pravila filtriranja",
|
||||
"custom_filter_rules_hint": "Unesite jedno pravilo po liniji. Možete koristiti sintaksu za pravila blokiranja oglasa ili za hosts datoteke.",
|
||||
"examples_title": "Primjeri",
|
||||
"example_meaning_filter_block": "blokira pristup domeni example.org kao i svim njenim poddomenama",
|
||||
"example_meaning_filter_whitelist": "odblokira pristup domeni example.org kao i svim njenim poddomenama",
|
||||
"example_meaning_host_block": "AdGuard Home će sada vratiti 127.0.0.1 adresu na example.org domenu (ali ne i poddomene).",
|
||||
"example_comment": "! Ovdje ide komentar",
|
||||
"example_comment_meaning": "samo komentar",
|
||||
"example_comment_hash": "# Također komentar",
|
||||
"example_regex_meaning": "blokira pristup domenama koje se podudaraju s regularnim izrazom",
|
||||
"example_upstream_regular": "zadani DNS (putem UDP)",
|
||||
"example_upstream_dot": "šifrirano <0>DNS-over-TLS</0>",
|
||||
"example_upstream_doh": "šifrirano <0>DNS-over-HTTPS</0>",
|
||||
"example_upstream_sdns": "možete koristiti <0>DNS Stamps</0> za <1>DNSCrypt</1> ili <2>DNS-over-HTTPS</2> rezolvere",
|
||||
"example_upstream_tcp": "zadani DNS (putem TCP)",
|
||||
"all_filters_up_to_date_toast": "Svi filtri su ažurirani",
|
||||
"updated_upstream_dns_toast": "Ažurirani su upstream DNS poslužitelji",
|
||||
"dns_test_ok_toast": "Odabrani DNS poslužitelji su trenutno aktivni",
|
||||
"dns_test_not_ok_toast": "\"{{key}}\" poslužitelja: ne može se upotrijebiti, provjerite jeste li to ispravno napisali",
|
||||
"unblock_btn": "Odblokiraj",
|
||||
"block_btn": "Blokiraj",
|
||||
"time_table_header": "Vrijeme",
|
||||
"domain_name_table_header": "Naziv domene",
|
||||
"type_table_header": "Vrsta",
|
||||
"response_table_header": "Odgovor",
|
||||
"client_table_header": "Klijent",
|
||||
"empty_response_status": "Prazno",
|
||||
"show_all_filter_type": "Prikaži sve",
|
||||
"show_filtered_type": "Prikaži filtrirano",
|
||||
"no_logs_found": "Nema zapisa",
|
||||
"refresh_btn": "Osvježi",
|
||||
"previous_btn": "Prethodno",
|
||||
"next_btn": "Sljedeće",
|
||||
"loading_table_status": "Učitavanje...",
|
||||
"page_table_footer_text": "Stranica",
|
||||
"of_table_footer_text": "od",
|
||||
"rows_table_footer_text": "redova",
|
||||
"updated_custom_filtering_toast": "Ažurirana su prilagođena pravila filtriranja",
|
||||
"rule_removed_from_custom_filtering_toast": "Pravilo je uklonjeno iz prilagođenih pravila filtriranja",
|
||||
"rule_added_to_custom_filtering_toast": "Pravilo je dodano u prilagođena pravila filtriranja",
|
||||
"query_log_response_status": "Status: {{value}}",
|
||||
"query_log_filtered": "Filtrirao {{filter}}",
|
||||
"query_log_confirm_clear": "Jeste li sigurni da želite ukloniti zapise upita?",
|
||||
"query_log_cleared": "Zapisnik upita je uspješno uklonjen",
|
||||
"query_log_clear": "Očisti zapisnik upita",
|
||||
"query_log_retention": "Spremanje zapisnika upita",
|
||||
"query_log_enable": "Omogući zapise",
|
||||
"query_log_configuration": "Postavke zapisa",
|
||||
"query_log_disabled": "Zapisnik upita je onemogućen i može se postaviti u <0>postavkama</0>",
|
||||
"query_log_strict_search": "Koristite dvostruke navodnike za strogo pretraživanje",
|
||||
"query_log_retention_confirm": "Jeste li sigurni da želite promijeniti zadržavanje zapisnika upita? Ako smanjite vrijednost intervala, neki će podaci biti izgubljeni",
|
||||
"source_label": "Izvor",
|
||||
"found_in_known_domain_db": "Pronađeno u bazi poznatih domena.",
|
||||
"category_label": "Kategorija",
|
||||
"rule_label": "Pravilo",
|
||||
"filter_label": "Filtar",
|
||||
"unknown_filter": "Nepoznati filtar {{filterId}}",
|
||||
"install_welcome_title": "Dobrodošli u AdGuard Home!",
|
||||
"install_welcome_desc": "AdGuard Home je DNS poslužitelj za blokiranje oglasa i pratitelja na cijeloj mreži. Njegova je svrha omogućiti vam upravljanje cijelom mrežom i svim svojim uređajima, a da to ne zahtijeva korištenje programa na strani klijenta.",
|
||||
"install_settings_title": "Administratorsko web sučelje",
|
||||
"install_settings_listen": "Osluškuj sučelje",
|
||||
"install_settings_port": "Port",
|
||||
"install_settings_interface_link": "Web administratorsko sučelje AdGuard Home-a će biti dostupno na sljedećim adresama:",
|
||||
"form_error_port": "Unesite važeću vrijednost za port",
|
||||
"install_settings_dns": "DNS poslužitelj",
|
||||
"install_settings_dns_desc": "Potrebno je postaviti uređaj ili router da koristi DNS poslužitelj na sljedećim adresama:",
|
||||
"install_settings_all_interfaces": "Sva sučelja",
|
||||
"install_auth_title": "Autentikacija",
|
||||
"install_auth_desc": "Izrazito se preporučuje postavljanje autentikacije za web administratorsko sučelje AdGuard Home. Iako je dostupna samo u vašoj lokalnoj mreži, važno je zaštititi je od ne dozvoljenog pristupa.",
|
||||
"install_auth_username": "Korisničko ime",
|
||||
"install_auth_password": "Lozinka",
|
||||
"install_auth_confirm": "Potvrdi lozinku",
|
||||
"install_auth_username_enter": "Unesite korisničko ime",
|
||||
"install_auth_password_enter": "Unesite lozinku",
|
||||
"install_step": "Korak",
|
||||
"install_devices_title": "Postavite vaše uređaje",
|
||||
"install_devices_desc": "Da biste započeli koristiti AdGuard Home, morate postaviti uređaje da ga koriste.",
|
||||
"install_submit_title": "Čestitamo!",
|
||||
"install_submit_desc": "Postavljanje je dovršeno i spremni ste koristiti AdGuard Home.",
|
||||
"install_devices_router": "Usmjerivač (Router)",
|
||||
"install_devices_router_desc": "Ovo postavljanje će automatski pokriti sve uređaje povezane na vaš kućni router i nećete trebati ručno postavljati svaki od njih.",
|
||||
"install_devices_address": "AdGuard Home DNS poslužitelj osluškuje sljedeće adrese",
|
||||
"install_devices_router_list_1": "Otvorite postavke za router. Obično mu možete pristupiti iz preglednika putem URL-a (kao što je http://192.168.0.1/ ili http://192.168.1.1/). Od vas će se možda tražiti da unesete lozinku. Ako je se ne sjećate, lozinku možete često poništiti pritiskom na dumge na samom routeru. Neki routeri trebaju određenu aplikaciju, koja bi u tom slučaju trebala biti već instalirana na vašem računalu/telefonu.",
|
||||
"install_devices_router_list_2": "Pronađite DHCP/DNS postavke. Potražite DNS slova pored polja koje dopušta dva ili tri skupa brojeva, svaki razdvojen u četiri skupine od jedne do tri znamenke.",
|
||||
"install_devices_router_list_3": "Unesite adresu AdGuard Home poslužitelja ovdje.",
|
||||
"install_devices_windows_list_1": "Otvorite Upravljačku ploču putem Start izbornika ili Windows pretrage.",
|
||||
"install_devices_windows_list_2": "Idite na kategoriju Mreža i Internet i odaberite Centar za mreže i zajedničko korištenje.",
|
||||
"install_devices_windows_list_3": "Na lijevoj strani zaslona pronađite Promjeni postavke adaptera i pritisnite na to.",
|
||||
"install_devices_windows_list_4": "Odaberite aktivnu vezu, pritisnite desni klik na nju i odaberite Svojstva.",
|
||||
"install_devices_windows_list_5": "Pronađite Internet Protocol Version 4 (TCP/IP) na listi, odaberite ga i zatim pritisnite opet Postavke.",
|
||||
"install_devices_windows_list_6": "Odaberite Koristi sljedeće DNS adrese poslužitelja i unesite vaše adrese od AdGuard Home poslužitelja.",
|
||||
"install_devices_macos_list_1": "Pritisnite na Apple ikonu i idite u Postavke sustava.",
|
||||
"install_devices_macos_list_2": "Pritisnite na Mreža.",
|
||||
"install_devices_macos_list_3": "Odaberite prvu vezu s vašeg popisa i pritisnite Napredno.",
|
||||
"install_devices_macos_list_4": "Odaberite DNS karticu i unesite adrese svog AdGuard Home poslužitelja.",
|
||||
"install_devices_android_list_1": "Na početnom zaslonu Androida, odaberite Postavke.",
|
||||
"install_devices_android_list_2": "Pritisnite Wi-Fi u izborniku. Prikazat će se zaslon s popisom svih dostupnih mreža (nemoguće je postaviti prilagođeni DNS za mobilnu vezu).",
|
||||
"install_devices_android_list_3": "Dugo pritisnite na mrežu na koju ste povezani i odaberite Uredi mrežu.",
|
||||
"install_devices_android_list_4": "Na nekim će uređajima možda trebati označiti Napredno za prikaz dodatnih postavki. Da biste prilagodili postavke Android DNS-a, morati će te prebaciti IP postavke s DHCP-a na Statičke.",
|
||||
"install_devices_android_list_5": "Promijenite DNS 1 i DNS 2 vrijednosti u one adrese AdGuard Home poslužitelja.",
|
||||
"install_devices_ios_list_1": "Na početnom zaslonu odaberite Postavke.",
|
||||
"install_devices_ios_list_2": "Odaberite Wi-Fi u lijevom izborniku (ne moguće je postaviti DNS za mobilne mreže).",
|
||||
"install_devices_ios_list_3": "Pritisnite na naziv vaše trenutne mreže.",
|
||||
"install_devices_ios_list_4": "U DNS polje unesite adrese svog AdGuard Home poslužitelja.",
|
||||
"get_started": "Započni",
|
||||
"next": "Sljedeće",
|
||||
"open_dashboard": "Otvori upravljačku ploču",
|
||||
"install_saved": "Uspješno spremljeno",
|
||||
"encryption_title": "Šifriranje",
|
||||
"encryption_desc": "Podrška šifriranja (HTTPS/TLS) za DNS i administratorsko web sučelje",
|
||||
"encryption_config_saved": "Spremljene postavke šifriranja",
|
||||
"encryption_server": "Naziv poslužitelja",
|
||||
"encryption_server_enter": "Unesite naziv domene",
|
||||
"encryption_server_desc": "Kako biste koristili HTTPS, morate unijeti naziv poslužitelja koji odgovara vašem SSL certifikatu.",
|
||||
"encryption_redirect": "Automatski preusmjeri na HTTPS",
|
||||
"encryption_redirect_desc": "Ako je omogućeno, AdGuard Home će vas automatski preusmjeravati s HTTP na HTTPS adrese.",
|
||||
"encryption_https": "HTTPS port",
|
||||
"encryption_https_desc": "Ako je HTTPS port postavljen, AdGuard Home administracijsko sučelje biti će dostupno putem HTTPS-a, a također će pružiti DNS-over-HTTPS na '/dns-query' lokaciji.",
|
||||
"encryption_dot": "DNS-over-TLS port",
|
||||
"encryption_dot_desc": "Ako je ovaj port postavljen, AdGuard Home će pokrenuti DNS-over-TLS poslužitelj na ovom portu.",
|
||||
"encryption_certificates": "Certifikati",
|
||||
"encryption_certificates_desc": "Da biste koristili šifriranje, za svoju domenu morate osigurati važeći lanac SSL certifikata. Besplatan certifikat možete dobiti na <0>{{link}}</0> ili ga možete kupiti od jednog od pouzdanih izdavatelja certifikata.",
|
||||
"encryption_certificates_input": "Zalijepite svoje PEM-kodirane certifikate ovdje.",
|
||||
"encryption_status": "Status",
|
||||
"encryption_expire": "Istječe",
|
||||
"encryption_key": "Privatni ključ",
|
||||
"encryption_key_input": "Zalijepite svoj PEM-kodiran privatni ključ certifikata ovdje.",
|
||||
"encryption_enable": "Omogući šifriranje (HTTPS, DNS-over-HTTPS i DNS-over-TLS)",
|
||||
"encryption_enable_desc": "Ako je šifriranje omogućeno, administratorsko sučelje AdGuard Home će raditi preko HTTPS-a, a DNS poslužitelj će osluškivati zahtjeve preko DNS-over-HTTPS i DNS-over-TLS.",
|
||||
"encryption_chain_valid": "Lanac certifikata je valjan",
|
||||
"encryption_chain_invalid": "Lanac certifikata nije valjan",
|
||||
"encryption_key_valid": "Ovo je valjani {{type}} privatni ključ",
|
||||
"encryption_key_invalid": "Ovo je nevažeći {{type}} privatni ključ",
|
||||
"encryption_subject": "Predmet",
|
||||
"encryption_issuer": "Izdavač",
|
||||
"encryption_hostnames": "Nazivi računala",
|
||||
"encryption_reset": "Jeste li sigurni da želite poništiti postavke šifriranja?",
|
||||
"topline_expiring_certificate": "Vaš SSL certifikat uskoro ističe. Ažurirajte <0>Postavke šifriranja</0>.",
|
||||
"topline_expired_certificate": "Vaš SSL certifikat je istekao. Ažurirajte <0>Postavke šifriranja</0>.",
|
||||
"form_error_port_range": "Unesite vrijednost porta od 80 do 65536",
|
||||
"form_error_port_unsafe": "Ovo je nesigurna port",
|
||||
"form_error_equal": "Ne bi trebalo biti jednako",
|
||||
"form_error_password": "Lozinka se ne podudara",
|
||||
"reset_settings": "Poništi postavke",
|
||||
"update_announcement": "AdGuard Home {{version}} je dostupan! <0>Pritisnite ovdje</0> za više informacija.",
|
||||
"setup_guide": "Vodič za postavljanje",
|
||||
"dns_addresses": "DNS adrese",
|
||||
"dns_start": "Pokreće se DNS poslužitelj",
|
||||
"dns_status_error": "Pogreška pri dohvatu statusa DNS poslužitelja",
|
||||
"down": "Ne radi",
|
||||
"fix": "Popravi",
|
||||
"dns_providers": "Ovo je <0>popis poznatih DNS poslužitelja</0> za izbor.",
|
||||
"update_now": "Ažuriraj sada",
|
||||
"update_failed": "Ne uspješno automatsko ažuriranje. Molimo, <a href='https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started#update'>pratite ove korake</a> za ručno ažuriranje.",
|
||||
"processing_update": "Molimo pričekajte, AdGuard Home se ažurira",
|
||||
"clients_title": "Klijenti",
|
||||
"clients_desc": "Postavite uređaje povezane na AdGuard Home",
|
||||
"settings_global": "Globalno",
|
||||
"settings_custom": "Prilagođeno",
|
||||
"table_client": "Klijent",
|
||||
"table_name": "Naziv",
|
||||
"save_btn": "Spremi",
|
||||
"client_add": "Dodaj klijenta",
|
||||
"client_new": "Novi klijent",
|
||||
"client_edit": "Uredi klijenta",
|
||||
"client_identifier": "Identifikator",
|
||||
"ip_address": "IP adresa",
|
||||
"client_identifier_desc": "Klijenti se mogu prepoznati po IP adresi, CIDR-u ili MAC adresi. Imajte na umu da je upotreba MAC-a kao identifikatora, moguća samo ako je AdGuard Home također <0>DHCP poslužitelj</0>",
|
||||
"form_enter_ip": "Unesite IP adresu",
|
||||
"form_enter_mac": "Unesite MAC adresu",
|
||||
"form_enter_id": "Unesi identifikator",
|
||||
"form_add_id": "Dodaj identifikator",
|
||||
"form_client_name": "Unesite naziv klijenta",
|
||||
"client_global_settings": "Koristi globalne postavke",
|
||||
"client_deleted": "Klijent \"{{key}}\" je uspješno uklonjen",
|
||||
"client_added": "Klijent \"{{key}}\" je uspješno dodan",
|
||||
"client_updated": "Klijent \"{{key}}\" je uspješno ažuriran",
|
||||
"clients_not_found": "Nema pronađenih klijenata",
|
||||
"client_confirm_delete": "Jeste li sigurni da želite ukloniti \"{{key}}\" klijenta?",
|
||||
"filter_confirm_delete": "Jeste li sigurni da želite ukloniti ovaj filtar?",
|
||||
"auto_clients_title": "Klijenti (runtime)",
|
||||
"auto_clients_desc": "Podaci na klijentu koji koriste AdGuard Home, ali se ne spremaju u postavke",
|
||||
"access_title": "Postavke pristupa",
|
||||
"access_desc": "Postavite pravila pristupa za AdGuard Home DNS poslužitelj.",
|
||||
"access_allowed_title": "Dopušteni klijenti",
|
||||
"access_allowed_desc": "Popis CIDR-a ili IP adresa. Ukoliko je postavljeno, AdGuard Home će prihvatiti samo zahtjeve s ovih IP adresa.",
|
||||
"access_disallowed_title": "Nedopušteni klijenti",
|
||||
"access_disallowed_desc": "Popis CIDR-a ili IP adresa. Ukoliko je postavljeno, AdGuard Home će zaustaviti zahtjeve s ovih IP adresa.",
|
||||
"access_blocked_title": "Blokirane domene",
|
||||
"access_blocked_desc": "Ne miješajte ovo s filtrima. AdGuard Home će zaustaviti DNS upite s tim ovim domenama u podnesenim upitima.",
|
||||
"access_settings_saved": "Postavke pristupa su uspješno spremljene",
|
||||
"updates_checked": "Uspješna provjera ažuriranja",
|
||||
"updates_version_equal": "AdGuard Home je ažuriran",
|
||||
"check_updates_now": "Provjeri ažuriranja sada",
|
||||
"dns_privacy": "DNS privatnost",
|
||||
"setup_dns_privacy_1": "<0>DNS-over-TLS:</0> Koristite <1>{{address}}</1>.",
|
||||
"setup_dns_privacy_2": "<0>DNS-over-HTTPS:</0> Koristite <1>{{address}}</1>.",
|
||||
"setup_dns_privacy_3": "<0>Imajte na umu da su šifrirani DNS protokoli podržani samo na Androidu 9. Stoga morate instalirati dodatni softver za ostale operativne sustave.</0><0>Evo popisa softvera koji možete koristiti.</0>",
|
||||
"setup_dns_privacy_android_1": "Android 9 nativno podržava DNS-over-TLS. Da biste ga postavili, idite na Postavke → Mreža i internet → Napredno → Privatni DNS i tamo unesite svoje naziv domene.",
|
||||
"setup_dns_privacy_android_2": "<0>AdGuard za Android</0> podržava <1>DNS-over-HTTPS</1> i <1>DNS-over-TLS</1>.",
|
||||
"setup_dns_privacy_android_3": "<0>Intra</0> dodaje <1>DNS-over-HTTPS</1> podršku za Android.",
|
||||
"setup_dns_privacy_ios_1": "<0>DNSCloak</0> podržava <1>DNS-over-HTTPS</1>, ali da biste ga postavili za upotrebu vašeg vlastitog poslužitelja, trebati će te generirati <2>DNS Stamp</2> za njega.",
|
||||
"setup_dns_privacy_ios_2": "<0>AdGuard za iOS</0> podržava <1>DNS-over-HTTPS</1> i <1>DNS-over-TLS</1>.",
|
||||
"setup_dns_privacy_other_title": "Ostale implementacije",
|
||||
"setup_dns_privacy_other_1": "AdGuard Home može poslužiti kao sigurni DNS klijent na svim platformama.",
|
||||
"setup_dns_privacy_other_2": "<0>dnsproxy</0> podržava sve poznate sigurne DNS protokole.",
|
||||
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> podržava <1>DNS-over-HTTPS</1>.",
|
||||
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> podržava <1>DNS-over-HTTPS</1>.",
|
||||
"setup_dns_privacy_other_5": "Možete pronaći više implementacija <0>ovdje</0> i <1>ovdje</1>.",
|
||||
"setup_dns_notice": "Da biste koristili <1>DNS-over-HTTPS</1> ili <1>DNS-over-TLS</1>, morate <0>postaviti šifriranje</0> u AdGuard Home postavkama.",
|
||||
"rewrite_added": "DNS prijepis za \"{{key}}\" je uspješno dodan",
|
||||
"rewrite_deleted": "DNS prijepis za \"{{key}}\" je uspješno uklonjen",
|
||||
"rewrite_add": "Dodaj DNS prijepis",
|
||||
"rewrite_not_found": "Nema DNS prijepisa",
|
||||
"rewrite_confirm_delete": "Jeste li sigurni da želite ukloniti DNS prijepis za \"{{key}}\" klijenta?",
|
||||
"rewrite_desc": "Omogućuje jednostavno postavljanje prilagođenog DNS odgovora za određenu domenu.",
|
||||
"rewrite_applied": "Primijenjena pravila prijepisa",
|
||||
"dns_rewrites": "DNS prijepisi",
|
||||
"form_domain": "Unesite domenu",
|
||||
"form_answer": "Unesite IP adresu ili naziv domene",
|
||||
"form_error_domain_format": "Nevažeći format domene",
|
||||
"form_error_answer_format": "Nevažeći format odgovora",
|
||||
"configure": "Konfiguriraj",
|
||||
"main_settings": "Opće postavke",
|
||||
"block_services": "Blokiraj specifične usluge",
|
||||
"blocked_services": "Blokirane usluge",
|
||||
"blocked_services_desc": "Omogućuje brzo blokiranje popularnih stranica i usluga.",
|
||||
"blocked_services_saved": "Blokirane usluge su uspješno spremljene",
|
||||
"blocked_services_global": "Koristi globalno blokirane usluge",
|
||||
"blocked_service": "Blokirane usluge",
|
||||
"block_all": "Blokiraj sve",
|
||||
"unblock_all": "Odblokiraj sve",
|
||||
"encryption_certificate_path": "Putanja certifikata",
|
||||
"encryption_private_key_path": "Putanja privatnog ključa",
|
||||
"encryption_certificates_source_path": "Dodajte datoteku certifikata",
|
||||
"encryption_certificates_source_content": "Zalijepi sadržaj certifikata",
|
||||
"encryption_key_source_path": "Dodajte datoteku privatnog ključa",
|
||||
"encryption_key_source_content": "Zalijepi sadržaj privatnog ključa",
|
||||
"stats_params": "Postavke statistike",
|
||||
"config_successfully_saved": "Postavke su uspješno spremljene",
|
||||
"interval_24_hour": "24 sata",
|
||||
"interval_days": "{{count}} dan",
|
||||
"interval_days_plural": "{{count}} dana",
|
||||
"domain": "Domena",
|
||||
"answer": "Odgovor",
|
||||
"filter_added_successfully": "Filtar je uspješno dodan",
|
||||
"statistics_configuration": "Postavke statistike",
|
||||
"statistics_retention": "Spremanje statistike",
|
||||
"statistics_retention_desc": "Ako smanjite vrijednost intervala, neki će podaci biti izgubljeni",
|
||||
"statistics_clear": " Poništi statistiku",
|
||||
"statistics_clear_confirm": "Jeste li sigurni da želite poništiti statistiku?",
|
||||
"statistics_retention_confirm": "Jeste li sigurni da želite promijeniti zadržavanje statistike? Ako smanjite vrijednost intervala, neki će podaci biti izgubljeni",
|
||||
"statistics_cleared": "Statistika je uspješno uklonjenja",
|
||||
"interval_hours": "{{count}} sata/i",
|
||||
"interval_hours_plural": "{{count}} sata/i",
|
||||
"filters_configuration": "Postavke filtara",
|
||||
"filters_enable": "Omogući filtre",
|
||||
"filters_interval": "Interval ažuriranja filtara",
|
||||
"disabled": "Onemogućeno",
|
||||
"username_label": "Korisničko ime",
|
||||
"username_placeholder": "Unesite korisničko ime",
|
||||
"password_label": "Lozinka",
|
||||
"password_placeholder": "Unesite lozinku",
|
||||
"sign_in": "Prijava",
|
||||
"sign_out": "Odjava",
|
||||
"forgot_password": "Zaboravljena lozinka?",
|
||||
"forgot_password_desc": "Slijedite <0>ove korake</0> da biste stvorili novu lozinku za svoj korisnički račun.",
|
||||
"location": "Lokacija",
|
||||
"orgname": "Naziv organizacije",
|
||||
"netname": "Naziv mreže",
|
||||
"descr": "Opis",
|
||||
"whois": "Whois",
|
||||
"filtering_rules_learn_more": "<0>Saznajte više</0> o stvaranju vlastitog hosts popisa neželjenih.",
|
||||
"blocked_by_response": "Blokirano od strane CNAME-a ili IP-a u odgovoru"
|
||||
}
|
||||
@@ -17,9 +17,7 @@
|
||||
"dhcp_leases": "DHCP leases",
|
||||
"dhcp_static_leases": "DHCP static leases",
|
||||
"dhcp_leases_not_found": "DHCP lease tidak ditemukan",
|
||||
"dhcp_config_saved": "Pengaturan server DHCP tersimpan",
|
||||
"form_error_required": "Kolom yang harus diisi",
|
||||
"form_error_ip_format": "Format IPv4 tidak valid",
|
||||
"form_error_mac_format": "Format MAC tidak valid",
|
||||
"form_error_positive": "Harus lebih dari 0",
|
||||
"dhcp_form_gateway_input": "IP gateway",
|
||||
@@ -96,14 +94,12 @@
|
||||
"use_adguard_parental": "Gunakan layanan web kontrol orang tua AdGuard",
|
||||
"use_adguard_parental_hint": "AdGuard Home akan mengecek jika domain mengandung materi dewasa. Akan menggunakan API yang ramah privasi yang sama sebagai layanan web keamanan penjelajahan.",
|
||||
"enforce_safe_search": "Paksa penelusuran aman",
|
||||
"enforce_save_search_hint": "AdGuard Home dapat memaksa penelusuran aman pada mesin pencari berikut: Google, Youtube, Bing, dan Yandex.",
|
||||
"no_servers_specified": "Sever tidak disebutkan",
|
||||
"general_settings": "Pengaturan umum",
|
||||
"dns_settings": "Pengaturan DNS",
|
||||
"encryption_settings": "Pengaturan enkripsi",
|
||||
"dhcp_settings": "Pengaturan DHCP",
|
||||
"upstream_dns": "Server DNS hulu",
|
||||
"upstream_dns_hint": "Jika Anda mengosongkan kolom ini, AdGuard Home akan menggunakan <a href='https://1.1.1.1/' target='_blank'>Cloudflare DNS</a> sebagai hulu. Gunakan tls:// untuk server DNS over TLS.",
|
||||
"test_upstream_btn": "Uji hulu",
|
||||
"apply_btn": "Terapkan",
|
||||
"disabled_filtering_toast": "Penyaringan nonaktif",
|
||||
@@ -141,7 +137,6 @@
|
||||
"example_comment": "! Komentar di sini",
|
||||
"example_comment_meaning": "hanya sebuah komentar",
|
||||
"example_comment_hash": "Juga sebuah komentar",
|
||||
"example_regex_meaning": "blokir akses ke domain yang cocok dengan ekspresi reguler yang ditentukan",
|
||||
"example_upstream_regular": "DNS reguler (melalui UDP)",
|
||||
"example_upstream_dot": "terenkripsi <a href='https://en.wikipedia.org/wiki/DNS_over_TLS' target='_blank'>DNS-over-TLS</a>",
|
||||
"example_upstream_doh": "terenkripsi <a href='https://en.wikipedia.org/wiki/DNS_over_HTTPS' target='_blank'>DNS-over-HTTPS</a>",
|
||||
@@ -287,7 +282,6 @@
|
||||
"client_edit": "Ubah Klien",
|
||||
"client_identifier": "Identifikasi",
|
||||
"ip_address": "Alamat IP",
|
||||
"client_identifier_desc": "Klien dapat diidentifikasi dengan alamat IP atau alamat MAC. Harap dicatat bahwa menggunakan MAC sebagai pengidentifikasi hanya dimungkinkan jika AdGuard Home juga merupakan <0>server DHCP</0>",
|
||||
"form_enter_ip": "Masukkan IP",
|
||||
"form_enter_mac": "Masukkan MAC",
|
||||
"form_client_name": "Masukkan nama klien",
|
||||
|
||||
@@ -17,9 +17,7 @@
|
||||
"dhcp_leases": "Leases DHCP",
|
||||
"dhcp_static_leases": "Leases DHCP statici",
|
||||
"dhcp_leases_not_found": "Nessun lease DHCP trovato",
|
||||
"dhcp_config_saved": "Configurazione server DHCP salvata",
|
||||
"form_error_required": "Campo richiesto",
|
||||
"form_error_ip_format": "Formato IPv4 non valido",
|
||||
"form_error_mac_format": "Formato MAC non valido",
|
||||
"form_error_positive": "Deve essere maggiore di 0",
|
||||
"dhcp_form_gateway_input": "IP Gateway",
|
||||
@@ -96,14 +94,12 @@
|
||||
"use_adguard_parental": "Usa il servizio parental control di AdGuard",
|
||||
"use_adguard_parental_hint": "AdGuard Home controllerà se il dominio contiene materiale per adulti. Usa le stesse API privacy-friendly del servizio web browsing security.",
|
||||
"enforce_safe_search": "Forza ricerca sicura",
|
||||
"enforce_save_search_hint": "AdGuard Home può forzare la ricerca sicura sui seguenti motori di ricerca: Google, YouTube, Bing e Yandex",
|
||||
"no_servers_specified": "Nessun server specificato",
|
||||
"general_settings": "Impostazioni generali",
|
||||
"dns_settings": "Impostazioni DNS",
|
||||
"encryption_settings": "Impostazioni di criptazione",
|
||||
"dhcp_settings": "Impostazioni DHCP",
|
||||
"upstream_dns": "Server DNS upstream",
|
||||
"upstream_dns_hint": "Se lasci vuoto questo campo, AdGuard Home imposterà i <a href='https://1.1.1.1/' target='_blank'>DNS di Cloudflare</a> come upstream. Inserisci il prefisso tls:// per i server con DNS over TLS",
|
||||
"test_upstream_btn": "Testa gli upstream",
|
||||
"apply_btn": "Applica",
|
||||
"disabled_filtering_toast": "Disabilita filtri",
|
||||
@@ -141,6 +137,7 @@
|
||||
"example_comment": "! Qui va un commento",
|
||||
"example_comment_meaning": "un commento",
|
||||
"example_comment_hash": "# Un altro commento",
|
||||
"example_regex_meaning": "blocca l'accesso ai domini che corrispondono alla specifica espressione regolare",
|
||||
"example_upstream_regular": "DNS regolari (via UDP)",
|
||||
"example_upstream_dot": "<a href='https://en.wikipedia.org/wiki/DNS_over_TLS' target='_blank'>DNS-over-TLS</a> criptato",
|
||||
"example_upstream_doh": "<a href='https://en.wikipedia.org/wiki/DNS_over_HTTPS' target='_blank'>DNS-over-HTTPS</a> criptato",
|
||||
@@ -172,6 +169,16 @@
|
||||
"rule_removed_from_custom_filtering_toast": "Regola rimossa dalle regole dei filtri personalizzate",
|
||||
"rule_added_to_custom_filtering_toast": "Regola aggiunta alle regole dei filtri personalizzate",
|
||||
"query_log_response_status": "Status: {{value}}",
|
||||
"query_log_filtered": "Filtrato da {{filter}}",
|
||||
"query_log_confirm_clear": "Sei sicuro di voler eliminare la query log?",
|
||||
"query_log_cleared": "La query log è stata cancellata correttamente",
|
||||
"query_log_clear": "Cancella query logs",
|
||||
"query_log_retention": "Ritenzione query logs",
|
||||
"query_log_enable": "Abilita log",
|
||||
"query_log_configuration": "Configurazione logs",
|
||||
"query_log_disabled": "La query log è stata disabilitata e può essere configurata nel <0>impostazioni</0>",
|
||||
"query_log_strict_search": "Utilizzare le virgolette doppie per la ricerca rigorosa",
|
||||
"query_log_retention_confirm": "Sei sicuro di voler modificare il registro di query? Se si diminuisce il valore di intervallo, alcuni dati saranno persi",
|
||||
"source_label": "Fonte",
|
||||
"found_in_known_domain_db": "Trovato nel database dei domini conosciuti.",
|
||||
"category_label": "Categoria",
|
||||
@@ -286,7 +293,6 @@
|
||||
"client_edit": "Modifica Client",
|
||||
"client_identifier": "Identificatore",
|
||||
"ip_address": "Indirizzo IP",
|
||||
"client_identifier_desc": "I client possono essere identificati dall indirizzo IP o dall' indirizzo MAC. Nota che l' utilizzo dell' indirizzo MAC come identificatore è consentito solo se AdGuard Home è anche il <0>server DHCP</0>",
|
||||
"form_enter_ip": "Inserisci IP",
|
||||
"form_enter_mac": "Inserisci MAC",
|
||||
"form_client_name": "Inserisci nome client",
|
||||
@@ -349,11 +355,45 @@
|
||||
"blocked_service": "Servizio bloccato",
|
||||
"block_all": "Blocca tutto",
|
||||
"unblock_all": "Sblocca tutto",
|
||||
"encryption_certificate_path": "Percorso di certificato",
|
||||
"encryption_private_key_path": "Percorso della chiave privata",
|
||||
"encryption_certificates_source_path": "Definisci un percorso alle file dei certificati",
|
||||
"encryption_certificates_source_content": "Incolla i contenuti di certificato",
|
||||
"encryption_key_source_path": "Imposta un file chiave privata",
|
||||
"encryption_key_source_content": "Incolla i contenuti della chiave privata",
|
||||
"stats_params": "Configurazione delle statistiche",
|
||||
"config_successfully_saved": "Configurazione salvata correttamente",
|
||||
"interval_24_hour": "24 ore",
|
||||
"interval_days": "{{count}} giorni",
|
||||
"interval_days_plural": "{{count}} giorni",
|
||||
"domain": "Dominio",
|
||||
"answer": "Risposta",
|
||||
"filter_added_successfully": "Il filtro è stato aggiunto correttamente"
|
||||
"filter_added_successfully": "Il filtro è stato aggiunto correttamente",
|
||||
"statistics_configuration": "Configurazione delle statistiche",
|
||||
"statistics_retention": "Conservazione statistiche",
|
||||
"statistics_retention_desc": "Se si diminuisce il valore di intervallo, alcuni dati saranno persi",
|
||||
"statistics_clear": " Azzera statistiche",
|
||||
"statistics_clear_confirm": "Sei sicuro di voler azzerare le statistiche?",
|
||||
"statistics_retention_confirm": "Sei sicuro di modificare la conservazione statistiche? Se si diminuisce il valore di intervallo, alcuni dati saranno persi",
|
||||
"statistics_cleared": "Statistiche azzerate correttamente",
|
||||
"interval_hours": "{{count}} ora",
|
||||
"interval_hours_plural": "{{count}} ore",
|
||||
"filters_configuration": "Configurazione filtri",
|
||||
"filters_enable": "Abilita i filtri",
|
||||
"filters_interval": "Intervallo aggiornamento filtri",
|
||||
"disabled": "Disabilitato",
|
||||
"username_label": "Nome utente",
|
||||
"username_placeholder": "Inserisci nome utente",
|
||||
"password_label": "Password",
|
||||
"password_placeholder": "Inserisci password",
|
||||
"sign_in": "Accedi",
|
||||
"sign_out": "Esci",
|
||||
"forgot_password": "Hai perso la password?",
|
||||
"forgot_password_desc": "Per favore segui <0>questi punti</0> per creare una nuova password per il tuo account.",
|
||||
"location": "Locazione",
|
||||
"orgname": "Nome dell'organizzazione",
|
||||
"netname": "Nome Network",
|
||||
"descr": "Descrizione",
|
||||
"whois": "Chi è",
|
||||
"filtering_rules_learn_more": "<0>Impara di più</0> come creare i tuoi elenchi di blocco per i hosts."
|
||||
}
|
||||
@@ -17,9 +17,7 @@
|
||||
"dhcp_leases": "DHCP割り当て",
|
||||
"dhcp_static_leases": "DHCP静的割り当て",
|
||||
"dhcp_leases_not_found": "DHCP割当はありません",
|
||||
"dhcp_config_saved": "DHCPサーバの設定を保存しました",
|
||||
"form_error_required": "必須項目",
|
||||
"form_error_ip_format": "IPv4フォーマットではありません",
|
||||
"form_error_mac_format": "MACフォーマットではありません",
|
||||
"form_error_positive": "0より大きい必要があります",
|
||||
"dhcp_form_gateway_input": "ゲートウェイIP",
|
||||
@@ -90,14 +88,12 @@
|
||||
"use_adguard_parental": "AdGuardペアレンタルコントロール・ウェブサービスを使用する",
|
||||
"use_adguard_parental_hint": "AdGuard Homeは、ドメインにアダルトコンテンツが含まれているかどうかを確認します。 ブラウジングセキュリティ・ウェブサービスと同じプライバシーに優しいAPIを使用します。",
|
||||
"enforce_safe_search": "セーフサーチを強制する",
|
||||
"enforce_save_search_hint": "AdGuard Homeは、Google、Youtube、Bing、Yandexの検索エンジンでセーフサーチを強制できます。",
|
||||
"no_servers_specified": "サーバが指定されていません",
|
||||
"general_settings": "一般設定",
|
||||
"dns_settings": "DNS設定",
|
||||
"encryption_settings": "暗号化設定",
|
||||
"dhcp_settings": "DHCP設定",
|
||||
"upstream_dns": "上流DNSサーバ",
|
||||
"upstream_dns_hint": "このフィールドを未入力のままにすると、AdGuard Homeは上流として<a href='https://1.1.1.1/' target='_blank'>Cloudflare DNS</a>を使用します。DNS over TLSサーバには、「tls://」プレフィックスを使用してください。",
|
||||
"test_upstream_btn": "上流サーバをテストする",
|
||||
"apply_btn": "適用する",
|
||||
"disabled_filtering_toast": "フィルタリングを無効にしました",
|
||||
@@ -135,7 +131,6 @@
|
||||
"example_comment": "! ここにはコメントが入ります",
|
||||
"example_comment_meaning": "ただのコメントです",
|
||||
"example_comment_hash": "# ここもコメントです",
|
||||
"example_regex_meaning": "指定の正規表現に一致するドメインへのアクセスをブロックします",
|
||||
"example_upstream_regular": "通常のDNS(UDPでの問い合わせ)",
|
||||
"example_upstream_dot": "暗号化されている <a href='https://en.wikipedia.org/wiki/DNS_over_TLS' target='_blank'>DNS-over-TLS</a>",
|
||||
"example_upstream_doh": "暗号化されている <a href='https://en.wikipedia.org/wiki/DNS_over_HTTPS' target='_blank'>DNS-over-HTTPS</a>",
|
||||
@@ -279,7 +274,6 @@
|
||||
"client_edit": "クライアントの編集",
|
||||
"client_identifier": "識別子",
|
||||
"ip_address": "IPアドレス",
|
||||
"client_identifier_desc": "クライアントはIPアドレスまたはMACアドレスで識別できます。AdGuard Homeが<0>DHCPサーバ</0>でもある場合にのみ、識別子としてMACを使用することが可能であることにご注意ください。",
|
||||
"form_enter_ip": "IPアドレスを入力してください",
|
||||
"form_enter_mac": "MACアドレスを入力してください",
|
||||
"form_client_name": "クライアント名を入力してください",
|
||||
|
||||
@@ -17,9 +17,7 @@
|
||||
"dhcp_leases": "DHCP 임대",
|
||||
"dhcp_static_leases": "DHCP 고정 임대",
|
||||
"dhcp_leases_not_found": "DHCP 임대를 찾을 수 없음",
|
||||
"dhcp_config_saved": "저장된 DHCP 서버 구성",
|
||||
"form_error_required": "필수 필드",
|
||||
"form_error_ip_format": "잘못된 IPv4 형식",
|
||||
"form_error_mac_format": "잘못된 MAC 형식",
|
||||
"form_error_positive": "0보다 커야 합니다",
|
||||
"dhcp_form_gateway_input": "게이트웨이 IP",
|
||||
@@ -96,14 +94,12 @@
|
||||
"use_adguard_parental": "AdGuard 자녀 보호 웹 서비스 사용",
|
||||
"use_adguard_parental_hint": "AdGuard Home은 도메인에 성인 자료가 포함되어 있는지 확인합니다. 브라우징 보안 웹 서비스와 동일한 개인정보 보호 API를 사용함.",
|
||||
"enforce_safe_search": "세이프서치 강제",
|
||||
"enforce_save_search_hint": "AdGuard Home은 다음과 같은 검색엔진(구글, 유투브, 빙, 덕덕고, 얀덱스)에서 안전검색이 가능합니다.",
|
||||
"no_servers_specified": "지정된 서버 없음",
|
||||
"general_settings": "일반 설정",
|
||||
"dns_settings": "DNS 설정",
|
||||
"encryption_settings": "암호화 설정",
|
||||
"dhcp_settings": "DHCP 설정",
|
||||
"upstream_dns": "업스트림 DNS 서버",
|
||||
"upstream_dns_hint": "이 항목을 비워 두면 AdGuard Home에서 <a href='https://1.1.1.1/' target='_blank'> Cloudflare DNS </a>를 업스트림으로 사용합니다.",
|
||||
"test_upstream_btn": "업스트림 테스트",
|
||||
"apply_btn": "적용",
|
||||
"disabled_filtering_toast": "필터링 비활성화됨",
|
||||
@@ -297,7 +293,6 @@
|
||||
"client_edit": "클라이언트 수정",
|
||||
"client_identifier": "식별자",
|
||||
"ip_address": "IP 주소",
|
||||
"client_identifier_desc": "사용자는 IP 주소 또는 MAC 주소로 식별할 수 있지만 AdGuard Home이 <0>DHCP 서버인 </0> 경우에만 사용자는 MAC 주소로 식별할 수 있습니다.",
|
||||
"form_enter_ip": "IP 입력",
|
||||
"form_enter_mac": "MAC 입력",
|
||||
"form_client_name": "클라이언트 이름 입력",
|
||||
@@ -399,5 +394,6 @@
|
||||
"orgname": "단체 이름",
|
||||
"netname": "네트워크 이름",
|
||||
"descr": "설명",
|
||||
"whois": "후이즈"
|
||||
"whois": "후이즈",
|
||||
"filtering_rules_learn_more": "차단 리스트를 직접 호스트하는 법을 <0>알아보세요</0>."
|
||||
}
|
||||
@@ -16,9 +16,9 @@
|
||||
"dhcp_found": "Actieve DHCP server(s) gevonden op het netwerk. het is NIET veilig om de ingebouwde DHCP server in te schakelen.",
|
||||
"dhcp_leases": "DHCP lease overzicht",
|
||||
"dhcp_leases_not_found": "Geen DHCP lease gevonden",
|
||||
"dhcp_config_saved": "DHCP server configuratie opgeslagen",
|
||||
"form_error_required": "Vereist veld",
|
||||
"form_error_ip_format": "Ongeldig IPv4 formaat",
|
||||
"form_error_ip4_format": "Ongeldig IPv4 formaat",
|
||||
"form_error_ip6_format": "Ongeldig IPv6 formaat",
|
||||
"form_error_mac_format": "Ongeldig MAC formaat.",
|
||||
"form_error_positive": "Moet groter zijn dan 0",
|
||||
"dhcp_form_gateway_input": "Gateway IP",
|
||||
@@ -37,6 +37,7 @@
|
||||
"dhcp_new_static_lease": "Voeg static lease toe",
|
||||
"dhcp_static_leases_not_found": "Geen DHCP static lease gevonden",
|
||||
"dhcp_add_static_lease": "Voeg statische lease toe",
|
||||
"dhcp_reset": "Weet je zeker dat je de DHCP configuratie wil resetten?",
|
||||
"delete_confirm": "Ben je zeker dat je \"{{key}}\" wilt verwijderen?",
|
||||
"form_enter_hostname": "Vul hostnaam in",
|
||||
"error_details": "Fout details",
|
||||
@@ -87,15 +88,14 @@
|
||||
"use_adguard_parental": "Gebruik AdGuard Ouderlijk toezicht web service",
|
||||
"use_adguard_parental_hint": "AdGuard Home controleert of het domein 18+ content bevat. Dit gebeurt dmv dezelfde privacy vriendelijke API als de Browsing Security web service.",
|
||||
"enforce_safe_search": "Forceer Veilig Zoeken",
|
||||
"enforce_save_search_hint": "AdGuard Home kan veilig zoeken forceren voor de volgende zoekmachines: Google, Youtube, Bing, en Yandex.",
|
||||
"no_servers_specified": "Geen servers gespecificeerd",
|
||||
"general_settings": "Generieke instellingen",
|
||||
"dns_settings": "DNS Instellingen",
|
||||
"encryption_settings": "Encryptie Instellingen",
|
||||
"dhcp_settings": "DHCP Instellingen",
|
||||
"upstream_dns": "Upstream DNS servers",
|
||||
"upstream_dns_hint": "Indien je dit veld leeg laat, zal AdGuard Home <a href='https://1.1.1.1/' target='_blank'>Cloudflare DNS</a> gebruiken als upstream. Gebruik tls:// voor DNS over TLS servers.",
|
||||
"test_upstream_btn": "Test upstream",
|
||||
"upstreams": "Upstreams",
|
||||
"apply_btn": "Toepassen",
|
||||
"disabled_filtering_toast": "Filters uitgeschakeld",
|
||||
"enabled_filtering_toast": "Filters ingeschakeld",
|
||||
@@ -132,7 +132,6 @@
|
||||
"example_comment": "! Hier komt een opmerking",
|
||||
"example_comment_meaning": "zomaar een opmerking",
|
||||
"example_comment_hash": "# Nog een opmerking",
|
||||
"example_regex_meaning": "blokkeer de toegang tot de domeinen die overeenkomen met de opgegeven reguliere expressie",
|
||||
"example_upstream_regular": "standaard DNS (over UDP)",
|
||||
"example_upstream_dot": "versleutelde<a href='https://en.wikipedia.org/wiki/DNS_over_TLS' target='_blank'>DNS-over-TLS</a>",
|
||||
"example_upstream_doh": "versleutelde<a href='https://en.wikipedia.org/wiki/DNS_over_HTTPS' target='_blank'>DNS-over-HTTPS</a>",
|
||||
@@ -260,6 +259,8 @@
|
||||
"update_announcement": "AdGuard Home{{version}} is nu beschikbaar! <0>klik hier</0> voor meer info.",
|
||||
"setup_guide": "Installatie gids",
|
||||
"dns_addresses": "DNS adressen",
|
||||
"dns_start": "DNS server aan het opstarten",
|
||||
"dns_status_error": "Fout bij het oproepen van de DNS server status",
|
||||
"down": "Uitgeschakeld",
|
||||
"fix": "Los op",
|
||||
"dns_providers": "hier is een <0>lijst of gekende DNS providers</0> waarvan je kan kiezen.",
|
||||
@@ -278,9 +279,10 @@
|
||||
"client_edit": "Wijzig gebruiker",
|
||||
"client_identifier": "Identificeer via",
|
||||
"ip_address": "IP adres",
|
||||
"client_identifier_desc": "Gebruikers kunnen worden geïdentificeerd door het IP-adres of MAC-adres. Houd er rekening mee dat het gebruik van MAC als ID alleen mogelijk is als AdGuard Home ook een <0> DHCP-server </0> is",
|
||||
"form_enter_ip": "Vul IP in",
|
||||
"form_enter_mac": "Vul MAC in",
|
||||
"form_enter_id": "ID invoeren",
|
||||
"form_add_id": "ID toevoegen",
|
||||
"form_client_name": "Vul gebruikersnaam in",
|
||||
"client_global_settings": "Gebruik globale instelling",
|
||||
"client_deleted": "Gebruiker \"{{key}}\" met succes verwijderd",
|
||||
@@ -340,5 +342,6 @@
|
||||
"statistics_retention_desc": "Als je de interval waarde vermindert, zullen sommige gegevens verloren gaan",
|
||||
"statistics_clear": " Statistieken wissen",
|
||||
"statistics_clear_confirm": "Alle statistieken werkelijk wissen?",
|
||||
"statistics_cleared": "Statistieken succesvol gewist"
|
||||
"statistics_cleared": "Statistieken succesvol gewist",
|
||||
"blocked_by_response": "Geblokkeerd door CNAME of IP als antwoord"
|
||||
}
|
||||
403
client/src/__locales/no.json
Normal file
403
client/src/__locales/no.json
Normal file
@@ -0,0 +1,403 @@
|
||||
{
|
||||
"client_settings": "Klientinnstillinger",
|
||||
"example_upstream_reserved": "du kan bestemme en oppstrøms-DNS <0>for et spesifikt domene(r)</0>",
|
||||
"upstream_parallel": "Bruk parallele forespørsler for å få oppfarten på behandlinger, ved å forespørre til alle oppstrømstjenerne samtidig",
|
||||
"bootstrap_dns": "Bootstrap-DNS-tjenere",
|
||||
"bootstrap_dns_desc": "Bootstrap-DNS-tjenere brukes til å oppklare IP-adressene til DoH/DoT-oppklarerene som du har valgt som oppstrømstjenere.",
|
||||
"check_dhcp_servers": "Se etter DHCP-tjenere",
|
||||
"save_config": "Lagre oppsettet",
|
||||
"enabled_dhcp": "DHCP-tjeneren ble skrudd på",
|
||||
"disabled_dhcp": "DHCP-tjeneren ble skrudd av",
|
||||
"dhcp_title": "DHCP-tjener (eksperimentell!)",
|
||||
"dhcp_description": "Dersom ruteren din ikke har DHCP-innstillinger, kan du bruke AdGuard Home sin egen innebygde DHCP-tjener.",
|
||||
"dhcp_enable": "Skru på DHCP-tjeneren",
|
||||
"dhcp_disable": "Skru av DHCP-tjeneren",
|
||||
"dhcp_not_found": "Det er trygt å skru på den innebygde DHCP-tjeneren - vi kunne ikke finne noen aktive DHCP-tjenere i nettverket. Men vi oppfordrer deg til å dobbeltsjekke manuelt, siden vår automatiske test ikke gir 100% sikre svar ennå.",
|
||||
"dhcp_found": "En aktiv DHCP-tjener ble oppdaget i nettverket. Det er ikke trygt å bruke den innebygde DHCP-tjeneren.",
|
||||
"dhcp_leases": "DHCP-leieavtaler",
|
||||
"dhcp_static_leases": "Statiske DHCP-leieavtaler",
|
||||
"dhcp_leases_not_found": "Ingen DHCP-leieavtaler ble funnet",
|
||||
"form_error_required": "Påkrevd felt",
|
||||
"form_error_ip4_format": "Ugyldig IPv4-format",
|
||||
"form_error_ip6_format": "Ugyldig IPv6-format",
|
||||
"form_error_ip_format": "Ugyldig IPv4-format",
|
||||
"form_error_mac_format": "Ugyldig MAC-format",
|
||||
"form_error_positive": "Må være høyere enn 0",
|
||||
"dhcp_form_gateway_input": "Gateway-IP",
|
||||
"dhcp_form_subnet_input": "Nettverksmaske",
|
||||
"dhcp_form_range_title": "Spennvidden til IP-adressene",
|
||||
"dhcp_form_range_start": "Rekkeviddestart",
|
||||
"dhcp_form_range_end": "Rekkeviddeslutt",
|
||||
"dhcp_form_lease_title": "DHCP-leieavtalevarighet (i sekunder)",
|
||||
"dhcp_form_lease_input": "Leieavtalenes varighet",
|
||||
"dhcp_interface_select": "Velg DHCP-grensesnitt",
|
||||
"dhcp_hardware_address": "Maskinvareadresse",
|
||||
"dhcp_ip_addresses": "IP-adresser",
|
||||
"dhcp_table_hostname": "Vertsnavn",
|
||||
"dhcp_table_expires": "Utløper",
|
||||
"dhcp_warning": "Hvis du vil aktivere DHCP-tjeneren likevel, så sørg for at det ikke er noen andre aktive DHCP-tjenere i nettverket ditt. Ellers kan det knekke internettilgangen til tilkoblede enheter!",
|
||||
"dhcp_error": "Vi klarte ikke å fastslå om det er en annen DHCP-tjener i nettverket ditt eller ikke.",
|
||||
"dhcp_static_ip_error": "For å kunne bruke DHCP-tjeneren, må det være satt en statisk IP-adresse. Vi klarte ikke å finne ut om dette nettverksgrensesnittet har blitt satt opp med en statisk IP-adresse. Vennligst sett opp en statisk IP-adresse manuelt.",
|
||||
"dhcp_dynamic_ip_found": "Systemet ditt bruker et oppsett med dynamisk IP-adresse for grensesnittet <0>{{interfaceName}}</0>. For å kunne bruke DHCP-tjeneren, må en statisk IP-adresse ha blitt satt opp. Din nåværende IP-adresse er <0>{{ipAddress}}</0>. Vi vil automatisk gjøre denne IP-adressen statisk hvis du trykker på «Skru på DHCP»-knappen.",
|
||||
"dhcp_lease_added": "Den statiske leieavtalen «{{key}}» ble vellykket lagt til",
|
||||
"dhcp_lease_deleted": "Den statiske leieavtalen «{{key}}» ble vellykket lagt slettet",
|
||||
"dhcp_new_static_lease": "Ny statisk leieavtale",
|
||||
"dhcp_static_leases_not_found": "Ingen statiske DHCP-leieavtaler ble funnet",
|
||||
"dhcp_add_static_lease": "Legg til statisk leieavtale",
|
||||
"delete_confirm": "Er du sikker på at du vil slette «{{key}}»?",
|
||||
"form_enter_hostname": "Skriv inn vertsnavnet",
|
||||
"error_details": "Feildetaljer",
|
||||
"back": "Tilbake",
|
||||
"dashboard": "Kontrollsenter",
|
||||
"settings": "Innstillinger",
|
||||
"filters": "Filtre",
|
||||
"query_log": "Forespørselslogg",
|
||||
"faq": "OSS",
|
||||
"version": "Versjon",
|
||||
"address": "adresse",
|
||||
"on": "PÅ",
|
||||
"off": "AV",
|
||||
"copyright": "Opphavsrett",
|
||||
"homepage": "Hjemmeside",
|
||||
"report_an_issue": "Send inn feilrapport",
|
||||
"privacy_policy": "Personvernretningslinjer",
|
||||
"enable_protection": "Skru på beskyttelse",
|
||||
"enabled_protection": "Beskyttelse ble skrudd på",
|
||||
"disable_protection": "Skru av beskyttelse",
|
||||
"disabled_protection": "Beskyttelsen ble skrudd av",
|
||||
"refresh_statics": "Oppfrisk statistikkene",
|
||||
"dns_query": "DNS-forespørsler",
|
||||
"blocked_by": "<0>Blokkert av filtre</0>",
|
||||
"stats_malware_phishing": "Blokkert skadevare/phishing",
|
||||
"stats_adult": "Blokkerte voksennettsteder",
|
||||
"stats_query_domain": "Mest forespurte domener",
|
||||
"for_last_24_hours": "de siste 24 timene",
|
||||
"for_last_days": "det siste døgnet",
|
||||
"for_last_days_plural": "de siste {{count}} dagene",
|
||||
"no_domains_found": "Ingen domener ble funnet",
|
||||
"requests_count": "Antall forespørsler",
|
||||
"top_blocked_domains": "Mest blokkerte domener",
|
||||
"top_clients": "Vanligste klienter",
|
||||
"no_clients_found": "Ingen klienter ble funnet",
|
||||
"general_statistics": "Generelle statistikker",
|
||||
"number_of_dns_query_days": "Antall DNS-forespørsler som ble behandlet det siste døgnet",
|
||||
"number_of_dns_query_days_plural": "Antall DNS-forespørsler som ble behandlet de siste {{count}} dagene",
|
||||
"number_of_dns_query_24_hours": "Antall DNS-forespørsler som ble behandlet de siste 24 timene",
|
||||
"number_of_dns_query_blocked_24_hours": "Antall DNS-forespørsler som ble blokkert av adblock-filtre, hosts-lister, og domene-lister",
|
||||
"number_of_dns_query_blocked_24_hours_by_sec": "Antall DNS-forespørsler som ble blokkert av AdGuard sin nettlesersikkerhetsmodul",
|
||||
"number_of_dns_query_blocked_24_hours_adult": "Antall voksennettsteder som ble blokkert",
|
||||
"enforced_save_search": "Påtvungede barnevennlige søk",
|
||||
"number_of_dns_query_to_safe_search": "Antall DNS-forespørsler til søkemotorer der \"Safe Search\" ble fremtvunget",
|
||||
"average_processing_time": "Gjennomsnittlig behandlingstid",
|
||||
"average_processing_time_hint": "Gjennomsnittstid for behandling av DNS-forespørsler i millisekunder",
|
||||
"block_domain_use_filters_and_hosts": "Blokker domener ved hjelp av filtre, «hosts»-filer, og rå domener",
|
||||
"filters_block_toggle_hint": "Du kan sette opp blokkeringsoppføringer i <a href='#filters'>Filtre</a>-innstillingene.",
|
||||
"use_adguard_browsing_sec": "Benytt AdGuard sin nettlesersikkerhetstjeneste",
|
||||
"use_adguard_browsing_sec_hint": "AdGuard Home vil sjekke om domenet har blitt svartelistet av nettlesersikkerhetstjenesten. Den vil bruke en privatlivsvennlig søke-API til å utføre sjekken: kun en kort prefiks av domenenavnet med SHA256-salting blir sendt til tjeneren.",
|
||||
"use_adguard_parental": "Benytt AdGuard sin foreldrekontroll-nettjeneste",
|
||||
"use_adguard_parental_hint": "AdGuard Home vil sjekke om domenet inneholder erotisk materiale. Den benytter den samme privatlivsvennlige API-en som nettlesersikkerhetstjenesten.",
|
||||
"enforce_safe_search": "Påtving barnevennlige søk",
|
||||
"enforce_save_search_hint": "AdGuard Home kan fremtvinge \"Safe Search\" i de følgende søkemotorene: Google, YouTube, Bing, DuckDuckGo, og Yandex.",
|
||||
"no_servers_specified": "Ingen tjenere er spesifisert",
|
||||
"general_settings": "Generelle innstillinger",
|
||||
"dns_settings": "DNS-innstillinger",
|
||||
"encryption_settings": "Krypteringsinnstillinger",
|
||||
"dhcp_settings": "DHCP-innstillinger",
|
||||
"upstream_dns": "Oppstrøms-DNS-tjenere",
|
||||
"test_upstream_btn": "Test oppstrømstilkoblinger",
|
||||
"apply_btn": "Benytt",
|
||||
"disabled_filtering_toast": "Skrudde av filtrering",
|
||||
"enabled_filtering_toast": "Skrudde på filtrering",
|
||||
"disabled_safe_browsing_toast": "Skrudde av barnevennlig nettlesing",
|
||||
"enabled_safe_browsing_toast": "Skrudde på barnevennlig nettlesing",
|
||||
"disabled_parental_toast": "Skrudde av foreldrekontroll",
|
||||
"enabled_parental_toast": "Skrudde på foreldrekontroll",
|
||||
"disabled_safe_search_toast": "Skrudde av barnevennlige søk",
|
||||
"enabled_save_search_toast": "Skrudde på barnevennlige søk",
|
||||
"enabled_table_header": "Skrudd på",
|
||||
"name_table_header": "Navn",
|
||||
"filter_url_table_header": "Filterets URL",
|
||||
"rules_count_table_header": "Antall oppføringer",
|
||||
"last_time_updated_table_header": "Senest oppdatert",
|
||||
"actions_table_header": "Handlinger",
|
||||
"edit_table_action": "Rediger",
|
||||
"delete_table_action": "Slett",
|
||||
"filters_and_hosts": "Filtre og «hosts»-lister",
|
||||
"filters_and_hosts_hint": "AdGuard Home forstår grunnleggende adblock-oppføringer, «hosts»-filsyntaks, og domenelister.",
|
||||
"no_filters_added": "Ingen filtre er lagt til",
|
||||
"add_filter_btn": "Legg til et filter",
|
||||
"cancel_btn": "Avbryt",
|
||||
"enter_name_hint": "Skriv inn navn",
|
||||
"enter_url_hint": "Skriv inn nettadresse",
|
||||
"check_updates_btn": "Se etter oppdateringer",
|
||||
"new_filter_btn": "Nytt filterabonnement",
|
||||
"enter_valid_filter_url": "Skriv inn en gyldig URL til et filterlisteabonnement.",
|
||||
"custom_filter_rules": "Selvvalgte filtreringsregler",
|
||||
"custom_filter_rules_hint": "Skriv inn én oppføring per linje. Du kan bruke adblock-oppføringer, «hosts»-filsyntaks, eller rå domener.",
|
||||
"examples_title": "Eksempler",
|
||||
"example_meaning_filter_block": "blokker tilgang til 'example.org'-domenet og alle dens underdomener",
|
||||
"example_meaning_filter_whitelist": "opphev blokkeringen av 'example.org'-domenet og alle dens underdomener",
|
||||
"example_meaning_host_block": "AdGuard Home vil nå videresende 'example.org'-domenet (men ikke dens underdomener) til 127.0.0.1.",
|
||||
"example_comment": "! Her er det en kommentar",
|
||||
"example_comment_meaning": "bare en kommentar",
|
||||
"example_comment_hash": "# Også en kommentar",
|
||||
"example_regex_meaning": "blokker tilgang til domener som samsvarer med den valgte ordinære oppføringen",
|
||||
"example_upstream_regular": "vanlig DNS (over UDP)",
|
||||
"example_upstream_dot": "kryptert <0>DNS-over-TLS</0>",
|
||||
"example_upstream_doh": "kryptert <0>DNS-over-HTTPS</0>",
|
||||
"example_upstream_sdns": "du kan bruke <0>DNS-stempler</0> med <1>DNSCrypt</1> eller <2>DNS-over-HTTPS</2>-behandlere",
|
||||
"example_upstream_tcp": "vanlig DNS (over TCP)",
|
||||
"all_filters_up_to_date_toast": "Alle filterlistene er allerede fullt oppdatert",
|
||||
"updated_upstream_dns_toast": "Oppdaterte oppstrøms-DNS-tjenerne",
|
||||
"dns_test_ok_toast": "De spesifiserte DNS-tjenerne fungerer riktig",
|
||||
"dns_test_not_ok_toast": "Tjeneren «{{key}}» kunne ikke brukes, vennligst dobbeltsjekk at du har skrevet den riktig",
|
||||
"unblock_btn": "Tillat",
|
||||
"block_btn": "Blokker",
|
||||
"time_table_header": "Tidspunkt",
|
||||
"domain_name_table_header": "Domenenavn",
|
||||
"type_table_header": "Type",
|
||||
"response_table_header": "Respons",
|
||||
"client_table_header": "Klient",
|
||||
"empty_response_status": "Tomt innhold",
|
||||
"show_all_filter_type": "Vis alle",
|
||||
"show_filtered_type": "Vis kun filtrerte",
|
||||
"no_logs_found": "Ingen loggføringer ble funnet",
|
||||
"refresh_btn": "Oppfrisk",
|
||||
"previous_btn": "Forrige",
|
||||
"next_btn": "Neste",
|
||||
"loading_table_status": "Laster inn …",
|
||||
"page_table_footer_text": "Side",
|
||||
"of_table_footer_text": "av",
|
||||
"rows_table_footer_text": "rekker",
|
||||
"updated_custom_filtering_toast": "Oppdaterte de selvvalgte filtreringsreglene",
|
||||
"rule_removed_from_custom_filtering_toast": "Oppføringen ble fjernet fra de selvvalgte filtreringsreglene",
|
||||
"rule_added_to_custom_filtering_toast": "Oppføringen ble lagt til i de selvvalgte filtreringsreglene",
|
||||
"query_log_response_status": "Status: {{value}}",
|
||||
"query_log_filtered": "Filtrert av {{filter}}",
|
||||
"query_log_confirm_clear": "Er du sikker på at du vil slette hele forespørselsloggen?",
|
||||
"query_log_cleared": "Forespørselsloggen ble vellykket slettet",
|
||||
"query_log_clear": "Tøm forespørselsloggene",
|
||||
"query_log_retention": "Beholding av forespørselsloggføringene",
|
||||
"query_log_enable": "Skru på loggføring",
|
||||
"query_log_configuration": "Loggføringskonfigurasjon",
|
||||
"query_log_disabled": "Forespørselsloggen er skrudd av og kan bli satt opp i <0>innstillingene</0>",
|
||||
"query_log_strict_search": "Bruk anførselstegn for strenge søk",
|
||||
"query_log_retention_confirm": "Er du sikker på at du vil endre hvor lenge forespørselsloggføringene skal beholdes? Hvis du reduserer den interne verdien, vil noe av dataene gå tapt",
|
||||
"source_label": "Kilde",
|
||||
"found_in_known_domain_db": "Funnet i databasen over kjente domener.",
|
||||
"category_label": "Kategori",
|
||||
"rule_label": "Oppføring",
|
||||
"filter_label": "Filter",
|
||||
"unknown_filter": "Ukjent filter {{filterId}}",
|
||||
"install_welcome_title": "Velkommen til AdGuard Home!",
|
||||
"install_welcome_desc": "AdGuard Home er en nettverksdekkende reklame-og-sporings-blokkerende DNS-tjener. Formålet dens er å la deg styre hele nettverket ditt og alle dine enheter, og den krever ikke at klientene bruker spesifikke programmer.",
|
||||
"install_settings_title": "Admin-nettgrensesnitt",
|
||||
"install_settings_listen": "Lytt til grensesnitt",
|
||||
"install_settings_port": "Port",
|
||||
"install_settings_interface_link": "Ditt AdGuard Home-admin-nettgrensesnitt vil være tilgjengelig på de følgende adressene:",
|
||||
"form_error_port": "Skriv inn en gyldig portverdi",
|
||||
"install_settings_dns": "DNS-tjener",
|
||||
"install_settings_dns_desc": "Du vil måtte sette opp enhetene eller ruteren din(e) til å bruke DNS-tjeneren på disse adressene:",
|
||||
"install_settings_all_interfaces": "Alle grensesnitt",
|
||||
"install_auth_title": "Autentisering",
|
||||
"install_auth_desc": "Det er høyst anbefalt å sette opp passordautentisering på ditt AdGuard Home-adminnettgrensesnitt. Selv om du velger å bare gjøre den tilgjengelig på ditt lokale nettverk, er det fortsatt viktig å beskytte den fra ubegrenset tilgang.",
|
||||
"install_auth_username": "Brukernavn",
|
||||
"install_auth_password": "Passord",
|
||||
"install_auth_confirm": "Bekreft passord",
|
||||
"install_auth_username_enter": "Skriv inn brukernavn",
|
||||
"install_auth_password_enter": "Skriv inn passord",
|
||||
"install_step": "Trinn",
|
||||
"install_devices_title": "Sett opp enhetene dine",
|
||||
"install_devices_desc": "For å begynne å bruke AdGuard Home, må du sette opp enhetene dine til å bruke den.",
|
||||
"install_submit_title": "Gratulerer!",
|
||||
"install_submit_desc": "Oppsettsprosedyren er ferdig, og du er klar til å begynne å bruke AdGuard Home.",
|
||||
"install_devices_router": "Ruter",
|
||||
"install_devices_router_desc": "Dette oppsettet vil automatisk dekke alle enhetene som er koblet til hjemmeruteren din, og du vil ikke måtte sette opp hver av dem manuelt.",
|
||||
"install_devices_address": "AdGuard Home-DNS-tjeneren lytter til de følgende adressene",
|
||||
"install_devices_router_list_1": "Åpne innstillingene til ruteren din. Vanligvis kan du få tilgang til den på nettleseren din gjennom en URL (f.eks. http://192.168.0.1/ eller http://192.168.1.1/). Du kan bli spurt om å skrive inn passordet ditt. Hvis du ikke husker det, kan du som oftest tilbakestille passordet ditt ved å trykke på knapp på selve ruteren. Noen rutere krever et spesifikt program, som i så fall er ment å allerede ha blitt installert på din PC/mobil.",
|
||||
"install_devices_router_list_2": "Finn DHCP-/DNS-innstillingene. Se etter DNS-bokstavene ved siden av et felt som tillater to eller tre sett med sifre, som hver er delt opp i fire grupper på 1-3 sifre.",
|
||||
"install_devices_router_list_3": "Skriv inn din AdGuard Home-tjeners adresser her.",
|
||||
"install_devices_windows_list_1": "Åpne «Kontrollpanel» gjennom Start-menyen eller et Windows-søk.",
|
||||
"install_devices_windows_list_2": "Gå til «Nettverk og internett»-kategorien, og så til «Nettverks- og delingssenter».",
|
||||
"install_devices_windows_list_3": "På den venstre siden av skjermen, finn «Endre innstillinger for nettverkskort» og klikk på den.",
|
||||
"install_devices_windows_list_4": "Velg din aktive tilkobling, høyreklikk på den, og velg «Egenskaper».",
|
||||
"install_devices_windows_list_5": "Finn «Internet Protocol versjon 4 (TCP/IP)» i listen, velg den, og så klikk på «Egenskaper» igjen.",
|
||||
"install_devices_windows_list_6": "Velg «Bruk følgende DNS-serveradresser» og så skriv inn din AdGuard Home-tjeners adresser.",
|
||||
"install_devices_macos_list_1": "Klikk på Apple-ikonet og gå til Systeminnstillinger.",
|
||||
"install_devices_macos_list_2": "Klikk på «Nettverk».",
|
||||
"install_devices_macos_list_3": "Velg den første tilkoblingen i listen din, og klikk på «Avansert».",
|
||||
"install_devices_macos_list_4": "Velg DNS-fanen og skriv inn din AdGuard Home-tjeners adresser der.",
|
||||
"install_devices_android_list_1": "Fra Android-startskjermen, trykk på «Innstillinger».",
|
||||
"install_devices_android_list_2": "Velg «Wi-Fi» i menyen. Skjermen som lister opp alle de tilgjengelige nettverkene vil bli vist (det er umulig å velge selvvalgte DNS-adresser for mobiltilkoblinger uten en DNS-endringsapp).",
|
||||
"install_devices_android_list_3": "Langtrykk på nettverket du er koblet til, og så trykk «Endre nettverket».",
|
||||
"install_devices_android_list_4": "På noen enheter, vil du måtte huke av boksen for Avansert for se flere innstillinger. For å justere dine Android-DNS-innstillinger, vil du måtte endre IP-innstillingene fra DHCP til Statisk.",
|
||||
"install_devices_android_list_5": "Endre de forvalgte 'DNS 1' og 'DNS 2'-verdiene til din AdGuard Home-tjeners adresser.",
|
||||
"install_devices_ios_list_1": "Fra startskjermen, trykk på «Innstillinger».",
|
||||
"install_devices_ios_list_2": "Velg Wi-Fi i den venstre menyen (det er umulig å sette opp DNS for mobildata-nettverk).",
|
||||
"install_devices_ios_list_3": "Trykk på navnet til det nettverket som er aktivt for øyeblikket.",
|
||||
"install_devices_ios_list_4": "I DNS-feltet, skriv inn din AdGuard Home-tjeners adresser.",
|
||||
"get_started": "Kom i gang",
|
||||
"next": "Neste",
|
||||
"open_dashboard": "Åpne kontrollsenteret",
|
||||
"install_saved": "Lagringen var vellykket",
|
||||
"encryption_title": "Kryptering",
|
||||
"encryption_desc": "Krypteringsstøtte (HTTPS/TLS) for både DNS og admin-nettgrensesnittet",
|
||||
"encryption_config_saved": "Krypteringsoppsettet ble lagret",
|
||||
"encryption_server": "Tjenerens navn",
|
||||
"encryption_server_enter": "Skriv inn domenenavnet ditt",
|
||||
"encryption_server_desc": "For å kunne bruke HTTPS, må du skrive inn tjenernavnet som samsvarer med ditt SSL-sertifikat.",
|
||||
"encryption_redirect": "Automatisk omdiriger til HTTPS",
|
||||
"encryption_redirect_desc": "Dersom dette er valgt, vil AdGuard Home automatisk omdirigere deg fra HTTP til HTTPS-adresser.",
|
||||
"encryption_https": "HTTPS-port",
|
||||
"encryption_https_desc": "Dersom HTTPS-porten er satt opp, vil AdGuard Home sitt admin-grensesnitt være tilgjengelig gjennom HTTPS, og vil også sørge for DNS-over-HTTPS på «/dns-query»-plasseringen.",
|
||||
"encryption_dot": "'DNS-over-TLS'-port",
|
||||
"encryption_dot_desc": "Dersom denne porten er satt opp, vil AdGuard Home kjøre en 'DNS-over-TLS'-tjener på denne porten.",
|
||||
"encryption_certificates": "Sertifikater",
|
||||
"encryption_certificates_desc": "For å bruke kryptering, må du skrive inn et gyldig SSL-sertifikatkjede for domenet ditt. Du kan få et gratis sertifikat hos <0>{{link}}</0>, eller kjøpe et fra en av de troverdige sertifikatsautoritetene.",
|
||||
"encryption_certificates_input": "Kopier / lim inn dine PEM-kodede sertifikater her.",
|
||||
"encryption_status": "Status",
|
||||
"encryption_expire": "Utløper",
|
||||
"encryption_key": "Privat nøkkel",
|
||||
"encryption_key_input": "Kopier / lim inn ditt sertifikats PEM-kodede private nøkkel her.",
|
||||
"encryption_enable": "Skru på kryptering (HTTPS, DNS-over-HTTPS, og DNS-over-TLS)",
|
||||
"encryption_enable_desc": "Dersom kryptering er skrudd på, vil AdGuard Home sitt admingrensesnitt virke over HTTPS, og DNS-tjeneren vil lytte etter forespørseler over DNS-over-HTTPS og DNS-over-TLS.",
|
||||
"encryption_chain_valid": "Sertifikatskjeden er gyldig",
|
||||
"encryption_chain_invalid": "Sertifikatskjeden er ugyldig",
|
||||
"encryption_key_valid": "Dette er en gyldig {{type}}-type privat nøkkel",
|
||||
"encryption_key_invalid": "Dette er en ugyldig {{type}}-type privat nøkkel",
|
||||
"encryption_subject": "Tema",
|
||||
"encryption_issuer": "Utsteder",
|
||||
"encryption_hostnames": "Vertsnavn",
|
||||
"encryption_reset": "Er du sikker på at du vil tilbakestille krypteringsinnstillingene?",
|
||||
"topline_expiring_certificate": "Ditt SSL-sertifikat er i ferd med å utløpe. Oppdater <0>Krypteringsinnstillinger</0>.",
|
||||
"topline_expired_certificate": "SSL-sertifikatet har utløpt. Oppdater <0>Krypteringsinnstillinger</0>.",
|
||||
"form_error_port_range": "Skriv inn et portnummer i området 80-65535",
|
||||
"form_error_port_unsafe": "Denne porten er ikke trygg",
|
||||
"form_error_equal": "Burde ikke være de samme",
|
||||
"form_error_password": "Passordet samsvarer ikke",
|
||||
"reset_settings": "Tilbakestill innstillinger",
|
||||
"update_announcement": "AdGuard Home {{version}} er nå tilgjengelig! <0>Klikk her</0> for mere informasjon.",
|
||||
"setup_guide": "Oppsettsveiledning",
|
||||
"dns_addresses": "DNS-adresser",
|
||||
"down": "Nedstrøm",
|
||||
"fix": "Fiks",
|
||||
"dns_providers": "Her er en <0>liste over kjente DNS-leverandører</0> som du kan velge blant.",
|
||||
"update_now": "Oppdater nå",
|
||||
"update_failed": "Auto-oppdatering mislyktes. Vennligst <a href='https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started#update'>følg trinnene</a> for manuelle oppdateringer.",
|
||||
"processing_update": "Vennligst vent, AdGuard Home blir oppdatert",
|
||||
"clients_title": "Klienter",
|
||||
"clients_desc": "Konfigurer enheter som er koblet til AdGuard Home",
|
||||
"settings_global": "Overbestyrt",
|
||||
"settings_custom": "Tilpasset",
|
||||
"table_client": "Klient",
|
||||
"table_name": "Navn",
|
||||
"save_btn": "Lagre",
|
||||
"client_add": "Legg til klient",
|
||||
"client_new": "Ny klient",
|
||||
"client_edit": "Rediger klienten",
|
||||
"client_identifier": "Identifikator",
|
||||
"ip_address": "IP-adresse",
|
||||
"form_enter_ip": "Skriv inn IP",
|
||||
"form_enter_mac": "Skriv inn MAC",
|
||||
"form_client_name": "Skriv inn klientnavnet",
|
||||
"client_global_settings": "Bruk de overbestyrte innstillingene",
|
||||
"client_deleted": "Klienten «{{key}}» ble vellykket slettet",
|
||||
"client_added": "Klienten «{{key}}» ble vellykket lagt til",
|
||||
"client_updated": "Klienten «{{key}}» ble vellykket oppdatert",
|
||||
"clients_not_found": "Ingen klienter ble funnet",
|
||||
"client_confirm_delete": "Er du sikker på at du vil slette klienten «{{key}}»?",
|
||||
"filter_confirm_delete": "Er du sikker på at du vil slette filteret?",
|
||||
"auto_clients_title": "Klienter (kjørende)",
|
||||
"auto_clients_desc": "Loggføring over klientene som bruker AdGuard Home, men som ikke har blitt lagret i oppsettet",
|
||||
"access_title": "Tilgangsinnstillinger",
|
||||
"access_desc": "Her kan du sette opp tilgangsregler for AdGuard Home-DNS-tjeneren.",
|
||||
"access_allowed_title": "Tillatte klienter",
|
||||
"access_allowed_desc": "En liste over CIDR- eller IP-adresser. Dersom dette er satt opp, vil AdGuard Home kun akseptere forespørsler fra disse IP-adressene.",
|
||||
"access_disallowed_title": "Klienter som skal avvises",
|
||||
"access_disallowed_desc": "En liste over CIDR- eller IP-adresser. Dersom dette er satt opp, vil AdGuard Home avslå forespørsler fra disse IP-adressene.",
|
||||
"access_blocked_title": "Blokkerte domener",
|
||||
"access_blocked_desc": "Ikke forveksle dette med filtre. AdGuard Home vil nekte å behandle DNS-forespørsler som har disse domenene i forespørselene.",
|
||||
"access_settings_saved": "Tilgangsinnstillingene ble vellykket lagret",
|
||||
"updates_checked": "Oppdateringene ble vellykket sett etter",
|
||||
"updates_version_equal": "AdGuard Home er fullt oppdatert",
|
||||
"check_updates_now": "Se etter oppdateringer nå",
|
||||
"dns_privacy": "DNS-privatliv",
|
||||
"setup_dns_privacy_1": "<0>DNS-over-TLS:</0> Benytt <1>{{address}}</1>-strengen.",
|
||||
"setup_dns_privacy_2": "<0>DNS-over-HTTPS:</0> Benytt <1>{{address}}</1>-strengen.",
|
||||
"setup_dns_privacy_3": "<0>Vennligst bemerk at krypterte DNS-protokoller bare er støttet på Android 9. Så du må installere ytterligere programvare for andre operativsystemer.</0><0>Her er en liste over programvare som du kan bruke.</0>",
|
||||
"setup_dns_privacy_android_1": "Android 9 har innebygd støtte for DNS-over-TLS. For å sette det opp, gå til Innstillinger → Nettverk og internett → Avansert → Privat DNS, og skriv inn domenenavnet ditt der.",
|
||||
"setup_dns_privacy_android_2": "<0>AdGuard for Android</0> støtter <1>DNS-over-HTTPS</1> og <1>DNS-over-TLS</1>.",
|
||||
"setup_dns_privacy_android_3": "<0>Intra</0> legger til <1>DNS-over-HTTPS</1>-støtte i Android.",
|
||||
"setup_dns_privacy_ios_1": "<0>DNSCloak</0> støtter <1>DNS-over-HTTPS</1>, men for å sette det opp til å bruke din egen tjener, vil du måtte generere et <2>DNS-stempel</2> for det.",
|
||||
"setup_dns_privacy_ios_2": "<0>AdGuard for iOS</0> støtter <1>DNS-over-HTTPS</1>- og <1>DNS-over-TLS</1>-oppsett.",
|
||||
"setup_dns_privacy_other_title": "Andre implementeringer",
|
||||
"setup_dns_privacy_other_1": "AdGuard Home i seg selv kan brukes som en sikker DNS-klient for enhver plattform.",
|
||||
"setup_dns_privacy_other_2": "<0>dnsproxy</0> støtter alle kjente sikre DNS-protokoller.",
|
||||
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> støtter <1>DNS-over-HTTPS</1>.",
|
||||
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> støtter <1>DNS-over-HTTPS</1>.",
|
||||
"setup_dns_privacy_other_5": "Du finner flere implementeringer <0>her</0> og <1>her</1>.",
|
||||
"setup_dns_notice": "For å benytte <1>DNS-over-HTTPS</1> eller <1>DNS-over-TLS</1>, må du <0>sette opp Kryptering</0> i AdGuard Home-innstillingene.",
|
||||
"rewrite_added": "DNS-omdirigeringen for «{{key}}» ble vellykket lagt til",
|
||||
"rewrite_deleted": "DNS-omdirigeringen for «{{key}}» ble vellykket slettet",
|
||||
"rewrite_add": "Legg til DNS-omdirigering",
|
||||
"rewrite_not_found": "Ingen DNS-omdirigeringer ble funnet",
|
||||
"rewrite_confirm_delete": "Er du sikker på at du vil slette DNS-omdirigeringen for «{{key}}»?",
|
||||
"rewrite_desc": "Lar deg enkelt konfigurere selvvalgte DNS-tilbakemeldinger for et spesifikt domenenavn.",
|
||||
"rewrite_applied": "Benyttet omdirigeringsregelen",
|
||||
"dns_rewrites": "DNS-omdirigeringer",
|
||||
"form_domain": "Skriv inn domene",
|
||||
"form_answer": "Skriv inn IP-adresse eller domenenavn",
|
||||
"form_error_domain_format": "Ugyldig domeneformat",
|
||||
"form_error_answer_format": "Ugyldig svarformat",
|
||||
"configure": "Sett opp",
|
||||
"main_settings": "Hovedinnstillinger",
|
||||
"block_services": "Blokker spesifikke tjenester",
|
||||
"blocked_services": "Blokkerte tjenester",
|
||||
"blocked_services_desc": "Gjør det mulig å blokkere populære nettsteder og tjenester med letthet.",
|
||||
"blocked_services_saved": "Tjenesteblokkeringene ble vellykket lagret",
|
||||
"blocked_services_global": "Bruk de overbestyrt blokkerte tjenestene",
|
||||
"blocked_service": "Blokkert tjeneste",
|
||||
"block_all": "Blokker alt",
|
||||
"unblock_all": "Tillat alt",
|
||||
"encryption_certificate_path": "Filbanen til sertifikatet",
|
||||
"encryption_private_key_path": "Filbanen til den private nøkkelen",
|
||||
"encryption_certificates_source_path": "Bestem en filbane for sertifikater",
|
||||
"encryption_certificates_source_content": "Lim inn innholdet til sertifikatet",
|
||||
"encryption_key_source_path": "Bestem en privat nøkkelfil",
|
||||
"encryption_key_source_content": "Lim inn innholdet til den private nøkkelen",
|
||||
"stats_params": "Statistikk-oppsett",
|
||||
"config_successfully_saved": "Oppsettet ble vellykket lagret",
|
||||
"interval_24_hour": "24 timer",
|
||||
"interval_days": "{{count}} dag",
|
||||
"interval_days_plural": "{{count}} dager",
|
||||
"domain": "Domene",
|
||||
"answer": "Svar",
|
||||
"filter_added_successfully": "Filteret har blitt vellykket lagt til",
|
||||
"statistics_configuration": "Statistikk-oppsett",
|
||||
"statistics_retention": "Statistikkbeholding",
|
||||
"statistics_retention_desc": "Hvis du reduserer intervallverdien, vil noen av dataene gå tapt",
|
||||
"statistics_clear": " Tøm statistikkene",
|
||||
"statistics_clear_confirm": "Er du sikker på at du vil slette statistikkene?",
|
||||
"statistics_retention_confirm": "Er du sikker på at du vil endre hvor lenge statistikkene skal beholdes? Hvis du reduserer den interne verdien, vil noe av dataene gå tapt",
|
||||
"statistics_cleared": "Statistikkene ble vellykket tømt",
|
||||
"interval_hours": "{{count}} time",
|
||||
"interval_hours_plural": "{{count}} timer",
|
||||
"filters_configuration": "Oppsett av filtre",
|
||||
"filters_enable": "Skru på filtre",
|
||||
"filters_interval": "Filteroppdateringsvanlighet",
|
||||
"disabled": "Skrudd av",
|
||||
"username_label": "Brukernavn",
|
||||
"username_placeholder": "Skriv inn brukernacvn",
|
||||
"password_label": "Passord",
|
||||
"password_placeholder": "Skriv inn passord",
|
||||
"sign_in": "Logg på",
|
||||
"sign_out": "Logg av",
|
||||
"forgot_password": "Har du glemt passordet?",
|
||||
"forgot_password_desc": "Vennligst følg <0>disse trinnene</0> for å lage et nytt passord til brukerkontoen din.",
|
||||
"location": "Posisjon",
|
||||
"orgname": "Firmanavn",
|
||||
"netname": "Nettverksnavn",
|
||||
"descr": "Beskrivelse",
|
||||
"whois": "Whois",
|
||||
"filtering_rules_learn_more": "<0>Lær mer</0> om å lage dine egne filterlister for AdGuard Home."
|
||||
}
|
||||
@@ -17,9 +17,7 @@
|
||||
"dhcp_leases": "Dzierżawa DHCP",
|
||||
"dhcp_static_leases": "Dzierżawy statyczne DHCP",
|
||||
"dhcp_leases_not_found": "Nie znaleziono dzierżaw DHCP",
|
||||
"dhcp_config_saved": "Zapisana konfiguracja serwera DHCP",
|
||||
"form_error_required": "Pole wymagane",
|
||||
"form_error_ip_format": "Nieprawidłowy format IPv4",
|
||||
"form_error_mac_format": "Nieprawidłowy format MAC",
|
||||
"form_error_positive": "Musi być większa niż 0",
|
||||
"dhcp_form_gateway_input": "Adres IP bramy",
|
||||
@@ -96,14 +94,12 @@
|
||||
"use_adguard_parental": "Użyj usługi Kontrola Rodzicielska AdGuard",
|
||||
"use_adguard_parental_hint": "AdGuard Home sprawdzi, czy domena zawiera materiały dla dorosłych. Używa tego samego interfejsu API przyjaznego prywatności, co usługa sieciowa Bezpieczne Przeglądanie. ",
|
||||
"enforce_safe_search": "Wymuszaj bezpieczne wyszukiwanie",
|
||||
"enforce_save_search_hint": "AdGuard Home może wymusić bezpieczne wyszukiwanie w następujących wyszukiwarkach: Google, Youtube, Bing i Yandex.",
|
||||
"no_servers_specified": "Nie określono serwerów",
|
||||
"general_settings": "Ustawienia główne",
|
||||
"dns_settings": "Ustawienia DNS",
|
||||
"encryption_settings": "Ustawienia szyfrowania",
|
||||
"dhcp_settings": "Ustawienia DHCP",
|
||||
"upstream_dns": "Serwery DNS z wyższego poziomu",
|
||||
"upstream_dns_hint": "Jeśli pozostawisz te pole puste, AdGuard użyje <a href='https://1.1.1.1/' target='_blank'>Cloudflare DNS</a> jako upstream. Użyj prefiksu tls:// dla serwera DNS over TLS.",
|
||||
"test_upstream_btn": "Test upstreamów",
|
||||
"apply_btn": "Zastosuj",
|
||||
"disabled_filtering_toast": "Wyłączone filtrowanie",
|
||||
@@ -141,7 +137,6 @@
|
||||
"example_comment": "! Tutaj idzie komentarz",
|
||||
"example_comment_meaning": "komentarz",
|
||||
"example_comment_hash": "# Również komentarz",
|
||||
"example_regex_meaning": "blokuj dostęp do domen pasujących do określonego wyrażenia regularnego",
|
||||
"example_upstream_regular": "normalny DNS (przez UDP)",
|
||||
"example_upstream_dot": "zaszyfrowany <a href='https://en.wikipedia.org/wiki/DNS_over_TLS' target='_blank'>DNS-przez-TLS</a>",
|
||||
"example_upstream_doh": "zaszyfrowany <a href='https://en.wikipedia.org/wiki/DNS_over_HTTPS' target='_blank'>DNS-przez-HTTPS</a>",
|
||||
@@ -287,7 +282,6 @@
|
||||
"client_edit": "Edytuj klienta",
|
||||
"client_identifier": "Identyfikator",
|
||||
"ip_address": "Adres IP",
|
||||
"client_identifier_desc": "Klientów można zidentyfikować po adresie IP lub adresie MAC. Należy pamiętać, że używanie MAC jako identyfikatora jest możliwe tylko wtedy, gdy AdGuard Home jest również <0>serwerem DHCP</0>",
|
||||
"form_enter_ip": "Wpisz adres IP",
|
||||
"form_enter_mac": "Wpisz adres MAC",
|
||||
"form_client_name": "Wpisz nazwę klienta",
|
||||
|
||||
@@ -17,9 +17,11 @@
|
||||
"dhcp_leases": "Concessões DHCP",
|
||||
"dhcp_static_leases": "Concessões de DHCP estático",
|
||||
"dhcp_leases_not_found": "Nenhuma concessão DHCP encontrada",
|
||||
"dhcp_config_saved": "Salvar configurações do servidor DHCP",
|
||||
"dhcp_config_saved": "Configurações DHCP salvas com sucesso",
|
||||
"form_error_required": "Campo obrigatório",
|
||||
"form_error_ip_format": "formato de endereço IPv4 inválido",
|
||||
"form_error_ip4_format": "Formato de endereço IPv4 inválido",
|
||||
"form_error_ip6_format": "Formato de endereço IPv6 inválido",
|
||||
"form_error_ip_format": "Formato de endereço IPv inválido",
|
||||
"form_error_mac_format": "Formato do endereço MAC inválido",
|
||||
"form_error_positive": "Deve ser maior que 0",
|
||||
"dhcp_form_gateway_input": "IP do gateway",
|
||||
@@ -43,6 +45,7 @@
|
||||
"dhcp_new_static_lease": "Nova concessão estática",
|
||||
"dhcp_static_leases_not_found": "Nenhuma concessão DHCP estática foi encontrada",
|
||||
"dhcp_add_static_lease": "Adicionar nova concessão estática",
|
||||
"dhcp_reset": "Você tem certeza de que deseja redefinir a configuração DHCP?",
|
||||
"delete_confirm": "Você tem certeza de que deseja excluir \"{{key}}\"?",
|
||||
"form_enter_hostname": "Digite o hostname",
|
||||
"error_details": "Detalhes do erro",
|
||||
@@ -96,15 +99,16 @@
|
||||
"use_adguard_parental": "Usar o serviço de controle parental do AdGuard",
|
||||
"use_adguard_parental_hint": "O AdGuard Home irá verificar se o domínio contém conteúdo adulto. Ele usa a mesma API amigável de privacidade que o serviço de segurança da navegação.",
|
||||
"enforce_safe_search": "Forçar pesquisa segura",
|
||||
"enforce_save_search_hint": "O AdGuard Home pode forçar a pesquisa segura nos seguintes motores de busca: Google, Youtube, Bing e Yandex.",
|
||||
"enforce_save_search_hint": "O AdGuard Home pode forçar a pesquisa segura nos seguintes motores de busca: Google, YouTube, Bing, DuckDuckGo, Yandex, Pixabay.",
|
||||
"no_servers_specified": "Nenhum servidor especificado",
|
||||
"general_settings": "Configurações gerais",
|
||||
"dns_settings": "Configurações de DNS",
|
||||
"encryption_settings": "Configurações de criptografia",
|
||||
"dhcp_settings": "Configurações de DHCP",
|
||||
"upstream_dns": "Servidores DNS upstream",
|
||||
"upstream_dns_hint": "Se você deixar este campo vazio, o AdGuard Home irá usar o<a href='https://1.1.1.1/' target='_blank'>DNS da Cloudflare</a> como upstream.",
|
||||
"upstream_dns_hint": "Se você deixar este campo em branco, o AdGuard Home irá usar o <a href='https://www.quad9.net/' target='_blank'>Quad9</a> como upstream.",
|
||||
"test_upstream_btn": "Testar upstreams",
|
||||
"upstreams": "Upstreams",
|
||||
"apply_btn": "Aplicar",
|
||||
"disabled_filtering_toast": "Filtragem desativada",
|
||||
"enabled_filtering_toast": "Filtragem ativada",
|
||||
@@ -279,6 +283,8 @@
|
||||
"update_announcement": "AdGuard Home {{version}} está disponível!<0>Clique aqui</0> para mais informações.",
|
||||
"setup_guide": "Guia de configuração",
|
||||
"dns_addresses": "Endereços DNS",
|
||||
"dns_start": "O servidor DNS está iniciando",
|
||||
"dns_status_error": "Ocorreu um erro ao obter o status do servidor DNS",
|
||||
"down": "Caiu",
|
||||
"fix": "Corrigido",
|
||||
"dns_providers": "Aqui está uma <0>lista de provedores de DNS conhecidos</0> para escolher.",
|
||||
@@ -297,9 +303,11 @@
|
||||
"client_edit": "Editar cliente",
|
||||
"client_identifier": "Identificador",
|
||||
"ip_address": "Endereço de IP",
|
||||
"client_identifier_desc": "Os clientes podem ser identificados pelo endereço de IP ou pelo endereço MAC. Observe que o uso do endereço MAC como identificador só é possível se o AdGuard Home também for um <0>servidor DHCP</0>",
|
||||
"client_identifier_desc": "Clientes podem ser identificados pelo endereço de IP ou pelo endereço MAC. Observe que o uso do endereço MAC como identificador só é possível se o AdGuard Home também for um <0>servidor DHCP</0>",
|
||||
"form_enter_ip": "Digite o endereço de IP",
|
||||
"form_enter_mac": "Digite o endereço MAC",
|
||||
"form_enter_id": "Inserir identificador",
|
||||
"form_add_id": "Adicionar identificador",
|
||||
"form_client_name": "Digite o nome do cliente",
|
||||
"client_global_settings": "Usar configurações global",
|
||||
"client_deleted": "Cliente \"{{key}}\" excluído com sucesso",
|
||||
@@ -400,5 +408,6 @@
|
||||
"netname": "Nome da rede",
|
||||
"descr": "Descrição",
|
||||
"whois": "Whois",
|
||||
"filtering_rules_learn_more": "<0>Saiba mais</0> sobre como criar as suas próprias listas negras de servidores."
|
||||
"filtering_rules_learn_more": "<0>Saiba mais</0> sobre como criar as suas próprias listas negras de servidores.",
|
||||
"blocked_by_response": "Bloqueado por CNAME ou IP na resposta"
|
||||
}
|
||||
@@ -17,8 +17,10 @@
|
||||
"dhcp_leases": "Concessões DHCP",
|
||||
"dhcp_static_leases": "Concessões de DHCP estático",
|
||||
"dhcp_leases_not_found": "Nenhuma concessão DHCP encontrada",
|
||||
"dhcp_config_saved": "Guardar configurações do servidor DHCP",
|
||||
"dhcp_config_saved": "Configurações DHCP guardadas com sucesso",
|
||||
"form_error_required": "Campo obrigatório",
|
||||
"form_error_ip4_format": "Formato de endereço IPv4 inválido",
|
||||
"form_error_ip6_format": "Formato de endereço IPv6 inválido",
|
||||
"form_error_ip_format": "Formato de endereço IPv4 inválido",
|
||||
"form_error_mac_format": "Formato do endereço MAC inválido",
|
||||
"form_error_positive": "Deve ser maior que 0",
|
||||
@@ -43,6 +45,7 @@
|
||||
"dhcp_new_static_lease": "Nova concessão estática",
|
||||
"dhcp_static_leases_not_found": "Nenhuma concessão DHCP estática foi encontrada",
|
||||
"dhcp_add_static_lease": "Adicionar nova concessão estática",
|
||||
"dhcp_reset": "Tem a certeza de que deseja redefinir a configuração DHCP?",
|
||||
"delete_confirm": "Tem a certeza de que deseja excluir \"{{key}}\"?",
|
||||
"form_enter_hostname": "Insira o hostname",
|
||||
"error_details": "Detalhes do erro",
|
||||
@@ -96,15 +99,16 @@
|
||||
"use_adguard_parental": "Usar o serviço de controlo parental do AdGuard",
|
||||
"use_adguard_parental_hint": "O AdGuard Home irá verificar se o domínio contém conteúdo adulto. Usa a mesma API amigável de privacidade que o serviço de segurança da navegação.",
|
||||
"enforce_safe_search": "Forçar pesquisa segura",
|
||||
"enforce_save_search_hint": "O AdGuard Home pode forçar a pesquisa segura nos seguintes motores de busca: Google, Youtube, Bing e Yandex.",
|
||||
"enforce_save_search_hint": "O AdGuard Home pode forçar a pesquisa segura nos seguintes motores de busca: Google, Youtube, Bing, DuckDuckGo, Yandex, Pixabay.",
|
||||
"no_servers_specified": "Nenhum servidor especificado",
|
||||
"general_settings": "Definições gerais",
|
||||
"dns_settings": "Definições de DNS",
|
||||
"encryption_settings": "Configurações de criptografia",
|
||||
"dhcp_settings": "Configurações de DHCP",
|
||||
"upstream_dns": "Servidores DNS upstream",
|
||||
"upstream_dns_hint": "Se deixar este campo vazio, o AdGuard Home irá usar o<a href='https://1.1.1.1/' target='_blank'>DNS da Cloudflare</a> como upstream.",
|
||||
"upstream_dns_hint": "Se deixar este campo vazio, o AdGuard Home irá usar <a href='https://www.quad9.net/' target='_blank'>Quad9</a> como upstream.",
|
||||
"test_upstream_btn": "Testar upstreams",
|
||||
"upstreams": "Upstreams",
|
||||
"apply_btn": "Aplicar",
|
||||
"disabled_filtering_toast": "Filtragem desactivada",
|
||||
"enabled_filtering_toast": "Filtragem activada",
|
||||
@@ -279,6 +283,8 @@
|
||||
"update_announcement": "AdGuard Home {{version}} está disponível!<0>Clique aqui</0> para mais informações.",
|
||||
"setup_guide": "Guia de instalação",
|
||||
"dns_addresses": "Endereços DNS",
|
||||
"dns_start": "O servidor DNS está a iniciar",
|
||||
"dns_status_error": "Erro ao obter o estado do servidor DNS",
|
||||
"down": "Caiu",
|
||||
"fix": "Corrigido",
|
||||
"dns_providers": "Aqui está uma <0>lista de provedores de DNS conhecidos</0> para escolher.",
|
||||
@@ -297,9 +303,11 @@
|
||||
"client_edit": "Editar cliente",
|
||||
"client_identifier": "Identificador",
|
||||
"ip_address": "Endereço de IP",
|
||||
"client_identifier_desc": "Os clientes podem ser identificados pelo endereço de IP ou pelo endereço MAC. Observe que o uso do endereço MAC como identificador só é possível se o AdGuard Home também for um <0>servidor DHCP</0>",
|
||||
"client_identifier_desc": "Os clientes podem ser identificados pelo endereço de IP, CIDR, ou pelo endereço MAC. Observe que o uso do endereço MAC como identificador só é possível se o AdGuard Home também for um <0>servidor DHCP</0>",
|
||||
"form_enter_ip": "Insira IP",
|
||||
"form_enter_mac": "Insira o endereço MAC",
|
||||
"form_enter_id": "Inserir identificador",
|
||||
"form_add_id": "Adicionar identificador",
|
||||
"form_client_name": "Insira o nome do cliente",
|
||||
"client_global_settings": "Usar configurações globais",
|
||||
"client_deleted": "Cliente \"{{key}}\" excluído com sucesso",
|
||||
@@ -400,5 +408,6 @@
|
||||
"netname": "Nome da rede",
|
||||
"descr": "Descrição",
|
||||
"whois": "Whois",
|
||||
"filtering_rules_learn_more": "<0>Saiba mais</0>sobre como criar as suas próprias listas negras de servidores."
|
||||
"filtering_rules_learn_more": "<0>Saiba mais</0>sobre como criar as suas próprias listas negras de servidores.",
|
||||
"blocked_by_response": "Bloqueado por CNAME ou IP em resposta"
|
||||
}
|
||||
@@ -17,11 +17,14 @@
|
||||
"dhcp_leases": "Аренда DHCP",
|
||||
"dhcp_static_leases": "Статические аренды DHCP",
|
||||
"dhcp_leases_not_found": "Аренда DHCP не обнаружена",
|
||||
"dhcp_config_saved": "Сохраненная конфигурация DHCP-сервера",
|
||||
"dhcp_config_saved": "Конфигурация DHCP-сервера успешно сохранена",
|
||||
"form_error_required": "Обязательное поле",
|
||||
"form_error_ip_format": "Неверный формат IPv4",
|
||||
"form_error_ip4_format": "Неверный формат IPv4",
|
||||
"form_error_ip6_format": "Неверный формат IPv6",
|
||||
"form_error_ip_format": "Неверный формат IP-адреса",
|
||||
"form_error_mac_format": "Некорректный формат MAC",
|
||||
"form_error_positive": "Должно быть больше 0",
|
||||
"form_error_negative": "Должно быть не меньше 0",
|
||||
"dhcp_form_gateway_input": "IP-адрес шлюза",
|
||||
"dhcp_form_subnet_input": "Маска подсети",
|
||||
"dhcp_form_range_title": "Диапазон IP-адресов",
|
||||
@@ -43,6 +46,7 @@
|
||||
"dhcp_new_static_lease": "Новая статическая аренда",
|
||||
"dhcp_static_leases_not_found": "Не найдено статических аренд DHCP",
|
||||
"dhcp_add_static_lease": "Добавить статическую аренду",
|
||||
"dhcp_reset": "Вы уверены, что хотите сбросить настройки DHCP?",
|
||||
"delete_confirm": "Are you sure you want to delete \"{{key}}\"?",
|
||||
"form_enter_hostname": "Введите имя хоста",
|
||||
"error_details": "Детализация ошибки",
|
||||
@@ -92,14 +96,14 @@
|
||||
"use_adguard_parental": "Используйте модуль Родительского контроля AdGuard ",
|
||||
"use_adguard_parental_hint": "AdGuard Home проверит, содержит ли домен материалы 18+. Он использует тот же API для обеспечения конфиденциальности, что и веб-служба безопасности браузера.",
|
||||
"enforce_safe_search": "Усилить безопасный поиск",
|
||||
"enforce_save_search_hint": "AdGuard Home может обеспечить безопасный поиск в следующих системах: Google, Youtube, Bing и Yandex.",
|
||||
"enforce_save_search_hint": "AdGuard Home может обеспечить безопасный поиск в следующих поисковых системах: Google, Youtube, Bing, DuckDuckGo, Yandex и Pixabay.",
|
||||
"no_servers_specified": "Нет указанных серверов",
|
||||
"general_settings": "Основные настройки",
|
||||
"dns_settings": "Настройки DNS",
|
||||
"encryption_settings": "Настройки шифрования",
|
||||
"dhcp_settings": "Настройки DHCP",
|
||||
"upstream_dns": "Upstream DNS-серверы",
|
||||
"upstream_dns_hint": "Если вы оставите это поле пустым, то AdGuard Home использует <a href='https://1.1.1.1/' target='_blank'>Cloudflare DNS</a> в качестве upstream. Используйте tls:// для DNS через серверы TLS.",
|
||||
"upstream_dns_hint": "Если вы оставите это поле пустым, то AdGuard Home использует <a href='https://www.quad9.net/' target='_blank'>Quad9</a> в качестве DNS сервера.",
|
||||
"test_upstream_btn": "Тест upstream серверов",
|
||||
"apply_btn": "Применить",
|
||||
"disabled_filtering_toast": "Фильтрация выкл.",
|
||||
@@ -179,6 +183,22 @@
|
||||
"query_log_disabled": "Журнал запросов выключен, его можно включить в <0>настройках</0>",
|
||||
"query_log_strict_search": "Используйте двойные кавычки для строгого поиска",
|
||||
"query_log_retention_confirm": "Вы уверены, что хотите изменить срок хранения запросов? При сокращении интервала данные могут быть утеряны",
|
||||
"dns_config": "Настройки DNS-сервера",
|
||||
"blocking_mode": "Режим блокировки",
|
||||
"nxdomain": "NXDOMAIN",
|
||||
"null_ip": "Нулевой IP",
|
||||
"custom_ip": "Свой IP",
|
||||
"blocking_ipv4": "Блокировка IPv4",
|
||||
"blocking_ipv6": "Блокировка IPv6",
|
||||
"form_enter_rate_limit": "Введите rate limit",
|
||||
"rate_limit": "Rate limit",
|
||||
"edns_enable": "Включить отправку EDNS Client Subnet",
|
||||
"edns_cs_desc": "Если включить эту опцию, AdGuard Home будет отправлять подсети клиентов на DNS-сервера.",
|
||||
"rate_limit_desc": "Ограничение на количество запросов в секунду для каждого клиента (0 — неограниченно)",
|
||||
"blocking_ipv4_desc": "IP-адрес, возвращаемый при блокировке A-запроса",
|
||||
"blocking_ipv6_desc": "IP-адрес, возвращаемый при блокировке AAAA-запроса",
|
||||
"blocking_mode_desc": "<0>«NXDOMAIN» — посылать в ответ код NXDOMAIN;</0><0>«Нулевой IP» — посылать в ответ нулевой IP-адрес (0.0.0.0 для A-запроса, :: для АААА-запроса);</0><0>«Свой IP» — посылать в ответ указанный IP-адрес.</0>",
|
||||
"upstream_dns_client_desc": "Если оставить поле пустым, AdGuard Home будет обращаться к серверам, указанным в <0>настройках DNS</0>.",
|
||||
"source_label": "Источник",
|
||||
"found_in_known_domain_db": "Найден в базе известных доменов.",
|
||||
"category_label": "Категория",
|
||||
@@ -275,6 +295,8 @@
|
||||
"update_announcement": "AdGuard Home {{version}} уже доступна! <0>Нажмите сюда</0>, чтобы узнать больше.",
|
||||
"setup_guide": "Инструкция по настройке",
|
||||
"dns_addresses": "Адреса DNS",
|
||||
"dns_start": "DNS-сервер запускается",
|
||||
"dns_status_error": "Ошибка при получении состояния DNS-сервера",
|
||||
"down": "Вниз",
|
||||
"fix": "Исправить",
|
||||
"dns_providers": "<0>Список известных DNS-провайдеров</0> на выбор.",
|
||||
@@ -293,9 +315,11 @@
|
||||
"client_edit": "Редактировать клиента",
|
||||
"client_identifier": "Идентификатор",
|
||||
"ip_address": "IP-адрес",
|
||||
"client_identifier_desc": "Клиенты могут быть идентифицированы по IP-адресу или MAC-адресу. Обратите внимание, что использование MAC как идентификатора, возможно только если AdGuard Home также является и <0>DHCP-сервером</0>",
|
||||
"client_identifier_desc": "Клиенты могут быть идентифицированы по IP-адресу, CIDR или MAC-адресу. Обратите внимание, что использование MAC как идентификатора возможно, только если AdGuard Home также является и <0>DHCP-сервером</0>",
|
||||
"form_enter_ip": "Введите IP",
|
||||
"form_enter_mac": "Введите MAC",
|
||||
"form_enter_id": "Введите идентификатор",
|
||||
"form_add_id": "Добавить идентификатор",
|
||||
"form_client_name": "Введите имя клиента",
|
||||
"client_global_settings": "Использовать глобальные настройки",
|
||||
"client_deleted": "Клиент \"{{key}}\" успешно удален",
|
||||
@@ -404,5 +428,7 @@
|
||||
"number_of_dns_query_days_0": "Количество DNS-запросов за {{count}} день",
|
||||
"number_of_dns_query_days_1": "Количество DNS-запросов за {{count}} дня",
|
||||
"number_of_dns_query_days_2": "Количество DNS-запросов за {{count}} дней",
|
||||
"filtering_rules_learn_more": "<0>Узнайте больше</0> о создании собственных списков блокировки хостов."
|
||||
"filtering_rules_learn_more": "<0>Узнайте больше</0> о создании собственных списков блокировки хостов.",
|
||||
"blocked_by_response": "Заблокировано по CNAME или IP в ответе",
|
||||
"try_again": "Попробовать еще раз"
|
||||
}
|
||||
@@ -19,6 +19,8 @@
|
||||
"dhcp_leases_not_found": "Neboli nájdené žiadne DHCP prenájmy",
|
||||
"dhcp_config_saved": "Konfigurácia DHCP servera uložená",
|
||||
"form_error_required": "Povinná položka",
|
||||
"form_error_ip4_format": "Nesprávny formát IPv4",
|
||||
"form_error_ip6_format": "Nesprávny formát IPv6",
|
||||
"form_error_ip_format": "Nesprávny formát IPv4",
|
||||
"form_error_mac_format": "Nesprávny MAC formát",
|
||||
"form_error_positive": "Musí byť väčšie ako 0",
|
||||
@@ -43,6 +45,7 @@
|
||||
"dhcp_new_static_lease": "Nový statický prenájom",
|
||||
"dhcp_static_leases_not_found": "Nebol nájdený žiadny statický DHCP prenájom",
|
||||
"dhcp_add_static_lease": "Pridať statický prenájom",
|
||||
"dhcp_reset": "Naozaj chcete obnoviť konfiguráciu DHCP?",
|
||||
"delete_confirm": "Naozaj chcete vymazať \"{{key}}\"?",
|
||||
"form_enter_hostname": "Zadajte názov hostiteľa",
|
||||
"error_details": "Podrobnosti chyby",
|
||||
@@ -66,16 +69,22 @@
|
||||
"disabled_protection": "Ochrana vypnutá",
|
||||
"refresh_statics": "Obnoviť štatistiku",
|
||||
"dns_query": "DNS dopyty",
|
||||
"blocked_by": "<0>Blokované filtrami<0>",
|
||||
"stats_malware_phishing": "Blokovaný škodlivý kód/pokus o podvod",
|
||||
"stats_adult": "Blokovaná stránka pre dospelých",
|
||||
"stats_query_domain": "Najčastejšie dopytované domény",
|
||||
"for_last_24_hours": "za posledných 24 hodín",
|
||||
"for_last_days": "za posledný {{count}} deň",
|
||||
"for_last_days_plural": "za posledných {{count}} dní",
|
||||
"no_domains_found": "Žiadna doména nebola nájdená",
|
||||
"requests_count": "Počet dopytov",
|
||||
"top_blocked_domains": "Najčastejšie zablokované domény",
|
||||
"top_clients": "Najčastejší klienti",
|
||||
"no_clients_found": "Neboli nájdení žiadni klienti",
|
||||
"general_statistics": "Všeobecná štatistika",
|
||||
"number_of_dns_query_days": "Počet DNS dopytov spracovaných za posledný {{count}} deň",
|
||||
"number_of_dns_query_days_plural": "Počet DNS dopytov spracovaných za posledných {{count}} dní",
|
||||
"number_of_dns_query_24_hours": "Počet DNS dopytov spracovaných za posledných 24 hodín",
|
||||
"number_of_dns_query_blocked_24_hours": "Počet DNS dopytov zablokovaných filtrami reklamy a zoznamami adries",
|
||||
"number_of_dns_query_blocked_24_hours_by_sec": "Počet DNS dopytov zablokovaných AdGuard modulom Bezpečné prehliadanie",
|
||||
"number_of_dns_query_blocked_24_hours_adult": "Počet zablokovaných stránok pre dospelých",
|
||||
@@ -97,8 +106,9 @@
|
||||
"encryption_settings": "Nastavenia šifrovania",
|
||||
"dhcp_settings": "Nastavenia DHCP",
|
||||
"upstream_dns": "Upstream DNS servery",
|
||||
"upstream_dns_hint": "Ak toto pole ponecháte prázdne, AdGuard Home použije <a href='https://1.1.1.1/' target='_blank'>Cloudflare DNS</a> ako upstream.",
|
||||
"upstream_dns_hint": "Ak toto pole ponecháte prázdne, AdGuard Home použije <a href='https://www.quad9.net/' target='_blank'>Quad9</a> ako upstream.",
|
||||
"test_upstream_btn": "Test upstreamov",
|
||||
"upstreams": "Upstreams",
|
||||
"apply_btn": "Použiť",
|
||||
"disabled_filtering_toast": "Vypnutá filtrácia",
|
||||
"enabled_filtering_toast": "Zapnutá filtrácia",
|
||||
@@ -135,6 +145,7 @@
|
||||
"example_comment": "! Sem sa pridáva komentár",
|
||||
"example_comment_meaning": "len komentár",
|
||||
"example_comment_hash": "# Tiež komentár",
|
||||
"example_regex_meaning": "zablokovať prístup k doménam, ktoré zodpovedajú zadanému regulárnemu výrazu",
|
||||
"example_upstream_regular": "radová DNS (cez UDP)",
|
||||
"example_upstream_dot": "šifrované <0>DNS-over-TLS</0>",
|
||||
"example_upstream_doh": "šifrované <0>DNS-over-HTTPS</0>",
|
||||
@@ -165,6 +176,17 @@
|
||||
"updated_custom_filtering_toast": "Aktualizované vlastné filtračné pravidlá",
|
||||
"rule_removed_from_custom_filtering_toast": "Pravidlo odstránené z vlastných filtračných pravidiel",
|
||||
"rule_added_to_custom_filtering_toast": "Pravidlo pridané do vlastných filtračných pravidiel",
|
||||
"query_log_response_status": "Stav: {{value}}",
|
||||
"query_log_filtered": "Vyfiltrované pomocou {{filter}}",
|
||||
"query_log_confirm_clear": "Naozaj chcete vymazať celý denník dopytov?",
|
||||
"query_log_cleared": "Denník dopytov bol úspešne vymazaný",
|
||||
"query_log_clear": "Vymazať denníky dopytov",
|
||||
"query_log_retention": "Obdobie záznamu denníka dopytov",
|
||||
"query_log_enable": "Zapnúť denník",
|
||||
"query_log_configuration": "Konfigurácia denníka",
|
||||
"query_log_disabled": "Protokol dopytov je vypnutý a možno ho nakonfigurovať v <0>nastaveniach</0>",
|
||||
"query_log_strict_search": "Na prísne vyhľadávanie použite dvojité úvodzovky",
|
||||
"query_log_retention_confirm": "Naozaj chcete zmeniť uchovávanie denníku dopytov? Ak znížite hodnotu intervalu, niektoré údaje sa stratia",
|
||||
"source_label": "Zdroj",
|
||||
"found_in_known_domain_db": "Nájdené v databáze známych domén.",
|
||||
"category_label": "Kategória",
|
||||
@@ -182,6 +204,7 @@
|
||||
"install_settings_dns_desc": "Budete musieť konfigurovať Vaše zariadenia alebo smerovač, aby používali DNS server na nasledujúcich adresách:",
|
||||
"install_settings_all_interfaces": "Všetky rozhrania",
|
||||
"install_auth_title": "Overenie identity",
|
||||
"install_auth_desc": "Odporúčame Vám nakonfigurovať na administrátorskom webovom rozhraní AdGuard Home overenie Vašej identity heslom. Aj keď je prístupné iba vo Vašej lokálnej sieti, je stále dôležité chrániť ho pred neobmedzeným prístupom.",
|
||||
"install_auth_username": "Meno používateľa",
|
||||
"install_auth_password": "Heslo",
|
||||
"install_auth_confirm": "Potvrdenie hesla",
|
||||
@@ -260,6 +283,8 @@
|
||||
"update_announcement": "AdGuard Home {{version}} je teraz k dispozícii! <0>Viac informácií nájdete tu</0>.",
|
||||
"setup_guide": "Sprievodca nastavením",
|
||||
"dns_addresses": "DNS adresy",
|
||||
"dns_start": "Spúšťa sa DNS server",
|
||||
"dns_status_error": "Chyba pri zisťovaní stavu DNS servera",
|
||||
"down": "Nadol",
|
||||
"fix": "Opraviť",
|
||||
"dns_providers": "Tu je <0>zoznam známych poskytovateľov DNS</0>, z ktorého si vyberiete.",
|
||||
@@ -281,6 +306,8 @@
|
||||
"client_identifier_desc": "Klienti môžu byť identifikovaní podľa IP adresy alebo MAC adresy. Upozorňujeme, že používanie MAC ako identifikátora je možné len vtedy, ak je AdGuard Home tiež <0>DHCP server</0>",
|
||||
"form_enter_ip": "Zadajte IP",
|
||||
"form_enter_mac": "Zadajte MAC",
|
||||
"form_enter_id": "Zadajte identifikátor",
|
||||
"form_add_id": "Pridajte identifikátor",
|
||||
"form_client_name": "Zadajte názov klienta",
|
||||
"client_global_settings": "Použiť globálne nastavenia",
|
||||
"client_deleted": "\"{{key}}\" klienta bol úspešne vymazaný",
|
||||
@@ -318,5 +345,69 @@
|
||||
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> podporuje <1>DNS-over-HTTPS</1>.",
|
||||
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> podporuje <1>DNS-over-HTTPS</1>.",
|
||||
"setup_dns_privacy_other_5": "Viac implementácií nájdete <0>tu</0> a <1>tu</1>.",
|
||||
"setup_dns_notice": "Pre použitie <1>DNS-over-HTTPS</1> alebo <1>DNS-over-TLS</1>, potrebujete v nastaveniach AdGuard Home <0>nakonfigurovať šifrovanie</0>."
|
||||
"setup_dns_notice": "Pre použitie <1>DNS-over-HTTPS</1> alebo <1>DNS-over-TLS</1>, potrebujete v nastaveniach AdGuard Home <0>nakonfigurovať šifrovanie</0>.",
|
||||
"rewrite_added": "DNS prepísanie pre \"{{key}}\" bolo úspešne pridané",
|
||||
"rewrite_deleted": "DNS prepísanie pre \"{{key}}\" bolo úspešne vymazané",
|
||||
"rewrite_add": "Pridať DNS prepísanie",
|
||||
"rewrite_not_found": "Neboli nájdené žiadne DNS prepísania",
|
||||
"rewrite_confirm_delete": "Naozaj chcete odstrániť prepísanie DNS pre \"{{key}}\"?",
|
||||
"rewrite_desc": "Umožňuje ľahko nakonfigurovať vlastnú odpoveď DNS pre konkrétny názov domény.",
|
||||
"rewrite_applied": "Použilo sa pravidlo prepisovania",
|
||||
"dns_rewrites": "DNS prepisovanie",
|
||||
"form_domain": "Vložte meno domény",
|
||||
"form_answer": "Zadajte IP adresu alebo názov domény",
|
||||
"form_error_domain_format": "Neplatný formát domény",
|
||||
"form_error_answer_format": "Neplatný formát odpovede",
|
||||
"configure": "Konfigurovať",
|
||||
"main_settings": "Hlavné nastavenia",
|
||||
"block_services": "Blokovať vybrané služby",
|
||||
"blocked_services": "Blokované služby",
|
||||
"blocked_services_desc": "Umožňuje rýchlo blokovať populárne stránky a služby.",
|
||||
"blocked_services_saved": "Blokované služby boli úspešne uložené",
|
||||
"blocked_services_global": "Použite globálne blokované služby",
|
||||
"blocked_service": "Blokované služby",
|
||||
"block_all": "Blokovať všetko",
|
||||
"unblock_all": "Odblokovať všetko",
|
||||
"encryption_certificate_path": "Cesta k certifikátu",
|
||||
"encryption_private_key_path": "Cesta k súkromného kľúču",
|
||||
"encryption_certificates_source_path": "Nastavte cestu k súboru s certifikátom",
|
||||
"encryption_certificates_source_content": "Vložiť obsah certifikátu",
|
||||
"encryption_key_source_path": "Nastaviť súbor s privátnym kľúčom",
|
||||
"encryption_key_source_content": "Vložte obsah privátneho kľúča",
|
||||
"stats_params": "Konfigurácia štatistiky",
|
||||
"config_successfully_saved": "Konfigurácia bola úspešne uložená",
|
||||
"interval_24_hour": "24 hodín",
|
||||
"interval_days": "{{count}} deň",
|
||||
"interval_days_plural": "{{count}} dní",
|
||||
"domain": "Doména",
|
||||
"answer": "Odpoveď",
|
||||
"filter_added_successfully": "Filter bol úspešne pridaný",
|
||||
"statistics_configuration": "Konfigurácia štatistiky",
|
||||
"statistics_retention": "Štatistika za obdobie",
|
||||
"statistics_retention_desc": "Ak znížite hodnotu intervalu, niektoré údaje sa stratia",
|
||||
"statistics_clear": " Vynulovať štatistiku",
|
||||
"statistics_clear_confirm": "Naozaj chcete vynulovať štatistiku?",
|
||||
"statistics_retention_confirm": "Naozaj chcete zmeniť uchovávanie štatistík? Ak znížite hodnotu intervalu, niektoré údaje sa stratia",
|
||||
"statistics_cleared": "Štatistika bola úspešne vynulovaná",
|
||||
"interval_hours": "{{count}} hodina",
|
||||
"interval_hours_plural": "{{count}} hodín",
|
||||
"filters_configuration": "Konfigurácia filtrov",
|
||||
"filters_enable": "Zapnúť filtre",
|
||||
"filters_interval": "Interval aktualizácie filtrov",
|
||||
"disabled": "Vypnuté",
|
||||
"username_label": "Meno používateľa",
|
||||
"username_placeholder": "Zadajte meno používateľa",
|
||||
"password_label": "Heslo",
|
||||
"password_placeholder": "Zadajte heslo",
|
||||
"sign_in": "Prihlásiť sa",
|
||||
"sign_out": "Odhlásiť sa",
|
||||
"forgot_password": "Zabudnuté heslo?",
|
||||
"forgot_password_desc": "Postupujte podľa <0>týchto krokov</0> a vytvorte nové heslo pre svoj používateľský účet.",
|
||||
"location": "Poloha",
|
||||
"orgname": "Meno organizácie",
|
||||
"netname": "Meno siete",
|
||||
"descr": "Popis",
|
||||
"whois": "Whois",
|
||||
"filtering_rules_learn_more": "<0>Viac informácií</0> o vytváraní vlastných zoznamov hostiteľov.",
|
||||
"blocked_by_response": "Blokované pomocou CNAME alebo IP v odpovedi"
|
||||
}
|
||||
@@ -19,7 +19,9 @@
|
||||
"dhcp_leases_not_found": "Ni najdenih najemov DHCP",
|
||||
"dhcp_config_saved": "Shranjena konfiguracija DHCP strežnika",
|
||||
"form_error_required": "Zahtevano polje",
|
||||
"form_error_ip_format": "Neveljaven format IPv4",
|
||||
"form_error_ip4_format": "Neveljaven format IPv4",
|
||||
"form_error_ip6_format": "Neveljaven format IPv6",
|
||||
"form_error_ip_format": "Neveljaven format IP",
|
||||
"form_error_mac_format": "Neveljaven MAC format",
|
||||
"form_error_positive": "Mora biti večja od 0",
|
||||
"dhcp_form_gateway_input": "IP prehoda",
|
||||
@@ -43,6 +45,7 @@
|
||||
"dhcp_new_static_lease": "Nov statični najem",
|
||||
"dhcp_static_leases_not_found": "Ni najdenih statičnih najemov DHCP",
|
||||
"dhcp_add_static_lease": "Dodaj statičen najem",
|
||||
"dhcp_reset": "Ali ste prepričani, da želite ponastaviti konfiguracijo DHCP?",
|
||||
"delete_confirm": "Ali ste prepričani, da želite izbrisati \"{{key}}\"?",
|
||||
"form_enter_hostname": "Vnesite ime gostitelja",
|
||||
"error_details": "Podrobnosti o napaki",
|
||||
@@ -96,15 +99,16 @@
|
||||
"use_adguard_parental": "Uporabi AdGuardovo spletno storitev 'Starševski nadzor'",
|
||||
"use_adguard_parental_hint": "AdGuard Home bo preveril, če domena vsebuje vsebine za odrasle. Uporablja enako, za zasebnost prijazen API, kot spletno storitev za varnost brskanja.",
|
||||
"enforce_safe_search": "Vsili varno iskanje",
|
||||
"enforce_save_search_hint": "AdGuard Home lahko uveljavi varno iskanje v naslednjih iskalnikih: Google, Youtube, Bing, DuckDuckGo in Yandex.",
|
||||
"enforce_save_search_hint": "AdGuard Home lahko uveljavi varno iskanje v naslednjih iskalnikih: Google, Youtube, Bing, DuckDuckGo, Yandex, Pixabay.",
|
||||
"no_servers_specified": "Ni določenih strežnikov",
|
||||
"general_settings": "Splošne nastavitve",
|
||||
"dns_settings": "Nastavitve DNS",
|
||||
"encryption_settings": "Nastavitve šifriranja",
|
||||
"dhcp_settings": "Nastavitve DHCP",
|
||||
"upstream_dns": "Zagonski DNS strežniki",
|
||||
"upstream_dns_hint": "Če pustite to polje prazno, bo AdGuard Home uporabil <a href='https://1.1.1.1/' target='_blank'>Cloudflare DNS</a> kot upstream.",
|
||||
"upstream_dns_hint": "Če pustite to polje prazno, bo AdGuard Home uporabil <a href='https://www.quad9.net/' target='_blank'>Quad9</a> kot tok navzgor.",
|
||||
"test_upstream_btn": "Preizkusi upstreame",
|
||||
"upstreams": "Tokovi navzgor",
|
||||
"apply_btn": "Uporabi",
|
||||
"disabled_filtering_toast": "Onemogočeno filtriranje",
|
||||
"enabled_filtering_toast": "Omogočeno filtriranje",
|
||||
@@ -279,6 +283,8 @@
|
||||
"update_announcement": "Zdaj je na voljo AdGuard Home {{version}}! <0>Klinite tukaj</0> za več informacij.",
|
||||
"setup_guide": "Navodila za nastavitev",
|
||||
"dns_addresses": "DNS naslovi",
|
||||
"dns_start": "Zaganja se strežnik DNS",
|
||||
"dns_status_error": "Napaka pri pridobivanju stanja strežnika DNS",
|
||||
"down": "Navzdol",
|
||||
"fix": "Popravi",
|
||||
"dns_providers": "Tukaj je <0>seznam znanih ponudnikov DNS</0>, med katerimi lahko izbirate.",
|
||||
@@ -297,9 +303,11 @@
|
||||
"client_edit": "Uredi odjemalca",
|
||||
"client_identifier": "Identifikator",
|
||||
"ip_address": "IP naslov",
|
||||
"client_identifier_desc": "Odjemalce lahko identificirate po naslovu IP ali naslovu MAC. Upoštevajte, da je uporaba MAC kot identifikatorja mogoča le, če je AdGuard Home tudi <0>DHCP strežnik</0>",
|
||||
"client_identifier_desc": "Odjemalce je mogoče identificirati po naslovu IP, CIDR, MAC. Upoštevajte, da je uporaba MAC kot identifikatorja mogoča le, če je AdGuard Home tudi <0>DHCP strežnik</0>",
|
||||
"form_enter_ip": "Vnesite IP",
|
||||
"form_enter_mac": "Vnesite MAC",
|
||||
"form_enter_id": "Vnesi identifikatorja",
|
||||
"form_add_id": "Dodaj identifikatorja",
|
||||
"form_client_name": "Vnesite ime odjemalca",
|
||||
"client_global_settings": "Uporabi splošne nastavitve",
|
||||
"client_deleted": "Odjemalec \"{{key}}\" je bil uspešno izbrisan",
|
||||
@@ -400,5 +408,6 @@
|
||||
"netname": "Ime omrežja",
|
||||
"descr": "Opis",
|
||||
"whois": "Whois",
|
||||
"filtering_rules_learn_more": "<0>Več o tem</0>, o ustvarjanju lastnih Seznamov nedovoljenih gostiteljev."
|
||||
"filtering_rules_learn_more": "<0>Več o tem</0>, o ustvarjanju lastnih Seznamov nedovoljenih gostiteljev.",
|
||||
"blocked_by_response": "Onemogočeno z CNAME ali IP v odgovoru"
|
||||
}
|
||||
431
client/src/__locales/sr-cs.json
Normal file
431
client/src/__locales/sr-cs.json
Normal file
@@ -0,0 +1,431 @@
|
||||
{
|
||||
"client_settings": "Postavke klijenta",
|
||||
"example_upstream_reserved": "možete odrediti DNS upstream <0>za određene domene</0>",
|
||||
"upstream_parallel": "Koristite paralelne zahteve da ubrzate rešavanje istovremenim zahtevanjem svih servera",
|
||||
"bootstrap_dns": "Bootstrap DNS serveri",
|
||||
"bootstrap_dns_desc": "Bootstrap DNS serveri se koriste da reše IP adrese od DoH/DoT razrešivača koje ste odredili kao upstream.",
|
||||
"check_dhcp_servers": "Proveri DHCP servere",
|
||||
"save_config": "Sačuvaj konfiguraciju",
|
||||
"enabled_dhcp": "DHCP server uključen",
|
||||
"disabled_dhcp": "DHCP server isključen",
|
||||
"dhcp_title": "DHCP server (eksperimentalno!)",
|
||||
"dhcp_description": "Ako vaš ruter nema DHCP postavke, možete koristiti AdGuard' ugrađen DHCP server.",
|
||||
"dhcp_enable": "Uključi DHCP server",
|
||||
"dhcp_disable": "Isključi DHCP server",
|
||||
"dhcp_not_found": "Bezbedno je da uključite ugrađeni DHCP server. Nismo pronašli nijedan aktivan DHCP server na mreži. međutim, ohrabrujemo vas da to ponovo proverite ručno, jer naš automatski test trenutno nije 100% pouzdan.",
|
||||
"dhcp_found": "Pronađen je aktivan DHCP server na mreži. Nije bezbedno da uključite ugrađeni DHCP server.",
|
||||
"dhcp_leases": "DHCP pozajmljivanja",
|
||||
"dhcp_static_leases": "DHCP statička pozajmljivanja",
|
||||
"dhcp_leases_not_found": "DHCP pozajmljivanja nisu pronađena",
|
||||
"dhcp_config_saved": "Sačuvaj DHCP konfiguraciju servera",
|
||||
"form_error_required": "Obavezno polje",
|
||||
"form_error_ip4_format": "Nevažeći IPv4 format",
|
||||
"form_error_ip6_format": "Nevažeći IPv6 format",
|
||||
"form_error_ip_format": "Nevažeći IPv4 oblik",
|
||||
"form_error_mac_format": "Nevažeći MAC format",
|
||||
"form_error_positive": "Mora biti veće od 0",
|
||||
"form_error_negative": "Mora biti 0 ili veće",
|
||||
"dhcp_form_gateway_input": "IP mrežnog prolaza",
|
||||
"dhcp_form_subnet_input": "Subnet mask",
|
||||
"dhcp_form_range_title": "Opseg IP adresa",
|
||||
"dhcp_form_range_start": "Početak opsega",
|
||||
"dhcp_form_range_end": "Kraj opsega",
|
||||
"dhcp_form_lease_title": "DHCP vreme pozajmljivanja (u sekundama)",
|
||||
"dhcp_form_lease_input": "Trajanje iznajmljivanja",
|
||||
"dhcp_interface_select": "Izaberite DHCP okruženje",
|
||||
"dhcp_hardware_address": "Adresa hardvera",
|
||||
"dhcp_ip_addresses": "IP adrese",
|
||||
"dhcp_table_hostname": "Ime hosta",
|
||||
"dhcp_table_expires": "Ističe",
|
||||
"dhcp_warning": "Ako svakako želite da uključite DHCP server, uverite se da nema drugih aktivnih DHCP servera u vašoj mreži. U suprotnom, to može pokvariti Internet za povezane uređaje!",
|
||||
"dhcp_error": "Ne možemo da odredimo da li ima drugih DHCP servera na mreži.",
|
||||
"dhcp_static_ip_error": "Kako biste koristili DHCP server, morate podesiti statičku IP adresu. Nismo mogli da odredimo da li je ovo mrežno okruženje podešeno za korišćenje statičke IP adrese. Molimo vas da podesite statičku IP adresu ručno.",
|
||||
"dhcp_dynamic_ip_found": "Vaš sistem koristi dinamičku IP adresu za okruženje <0>{{interfaceName}}</0>. Kako biste koristili DHCP server, morate podesiti statičku IP adresu. Vaša trenutna IP adresa je <0>{{ipAddress}}</0>. Automatski ćemo podesiti ovu IP adresu kao statičku ako pritisnete Uključi DHCP dugme.",
|
||||
"dhcp_lease_added": "Statičko iznajmljivanje \"{{key}}\" uspešno dodato",
|
||||
"dhcp_lease_deleted": "Statičko iznajmljivanje lease \"{{key}}\" uspešno izbrisano",
|
||||
"dhcp_new_static_lease": "Novo statičko iznajmljivanje",
|
||||
"dhcp_static_leases_not_found": "Nisu pronađena statička DHCP iznajmljivanja",
|
||||
"dhcp_add_static_lease": "Dodaj statičko iznajmljivanje",
|
||||
"dhcp_reset": "Jeste li sigurni da želite da resetujete DHCP konfiguraciju?",
|
||||
"delete_confirm": "Jeste li sigurni da želite da izbrišete \"{{key}}\"?",
|
||||
"form_enter_hostname": "Unesite ime hosta",
|
||||
"error_details": "Detalji greške",
|
||||
"back": "Nazad",
|
||||
"dashboard": "Kontrolna tabla",
|
||||
"settings": "Postavke",
|
||||
"filters": "Filteri",
|
||||
"query_log": "Dnevnik zahteva",
|
||||
"faq": "ČPP",
|
||||
"version": "Verzija",
|
||||
"address": "adresa",
|
||||
"on": "Uključeno",
|
||||
"off": "Isključeno",
|
||||
"copyright": "Autorska prava",
|
||||
"homepage": "Početna stranica",
|
||||
"report_an_issue": "Prijavi poteškoću",
|
||||
"privacy_policy": "Politika privatnosti",
|
||||
"enable_protection": "Uključi zaštitu",
|
||||
"enabled_protection": "Uključena zaštita",
|
||||
"disable_protection": "Isključi zaštitu",
|
||||
"disabled_protection": "Isključena zaštita",
|
||||
"refresh_statics": "Osveži statistike",
|
||||
"dns_query": "DNS zahtevi",
|
||||
"blocked_by": "<0>blokirano od filtera</0>",
|
||||
"stats_malware_phishing": "Blokiraj štetan softver i fišing",
|
||||
"stats_adult": "Blokiraj sajtove za odrasle",
|
||||
"stats_query_domain": "Najčešće unošeni domeni",
|
||||
"for_last_24_hours": "u poslednja 24 časa",
|
||||
"for_last_days": "u poslednjih {{count}} dana",
|
||||
"for_last_days_plural": "u poslednjih {{count}} dana",
|
||||
"no_domains_found": "Domeni nisu pronađeni",
|
||||
"requests_count": "Broj zahteva",
|
||||
"top_blocked_domains": "Najčešće blokirani domeni",
|
||||
"top_clients": "Najčešći klijenti",
|
||||
"no_clients_found": "Nema pronađenih klijenata",
|
||||
"general_statistics": "Opšte statistike",
|
||||
"number_of_dns_query_days": "Broj obrađenih DNS unosa u poslednjih {{count}} dan",
|
||||
"number_of_dns_query_days_plural": "Broj obrađenih DNS unosa u poslednjih {{count}} dana",
|
||||
"number_of_dns_query_24_hours": "Broj obrađenih DNS unosa u poslednja 24 časa",
|
||||
"number_of_dns_query_blocked_24_hours": "Broj DNS zahteva blokiranih od filtera blokatora reklama i blok liste hostova",
|
||||
"number_of_dns_query_blocked_24_hours_by_sec": "Broj DNS zahteva blokiranih od AdGuard-ovog podprograma za bezbedno pregledanje",
|
||||
"number_of_dns_query_blocked_24_hours_adult": "Broj blokiranih sajtova za odrasle",
|
||||
"enforced_save_search": "Nametni sigurno pretraživanje",
|
||||
"number_of_dns_query_to_safe_search": "Broj DNS zahteva ka pretraživačima za koje je nametnuto sigurno pretraživanje",
|
||||
"average_processing_time": "Prosečno vreme obrade",
|
||||
"average_processing_time_hint": "Prosečno vreme u milisekundama za obradu DNS zahteva",
|
||||
"block_domain_use_filters_and_hosts": "Blokiraj domene koristeći filtere i hosts datoteke",
|
||||
"filters_block_toggle_hint": "Možete postaviti pravila blokiranja u <a href='#filters'>Filters</a> postavkama.",
|
||||
"use_adguard_browsing_sec": "Koristi AdGuard-ovu uslugu bezbednog pregledanja",
|
||||
"use_adguard_browsing_sec_hint": "AdGuard Home će proveriti da li je domen blokiran od strane usluge za bezbednosno pregledanje. Koristiće prijateljski API privatni pregled da izvrši proveru. Samo će se kratak prefiks domena SHA256 hash poslati na server.",
|
||||
"use_adguard_parental": "Koristi AdGuard-ovu uslugu roditeljske kontrole",
|
||||
"use_adguard_parental_hint": "AdGuard Home će proveriti da li domen sadrži sadržaj za odrasle. Koristi se isti privatni prijateljski API kao i kod usluge bezbednog pregledanja.",
|
||||
"enforce_safe_search": "Nametni sigurno pretraživanje",
|
||||
"enforce_save_search_hint": "AdGuard Home može nametnuti sigurno pretraživanje u sledećim pretraživačima: Google, Youtube, Bing, DuckDuckGo i Yandex.",
|
||||
"no_servers_specified": "Serveri nisu određeni",
|
||||
"general_settings": "Opšte postavke",
|
||||
"dns_settings": "DNS postavke",
|
||||
"encryption_settings": "Postavke šifrovanja",
|
||||
"dhcp_settings": "DHCP postavke",
|
||||
"upstream_dns": "Upstream DNS serveri",
|
||||
"upstream_dns_hint": "Ako ovo polje ostavite prazno, AdGuard Home će koristiti <a href='https://www.quad9.net/' target='_blank'>Quad9</a> kao upstream.",
|
||||
"test_upstream_btn": "Testiraj upstreams",
|
||||
"upstreams": "Upstreams",
|
||||
"apply_btn": "Primeni",
|
||||
"disabled_filtering_toast": "Isključeno filtriranje",
|
||||
"enabled_filtering_toast": "Uključeno filtriranje",
|
||||
"disabled_safe_browsing_toast": "Isključeno sigurno pregledanje",
|
||||
"enabled_safe_browsing_toast": "Uključeno sigurno pretraživanje",
|
||||
"disabled_parental_toast": "Isključena roditeljska kontrola",
|
||||
"enabled_parental_toast": "Uključena roditeljska kontrola",
|
||||
"disabled_safe_search_toast": "Isključena sigurna pretraga",
|
||||
"enabled_save_search_toast": "Uključeno sigurno pretraživanje",
|
||||
"enabled_table_header": "Uključeno",
|
||||
"name_table_header": "Ime",
|
||||
"filter_url_table_header": "URL do filtera",
|
||||
"rules_count_table_header": "Broj pravila",
|
||||
"last_time_updated_table_header": "Poslednji put ažurirano",
|
||||
"actions_table_header": "Radnje",
|
||||
"edit_table_action": "Izmeni",
|
||||
"delete_table_action": "Izbriši",
|
||||
"filters_and_hosts": "Blok lista filtera i hostova",
|
||||
"filters_and_hosts_hint": "AdGuard Home razume osnovna pravila blokiranja reklama i sintaksu hosts datoteke.",
|
||||
"no_filters_added": "Filteri nisu dodati",
|
||||
"add_filter_btn": "Dodaj filter",
|
||||
"cancel_btn": "Otkaži",
|
||||
"enter_name_hint": "Unesite ime",
|
||||
"enter_url_hint": "Unesite URL",
|
||||
"check_updates_btn": "Proveri ažuriranja",
|
||||
"new_filter_btn": "Nova pretplata na filter",
|
||||
"enter_valid_filter_url": "Unesite važeći URL do pretplate na filter ili važeću hosts datoteku.",
|
||||
"custom_filter_rules": "Prilagođena pravila filtriranja",
|
||||
"custom_filter_rules_hint": "Unesite jedno pravilo po redu. Možete koristiti pravila blokatora reklama ili sintaksu hosts datoteke.",
|
||||
"examples_title": "Primeri",
|
||||
"example_meaning_filter_block": "blokirajte pristup ka domenu primer.org i svim njegovim poddomenima",
|
||||
"example_meaning_filter_whitelist": "dozvolite pristup ka domenu primer.org i svim njegovim poddomenima",
|
||||
"example_meaning_host_block": "AdGuard Home će sada vratiti adresu 127.0.0.1 za domen primer.org (ali ne i za njegove poddomene).",
|
||||
"example_comment": "! Ovde ide komentar",
|
||||
"example_comment_meaning": "samo komentar",
|
||||
"example_comment_hash": "# Takođe komentar",
|
||||
"example_regex_meaning": "blokiranje pristupa domenima koji odgovaraju određenom uobičajenom izrazu",
|
||||
"example_upstream_regular": "uobičajeno DNS (preko UDP)",
|
||||
"example_upstream_dot": "šifrovano <0>DNS-over-TLS</0>",
|
||||
"example_upstream_doh": "šifrovano <0>DNS-over-HTTPS</0>",
|
||||
"example_upstream_sdns": "možete koristiti <0>DNS brojeve</0> za <1>DNSCrypt</1> ili <2>DNS-over-HTTPS</2>",
|
||||
"example_upstream_tcp": "uobičajeni DNS (preko TCP)",
|
||||
"all_filters_up_to_date_toast": "Svi filteri su već ažurirani",
|
||||
"updated_upstream_dns_toast": "Ažurirani upstream DNS serveri",
|
||||
"dns_test_ok_toast": "Dati DNS serveri rade ispravno",
|
||||
"dns_test_not_ok_toast": "Server \"{{key}}\": se ne može koristiti. Proverite da li ste ga ispravno uneli",
|
||||
"unblock_btn": "Odblokiraj",
|
||||
"block_btn": "Blokiraj",
|
||||
"time_table_header": "Vreme",
|
||||
"domain_name_table_header": "Ime domena",
|
||||
"type_table_header": "Vrsta",
|
||||
"response_table_header": "Odgovor",
|
||||
"client_table_header": "Klijent",
|
||||
"empty_response_status": "Prazno",
|
||||
"show_all_filter_type": "Pokaži sve",
|
||||
"show_filtered_type": "Pokaži filtrirano",
|
||||
"no_logs_found": "Dnevnici nisu pronađeni",
|
||||
"refresh_btn": "Osveži",
|
||||
"previous_btn": "Prethodno",
|
||||
"next_btn": "Sledeće",
|
||||
"loading_table_status": "Učitavanje...",
|
||||
"page_table_footer_text": "Stranica",
|
||||
"of_table_footer_text": "od",
|
||||
"rows_table_footer_text": "redovi",
|
||||
"updated_custom_filtering_toast": "Ažurirana prilagođena pravila filtriranja",
|
||||
"rule_removed_from_custom_filtering_toast": "Pravilo uklonjeno iz prilagođenih pravila filtriranja",
|
||||
"rule_added_to_custom_filtering_toast": "Pravilo dodato u prilagođena pravila filtriranja",
|
||||
"query_log_response_status": "Stanje: {{value}}",
|
||||
"query_log_filtered": "Filtrirano od {{filter}}",
|
||||
"query_log_confirm_clear": "Jeste li sigurni da želite da očistite ceo dnevnik unosa?",
|
||||
"query_log_cleared": "Dnevnik unosa je uspešno očišćen",
|
||||
"query_log_clear": "Očisti dnevnike unosa",
|
||||
"query_log_retention": "Zadržavanje dnevnika unosa",
|
||||
"query_log_enable": "Uključi dnevnik",
|
||||
"query_log_configuration": "Konfiguracija dnevnika",
|
||||
"query_log_disabled": "Dnevnik unosa je isključen ali se može konfigurisati u <0>postavkama</0>",
|
||||
"query_log_strict_search": "Koristi duple navodnike za striktnu pretragu",
|
||||
"query_log_retention_confirm": "Jeste li sigurni da želite da promenite zadržavanje dnevnika unosa? Ako smanjite vrednost intervala, neki podaci će biti izgubljeni",
|
||||
"dns_config": "Konfiguracija DNS servera",
|
||||
"blocking_mode": "Način blokiranja",
|
||||
"nxdomain": "NXDOMAIN",
|
||||
"null_ip": "Null IP",
|
||||
"custom_ip": "Prilagođeni IP",
|
||||
"blocking_ipv4": "Blokiranje IPv4",
|
||||
"blocking_ipv6": "Blokiranje IPv6",
|
||||
"form_enter_rate_limit": "Unesite ograničenje brzine",
|
||||
"rate_limit": "Ograničenje brzine",
|
||||
"edns_enable": "Uključi EDNS Client Subnet",
|
||||
"edns_cs_desc": "Ako je uključeno, AdGuard Home će slati klijente na DNS servere.",
|
||||
"rate_limit_desc": "Broj zahteva po sekundi koje pojedinačni klijent dozvoljava (0: neograničeno)",
|
||||
"blocking_ipv4_desc": "IP adresa koja će biti vraćena za blokirane zahteve",
|
||||
"blocking_ipv6_desc": "IP adresa koja će biti vraćena za blokirane AAAA zahteve",
|
||||
"blocking_mode_desc": "<0>NXDOMAIN – odgovara sa NXDOMAIN code;</0> <0>Null IP – odgovara sa nulom IP adresom (0.0.0.0 za A; :: za AAAA);</0> <0>Prilagođeni IP - odgovara sa ručno podešenom IP adresom",
|
||||
"upstream_dns_client_desc": "AKo ovo polje ostavite prazno, AdGuard Home će koristiti servere konfigurisane u <0>DNS postavkama</0>.",
|
||||
"source_label": "Izvor",
|
||||
"found_in_known_domain_db": "Pronađeno u poznatim bazama podataka domena.",
|
||||
"category_label": "Kategorija",
|
||||
"rule_label": "Pravilo",
|
||||
"filter_label": "Filter",
|
||||
"unknown_filter": "Nepoznat filter {{filterId}}",
|
||||
"install_welcome_title": "Dobrodošli u AdGuard home!",
|
||||
"install_welcome_desc": "AdGuard Home je mrežni DNS server, blokator reklama i praćenja. Dopušta vam da kontrolišete svoju čitavu mrežu i sve vaše uređaje i ne zahteva korišćenje nikakvog klijentskog programa.",
|
||||
"install_settings_title": "Administratorsko web okruženje",
|
||||
"install_settings_listen": "Okruženje slušanja",
|
||||
"install_settings_port": "Port",
|
||||
"install_settings_interface_link": "Vaše AdGuard Home administratorsko web okruženje će biti dostupno na sledećim adresama:",
|
||||
"form_error_port": "Unesite važeću vrednost za port",
|
||||
"install_settings_dns": "DNS server",
|
||||
"install_settings_dns_desc": "Potrebno je da konfigurišete vaše uređaje ili ruter da koristi DNS server sa sledećim adresama:",
|
||||
"install_settings_all_interfaces": "Sva okruženja",
|
||||
"install_auth_title": "Autentifikacija",
|
||||
"install_auth_desc": "Preporučujemo vam da konfigurišete autentifikaciju lozinkom za vaše AdGuard Home administratorsko okruženje. Čak iako mu je moguće pristupiti samo iz vaše lokalne mreže,, i dalje je važno da ga zaštitite od neograničenog pristupa.",
|
||||
"install_auth_username": "Korisničko ime",
|
||||
"install_auth_password": "Lozinka",
|
||||
"install_auth_confirm": "Potvrdite lozinku",
|
||||
"install_auth_username_enter": "Unesite korisničko ime",
|
||||
"install_auth_password_enter": "Unesite lozinku",
|
||||
"install_step": "Korak",
|
||||
"install_devices_title": "Konfigurišite vaše uređaje",
|
||||
"install_devices_desc": "Za početak korišćenja AdGuard Home, potrebno je da konfigurišete vaše uređaje da ga koriste.",
|
||||
"install_submit_title": "Čestitamo!",
|
||||
"install_submit_desc": "Postavljanje je završeno i sada ste spremni da započnete sa korišćenjem AdGuard Home.",
|
||||
"install_devices_router": "Ruter",
|
||||
"install_devices_router_desc": "Ovo postavljanje će automatski pokriti sve uređaje koji su povezani na vaš kućni ruter pa nećete morati da konfigurišete svaki uređaj posebno.",
|
||||
"install_devices_address": "AdGuard Home DNS server sluša na sledećim adresama",
|
||||
"install_devices_router_list_1": "Otvorite podešavanja vašeg rutera. Obično im možete pristupiti iz vašeg preglednika preko URL (kao http://192.168.0.1/ ili http://192.168.1.1/). Možda će vam biti zatraženo da unesete lozinku. Ako je ne znate ili je se ne sećate, najčešće je možete resetovati pritiskom na dugme na samom ruteru. Neki ruteri zahtevaju određenu aplikaciju, koja bi u tom slučaju već trebalo da bude instalirana na vašem računaru ili telefonu.",
|
||||
"install_devices_router_list_2": "Pronađite DHCP ili DNS postavke. Potražite DNS slova pored polja koje dozvoljava dve ili tri skupine brojeva, a svaka može da sadrži četiri grupe od jedne do tri cifre.",
|
||||
"install_devices_router_list_3": "Tamo unesite adrese AdGuard home servera.",
|
||||
"install_devices_windows_list_1": "Otvorite kontrolnu tablu iz startnog menija ili kroz Windows pretragu.",
|
||||
"install_devices_windows_list_2": "Otvorite kategoriju mreža i internet a onda otiđite u centar za mrežu i deljenje.",
|
||||
"install_devices_windows_list_3": "Na levoj strani ekrana pronađite Promena postavke adaptera i kliknite tu.",
|
||||
"install_devices_windows_list_4": "Izaberite vašu aktivnu vezu, desnim tasterom kliknite na nju i izaberite Svojstva.",
|
||||
"install_devices_windows_list_5": "Na listi pronađite Internet Protokol verzija 4 (TCP/IP), izaberite ga pa kliknite ponovo na Svojstva.",
|
||||
"install_devices_windows_list_6": "Izaberite Koristi sledeće adrese DNS servera pa unesite vaše adrese AdGuard Home servera.",
|
||||
"install_devices_macos_list_1": "Kliknite na ikonicu jabuke pa otiđite na postavke sistema.",
|
||||
"install_devices_macos_list_2": "Kliknite na mrežu.",
|
||||
"install_devices_macos_list_3": "Izaberite prvu vezu sa liste pa kliknite na više opcija.",
|
||||
"install_devices_macos_list_4": "Izaberite karticu DNS pa tu unesite adrese vašeg AdGuard Home servera.",
|
||||
"install_devices_android_list_1": "Sa Android početnog ekrana, dodirnite Postavke.",
|
||||
"install_devices_android_list_2": "Dodirnite Wi-Fi. Pojaviće se ekran sa svim dostupnim mrežama. Nije moguće da podesite prilagođeni DNS za mobilne veze).",
|
||||
"install_devices_android_list_3": "Dugo pritisnite na mrežu na koju ste povezani, pa dodirnite Izmeni mrežu.",
|
||||
"install_devices_android_list_4": "Na nekim uređajima će možda biti potrebno da označite kućicu za napredne opcije kako bi videli dalje postavke. Da biste prilagodili vaše Android DNS postavke, prebacite IP postavke sa DHCP na statičke.",
|
||||
"install_devices_android_list_5": "Promenite DNS 1 i DNS 2 vrednosti na adrese vašeg AdGuard Home servera.",
|
||||
"install_devices_ios_list_1": "Sa početnog ekrana, dodirnite postavke.",
|
||||
"install_devices_ios_list_2": "U levom meniju izaberite Wi-Fi. Nije moguće da konfigurišete DNS za mobilne mreže).",
|
||||
"install_devices_ios_list_3": "Dodirnite ime trenutno aktivne mreže.",
|
||||
"install_devices_ios_list_4": "U DNS polje unesite adrese vašeg AdGuard Home servera.",
|
||||
"get_started": "Počnimo",
|
||||
"next": "Dalje",
|
||||
"open_dashboard": "Otvori kontrolnu tablu",
|
||||
"install_saved": "Uspešno sačuvano",
|
||||
"encryption_title": "Šifrovanje",
|
||||
"encryption_desc": "Šifrovanje (HTTPS/TLS) podrška za oba DNS i administratorsko okruženje",
|
||||
"encryption_config_saved": "Konfiguracija šifrovanja je sačuvana",
|
||||
"encryption_server": "Ime servera",
|
||||
"encryption_server_enter": "Unesite vaše ime domena",
|
||||
"encryption_server_desc": "Kako biste koristili HTTPS, potrebno je da unesete ime servera koje se podudara sa SSL sertifikatom.",
|
||||
"encryption_redirect": "Automatski preusmeri na HTTPS",
|
||||
"encryption_redirect_desc": "Ako je označeno, AdGuard Home će vas automatski preusmeravati sa HTTP na HTTPS adrese.",
|
||||
"encryption_https": "HTTPS port",
|
||||
"encryption_https_desc": "Ako je HTTPS port konfigurisan, AdGuard Home administratorskom okruženju će se moći pristupati preko HTTPS, a to će takođe omogućiti DNS-over-HTTPS na '/dns-query' lokaciji.",
|
||||
"encryption_dot": "DNS-over-TLS port",
|
||||
"encryption_dot_desc": "Ako je ovaj port konfigurisan, AdGuard Home će pokretati DNS-over-TLS server na ovom portu.",
|
||||
"encryption_certificates": "Sertifikati",
|
||||
"encryption_certificates_desc": "Da biste koristili šifrovanje, morate obezbediti važeći lanac SSL sertifikata za vaš domen. Besplatan sertifikat možete nabaviti na <0>{{link}}</0> ili ga možete kupiti od nekog od pouzdanih izdavalaca sertifikata.",
|
||||
"encryption_certificates_input": "Kopirajte/nalepite vaše PEM-kodirane sertifikate ovde.",
|
||||
"encryption_status": "Stanje",
|
||||
"encryption_expire": "Ističe",
|
||||
"encryption_key": "Privatni ključ",
|
||||
"encryption_key_input": "Kopirajte/nalepite vaš PEM-kodirani privatni ključ za vaš sertifikat ovde.",
|
||||
"encryption_enable": "Uključi šifrovanje (HTTPS, DNS-over-HTTPS, i DNS-over-TLS)",
|
||||
"encryption_enable_desc": "Ako je šifrovanje uključeno, AdGuard Home administratorsko okruženje će raditi preko HTTPS, i DNS server će slušati zahteve preko DNS-over-HTTPS i DNS-over-TLS.",
|
||||
"encryption_chain_valid": "Lanac sertifikata je važeći",
|
||||
"encryption_chain_invalid": "Lanac sertifikata je nevažeći",
|
||||
"encryption_key_valid": "Ovo je važeći {{type}} privatni ključ",
|
||||
"encryption_key_invalid": "Ovo je nevažeći {{type}} privatni ključ",
|
||||
"encryption_subject": "Predmet",
|
||||
"encryption_issuer": "Izdavač",
|
||||
"encryption_hostnames": "Imena hostova",
|
||||
"encryption_reset": "Jeste li sigurni da želite dda resetujete postavke šifrovanja?",
|
||||
"topline_expiring_certificate": "Vaš SSL sertifikat uskoro ističe. Ažurirajte <0>postavke šifrovanja</0>.",
|
||||
"topline_expired_certificate": "Vaš SSL sertifikat je istekao. Ažurirajte <0>postavke šifrovanja</0>.",
|
||||
"form_error_port_range": "Unesite vrednost porta u opsegu od 80-65535",
|
||||
"form_error_port_unsafe": "Ovo nije siguran port",
|
||||
"form_error_equal": "Ne bi trebalo da bude jednako",
|
||||
"form_error_password": "Lozinke se ne podudaraju",
|
||||
"reset_settings": "Vrati postavke na podrazumevano",
|
||||
"update_announcement": "AdGuard Home {{version}} je sada dostupan! <0>Kliknite ovde</0> za više informacija.",
|
||||
"setup_guide": "Uputstvo za podešavanje",
|
||||
"dns_addresses": "DNS adrese",
|
||||
"dns_start": "DNS server se pokreće",
|
||||
"dns_status_error": "Greška prilikom pribavljanja stanja DNS servera",
|
||||
"down": "Dole",
|
||||
"fix": "Popravi",
|
||||
"dns_providers": "Ovo je a <0>lista poznatih DNS dobavljača</0> sa koje možete da izaberete.",
|
||||
"update_now": "Ažuriraj sada",
|
||||
"update_failed": "Automatsko ažuriranje nije uspelo. Molimo vas <a href='https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started#update'>pratite korake</a> za ručno ažuriranje.",
|
||||
"processing_update": "Molimo sačekajte. AdGuard Home se ažurira",
|
||||
"clients_title": "Klijenti",
|
||||
"clients_desc": "Konfigurišite uređaje koji su povezani na AdGuard Home",
|
||||
"settings_global": "Globalno",
|
||||
"settings_custom": "Prilagođeno",
|
||||
"table_client": "Klijent",
|
||||
"table_name": "Ime",
|
||||
"save_btn": "Sačuvaj",
|
||||
"client_add": "Dodaj klijent",
|
||||
"client_new": "Novi klijent",
|
||||
"client_edit": "Izmeni klijent",
|
||||
"client_identifier": "Identifikator",
|
||||
"ip_address": "IP adresa",
|
||||
"client_identifier_desc": "Klijenti mogu da budu prepoznati po IP adresi ili MAC adresi. Imajte na umu da je korišćenje MAC adrese kao identifikatora moguće samo ako je AdGuard Home takođe a <0>DHCP server</0>",
|
||||
"form_enter_ip": "Unesite IP",
|
||||
"form_enter_mac": "Unesite MAC",
|
||||
"form_enter_id": "Unesite identifikator",
|
||||
"form_add_id": "Dodaj identifikator",
|
||||
"form_client_name": "Unesite ime klijenta",
|
||||
"client_global_settings": "Koristi globalne postavke",
|
||||
"client_deleted": "Klijent \"{{key}}\" uspešno izbrisan",
|
||||
"client_added": "Klijent \"{{key}}\" uspešno dodat",
|
||||
"client_updated": "Klijent \"{{key}}\" uspešno ažuriran",
|
||||
"clients_not_found": "Nema pronađenih klijenata",
|
||||
"client_confirm_delete": "Jeste li sigurni da želite da izbrišete klijenta \"{{key}}\"?",
|
||||
"filter_confirm_delete": "Jeste li sigurni da želite da izbrišete filter?",
|
||||
"auto_clients_title": "Klijenti (runtime)",
|
||||
"auto_clients_desc": "Podaci o klijentima koji koriste AdGuard Home, ali nisu sačuvani u konfiguraciji",
|
||||
"access_title": "Postavke pristupa",
|
||||
"access_desc": "Ovde možete konfigurisati pravila pristupa za AdGuard home DNS server.",
|
||||
"access_allowed_title": "Dozvoljeni klijenti",
|
||||
"access_allowed_desc": "Spisak CIDR ili IP adresa. Ako je podešeno, AdGuard Home će prihvatiti zahteve samo od ovih IP adresa.",
|
||||
"access_disallowed_title": "Zabranjeni klijenti",
|
||||
"access_disallowed_desc": "Lista CIDR ili IP adresa.. Ako je podešeno, AdGuard Home će odbijati zahteve od ovih IP adresa.",
|
||||
"access_blocked_title": "Blokirani domeni",
|
||||
"access_blocked_desc": "Nemojte ovo mešati sa filterima. AdGuard Home će odbijati DNS unose ka ovim domenima.",
|
||||
"access_settings_saved": "Postavke pristupa su uspešno sačuvane",
|
||||
"updates_checked": "Ažuriranja su uspešno proverena",
|
||||
"updates_version_equal": "AdGuard Home je ažuriran na najnoviju verziju",
|
||||
"check_updates_now": "Proveri da li postoje ispravke",
|
||||
"dns_privacy": "DNS privatnost",
|
||||
"setup_dns_privacy_1": "<0>DNS-over-TLS:</0> koristi <1>{{address}}</1> string.",
|
||||
"setup_dns_privacy_2": "<0>DNS-over-HTTPS:</0> koristi <1>{{address}}</1> string.",
|
||||
"setup_dns_privacy_3": "<0>Imajte na umu da su protokoli šifrovanog DNS-a podržani samo od Androida 9. Biće potrebno da instalirate dodatni softver za druge operativne sisteme.</0><0>Ovde je spisak softvera koje možete koristiti.</0>",
|
||||
"setup_dns_privacy_android_1": "Android 9 podržava DNS-over-TLS. Za konfiguraciju, idite u postavke → mreža i internet → Napredno → Privatni DNS i tamo unesite ime vašeg domena.",
|
||||
"setup_dns_privacy_android_2": "<0>AdGuard for Android</0> podržava <1>DNS-over-HTTPS</1> i <1>DNS-over-TLS</1>.",
|
||||
"setup_dns_privacy_android_3": "<0>Intra</0> dodaje <1>DNS-over-HTTPS</1> podršku za Android.",
|
||||
"setup_dns_privacy_ios_1": "<0>DNSCloak</0> podržava <1>DNS-over-HTTPS</1>, ali da biste mogli da ga konfigurišete da koristi vaš lični server, biće potrebno da generišete a <2>DNS Stamp</2> za njega.",
|
||||
"setup_dns_privacy_ios_2": "<0>AdGuard za iOS</0> podržava <1>DNS-over-HTTPS</1> i <1>DNS-over-TLS</1>.",
|
||||
"setup_dns_privacy_other_title": "Druge implementacije",
|
||||
"setup_dns_privacy_other_1": "AdGuard home može biti bezbedan DNS server na bilo kojoj platformi.",
|
||||
"setup_dns_privacy_other_2": "<0>dnsproxy</0> podržava sve poznate bezbedne DNS protokole.",
|
||||
"setup_dns_privacy_other_3": "<0>dnscrypt-proxy</0> podržava <1>DNS-over-HTTPS</1>.",
|
||||
"setup_dns_privacy_other_4": "<0>Mozilla Firefox</0> podržava <1>DNS-over-HTTPS</1>.",
|
||||
"setup_dns_privacy_other_5": "Više implementacija ćete pronaći <0>ovde</0> i <1>ovde</1>.",
|
||||
"setup_dns_notice": "Kako biste koristili <1>DNS-over-HTTPS</1> ili <1>DNS-over-TLS</1>, potrebno je da <0>konfigurišete šifrovanje</0> u AdGuard Home postavkama.",
|
||||
"rewrite_added": "DNS prepisivanje za \"{{key}}\" je uspešno dodato",
|
||||
"rewrite_deleted": "DNS prepisivanje za \"{{key}}\" uspešno izbrisano",
|
||||
"rewrite_add": "Dodaj DNS prepisivanje",
|
||||
"rewrite_not_found": "DNS prepisivanja nisu pronađena",
|
||||
"rewrite_confirm_delete": "Jeste li sigurni da želite da izbrišete DNS prepisivanje za \"{{key}}\"?",
|
||||
"rewrite_desc": "Dozvoljava da jednostavno konfigurišete prilagođeni DNS odgovor za određeni domen.",
|
||||
"rewrite_applied": "Primenjeno pravilo prepisivanja",
|
||||
"dns_rewrites": "DNS prepisivanja",
|
||||
"form_domain": "Unesite domen",
|
||||
"form_answer": "Unesite IP adresu ili domen",
|
||||
"form_error_domain_format": "Nevažeći format domena",
|
||||
"form_error_answer_format": "Nevažeći format odgovora",
|
||||
"configure": "Konfiguriši",
|
||||
"main_settings": "Glavne postavke",
|
||||
"block_services": "Blokiraj određene usluge",
|
||||
"blocked_services": "Blokiraj usluge",
|
||||
"blocked_services_desc": "Dozvoljava vam da brzo blokirate popularne sajtove i usluge.",
|
||||
"blocked_services_saved": "Blokirane usluge su uspešno sačuvane",
|
||||
"blocked_services_global": "Koristi globalne blokirane usluge",
|
||||
"blocked_service": "Blokirana usluga",
|
||||
"block_all": "Blokiraj sve",
|
||||
"unblock_all": "Odblokiraj sve",
|
||||
"encryption_certificate_path": "Putanja sertifikata",
|
||||
"encryption_private_key_path": "Putanja privatnog ključa",
|
||||
"encryption_certificates_source_path": "Postavi putanju do datoteke sertifikata",
|
||||
"encryption_certificates_source_content": "Nalepite sadržaj sertifikata",
|
||||
"encryption_key_source_path": "Podesi datoteku privatnog ključa",
|
||||
"encryption_key_source_content": "Nalepi sadržaj privatnog ključa",
|
||||
"stats_params": "Konfiguracija statistike",
|
||||
"config_successfully_saved": "Konfiguracija je uspešno sačuvana",
|
||||
"interval_24_hour": "24 časa",
|
||||
"interval_days": "{{count}} dan",
|
||||
"interval_days_plural": "{{count}} dana",
|
||||
"domain": "Domen",
|
||||
"answer": "Odgovor",
|
||||
"filter_added_successfully": "Filter je uspešno dodat",
|
||||
"statistics_configuration": "Konfiguracija statistike",
|
||||
"statistics_retention": "Zadržavanje statistike",
|
||||
"statistics_retention_desc": "Ako smanjite vrednost intervala, neki podaci će biti izgubljeni",
|
||||
"statistics_clear": " Očisti statistiku",
|
||||
"statistics_clear_confirm": "Jeste li sigurni da želite da očistite statistiku?",
|
||||
"statistics_retention_confirm": "Jeste li sigurni da želite da promenite zadržavanje statistike? Ako smanjite vrednost intervala, neki podaci će biti izgubljeni",
|
||||
"statistics_cleared": "Statistika je uspešno očišćena",
|
||||
"interval_hours": "{{count}} čas",
|
||||
"interval_hours_plural": "{{count}} časova",
|
||||
"filters_configuration": "Konfiguracija filtera",
|
||||
"filters_enable": "Uključi filtere",
|
||||
"filters_interval": "Interval ažuriranja filtera",
|
||||
"disabled": "Isključeno",
|
||||
"username_label": "Korisničko ime",
|
||||
"username_placeholder": "Unesite korisničko ime",
|
||||
"password_label": "Lozinka",
|
||||
"password_placeholder": "Unesite lozinku",
|
||||
"sign_in": "Prijavi se",
|
||||
"sign_out": "Odjavi se",
|
||||
"forgot_password": "Zaboravili ste lozinku?",
|
||||
"forgot_password_desc": "Ispratite <0>ove korake</0> za stvaranje nove lozinke za vaš korisnički nalog.",
|
||||
"location": "Lokacija",
|
||||
"orgname": "Ime organizacije",
|
||||
"netname": "Ime mreže",
|
||||
"descr": "Opis",
|
||||
"whois": "Whois",
|
||||
"filtering_rules_learn_more": "<0>Saznajte više</0> o stvaranju vaše lične blokliste hostova.",
|
||||
"blocked_by_response": "Blokirano od CNAME ili IP u odgovoru",
|
||||
"try_again": "Pokušaj ponovo"
|
||||
}
|
||||
@@ -15,10 +15,10 @@
|
||||
"dhcp_not_found": "Ingen aktiv DHCP-server hittades i nätverkat.",
|
||||
"dhcp_found": "Några aktiva DHCP-servar upptäcktes. Det är inte säkert att aktivera inbyggda DHCP-servrar.",
|
||||
"dhcp_leases": "DHCP-lease",
|
||||
"dhcp_static_leases": "Statiska DHCP-leases",
|
||||
"dhcp_leases_not_found": "Ingen DHCP-lease hittad",
|
||||
"dhcp_config_saved": "Sparade inställningar för DHCP-servern",
|
||||
"form_error_required": "Obligatoriskt fält",
|
||||
"form_error_ip_format": "Ogiltigt IPv4-format",
|
||||
"form_error_mac_format": "Ogiltigt MAC-format",
|
||||
"form_error_positive": "Måste vara större än noll",
|
||||
"dhcp_form_gateway_input": "Gateway-IP",
|
||||
"dhcp_form_subnet_input": "Subnetmask",
|
||||
@@ -36,6 +36,12 @@
|
||||
"dhcp_error": "Vi kunde inte avgöra om det finns en till DHCP-server på nätverket.",
|
||||
"dhcp_static_ip_error": "För att kunna använda en DHCP-server måste det finnas en statisk IP-adress. Vi kunde inte avgöra om nätverksgränssnittet är konfigurerat med en statisk IP-adress. Ställ in en statistik IP-adress manuellt.",
|
||||
"dhcp_dynamic_ip_found": "Din enhet använder en dynamisk IP-adress för gränssnittet <0>{{interfaceName}}</0>. För att kunna använda DHCP-servern behövs en statisk IP-adress. Din nuvarande IP-adress är <0>{{ipAddress}}</0>. Vi kommer att göra denna IP-adress statisk automatiskt om du trycker på knappen \"Aktivera DHCP\".",
|
||||
"dhcp_lease_added": "Statisk lease \"{{key}}\" har lagts till",
|
||||
"dhcp_lease_deleted": "Statisk lease \"{{key}}\" har raderats",
|
||||
"dhcp_new_static_lease": "Ny statisk lease",
|
||||
"dhcp_static_leases_not_found": "Inga statiska DHCP-leases hittade",
|
||||
"dhcp_add_static_lease": "Lägg till statisk lease",
|
||||
"delete_confirm": "Är du säker på att du vill ta bort \"{{key}}\"?",
|
||||
"form_enter_hostname": "Skriv in värdnamn",
|
||||
"error_details": "Felinformation",
|
||||
"back": "Tiilbaka",
|
||||
@@ -48,6 +54,7 @@
|
||||
"address": "adress",
|
||||
"on": "PÅ",
|
||||
"off": "AV",
|
||||
"copyright": "Copyright",
|
||||
"homepage": "Hemsida",
|
||||
"report_an_issue": "Rapportera ett problem",
|
||||
"privacy_policy": "Integritetspolicy",
|
||||
@@ -62,6 +69,8 @@
|
||||
"stats_adult": "Blockerade vuxensajter",
|
||||
"stats_query_domain": "Mest eftersökta domäner",
|
||||
"for_last_24_hours": "under de senaste 24 timamrna",
|
||||
"for_last_days": "för den senaste {{count}} dagen",
|
||||
"for_last_days_plural": "för de senaste {{count}} dagarna",
|
||||
"no_domains_found": "Inga domäner hittade",
|
||||
"requests_count": "Förfrågningsantal",
|
||||
"top_blocked_domains": "Flest blockerade domäner",
|
||||
@@ -83,11 +92,12 @@
|
||||
"use_adguard_parental": "Använda AdGuards webbservice för färäldrakontroll",
|
||||
"use_adguard_parental_hint": "AdGuard Home kommer att kontrollera domäner för innehåll av vuxenmaterial . Samma integritetsvänliga metod för API-lookup som tillämpas i webbservicens surfsäkerhet används.",
|
||||
"enforce_safe_search": "Tillämpa Säker surf",
|
||||
"enforce_save_search_hint": "AdGuard Home kan framtvinga säker surf i följande sökmoterer: Google, Youtube, Bing, och Yandex.",
|
||||
"no_servers_specified": "Inga servrar angivna",
|
||||
"general_settings": "Allmänna inställningar",
|
||||
"dns_settings": "DNS-inställningar",
|
||||
"encryption_settings": "Krypteringsinställningar",
|
||||
"dhcp_settings": "DHCP-inställningar",
|
||||
"upstream_dns": "Upstream DNS-servrar",
|
||||
"upstream_dns_hint": "Om du låter fältet vara tomt kommer AdGuard Home att använda <a href='https://1.1.1.1/' target='_blank'>Cloudflare DNS</a> för uppström.",
|
||||
"test_upstream_btn": "Testa uppströmmar",
|
||||
"apply_btn": "Tillämpa",
|
||||
"disabled_filtering_toast": "Filtrering bortkopplad",
|
||||
@@ -125,6 +135,7 @@
|
||||
"example_comment": "! Här kommer en kommentar",
|
||||
"example_comment_meaning": "Endast en kommentar",
|
||||
"example_comment_hash": "# Också en kommentar",
|
||||
"example_regex_meaning": "blockera åtkomst till domäner som matchar det angivna uttrycket",
|
||||
"example_upstream_regular": "vanlig DNS (över UDP)",
|
||||
"example_upstream_dot": "krypterat <a href='https://en.wikipedia.org/wiki/DNS_over_TLS' target='_blank'>DNS-over-TLS</a>",
|
||||
"example_upstream_doh": "krypterat <a href='https://en.wikipedia.org/wiki/DNS_over_HTTPS' target='_blank'>DNS-over-HTTPS</a>",
|
||||
@@ -155,6 +166,17 @@
|
||||
"updated_custom_filtering_toast": "Uppdaterade de egna filterreglerna",
|
||||
"rule_removed_from_custom_filtering_toast": "Regel borttagen från de egna filterreglerna",
|
||||
"rule_added_to_custom_filtering_toast": "Regel tillagd till de egna filterreglerna",
|
||||
"query_log_response_status": "Status: {{value}}",
|
||||
"query_log_filtered": "Filtrerat av {{filter}}",
|
||||
"query_log_confirm_clear": "Är du säker på att du vill rensa hela förfrågningsloggen?",
|
||||
"query_log_cleared": "Förfrågningsloggen har rensats",
|
||||
"query_log_clear": "Rensa förfrågningsloggar",
|
||||
"query_log_retention": "Förfrågningsloggars retentionstid",
|
||||
"query_log_enable": "Aktivera logg",
|
||||
"query_log_configuration": "Logginställningar",
|
||||
"query_log_disabled": "Förfrågningsloggen är avaktiverad och kan konfigureras i <0>inställningar</0>",
|
||||
"query_log_strict_search": "Använd dubbla citattecken för strikt sökning",
|
||||
"query_log_retention_confirm": "Är du säker på att du vill ändra förfrågningsloggars retentionstid? Om du minskar intervallet kommer viss data att gå förlorad",
|
||||
"source_label": "Källa",
|
||||
"found_in_known_domain_db": "Hittad i domändatabas.",
|
||||
"category_label": "Kategori",
|
||||
@@ -253,9 +275,14 @@
|
||||
"dns_addresses": "DNS-adresser",
|
||||
"down": "Ner",
|
||||
"fix": "Fixa",
|
||||
"dns_providers": "Här är en <0>lista över kända DNS-leverantörer</0> att välja från.",
|
||||
"update_now": "Uppdatera nu",
|
||||
"update_failed": "Automatisk uppdatering misslyckad. Var god <a href='https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started#update'>följ stegen</a> för att uppdatera manuellt.",
|
||||
"processing_update": "Vänta, AdGuard Home uppdateras",
|
||||
"clients_title": "Klienter",
|
||||
"clients_desc": "Konfigurera enheter uppkopplade mot AdGuard Home",
|
||||
"settings_global": "Global",
|
||||
"settings_custom": "Anpassade",
|
||||
"table_client": "Klient",
|
||||
"table_name": "Namn",
|
||||
"save_btn": "Spara",
|
||||
@@ -268,8 +295,45 @@
|
||||
"form_enter_mac": "Skriv in MAC",
|
||||
"form_client_name": "Skriv in klientnamn",
|
||||
"client_global_settings": "Använda globala inställningar",
|
||||
"client_deleted": "Klient \"{{key}}\" har raderats",
|
||||
"client_added": "Klient \"{{key}}\" har lagts till",
|
||||
"client_updated": "Klient \"{{key}}\" har uppdaterats",
|
||||
"clients_not_found": "Inga klienter hittade",
|
||||
"client_confirm_delete": "Är du säker på att du vill ta bort klient \"{{key}}\"?",
|
||||
"filter_confirm_delete": "Är du säker på att du ska ta bort filtret?",
|
||||
"auto_clients_title": "Klienter (körtid)",
|
||||
"auto_clients_desc": "Data från klienter som använder AdGuard Home, men inte är sparade i konfigurationen",
|
||||
"access_title": "Åtkomstinställningar",
|
||||
"access_desc": "Här kan du konfigurera åtkomstregler för AdGuard Homes DNS-server.",
|
||||
"access_allowed_title": "Tillåtna klienter",
|
||||
"access_disallowed_title": "Otillåtna klienter",
|
||||
"access_blocked_title": "Blockerade domäner",
|
||||
"access_settings_saved": "Åtkomstinställningar sparade",
|
||||
"updates_checked": "Sökning efter uppdateringar genomförd",
|
||||
"updates_version_equal": "AdGuard Home är uppdaterat",
|
||||
"check_updates_now": "Sök efter uppdateringar nu",
|
||||
"interval_24_hour": "24 timmar",
|
||||
"domain": "Domän",
|
||||
"answer": "Svar"
|
||||
"answer": "Svar",
|
||||
"statistics_clear": " Rensa statistik",
|
||||
"interval_hours": "{{count}} timme",
|
||||
"interval_hours_plural": "{{count}} timmar",
|
||||
"filters_configuration": "Filterinställningar",
|
||||
"filters_enable": "Aktivera filter",
|
||||
"filters_interval": "Filterppdateringsintervall",
|
||||
"disabled": "Avaktiverad",
|
||||
"username_label": "Användarnamn",
|
||||
"username_placeholder": "Skriv in användarnamn",
|
||||
"password_label": "Lösenord",
|
||||
"password_placeholder": "Skriv in lösenord",
|
||||
"sign_in": "Logga in",
|
||||
"sign_out": "Logga ut",
|
||||
"forgot_password": "Glömt lösenord?",
|
||||
"forgot_password_desc": "Följ <0>dessa steg</0> för att skapa ett nytt lösenord till ditt konto.",
|
||||
"location": "Plats",
|
||||
"orgname": "Organisationsnamn",
|
||||
"netname": "Nätverksnamn",
|
||||
"descr": "Beskrivning",
|
||||
"whois": "Whois",
|
||||
"filtering_rules_learn_more": "<0>Mer info</0> om att skapa dina egna blockeringslistor för värdar."
|
||||
}
|
||||
@@ -17,9 +17,7 @@
|
||||
"dhcp_leases": "DHCP kiralamaları",
|
||||
"dhcp_static_leases": "Sabit DHCP kiralamaları",
|
||||
"dhcp_leases_not_found": "DHCP kiralaması bulunamadı",
|
||||
"dhcp_config_saved": "DHCP sunucusu ayarı kaydedildi",
|
||||
"form_error_required": "Gerekli alan",
|
||||
"form_error_ip_format": "Geçersiz IPv4 formatı",
|
||||
"form_error_mac_format": "Geçersiz MAC biçimi",
|
||||
"form_error_positive": "0'dan büyük olmalı",
|
||||
"dhcp_form_gateway_input": "Ağ Geçidi IP'si",
|
||||
@@ -96,14 +94,12 @@
|
||||
"use_adguard_parental": "AdGuard ebeveyn kontrolü web hizmetini kullan",
|
||||
"use_adguard_parental_hint": "AdGuard Home, alan adının yetişkin içerik bulundurup bulundurmadığını kontrol edecek. Gezinti güvenliği web hizmeti ile kullandığımız aynı gizlilik dostu API'yi kullanıyoruz.",
|
||||
"enforce_safe_search": "Güvenli aramayı zorunlu kıl",
|
||||
"enforce_save_search_hint": "AdGuard Home şu arama motorlarında güvenli aramayı zorunlu kılabilir: Google, Youtube, Bing, DuckDuckGo ve Yandex.",
|
||||
"no_servers_specified": "Sunucu adresi girilmedi",
|
||||
"general_settings": "Genel ayarlar",
|
||||
"dns_settings": "DNS ayarları",
|
||||
"encryption_settings": "Şifreleme ayarları",
|
||||
"dhcp_settings": "DHCP ayarları",
|
||||
"upstream_dns": "Üst DNS sunucusu",
|
||||
"upstream_dns_hint": "Eğer bu alanı boş bırakırsanız AdGuard Home üst sunucu olarak <a href='https://1.1.1.1/' target='_blank'>Cloudflare DNS</a> adresini kullanacaktır.",
|
||||
"test_upstream_btn": "Üst sunucuyu test et",
|
||||
"apply_btn": "Uygula",
|
||||
"disabled_filtering_toast": "Filtreleme devre dışı",
|
||||
@@ -141,7 +137,6 @@
|
||||
"example_comment": "! Buraya bir yorum ekledim",
|
||||
"example_comment_meaning": "yorum eklemek",
|
||||
"example_comment_hash": "# Bir yorum daha ekledim",
|
||||
"example_regex_meaning": "belirtilen düzenli ifadelerle eşleşen alan adlarına erişimi engelle",
|
||||
"example_upstream_regular": "normal DNS (UDP üzerinden)",
|
||||
"example_upstream_dot": "<0>DNS-over-TLS</0> şifrelemesi",
|
||||
"example_upstream_doh": "<0>DNS-over-HTTPS</0> şifrelemesi",
|
||||
@@ -287,7 +282,6 @@
|
||||
"client_edit": "İstemciyi düzenle",
|
||||
"client_identifier": "Tanımlayıcı",
|
||||
"ip_address": "IP adresi",
|
||||
"client_identifier_desc": "İstemciler IP adresleri veya MAC adresleri ile tanımlanabilir. Lütfen not edin, MAC adresi ile tanımlamayı kullanmak için AdGuard Home'un <0>DHCP Sunucusu</0> olması gerekir.",
|
||||
"form_enter_ip": "IP Girin",
|
||||
"form_enter_mac": "MAC Girin",
|
||||
"form_client_name": "İstemci ismi girin",
|
||||
|
||||
@@ -55,11 +55,9 @@
|
||||
"use_adguard_parental": "Sử dụng dịch vụ quản lý của phụ huynh AdGuard",
|
||||
"use_adguard_parental_hint": "AdGuard Home sẽ kiểm tra nếu tên miền chứa từ khoá người lớn. Tính năng sử dụng API thân thiện với quyền riêng tư tương tự với dịch vụ bảo vệ duyệt web",
|
||||
"enforce_safe_search": "Bắt buộc tìm kiếm an toàn",
|
||||
"enforce_save_search_hint": "AdGuard Home có thể bắt buộc tìm kiếm an toàn với các dịch vụ tìm kiếm: Google, Youtube, Bing, Yandex.",
|
||||
"no_servers_specified": "Không có máy chủ nào được liệt kê",
|
||||
"general_settings": "Cài đặt chung",
|
||||
"upstream_dns": "Máy chủ DNS tìm kiếm",
|
||||
"upstream_dns_hint": "Nếu bạn để trống mục này, AdGuard Home sẽ sử dụng <a href='https://1.1.1.1/' target='_blank'>Cloudflare DNS</a> để tìm kiếm. Sử dụng tiền tố tls:// cho các máy chủ DNS dựa trên TLS.",
|
||||
"test_upstream_btn": "Kiểm tra",
|
||||
"apply_btn": "Áp dụng",
|
||||
"disabled_filtering_toast": "Đã tắt chặn quảng cáo",
|
||||
|
||||
@@ -17,9 +17,7 @@
|
||||
"dhcp_leases": "DHCP 租约",
|
||||
"dhcp_static_leases": "DHCP 静态租约",
|
||||
"dhcp_leases_not_found": "未检测到 DHCP 租约",
|
||||
"dhcp_config_saved": "保存 DHCP 服务器配置",
|
||||
"form_error_required": "必填字段",
|
||||
"form_error_ip_format": "无效的 IPv4 格式",
|
||||
"form_error_mac_format": "无效的 MAC 格式",
|
||||
"form_error_positive": "必须大于 0",
|
||||
"dhcp_form_gateway_input": "网关 IP",
|
||||
@@ -90,14 +88,12 @@
|
||||
"use_adguard_parental": "使用 AdGuard 【家长控制】服务",
|
||||
"use_adguard_parental_hint": "AdGuard Home 将使用与浏览安全服务相同的隐私性强的 API 来检查域名指向的网站是否包含成人内容。",
|
||||
"enforce_safe_search": "强制安全搜索",
|
||||
"enforce_save_search_hint": "AdGuard Home 将对以下搜索引擎强制启用安全搜索:Google、YouTube、Bing 和 Yandex。",
|
||||
"no_servers_specified": "未找到指定的服务器",
|
||||
"general_settings": "常规设置",
|
||||
"dns_settings": "DNS 设置",
|
||||
"encryption_settings": "加密设置",
|
||||
"dhcp_settings": "DHCP 设置",
|
||||
"upstream_dns": "上游 DNS 服务器",
|
||||
"upstream_dns_hint": "如果此处留空,AdGuard Home 将会使用 <a href='https://1.1.1.1/' target='_blank'>Cloudflare DNS</a> 作为上游 DNS。如果想要使用使用 DNS over TLS,请以 tls:// 为开头。",
|
||||
"test_upstream_btn": "测试上游 DNS",
|
||||
"apply_btn": "应用",
|
||||
"disabled_filtering_toast": "过滤器已禁用",
|
||||
@@ -278,7 +274,6 @@
|
||||
"client_edit": "编辑客户端",
|
||||
"client_identifier": "标识符",
|
||||
"ip_address": "IP 地址",
|
||||
"client_identifier_desc": "客户端可通过 IP 地址或 MAC 地址识别。请注意,如 AdGuard Home 也是 <0>DHCP 服务器</0>,则仅能将 MAC 用作标识符",
|
||||
"form_enter_ip": "输入 IP",
|
||||
"form_enter_mac": "输入 MAC",
|
||||
"form_client_name": "输入客户端名称",
|
||||
|
||||
@@ -17,9 +17,11 @@
|
||||
"dhcp_leases": "動態主機設定協定(DHCP)租賃",
|
||||
"dhcp_static_leases": "動態主機設定協定(DHCP)靜態租賃",
|
||||
"dhcp_leases_not_found": "無已發現之動態主機設定協定(DHCP)租賃",
|
||||
"dhcp_config_saved": "已儲存動態主機設定協定(DHCP)伺服器配置",
|
||||
"dhcp_config_saved": "動態主機設定協定(DHCP)配置被成功地儲存",
|
||||
"form_error_required": "必填的欄位",
|
||||
"form_error_ip_format": "無效的 IPv4 格式",
|
||||
"form_error_ip4_format": "無效的 IPv4 格式",
|
||||
"form_error_ip6_format": "無效的 IPv6 格式",
|
||||
"form_error_ip_format": "無效的 IP 格式",
|
||||
"form_error_mac_format": "無效的媒體存取控制(MAC)格式",
|
||||
"form_error_positive": "必須大於 0",
|
||||
"dhcp_form_gateway_input": "閘道 IP",
|
||||
@@ -43,6 +45,7 @@
|
||||
"dhcp_new_static_lease": "新的靜態租賃",
|
||||
"dhcp_static_leases_not_found": "無已發現之動態主機設定協定(DHCP)靜態租賃",
|
||||
"dhcp_add_static_lease": "增加靜態租賃",
|
||||
"dhcp_reset": "您確定您想要重置動態主機設定協定(DHCP)配置嗎?",
|
||||
"delete_confirm": "您確定您想要刪除 \"{{key}}\" 嗎?",
|
||||
"form_enter_hostname": "輸入主機名稱",
|
||||
"error_details": "錯誤細節",
|
||||
@@ -96,15 +99,16 @@
|
||||
"use_adguard_parental": "使用 AdGuard 家長監控之網路服務",
|
||||
"use_adguard_parental_hint": "AdGuard Home 將檢查網域是否包含成人資料。它使用如同瀏覽安全網路服務一樣之友好的隱私應用程式介面(API)。",
|
||||
"enforce_safe_search": "強制執行安全搜尋",
|
||||
"enforce_save_search_hint": "AdGuard Home 可在下列的搜尋引擎:Google、YouTube、Bing、DuckDuckGo 和 Yandex 中強制執行安全搜尋。",
|
||||
"enforce_save_search_hint": "AdGuard Home 可在下列的搜尋引擎:Google、YouTube、Bing、DuckDuckGo、Yandex 和 Pixabay 中強制執行安全搜尋。",
|
||||
"no_servers_specified": "無已明確指定的伺服器",
|
||||
"general_settings": "一般的設定",
|
||||
"dns_settings": "DNS 設定",
|
||||
"encryption_settings": "加密設定",
|
||||
"dhcp_settings": "動態主機設定協定(DHCP)設定",
|
||||
"upstream_dns": "上游的 DNS 伺服器",
|
||||
"upstream_dns_hint": "如果您將該欄位留空,AdGuard Home 將使用 <a href='https://1.1.1.1/' target='_blank'>Cloudflare DNS</a> 作為上游。",
|
||||
"upstream_dns_hint": "如果您將該欄位留空,AdGuard Home 將使用 <a href='https://www.quad9.net/' target='_blank'>Quad9</a> 作為上游。",
|
||||
"test_upstream_btn": "測試上行資料流",
|
||||
"upstreams": "上游",
|
||||
"apply_btn": "套用",
|
||||
"disabled_filtering_toast": "已禁用過濾",
|
||||
"enabled_filtering_toast": "已啟用過濾",
|
||||
@@ -279,6 +283,8 @@
|
||||
"update_announcement": "AdGuard Home {{version}} 現為可用的!關於更多的資訊,<0>點擊這裡</0>。",
|
||||
"setup_guide": "設置指南",
|
||||
"dns_addresses": "DNS 位址",
|
||||
"dns_start": "DNS 伺服器正在啟動",
|
||||
"dns_status_error": "取得 DNS 伺服器狀態之錯誤",
|
||||
"down": "停止運作的",
|
||||
"fix": "修復",
|
||||
"dns_providers": "這裡是一個從中選擇之<0>已知的 DNS 供應商之清單</0>。",
|
||||
@@ -297,9 +303,11 @@
|
||||
"client_edit": "編輯用戶端",
|
||||
"client_identifier": "識別碼",
|
||||
"ip_address": "IP 位址",
|
||||
"client_identifier_desc": "用戶端可被 IP 位址或媒體存取控制(MAC)位址識別。請注意,僅若 AdGuard Home 也是<0>動態主機設定協定(DHCP)伺服器</0>,使用 MAC 作為識別碼是可能的",
|
||||
"client_identifier_desc": "用戶端可被 IP 位址、無類別網域間路由(CIDR)或媒體存取控制(MAC)位址識別。請注意,僅若 AdGuard Home 也是<0>動態主機設定協定(DHCP)伺服器</0>,使用 MAC 作為識別碼是可能的",
|
||||
"form_enter_ip": "輸入 IP",
|
||||
"form_enter_mac": "輸入媒體存取控制(MAC)",
|
||||
"form_enter_id": "輸入識別碼",
|
||||
"form_add_id": "增加識別碼",
|
||||
"form_client_name": "輸入用戶端名稱",
|
||||
"client_global_settings": "使用全域的設定",
|
||||
"client_deleted": "用戶端 \"{{key}}\" 被成功地刪除",
|
||||
@@ -400,5 +408,6 @@
|
||||
"netname": "網路名稱",
|
||||
"descr": "說明",
|
||||
"whois": "Whois",
|
||||
"filtering_rules_learn_more": "<0>了解更多</0>有關創建您自己的主機(hosts)封鎖清單。"
|
||||
"filtering_rules_learn_more": "<0>了解更多</0>有關創建您自己的主機(hosts)封鎖清單。",
|
||||
"blocked_by_response": "被正規名稱(CNAME)或 IP 封鎖作為回應"
|
||||
}
|
||||
@@ -2,7 +2,6 @@ import { createAction } from 'redux-actions';
|
||||
import { t } from 'i18next';
|
||||
import apiClient from '../api/Api';
|
||||
import { addErrorToast, addSuccessToast, getClients } from './index';
|
||||
import { CLIENT_ID } from '../helpers/constants';
|
||||
|
||||
export const toggleClientModal = createAction('TOGGLE_CLIENT_MODAL');
|
||||
|
||||
@@ -13,18 +12,7 @@ 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);
|
||||
await apiClient.addClient(config);
|
||||
dispatch(addClientSuccess());
|
||||
dispatch(toggleClientModal());
|
||||
dispatch(addSuccessToast(t('client_added', { key: config.name })));
|
||||
@@ -59,16 +47,7 @@ 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 } };
|
||||
}
|
||||
const data = { name, data: { ...config } };
|
||||
|
||||
await apiClient.updateClient(data);
|
||||
dispatch(updateClientSuccess());
|
||||
|
||||
35
client/src/actions/dnsConfig.js
Normal file
35
client/src/actions/dnsConfig.js
Normal file
@@ -0,0 +1,35 @@
|
||||
import { createAction } from 'redux-actions';
|
||||
|
||||
import apiClient from '../api/Api';
|
||||
import { addErrorToast, addSuccessToast } from './index';
|
||||
|
||||
export const getDnsConfigRequest = createAction('GET_DNS_CONFIG_REQUEST');
|
||||
export const getDnsConfigFailure = createAction('GET_DNS_CONFIG_FAILURE');
|
||||
export const getDnsConfigSuccess = createAction('GET_DNS_CONFIG_SUCCESS');
|
||||
|
||||
export const getDnsConfig = () => async (dispatch) => {
|
||||
dispatch(getDnsConfigRequest());
|
||||
try {
|
||||
const data = await apiClient.getDnsConfig();
|
||||
dispatch(getDnsConfigSuccess(data));
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(getDnsConfigFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const setDnsConfigRequest = createAction('SET_DNS_CONFIG_REQUEST');
|
||||
export const setDnsConfigFailure = createAction('SET_DNS_CONFIG_FAILURE');
|
||||
export const setDnsConfigSuccess = createAction('SET_DNS_CONFIG_SUCCESS');
|
||||
|
||||
export const setDnsConfig = config => async (dispatch) => {
|
||||
dispatch(setDnsConfigRequest());
|
||||
try {
|
||||
await apiClient.setDnsConfig(config);
|
||||
dispatch(addSuccessToast('config_successfully_saved'));
|
||||
dispatch(setDnsConfigSuccess(config));
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(setDnsConfigFailure());
|
||||
}
|
||||
};
|
||||
@@ -91,17 +91,9 @@ 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();
|
||||
}
|
||||
|
||||
const successMessage = status ? 'disabled_protection' : 'enabled_protection';
|
||||
await apiClient.setDnsConfig({ protection_enabled: !status });
|
||||
dispatch(addSuccessToast(successMessage));
|
||||
dispatch(toggleProtectionSuccess());
|
||||
} catch (error) {
|
||||
@@ -140,55 +132,65 @@ export const getUpdateRequest = createAction('GET_UPDATE_REQUEST');
|
||||
export const getUpdateFailure = createAction('GET_UPDATE_FAILURE');
|
||||
export const getUpdateSuccess = createAction('GET_UPDATE_SUCCESS');
|
||||
|
||||
const checkStatus = async (handleRequestSuccess, handleRequestError, attempts = 60) => {
|
||||
let timeout;
|
||||
|
||||
if (attempts === 0) {
|
||||
handleRequestError();
|
||||
}
|
||||
|
||||
const rmTimeout = t => t && clearTimeout(t);
|
||||
|
||||
try {
|
||||
const response = await axios.get('control/status');
|
||||
rmTimeout(timeout);
|
||||
if (response && response.status === 200) {
|
||||
handleRequestSuccess(response);
|
||||
if (response.data.running === false) {
|
||||
timeout = setTimeout(
|
||||
checkStatus,
|
||||
CHECK_TIMEOUT,
|
||||
handleRequestSuccess,
|
||||
handleRequestError,
|
||||
attempts - 1,
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
rmTimeout(timeout);
|
||||
timeout = setTimeout(
|
||||
checkStatus,
|
||||
CHECK_TIMEOUT,
|
||||
handleRequestSuccess,
|
||||
handleRequestError,
|
||||
attempts - 1,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
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) {
|
||||
const handleRequestError = () => {
|
||||
dispatch(addNoticeToast({ error: 'update_failed' }));
|
||||
dispatch(getUpdateFailure());
|
||||
};
|
||||
|
||||
const handleRequestSuccess = (response) => {
|
||||
const responseVersion = response.data && response.data.version;
|
||||
|
||||
if (dnsVersion !== responseVersion) {
|
||||
dispatch(getUpdateSuccess());
|
||||
window.location.reload(true);
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
await apiClient.getUpdate();
|
||||
checkStatus(handleRequestSuccess, handleRequestError);
|
||||
} catch (error) {
|
||||
handleRequestError();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -231,18 +233,34 @@ export const getProfile = () => async (dispatch) => {
|
||||
export const dnsStatusRequest = createAction('DNS_STATUS_REQUEST');
|
||||
export const dnsStatusFailure = createAction('DNS_STATUS_FAILURE');
|
||||
export const dnsStatusSuccess = createAction('DNS_STATUS_SUCCESS');
|
||||
export const setDnsRunningStatus = createAction('SET_DNS_RUNNING_STATUS');
|
||||
|
||||
export const getDnsStatus = () => async (dispatch) => {
|
||||
dispatch(dnsStatusRequest());
|
||||
try {
|
||||
const dnsStatus = await apiClient.getGlobalStatus();
|
||||
dispatch(dnsStatusSuccess(dnsStatus));
|
||||
dispatch(getVersion());
|
||||
dispatch(getTlsStatus());
|
||||
dispatch(getProfile());
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
|
||||
const handleRequestError = () => {
|
||||
dispatch(addErrorToast({ error: 'dns_status_error' }));
|
||||
dispatch(dnsStatusFailure());
|
||||
window.location.reload(true);
|
||||
};
|
||||
const handleRequestSuccess = (response) => {
|
||||
const dnsStatus = response.data;
|
||||
const { running } = dnsStatus;
|
||||
const runningStatus = dnsStatus && running;
|
||||
if (runningStatus === true) {
|
||||
dispatch(dnsStatusSuccess(dnsStatus));
|
||||
dispatch(getVersion());
|
||||
dispatch(getTlsStatus());
|
||||
dispatch(getProfile());
|
||||
} else {
|
||||
dispatch(setDnsRunningStatus(running));
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
checkStatus(handleRequestSuccess, handleRequestError);
|
||||
} catch (error) {
|
||||
handleRequestError(error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -261,36 +279,6 @@ export const getDnsSettings = () => async (dispatch) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const enableDnsRequest = createAction('ENABLE_DNS_REQUEST');
|
||||
export const enableDnsFailure = createAction('ENABLE_DNS_FAILURE');
|
||||
export const enableDnsSuccess = createAction('ENABLE_DNS_SUCCESS');
|
||||
|
||||
export const enableDns = () => async (dispatch) => {
|
||||
dispatch(enableDnsRequest());
|
||||
try {
|
||||
await apiClient.startGlobalFiltering();
|
||||
dispatch(enableDnsSuccess());
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(enableDnsFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const disableDnsRequest = createAction('DISABLE_DNS_REQUEST');
|
||||
export const disableDnsFailure = createAction('DISABLE_DNS_FAILURE');
|
||||
export const disableDnsSuccess = createAction('DISABLE_DNS_SUCCESS');
|
||||
|
||||
export const disableDns = () => async (dispatch) => {
|
||||
dispatch(disableDnsRequest());
|
||||
try {
|
||||
await apiClient.stopGlobalFiltering();
|
||||
dispatch(disableDnsSuccess());
|
||||
} catch (error) {
|
||||
dispatch(disableDnsFailure(error));
|
||||
dispatch(addErrorToast({ error }));
|
||||
}
|
||||
};
|
||||
|
||||
export const handleUpstreamChange = createAction('HANDLE_UPSTREAM_CHANGE');
|
||||
export const setUpstreamRequest = createAction('SET_UPSTREAM_REQUEST');
|
||||
export const setUpstreamFailure = createAction('SET_UPSTREAM_FAILURE');
|
||||
@@ -470,6 +458,22 @@ export const toggleDhcp = values => async (dispatch) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const resetDhcpRequest = createAction('RESET_DHCP_REQUEST');
|
||||
export const resetDhcpSuccess = createAction('RESET_DHCP_SUCCESS');
|
||||
export const resetDhcpFailure = createAction('RESET_DHCP_FAILURE');
|
||||
|
||||
export const resetDhcp = () => async (dispatch) => {
|
||||
dispatch(resetDhcpRequest());
|
||||
try {
|
||||
const status = await apiClient.resetDhcp();
|
||||
dispatch(resetDhcpSuccess(status));
|
||||
dispatch(addSuccessToast('dhcp_config_saved'));
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(resetDhcpFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const toggleLeaseModal = createAction('TOGGLE_LEASE_MODAL');
|
||||
|
||||
export const addStaticLeaseRequest = createAction('ADD_STATIC_LEASE_REQUEST');
|
||||
|
||||
@@ -2,27 +2,104 @@ import { createAction } from 'redux-actions';
|
||||
|
||||
import apiClient from '../api/Api';
|
||||
import { addErrorToast, addSuccessToast } from './index';
|
||||
import { normalizeLogs } from '../helpers/helpers';
|
||||
import { normalizeLogs, getParamsForClientsSearch, addClientInfo } from '../helpers/helpers';
|
||||
import { TABLE_DEFAULT_PAGE_SIZE } from '../helpers/constants';
|
||||
|
||||
const getLogsWithParams = async (config) => {
|
||||
const { older_than, filter, ...values } = config;
|
||||
const rawLogs = await apiClient.getQueryLog({ ...filter, older_than });
|
||||
const { data, oldest } = rawLogs;
|
||||
const logs = normalizeLogs(data);
|
||||
const clientsParams = getParamsForClientsSearch(logs, 'client');
|
||||
const clients = await apiClient.findClients(clientsParams);
|
||||
const logsWithClientInfo = addClientInfo(logs, clients, 'client');
|
||||
|
||||
return {
|
||||
logs: logsWithClientInfo, oldest, older_than, filter, ...values,
|
||||
};
|
||||
};
|
||||
|
||||
export const getAdditionalLogsRequest = createAction('GET_ADDITIONAL_LOGS_REQUEST');
|
||||
export const getAdditionalLogsFailure = createAction('GET_ADDITIONAL_LOGS_FAILURE');
|
||||
export const getAdditionalLogsSuccess = createAction('GET_ADDITIONAL_LOGS_SUCCESS');
|
||||
|
||||
const checkFilteredLogs = async (data, filter, dispatch, total) => {
|
||||
const { logs, oldest } = data;
|
||||
const totalData = total || { logs };
|
||||
|
||||
const needToGetAdditionalLogs = (logs.length < TABLE_DEFAULT_PAGE_SIZE ||
|
||||
totalData.logs.length < TABLE_DEFAULT_PAGE_SIZE) &&
|
||||
oldest !== '';
|
||||
|
||||
if (needToGetAdditionalLogs) {
|
||||
dispatch(getAdditionalLogsRequest());
|
||||
|
||||
try {
|
||||
const additionalLogs = await getLogsWithParams({ older_than: oldest, filter });
|
||||
if (additionalLogs.logs.length > 0) {
|
||||
return await checkFilteredLogs(additionalLogs, filter, dispatch, {
|
||||
logs: [...totalData.logs, ...additionalLogs.logs],
|
||||
oldest: additionalLogs.oldest,
|
||||
});
|
||||
}
|
||||
dispatch(getAdditionalLogsSuccess());
|
||||
return totalData;
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(getAdditionalLogsFailure(error));
|
||||
}
|
||||
}
|
||||
|
||||
dispatch(getAdditionalLogsSuccess());
|
||||
return totalData;
|
||||
};
|
||||
|
||||
export const setLogsPagination = createAction('LOGS_PAGINATION');
|
||||
export const setLogsFilter = createAction('LOGS_FILTER');
|
||||
export const setLogsPage = createAction('SET_LOG_PAGE');
|
||||
|
||||
export const getLogsRequest = createAction('GET_LOGS_REQUEST');
|
||||
export const getLogsFailure = createAction('GET_LOGS_FAILURE');
|
||||
export const getLogsSuccess = createAction('GET_LOGS_SUCCESS');
|
||||
|
||||
export const getLogs = config => async (dispatch) => {
|
||||
export const getLogs = config => async (dispatch, getState) => {
|
||||
dispatch(getLogsRequest());
|
||||
try {
|
||||
const { filter, lastRowTime: older_than } = config;
|
||||
const logs = normalizeLogs(await apiClient.getQueryLog({ ...filter, older_than }));
|
||||
dispatch(getLogsSuccess({ logs, ...config }));
|
||||
const { isFiltered, filter, page } = getState().queryLogs;
|
||||
const data = await getLogsWithParams({ ...config, filter });
|
||||
|
||||
if (isFiltered) {
|
||||
const additionalData = await checkFilteredLogs(data, filter, dispatch);
|
||||
const updatedData = additionalData.logs ? { ...data, ...additionalData } : data;
|
||||
dispatch(getLogsSuccess(updatedData));
|
||||
dispatch(setLogsPagination({ page, pageSize: TABLE_DEFAULT_PAGE_SIZE }));
|
||||
} else {
|
||||
dispatch(getLogsSuccess(data));
|
||||
}
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(getLogsFailure(error));
|
||||
}
|
||||
};
|
||||
|
||||
export const setLogsFilterRequest = createAction('SET_LOGS_FILTER_REQUEST');
|
||||
export const setLogsFilterFailure = createAction('SET_LOGS_FILTER_FAILURE');
|
||||
export const setLogsFilterSuccess = createAction('SET_LOGS_FILTER_SUCCESS');
|
||||
|
||||
export const setLogsFilter = filter => async (dispatch) => {
|
||||
dispatch(setLogsFilterRequest());
|
||||
try {
|
||||
const data = await getLogsWithParams({ older_than: '', filter });
|
||||
const additionalData = await checkFilteredLogs(data, filter, dispatch);
|
||||
const updatedData = additionalData.logs ? { ...data, ...additionalData } : data;
|
||||
|
||||
dispatch(setLogsFilterSuccess({ ...updatedData, filter }));
|
||||
dispatch(setLogsPage(0));
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(setLogsFilterFailure(error));
|
||||
}
|
||||
};
|
||||
|
||||
export const clearLogsRequest = createAction('CLEAR_LOGS_REQUEST');
|
||||
export const clearLogsFailure = createAction('CLEAR_LOGS_FAILURE');
|
||||
export const clearLogsSuccess = createAction('CLEAR_LOGS_SUCCESS');
|
||||
|
||||
@@ -2,7 +2,7 @@ import { createAction } from 'redux-actions';
|
||||
|
||||
import apiClient from '../api/Api';
|
||||
import { addErrorToast, addSuccessToast } from './index';
|
||||
import { normalizeTopStats, secondsToMilliseconds } from '../helpers/helpers';
|
||||
import { normalizeTopStats, secondsToMilliseconds, getParamsForClientsSearch, addClientInfo } from '../helpers/helpers';
|
||||
|
||||
export const getStatsConfigRequest = createAction('GET_STATS_CONFIG_REQUEST');
|
||||
export const getStatsConfigFailure = createAction('GET_STATS_CONFIG_FAILURE');
|
||||
@@ -43,11 +43,15 @@ export const getStats = () => async (dispatch) => {
|
||||
dispatch(getStatsRequest());
|
||||
try {
|
||||
const stats = await apiClient.getStats();
|
||||
const normalizedTopClients = normalizeTopStats(stats.top_clients);
|
||||
const clientsParams = getParamsForClientsSearch(normalizedTopClients, 'name');
|
||||
const clients = await apiClient.findClients(clientsParams);
|
||||
const topClientsWithInfo = addClientInfo(normalizedTopClients, clients, 'name');
|
||||
|
||||
const normalizedStats = {
|
||||
...stats,
|
||||
top_blocked_domains: normalizeTopStats(stats.top_blocked_domains),
|
||||
top_clients: normalizeTopStats(stats.top_clients),
|
||||
top_clients: topClientsWithInfo,
|
||||
top_queried_domains: normalizeTopStats(stats.top_queried_domains),
|
||||
avg_processing_time: secondsToMilliseconds(stats.avg_processing_time),
|
||||
};
|
||||
|
||||
@@ -24,26 +24,12 @@ class Api {
|
||||
}
|
||||
|
||||
// Global methods
|
||||
GLOBAL_START = { path: 'start', method: 'POST' };
|
||||
GLOBAL_STATUS = { path: 'status', method: 'GET' };
|
||||
GLOBAL_STOP = { path: 'stop', 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' };
|
||||
|
||||
startGlobalFiltering() {
|
||||
const { path, method } = this.GLOBAL_START;
|
||||
return this.makeRequest(path, method);
|
||||
}
|
||||
|
||||
stopGlobalFiltering() {
|
||||
const { path, method } = this.GLOBAL_STOP;
|
||||
return this.makeRequest(path, method);
|
||||
}
|
||||
|
||||
getGlobalStatus() {
|
||||
const { path, method } = this.GLOBAL_STATUS;
|
||||
return this.makeRequest(path, method);
|
||||
@@ -76,16 +62,6 @@ class Api {
|
||||
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);
|
||||
@@ -248,6 +224,7 @@ class Api {
|
||||
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' };
|
||||
DHCP_RESET = { path: 'dhcp/reset', method: 'POST' };
|
||||
|
||||
getDhcpStatus() {
|
||||
const { path, method } = this.DHCP_STATUS;
|
||||
@@ -295,6 +272,11 @@ class Api {
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
|
||||
resetDhcp() {
|
||||
const { path, method } = this.DHCP_RESET;
|
||||
return this.makeRequest(path, method);
|
||||
}
|
||||
|
||||
// Installation
|
||||
INSTALL_GET_ADDRESSES = { path: 'install/get_addresses', method: 'GET' };
|
||||
INSTALL_CONFIGURE = { path: 'install/configure', method: 'POST' };
|
||||
@@ -353,6 +335,7 @@ class Api {
|
||||
|
||||
// Per-client settings
|
||||
GET_CLIENTS = { path: 'clients', method: 'GET' };
|
||||
FIND_CLIENTS = { path: 'clients/find', method: 'GET' };
|
||||
ADD_CLIENT = { path: 'clients/add', method: 'POST' };
|
||||
DELETE_CLIENT = { path: 'clients/delete', method: 'POST' };
|
||||
UPDATE_CLIENT = { path: 'clients/update', method: 'POST' };
|
||||
@@ -389,6 +372,12 @@ class Api {
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
|
||||
findClients(params) {
|
||||
const { path, method } = this.FIND_CLIENTS;
|
||||
const url = getPathWithQueryString(path, params);
|
||||
return this.makeRequest(url, method);
|
||||
}
|
||||
|
||||
// DNS access settings
|
||||
ACCESS_LIST = { path: 'access/list', method: 'GET' };
|
||||
ACCESS_SET = { path: 'access/set', method: 'POST' };
|
||||
@@ -533,6 +522,24 @@ class Api {
|
||||
const { path, method } = this.GET_PROFILE;
|
||||
return this.makeRequest(path, method);
|
||||
}
|
||||
|
||||
// DNS config
|
||||
GET_DNS_CONFIG = { path: 'dns_info', method: 'GET' };
|
||||
SET_DNS_CONFIG = { path: 'dns_config', method: 'POST' };
|
||||
|
||||
getDnsConfig() {
|
||||
const { path, method } = this.GET_DNS_CONFIG;
|
||||
return this.makeRequest(path, method);
|
||||
}
|
||||
|
||||
setDnsConfig(data) {
|
||||
const { path, method } = this.SET_DNS_CONFIG;
|
||||
const config = {
|
||||
data,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, config);
|
||||
}
|
||||
}
|
||||
|
||||
const apiClient = new Api();
|
||||
|
||||
@@ -29,6 +29,7 @@ import UpdateOverlay from '../ui/UpdateOverlay';
|
||||
import EncryptionTopline from '../ui/EncryptionTopline';
|
||||
import Icons from '../ui/Icons';
|
||||
import i18n from '../../i18n';
|
||||
import Loading from '../ui/Loading';
|
||||
|
||||
class App extends Component {
|
||||
componentDidMount() {
|
||||
@@ -41,8 +42,8 @@ class App extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleStatusChange = () => {
|
||||
this.props.enableDns();
|
||||
reloadPage = () => {
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
handleUpdate = () => {
|
||||
@@ -88,10 +89,14 @@ class App extends Component {
|
||||
<LoadingBar className="loading-bar" updateTime={1000} />
|
||||
<Route component={Header} />
|
||||
<div className="container container--wrap">
|
||||
{!dashboard.processing && !dashboard.isCoreRunning && (
|
||||
{dashboard.processing && <Loading />}
|
||||
{!dashboard.isCoreRunning && (
|
||||
<div className="row row-cards">
|
||||
<div className="col-lg-12">
|
||||
<Status handleStatusChange={this.handleStatusChange} />
|
||||
<Status reloadPage={this.reloadPage}
|
||||
message="dns_start"
|
||||
/>
|
||||
<Loading />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -28,19 +28,17 @@ const countCell = dnsQueries =>
|
||||
return <Cell value={value} percent={percent} color={percentColor} />;
|
||||
};
|
||||
|
||||
const clientCell = (clients, autoClients, t) =>
|
||||
const clientCell = t =>
|
||||
function cell(row) {
|
||||
const { value } = row;
|
||||
|
||||
return (
|
||||
<div className="logs__row logs__row--overflow logs__row--column">
|
||||
{formatClientCell(value, clients, autoClients, t)}
|
||||
{formatClientCell(row, t)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Clients = ({
|
||||
t, refreshButton, topClients, subtitle, clients, autoClients, dnsQueries,
|
||||
t, refreshButton, topClients, subtitle, dnsQueries,
|
||||
}) => (
|
||||
<Card
|
||||
title={t('top_clients')}
|
||||
@@ -49,9 +47,10 @@ const Clients = ({
|
||||
refresh={refreshButton}
|
||||
>
|
||||
<ReactTable
|
||||
data={topClients.map(({ name: ip, count }) => ({
|
||||
data={topClients.map(({ name: ip, count, info }) => ({
|
||||
ip,
|
||||
count,
|
||||
info,
|
||||
}))}
|
||||
columns={[
|
||||
{
|
||||
@@ -59,7 +58,7 @@ const Clients = ({
|
||||
accessor: 'ip',
|
||||
sortMethod: (a, b) =>
|
||||
parseInt(a.replace(/\./g, ''), 10) - parseInt(b.replace(/\./g, ''), 10),
|
||||
Cell: clientCell(clients, autoClients, t),
|
||||
Cell: clientCell(t),
|
||||
},
|
||||
{
|
||||
Header: <Trans>requests_count</Trans>,
|
||||
|
||||
@@ -20,7 +20,6 @@ class Dashboard extends Component {
|
||||
getAllStats = () => {
|
||||
this.props.getStats();
|
||||
this.props.getStatsConfig();
|
||||
this.props.getClients();
|
||||
};
|
||||
|
||||
getToggleFilteringButton = () => {
|
||||
@@ -42,11 +41,7 @@ class Dashboard extends Component {
|
||||
|
||||
render() {
|
||||
const { dashboard, stats, t } = this.props;
|
||||
const dashboardProcessing =
|
||||
dashboard.processing ||
|
||||
dashboard.processingClients ||
|
||||
stats.processingStats ||
|
||||
stats.processingGetConfig;
|
||||
const statsProcessing = stats.processingStats || stats.processingGetConfig;
|
||||
|
||||
const subtitle =
|
||||
stats.interval === 1
|
||||
@@ -83,8 +78,8 @@ class Dashboard extends Component {
|
||||
{refreshFullButton}
|
||||
</div>
|
||||
</PageTitle>
|
||||
{dashboardProcessing && <Loading />}
|
||||
{!dashboardProcessing && (
|
||||
{statsProcessing && <Loading />}
|
||||
{!statsProcessing && (
|
||||
<div className="row row-cards">
|
||||
<div className="col-lg-12">
|
||||
<Statistics
|
||||
|
||||
@@ -153,7 +153,7 @@ class Filters extends Component {
|
||||
nextText={t('next_btn')}
|
||||
loadingText={t('loading_table_status')}
|
||||
pageText={t('page_table_footer_text')}
|
||||
ofText={t('of_table_footer_text')}
|
||||
ofText="/"
|
||||
rowsText={t('rows_table_footer_text')}
|
||||
noDataText={t('no_filters_added')}
|
||||
/>
|
||||
|
||||
116
client/src/components/Logs/Filters/Form.js
Normal file
116
client/src/components/Logs/Filters/Form.js
Normal file
@@ -0,0 +1,116 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Field, reduxForm } from 'redux-form';
|
||||
import { withNamespaces, Trans } from 'react-i18next';
|
||||
import flow from 'lodash/flow';
|
||||
|
||||
import { renderField } from '../../../helpers/form';
|
||||
import { RESPONSE_FILTER } from '../../../helpers/constants';
|
||||
import Tooltip from '../../ui/Tooltip';
|
||||
|
||||
const renderFilterField = ({
|
||||
input,
|
||||
id,
|
||||
className,
|
||||
placeholder,
|
||||
type,
|
||||
disabled,
|
||||
autoComplete,
|
||||
tooltip,
|
||||
meta: { touched, error },
|
||||
}) => (
|
||||
<Fragment>
|
||||
<div className="logs__input-wrap">
|
||||
<input
|
||||
{...input}
|
||||
id={id}
|
||||
placeholder={placeholder}
|
||||
type={type}
|
||||
className={className}
|
||||
disabled={disabled}
|
||||
autoComplete={autoComplete}
|
||||
/>
|
||||
<span className="logs__notice">
|
||||
<Tooltip text={tooltip} type='tooltip-custom--logs' />
|
||||
</span>
|
||||
{!disabled &&
|
||||
touched &&
|
||||
(error && <span className="form__message form__message--error">{error}</span>)}
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
const Form = (props) => {
|
||||
const {
|
||||
t,
|
||||
handleChange,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<form onSubmit={handleChange}>
|
||||
<div className="row">
|
||||
<div className="col-6 col-sm-3 my-2">
|
||||
<Field
|
||||
id="filter_domain"
|
||||
name="filter_domain"
|
||||
component={renderFilterField}
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder={t('domain_name_table_header')}
|
||||
tooltip={t('query_log_strict_search')}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-6 col-sm-3 my-2">
|
||||
<Field
|
||||
id="filter_question_type"
|
||||
name="filter_question_type"
|
||||
component={renderField}
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder={t('type_table_header')}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-6 col-sm-3 my-2">
|
||||
<Field
|
||||
name="filter_response_status"
|
||||
component="select"
|
||||
className="form-control custom-select"
|
||||
>
|
||||
<option value={RESPONSE_FILTER.ALL}>
|
||||
<Trans>show_all_filter_type</Trans>
|
||||
</option>
|
||||
<option value={RESPONSE_FILTER.FILTERED}>
|
||||
<Trans>show_filtered_type</Trans>
|
||||
</option>
|
||||
</Field>
|
||||
</div>
|
||||
<div className="col-6 col-sm-3 my-2">
|
||||
<Field
|
||||
id="filter_client"
|
||||
name="filter_client"
|
||||
component={renderFilterField}
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder={t('client_table_header')}
|
||||
tooltip={t('query_log_strict_search')}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
Form.propTypes = {
|
||||
handleChange: PropTypes.func,
|
||||
t: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default flow([
|
||||
withNamespaces(),
|
||||
reduxForm({
|
||||
form: 'logsFilterForm',
|
||||
}),
|
||||
])(Form);
|
||||
52
client/src/components/Logs/Filters/index.js
Normal file
52
client/src/components/Logs/Filters/index.js
Normal file
@@ -0,0 +1,52 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import debounce from 'lodash/debounce';
|
||||
import classnames from 'classnames';
|
||||
|
||||
import { DEBOUNCE_FILTER_TIMEOUT, RESPONSE_FILTER } from '../../../helpers/constants';
|
||||
import { isValidQuestionType } from '../../../helpers/helpers';
|
||||
import Form from './Form';
|
||||
import Card from '../../ui/Card';
|
||||
|
||||
class Filters extends Component {
|
||||
getFilters = ({
|
||||
filter_domain, filter_question_type, filter_response_status, filter_client,
|
||||
}) => ({
|
||||
filter_domain: filter_domain || '',
|
||||
filter_question_type: isValidQuestionType(filter_question_type) ? filter_question_type.toUpperCase() : '',
|
||||
filter_response_status: filter_response_status === RESPONSE_FILTER.FILTERED ? filter_response_status : '',
|
||||
filter_client: filter_client || '',
|
||||
});
|
||||
|
||||
handleFormChange = debounce((values) => {
|
||||
const filter = this.getFilters(values);
|
||||
this.props.setLogsFilter(filter);
|
||||
}, DEBOUNCE_FILTER_TIMEOUT);
|
||||
|
||||
render() {
|
||||
const { filter, processingAdditionalLogs } = this.props;
|
||||
|
||||
const cardBodyClass = classnames({
|
||||
'card-body': true,
|
||||
'card-body--loading': processingAdditionalLogs,
|
||||
});
|
||||
|
||||
return (
|
||||
<Card bodyType={cardBodyClass}>
|
||||
<Form
|
||||
initialValues={filter}
|
||||
onChange={this.handleFormChange}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Filters.propTypes = {
|
||||
filter: PropTypes.object.isRequired,
|
||||
setLogsFilter: PropTypes.func.isRequired,
|
||||
processingGetLogs: PropTypes.bool.isRequired,
|
||||
processingAdditionalLogs: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default Filters;
|
||||
@@ -5,46 +5,39 @@ import escapeRegExp from 'lodash/escapeRegExp';
|
||||
import endsWith from 'lodash/endsWith';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
import { HashLink as Link } from 'react-router-hash-link';
|
||||
import debounce from 'lodash/debounce';
|
||||
|
||||
import {
|
||||
formatTime,
|
||||
formatDateTime,
|
||||
isValidQuestionType,
|
||||
} from '../../helpers/helpers';
|
||||
import { SERVICES, FILTERED_STATUS, DEBOUNCE_TIMEOUT, DEFAULT_LOGS_FILTER } from '../../helpers/constants';
|
||||
import { SERVICES, FILTERED_STATUS, TABLE_DEFAULT_PAGE_SIZE } from '../../helpers/constants';
|
||||
import { getTrackerData } from '../../helpers/trackers/trackers';
|
||||
import { formatClientCell } from '../../helpers/formatClientCell';
|
||||
|
||||
import Filters from './Filters';
|
||||
import PageTitle from '../ui/PageTitle';
|
||||
import Card from '../ui/Card';
|
||||
import Loading from '../ui/Loading';
|
||||
import PopoverFiltered from '../ui/PopoverFilter';
|
||||
import Popover from '../ui/Popover';
|
||||
import Tooltip from '../ui/Tooltip';
|
||||
import './Logs.css';
|
||||
|
||||
const TABLE_FIRST_PAGE = 0;
|
||||
const TABLE_DEFAULT_PAGE_SIZE = 50;
|
||||
const INITIAL_REQUEST_DATA = ['', DEFAULT_LOGS_FILTER, TABLE_FIRST_PAGE, TABLE_DEFAULT_PAGE_SIZE];
|
||||
const INITIAL_REQUEST_DATA = ['', TABLE_FIRST_PAGE, TABLE_DEFAULT_PAGE_SIZE];
|
||||
const FILTERED_REASON = 'Filtered';
|
||||
const RESPONSE_FILTER = {
|
||||
ALL: 'all',
|
||||
FILTERED: 'filtered',
|
||||
};
|
||||
|
||||
class Logs extends Component {
|
||||
componentDidMount() {
|
||||
this.props.setLogsPage(TABLE_FIRST_PAGE);
|
||||
this.getLogs(...INITIAL_REQUEST_DATA);
|
||||
this.props.getFilteringStatus();
|
||||
this.props.getClients();
|
||||
this.props.getLogsConfig();
|
||||
}
|
||||
|
||||
getLogs = (lastRowTime, filter, page, pageSize, filtered) => {
|
||||
getLogs = (older_than, page) => {
|
||||
if (this.props.queryLogs.enabled) {
|
||||
this.props.getLogs({
|
||||
lastRowTime, filter, page, pageSize, filtered,
|
||||
older_than, page, pageSize: TABLE_DEFAULT_PAGE_SIZE,
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -53,16 +46,6 @@ class Logs extends Component {
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
handleLogsFiltering = debounce((lastRowTime, filter, page, pageSize, filtered) => {
|
||||
this.props.getLogs({
|
||||
lastRowTime,
|
||||
filter,
|
||||
page,
|
||||
pageSize,
|
||||
filtered,
|
||||
});
|
||||
}, DEBOUNCE_TIMEOUT);
|
||||
|
||||
renderTooltip = (isFiltered, rule, filter, service) =>
|
||||
isFiltered && <PopoverFiltered rule={rule} filter={filter} service={service} />;
|
||||
|
||||
@@ -151,9 +134,16 @@ class Logs extends Component {
|
||||
);
|
||||
};
|
||||
|
||||
normalizeResponse = response => (
|
||||
response.map((response) => {
|
||||
const { value, type, ttl } = response;
|
||||
return `${type}: ${value} (ttl=${ttl})`;
|
||||
})
|
||||
);
|
||||
|
||||
getResponseCell = ({ value: responses, original }) => {
|
||||
const {
|
||||
reason, filterId, rule, status,
|
||||
reason, filterId, rule, status, originalAnswer,
|
||||
} = original;
|
||||
const { t, filtering } = this.props;
|
||||
const { filters } = filtering;
|
||||
@@ -166,6 +156,7 @@ class Logs extends Component {
|
||||
const isBlockedService = reason === FILTERED_STATUS.FILTERED_BLOCKED_SERVICE;
|
||||
const currentService = SERVICES.find(service => service.id === original.serviceName);
|
||||
const serviceName = currentService && currentService.name;
|
||||
const normalizedAnswer = originalAnswer && this.normalizeResponse(originalAnswer);
|
||||
let filterName = '';
|
||||
|
||||
if (filterId === 0) {
|
||||
@@ -185,7 +176,12 @@ class Logs extends Component {
|
||||
return (
|
||||
<div className="logs__row logs__row--column">
|
||||
<div className="logs__text-wrap">
|
||||
{(isFiltered || isBlockedService) && (
|
||||
{originalAnswer && (
|
||||
<span className="logs__text">
|
||||
<Trans>blocked_by_response</Trans>
|
||||
</span>
|
||||
)}
|
||||
{!originalAnswer && (isFiltered || isBlockedService) && (
|
||||
<span className="logs__text" title={parsedFilteredReason}>
|
||||
{parsedFilteredReason}
|
||||
</span>
|
||||
@@ -200,16 +196,19 @@ class Logs extends Component {
|
||||
)}
|
||||
</div>
|
||||
<div className="logs__list-wrap">
|
||||
{this.renderResponseList(responses, status)}
|
||||
{originalAnswer
|
||||
? this.renderResponseList(normalizedAnswer, status)
|
||||
: this.renderResponseList(responses, status)
|
||||
}
|
||||
{isWhiteList && this.renderTooltip(isWhiteList, rule, filterName)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
getClientCell = ({ original, value }) => {
|
||||
const { dashboard, t } = this.props;
|
||||
const { clients, autoClients } = dashboard;
|
||||
getClientCell = (row) => {
|
||||
const { original } = row;
|
||||
const { t } = this.props;
|
||||
const { reason, domain } = original;
|
||||
const isFiltered = this.checkFiltered(reason);
|
||||
const isRewrite = this.checkRewrite(reason);
|
||||
@@ -217,7 +216,7 @@ class Logs extends Component {
|
||||
return (
|
||||
<Fragment>
|
||||
<div className="logs__row logs__row--overflow logs__row--column">
|
||||
{formatClientCell(value, clients, autoClients, t)}
|
||||
{formatClientCell(row, t)}
|
||||
</div>
|
||||
{isRewrite ? (
|
||||
<div className="logs__action">
|
||||
@@ -232,81 +231,33 @@ class Logs extends Component {
|
||||
);
|
||||
};
|
||||
|
||||
getFilterInput = ({ filter, onChange }) => (
|
||||
<Fragment>
|
||||
<div className="logs__input-wrap">
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
onChange={event => onChange(event.target.value)}
|
||||
value={filter ? filter.value : ''}
|
||||
/>
|
||||
<span className="logs__notice">
|
||||
<Tooltip text={this.props.t('query_log_strict_search')} type='tooltip-custom--logs' />
|
||||
</span>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
getFilters = (filtered) => {
|
||||
const filteredObj = filtered.reduce((acc, cur) => ({ ...acc, [cur.id]: cur.value }), {});
|
||||
const {
|
||||
domain, client, type, response,
|
||||
} = filteredObj;
|
||||
|
||||
return {
|
||||
filter_domain: domain || '',
|
||||
filter_client: client || '',
|
||||
filter_question_type: isValidQuestionType(type) ? type.toUpperCase() : '',
|
||||
filter_response_status: response === RESPONSE_FILTER.FILTERED ? response : '',
|
||||
};
|
||||
};
|
||||
|
||||
fetchData = (state) => {
|
||||
const { pageSize, page, pages } = state;
|
||||
const { allLogs, filter } = this.props.queryLogs;
|
||||
const { pages } = state;
|
||||
const { oldest, page } = this.props.queryLogs;
|
||||
const isLastPage = pages && (page + 1 === pages);
|
||||
|
||||
if (isLastPage) {
|
||||
const lastRow = allLogs[allLogs.length - 1];
|
||||
const lastRowTime = (lastRow && lastRow.time) || '';
|
||||
this.getLogs(lastRowTime, filter, page, pageSize, true);
|
||||
} else {
|
||||
this.props.setLogsPagination({ page, pageSize });
|
||||
this.getLogs(oldest, page);
|
||||
}
|
||||
};
|
||||
|
||||
handleFilterChange = (filtered) => {
|
||||
const filters = this.getFilters(filtered);
|
||||
this.props.setLogsFilter(filters);
|
||||
this.handleLogsFiltering('', filters, TABLE_FIRST_PAGE, TABLE_DEFAULT_PAGE_SIZE, true);
|
||||
}
|
||||
|
||||
showTotalPagesCount = (pages) => {
|
||||
const { total, isEntireLog } = this.props.queryLogs;
|
||||
const showEllipsis = !isEntireLog && total >= 500;
|
||||
|
||||
return (
|
||||
<span className="-totalPages">
|
||||
{pages || 1}{showEllipsis && '…' }
|
||||
</span>
|
||||
);
|
||||
}
|
||||
changePage = (page) => {
|
||||
this.props.setLogsPage(page);
|
||||
this.props.setLogsPagination({ page, pageSize: TABLE_DEFAULT_PAGE_SIZE });
|
||||
};
|
||||
|
||||
renderLogs() {
|
||||
const { queryLogs, dashboard, t } = this.props;
|
||||
const { processingClients } = dashboard;
|
||||
const { queryLogs, t } = this.props;
|
||||
const {
|
||||
processingGetLogs, processingGetConfig, logs, pages,
|
||||
processingGetLogs, processingGetConfig, logs, pages, page,
|
||||
} = queryLogs;
|
||||
const isLoading = processingGetLogs || processingClients || processingGetConfig;
|
||||
const isLoading = processingGetLogs || processingGetConfig;
|
||||
|
||||
const columns = [
|
||||
{
|
||||
Header: t('time_table_header'),
|
||||
accessor: 'time',
|
||||
maxWidth: 100,
|
||||
filterable: false,
|
||||
Cell: this.getTimeCell,
|
||||
},
|
||||
{
|
||||
@@ -314,7 +265,6 @@ class Logs extends Component {
|
||||
accessor: 'domain',
|
||||
minWidth: 180,
|
||||
Cell: this.getDomainCell,
|
||||
Filter: this.getFilterInput,
|
||||
},
|
||||
{
|
||||
Header: t('type_table_header'),
|
||||
@@ -326,28 +276,6 @@ class Logs extends Component {
|
||||
accessor: 'response',
|
||||
minWidth: 250,
|
||||
Cell: this.getResponseCell,
|
||||
filterMethod: (filter, row) => {
|
||||
if (filter.value === RESPONSE_FILTER.FILTERED) {
|
||||
// eslint-disable-next-line no-underscore-dangle
|
||||
const { reason } = row._original;
|
||||
return this.checkFiltered(reason) || this.checkWhiteList(reason);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
Filter: ({ filter, onChange }) => (
|
||||
<select
|
||||
className="form-control custom-select"
|
||||
onChange={event => onChange(event.target.value)}
|
||||
value={filter ? filter.value : RESPONSE_FILTER.ALL}
|
||||
>
|
||||
<option value={RESPONSE_FILTER.ALL}>
|
||||
<Trans>show_all_filter_type</Trans>
|
||||
</option>
|
||||
<option value={RESPONSE_FILTER.FILTERED}>
|
||||
<Trans>show_filtered_type</Trans>
|
||||
</option>
|
||||
</select>
|
||||
),
|
||||
},
|
||||
{
|
||||
Header: t('client_table_header'),
|
||||
@@ -355,34 +283,36 @@ class Logs extends Component {
|
||||
maxWidth: 240,
|
||||
minWidth: 240,
|
||||
Cell: this.getClientCell,
|
||||
Filter: this.getFilterInput,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<ReactTable
|
||||
manual
|
||||
filterable
|
||||
minRows={5}
|
||||
page={page}
|
||||
pages={pages}
|
||||
columns={columns}
|
||||
filterable={false}
|
||||
sortable={false}
|
||||
data={logs || []}
|
||||
loading={isLoading}
|
||||
showPageJump={false}
|
||||
onFetchData={this.fetchData}
|
||||
onFilteredChange={this.handleFilterChange}
|
||||
className="logs__table"
|
||||
showPagination={true}
|
||||
showPaginationTop={true}
|
||||
showPageJump={false}
|
||||
showPageSizeOptions={false}
|
||||
onFetchData={this.fetchData}
|
||||
onPageChange={this.changePage}
|
||||
className="logs__table"
|
||||
defaultPageSize={TABLE_DEFAULT_PAGE_SIZE}
|
||||
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')}
|
||||
renderTotalPagesCount={this.showTotalPagesCount}
|
||||
pageText={''}
|
||||
ofText={''}
|
||||
renderTotalPagesCount={() => false}
|
||||
defaultFilterMethod={(filter, row) => {
|
||||
const id = filter.pivotId || filter.id;
|
||||
return row[id] !== undefined
|
||||
@@ -426,7 +356,9 @@ class Logs extends Component {
|
||||
|
||||
render() {
|
||||
const { queryLogs, t } = this.props;
|
||||
const { enabled, processingGetConfig } = queryLogs;
|
||||
const {
|
||||
enabled, processingGetConfig, processingAdditionalLogs, processingGetLogs,
|
||||
} = queryLogs;
|
||||
|
||||
const refreshButton = enabled ? (
|
||||
<button
|
||||
@@ -446,7 +378,17 @@ class Logs extends Component {
|
||||
<Fragment>
|
||||
<PageTitle title={t('query_log')}>{refreshButton}</PageTitle>
|
||||
{enabled && processingGetConfig && <Loading />}
|
||||
{enabled && !processingGetConfig && <Card>{this.renderLogs()}</Card>}
|
||||
{enabled && !processingGetConfig && (
|
||||
<Fragment>
|
||||
<Filters
|
||||
filter={queryLogs.filter}
|
||||
processingGetLogs={processingGetLogs}
|
||||
processingAdditionalLogs={processingAdditionalLogs}
|
||||
setLogsFilter={this.props.setLogsFilter}
|
||||
/>
|
||||
<Card>{this.renderLogs()}</Card>
|
||||
</Fragment>
|
||||
)}
|
||||
{!enabled && !processingGetConfig && (
|
||||
<Card>
|
||||
<div className="lead text-center py-6">
|
||||
@@ -479,6 +421,7 @@ Logs.propTypes = {
|
||||
getLogsConfig: PropTypes.func.isRequired,
|
||||
setLogsPagination: PropTypes.func.isRequired,
|
||||
setLogsFilter: PropTypes.func.isRequired,
|
||||
setLogsPage: PropTypes.func.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ class AutoClients extends Component {
|
||||
nextText={t('next_btn')}
|
||||
loadingText={t('loading_table_status')}
|
||||
pageText={t('page_table_footer_text')}
|
||||
ofText={t('of_table_footer_text')}
|
||||
ofText="/"
|
||||
rowsText={t('rows_table_footer_text')}
|
||||
noDataText={t('clients_not_found')}
|
||||
/>
|
||||
|
||||
@@ -3,7 +3,8 @@ 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 { MODAL_TYPE } from '../../../helpers/constants';
|
||||
import { normalizeTextarea } from '../../../helpers/helpers';
|
||||
import Card from '../../ui/Card';
|
||||
import Modal from './Modal';
|
||||
import WrapCell from './WrapCell';
|
||||
@@ -20,13 +21,20 @@ class ClientsTable extends Component {
|
||||
};
|
||||
|
||||
handleSubmit = (values) => {
|
||||
let config = values;
|
||||
const config = values;
|
||||
|
||||
if (values && values.blocked_services) {
|
||||
const blocked_services = Object
|
||||
.keys(values.blocked_services)
|
||||
.filter(service => values.blocked_services[service]);
|
||||
config = { ...values, blocked_services };
|
||||
if (values) {
|
||||
if (values.blocked_services) {
|
||||
config.blocked_services = Object
|
||||
.keys(values.blocked_services)
|
||||
.filter(service => values.blocked_services[service]);
|
||||
}
|
||||
|
||||
if (values.upstreams && typeof values.upstreams === 'string') {
|
||||
config.upstreams = normalizeTextarea(values.upstreams);
|
||||
} else {
|
||||
config.upstreams = [];
|
||||
}
|
||||
}
|
||||
|
||||
if (this.props.modalType === MODAL_TYPE.EDIT) {
|
||||
@@ -40,27 +48,24 @@ class ClientsTable extends Component {
|
||||
const client = clients.find(item => name === item.name);
|
||||
|
||||
if (client) {
|
||||
const identifier = client.mac ? CLIENT_ID.MAC : CLIENT_ID.IP;
|
||||
|
||||
const { upstreams, whois_info, ...values } = client;
|
||||
return {
|
||||
identifier,
|
||||
use_global_settings: true,
|
||||
use_global_blocked_services: true,
|
||||
...client,
|
||||
upstreams: (upstreams && upstreams.join('\n')) || '',
|
||||
...values,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
identifier: CLIENT_ID.IP,
|
||||
ids: [''],
|
||||
use_global_settings: true,
|
||||
use_global_blocked_services: true,
|
||||
};
|
||||
};
|
||||
|
||||
getStats = (ip, stats) => {
|
||||
getStats = (name, stats) => {
|
||||
if (stats) {
|
||||
const statsForCurrentIP = stats.find(item => item.name === ip);
|
||||
return statsForCurrentIP && statsForCurrentIP.count;
|
||||
const currentStats = stats.find(item => item.info && item.info.name === name);
|
||||
return currentStats && currentStats.count;
|
||||
}
|
||||
|
||||
return '';
|
||||
@@ -76,28 +81,22 @@ class ClientsTable extends Component {
|
||||
columns = [
|
||||
{
|
||||
Header: this.props.t('table_client'),
|
||||
accessor: 'ip',
|
||||
accessor: 'ids',
|
||||
minWidth: 150,
|
||||
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>
|
||||
);
|
||||
}
|
||||
const { value } = row;
|
||||
|
||||
return '';
|
||||
return (
|
||||
<div className="logs__row logs__row--overflow">
|
||||
<span className="logs__text">
|
||||
{value.map(address => (
|
||||
<div key={address} title={address}>
|
||||
{address}
|
||||
</div>
|
||||
))}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -119,9 +118,7 @@ class ClientsTable extends Component {
|
||||
|
||||
return (
|
||||
<div className="logs__row logs__row--overflow">
|
||||
<div className="logs__text" title={title}>
|
||||
{title}
|
||||
</div>
|
||||
<div className="logs__text">{title}</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
@@ -154,6 +151,24 @@ class ClientsTable extends Component {
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
Header: this.props.t('upstreams'),
|
||||
accessor: 'upstreams',
|
||||
minWidth: 120,
|
||||
Cell: ({ value }) => {
|
||||
const title = value && value.length > 0 ? (
|
||||
<Trans>settings_custom</Trans>
|
||||
) : (
|
||||
<Trans>settings_global</Trans>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="logs__row logs__row--overflow">
|
||||
<div className="logs__text">{title}</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
Header: this.props.t('whois'),
|
||||
accessor: 'whois_info',
|
||||
@@ -165,8 +180,8 @@ class ClientsTable extends Component {
|
||||
accessor: 'statistics',
|
||||
minWidth: 120,
|
||||
Cell: (row) => {
|
||||
const clientIP = row.original.ip;
|
||||
const clientStats = clientIP && this.getStats(clientIP, this.props.topClients);
|
||||
const { name } = row.original;
|
||||
const clientStats = this.getStats(name, this.props.topClients);
|
||||
|
||||
if (clientStats) {
|
||||
return (
|
||||
@@ -258,7 +273,7 @@ class ClientsTable extends Component {
|
||||
nextText={t('next_btn')}
|
||||
loadingText={t('loading_table_status')}
|
||||
pageText={t('page_table_footer_text')}
|
||||
ofText={t('of_table_footer_text')}
|
||||
ofText="/"
|
||||
rowsText={t('rows_table_footer_text')}
|
||||
noDataText={t('clients_not_found')}
|
||||
/>
|
||||
|
||||
@@ -1,14 +1,21 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Field, reduxForm, formValueSelector } from 'redux-form';
|
||||
import { Field, FieldArray, reduxForm, formValueSelector } from 'redux-form';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
import flow from 'lodash/flow';
|
||||
|
||||
import i18n from '../../../i18n';
|
||||
import Tabs from '../../ui/Tabs';
|
||||
import Examples from '../Dns/Upstream/Examples';
|
||||
import { toggleAllServices } from '../../../helpers/helpers';
|
||||
import { renderField, renderRadioField, renderSelectField, renderServiceField, ipv4, mac, required } from '../../../helpers/form';
|
||||
import { CLIENT_ID, SERVICES } from '../../../helpers/constants';
|
||||
import {
|
||||
renderField,
|
||||
renderGroupField,
|
||||
renderSelectField,
|
||||
renderServiceField,
|
||||
} from '../../../helpers/form';
|
||||
import { SERVICES } from '../../../helpers/constants';
|
||||
import './Service.css';
|
||||
|
||||
const settingsCheckboxes = [
|
||||
@@ -34,6 +41,67 @@ const settingsCheckboxes = [
|
||||
},
|
||||
];
|
||||
|
||||
const validate = (values) => {
|
||||
const errors = {};
|
||||
const { name, ids } = values;
|
||||
|
||||
if (!name || !name.length) {
|
||||
errors.name = i18n.t('form_error_required');
|
||||
}
|
||||
|
||||
if (ids && ids.length) {
|
||||
const idArrayErrors = [];
|
||||
ids.forEach((id, idx) => {
|
||||
if (!id || !id.length) {
|
||||
idArrayErrors[idx] = i18n.t('form_error_required');
|
||||
}
|
||||
});
|
||||
|
||||
if (idArrayErrors.length) {
|
||||
errors.ids = idArrayErrors;
|
||||
}
|
||||
}
|
||||
|
||||
return errors;
|
||||
};
|
||||
|
||||
const renderFields = (placeholder, buttonTitle) =>
|
||||
function cell(row) {
|
||||
const {
|
||||
fields,
|
||||
meta: { error },
|
||||
} = row;
|
||||
|
||||
return (
|
||||
<div className="form__group">
|
||||
{fields.map((ip, index) => (
|
||||
<div key={index} className="mb-1">
|
||||
<Field
|
||||
name={ip}
|
||||
component={renderGroupField}
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder={placeholder}
|
||||
isActionAvailable={index !== 0}
|
||||
removeField={() => fields.remove(index)}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-link btn-block btn-sm"
|
||||
onClick={() => fields.push()}
|
||||
title={buttonTitle}
|
||||
>
|
||||
<svg className="icon icon--close">
|
||||
<use xlinkHref="#plus" />
|
||||
</svg>
|
||||
</button>
|
||||
{error && <div className="error">{error}</div>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
let Form = (props) => {
|
||||
const {
|
||||
t,
|
||||
@@ -42,92 +110,53 @@ let Form = (props) => {
|
||||
change,
|
||||
pristine,
|
||||
submitting,
|
||||
clientIdentifier,
|
||||
useGlobalSettings,
|
||||
useGlobalServices,
|
||||
toggleClientModal,
|
||||
processingAdding,
|
||||
processingUpdating,
|
||||
invalid,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="modal-body">
|
||||
<div className="form__group">
|
||||
<div className="form__inline mb-2">
|
||||
<strong className="mr-3">
|
||||
<Trans>client_identifier</Trans>
|
||||
</strong>
|
||||
<div className="custom-controls-stacked">
|
||||
<Field
|
||||
name="identifier"
|
||||
component={renderRadioField}
|
||||
type="radio"
|
||||
className="form-control mr-2"
|
||||
value="ip"
|
||||
placeholder={t('ip_address')}
|
||||
/>
|
||||
<Field
|
||||
name="identifier"
|
||||
component={renderRadioField}
|
||||
type="radio"
|
||||
className="form-control mr-2"
|
||||
value="mac"
|
||||
placeholder="MAC"
|
||||
/>
|
||||
<div className="form__group mb-0">
|
||||
<div className="form__group">
|
||||
<Field
|
||||
id="name"
|
||||
name="name"
|
||||
component={renderField}
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder={t('form_client_name')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form__group">
|
||||
<div className="form__label">
|
||||
<strong className="mr-3">
|
||||
<Trans>client_identifier</Trans>
|
||||
</strong>
|
||||
</div>
|
||||
<div className="form__desc mt-0">
|
||||
<Trans
|
||||
components={[
|
||||
<a href="#dhcp" key="0">
|
||||
link
|
||||
</a>,
|
||||
]}
|
||||
>
|
||||
client_identifier_desc
|
||||
</Trans>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col col-sm-6">
|
||||
{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>
|
||||
<div className="col col-sm-6">
|
||||
<Field
|
||||
id="name"
|
||||
name="name"
|
||||
component={renderField}
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder={t('form_client_name')}
|
||||
validate={[required]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form__desc">
|
||||
<Trans
|
||||
components={[
|
||||
<a href="#dhcp" key="0">
|
||||
link
|
||||
</a>,
|
||||
]}
|
||||
>
|
||||
client_identifier_desc
|
||||
</Trans>
|
||||
|
||||
<div className="form__group">
|
||||
<FieldArray
|
||||
name="ids"
|
||||
component={renderFields(t('form_enter_id'), t('form_add_id'))}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -140,7 +169,11 @@ let Form = (props) => {
|
||||
type="checkbox"
|
||||
component={renderSelectField}
|
||||
placeholder={t(setting.placeholder)}
|
||||
disabled={setting.name !== 'use_global_settings' ? useGlobalSettings : false}
|
||||
disabled={
|
||||
setting.name !== 'use_global_settings'
|
||||
? useGlobalSettings
|
||||
: false
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
@@ -191,6 +224,22 @@ let Form = (props) => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div label="upstream" title={props.t('upstream_dns')}>
|
||||
<div className="form__desc mb-3">
|
||||
<Trans components={[<a href="#dns" key="0">link</a>]}>
|
||||
upstream_dns_client_desc
|
||||
</Trans>
|
||||
</div>
|
||||
<Field
|
||||
id="upstreams"
|
||||
name="upstreams"
|
||||
component="textarea"
|
||||
type="text"
|
||||
className="form-control form-control--textarea mb-5"
|
||||
placeholder={t('upstream_dns')}
|
||||
/>
|
||||
<Examples />
|
||||
</div>
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
@@ -210,7 +259,13 @@ let Form = (props) => {
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-success btn-standard"
|
||||
disabled={submitting || pristine || processingAdding || processingUpdating}
|
||||
disabled={
|
||||
submitting ||
|
||||
invalid ||
|
||||
pristine ||
|
||||
processingAdding ||
|
||||
processingUpdating
|
||||
}
|
||||
>
|
||||
<Trans>save_btn</Trans>
|
||||
</button>
|
||||
@@ -227,22 +282,20 @@ Form.propTypes = {
|
||||
change: PropTypes.func.isRequired,
|
||||
submitting: PropTypes.bool.isRequired,
|
||||
toggleClientModal: PropTypes.func.isRequired,
|
||||
clientIdentifier: PropTypes.string,
|
||||
useGlobalSettings: PropTypes.bool,
|
||||
useGlobalServices: PropTypes.bool,
|
||||
t: PropTypes.func.isRequired,
|
||||
processingAdding: PropTypes.bool.isRequired,
|
||||
processingUpdating: PropTypes.bool.isRequired,
|
||||
invalid: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
const selector = formValueSelector('clientForm');
|
||||
|
||||
Form = connect((state) => {
|
||||
const clientIdentifier = selector(state, 'identifier');
|
||||
const useGlobalSettings = selector(state, 'use_global_settings');
|
||||
const useGlobalServices = selector(state, 'use_global_blocked_services');
|
||||
return {
|
||||
clientIdentifier,
|
||||
useGlobalSettings,
|
||||
useGlobalServices,
|
||||
};
|
||||
@@ -253,5 +306,6 @@ export default flow([
|
||||
reduxForm({
|
||||
form: 'clientForm',
|
||||
enableReinitialize: true,
|
||||
validate,
|
||||
}),
|
||||
])(Form);
|
||||
|
||||
@@ -50,6 +50,23 @@ const renderInterfaceValues = (interfaceValues => (
|
||||
</ul>
|
||||
));
|
||||
|
||||
const clearFields = (change, resetDhcp, t) => {
|
||||
const fields = {
|
||||
interface_name: '',
|
||||
gateway_ip: '',
|
||||
subnet_mask: '',
|
||||
range_start: '',
|
||||
range_end: '',
|
||||
lease_duration: 86400,
|
||||
};
|
||||
|
||||
// eslint-disable-next-line no-alert
|
||||
if (window.confirm(t('dhcp_reset'))) {
|
||||
Object.keys(fields).forEach(field => change(field, fields[field]));
|
||||
resetDhcp();
|
||||
}
|
||||
};
|
||||
|
||||
let Form = (props) => {
|
||||
const {
|
||||
t,
|
||||
@@ -61,6 +78,8 @@ let Form = (props) => {
|
||||
interfaceValue,
|
||||
processingConfig,
|
||||
processingInterfaces,
|
||||
resetDhcp,
|
||||
change,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
@@ -160,31 +179,42 @@ let Form = (props) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-success btn-standard"
|
||||
disabled={submitting || invalid || processingConfig}
|
||||
>
|
||||
{t('save_config')}
|
||||
</button>
|
||||
<div className="btn-list">
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-success btn-standard"
|
||||
disabled={submitting || invalid || processingConfig}
|
||||
>
|
||||
{t('save_config')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary btn-standart"
|
||||
disabled={submitting || processingConfig}
|
||||
onClick={() => clearFields(change, resetDhcp, t)}
|
||||
>
|
||||
<Trans>reset_settings</Trans>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
Form.propTypes = {
|
||||
handleSubmit: PropTypes.func,
|
||||
submitting: PropTypes.bool,
|
||||
invalid: PropTypes.bool,
|
||||
interfaces: PropTypes.object,
|
||||
handleSubmit: PropTypes.func.isRequired,
|
||||
submitting: PropTypes.bool.isRequired,
|
||||
invalid: PropTypes.bool.isRequired,
|
||||
interfaces: PropTypes.object.isRequired,
|
||||
interfaceValue: PropTypes.string,
|
||||
initialValues: PropTypes.object,
|
||||
processingConfig: PropTypes.bool,
|
||||
processingInterfaces: PropTypes.bool,
|
||||
enabled: PropTypes.bool,
|
||||
t: PropTypes.func,
|
||||
initialValues: PropTypes.object.isRequired,
|
||||
processingConfig: PropTypes.bool.isRequired,
|
||||
processingInterfaces: PropTypes.bool.isRequired,
|
||||
enabled: PropTypes.bool.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
resetDhcp: PropTypes.func.isRequired,
|
||||
change: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
|
||||
const selector = formValueSelector('dhcpForm');
|
||||
|
||||
Form = connect((state) => {
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ReactTable from 'react-table';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
import { SMALL_TABLE_DEFAULT_PAGE_SIZE } from '../../../helpers/constants';
|
||||
|
||||
class Leases extends Component {
|
||||
cellWrap = ({ value }) => (
|
||||
@@ -36,7 +37,9 @@ class Leases extends Component {
|
||||
Cell: this.cellWrap,
|
||||
},
|
||||
]}
|
||||
showPagination={false}
|
||||
pageSize={SMALL_TABLE_DEFAULT_PAGE_SIZE}
|
||||
showPageSizeOptions={false}
|
||||
showPagination={leases.length > SMALL_TABLE_DEFAULT_PAGE_SIZE}
|
||||
noDataText={t('dhcp_leases_not_found')}
|
||||
minRows={6}
|
||||
className="-striped -highlight card-table-overflow"
|
||||
|
||||
@@ -2,6 +2,7 @@ import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ReactTable from 'react-table';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
import { SMALL_TABLE_DEFAULT_PAGE_SIZE } from '../../../../helpers/constants';
|
||||
|
||||
import Modal from './Modal';
|
||||
|
||||
@@ -16,7 +17,7 @@ class StaticLeases extends Component {
|
||||
|
||||
handleSubmit = (data) => {
|
||||
this.props.addStaticLease(data);
|
||||
}
|
||||
};
|
||||
|
||||
handleDelete = (ip, mac, hostname = '') => {
|
||||
const name = hostname || ip;
|
||||
@@ -24,7 +25,7 @@ class StaticLeases extends Component {
|
||||
if (window.confirm(this.props.t('delete_confirm', { key: name }))) {
|
||||
this.props.removeStaticLease({ ip, mac, hostname });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
@@ -74,7 +75,7 @@ class StaticLeases extends Component {
|
||||
}
|
||||
>
|
||||
<svg className="icons">
|
||||
<use xlinkHref="#delete" />
|
||||
<use xlinkHref="#delete"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
@@ -82,7 +83,9 @@ class StaticLeases extends Component {
|
||||
},
|
||||
},
|
||||
]}
|
||||
showPagination={false}
|
||||
pageSize={SMALL_TABLE_DEFAULT_PAGE_SIZE}
|
||||
showPageSizeOptions={false}
|
||||
showPagination={staticLeases.length > SMALL_TABLE_DEFAULT_PAGE_SIZE}
|
||||
noDataText={t('dhcp_static_leases_not_found')}
|
||||
className="-striped -highlight card-table-overflow"
|
||||
minRows={6}
|
||||
|
||||
@@ -154,7 +154,15 @@ class Dhcp extends Component {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { t, dhcp } = this.props;
|
||||
const {
|
||||
t,
|
||||
dhcp,
|
||||
resetDhcp,
|
||||
findActiveDhcp,
|
||||
addStaticLease,
|
||||
removeStaticLease,
|
||||
toggleLeaseModal,
|
||||
} = this.props;
|
||||
const statusButtonClass = classnames({
|
||||
'btn btn-primary btn-standard': true,
|
||||
'btn btn-primary btn-standard btn-loading': dhcp.processingStatus,
|
||||
@@ -184,6 +192,7 @@ class Dhcp extends Component {
|
||||
processingConfig={dhcp.processingConfig}
|
||||
processingInterfaces={dhcp.processingInterfaces}
|
||||
enabled={enabled}
|
||||
resetDhcp={resetDhcp}
|
||||
/>
|
||||
<hr />
|
||||
<div className="card-actions mb-3">
|
||||
@@ -191,9 +200,7 @@ class Dhcp extends Component {
|
||||
<button
|
||||
type="button"
|
||||
className={statusButtonClass}
|
||||
onClick={() =>
|
||||
this.props.findActiveDhcp(interface_name)
|
||||
}
|
||||
onClick={() => findActiveDhcp(interface_name)}
|
||||
disabled={
|
||||
enabled || !interface_name || dhcp.processingConfig
|
||||
}
|
||||
@@ -232,9 +239,9 @@ class Dhcp extends Component {
|
||||
<StaticLeases
|
||||
staticLeases={dhcp.staticLeases}
|
||||
isModalOpen={dhcp.isModalOpen}
|
||||
addStaticLease={this.props.addStaticLease}
|
||||
removeStaticLease={this.props.removeStaticLease}
|
||||
toggleLeaseModal={this.props.toggleLeaseModal}
|
||||
addStaticLease={addStaticLease}
|
||||
removeStaticLease={removeStaticLease}
|
||||
toggleLeaseModal={toggleLeaseModal}
|
||||
processingAdding={dhcp.processingAdding}
|
||||
processingDeleting={dhcp.processingDeleting}
|
||||
/>
|
||||
@@ -243,7 +250,7 @@ class Dhcp extends Component {
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-success btn-standard mt-3"
|
||||
onClick={() => this.props.toggleLeaseModal()}
|
||||
onClick={() => toggleLeaseModal()}
|
||||
>
|
||||
<Trans>dhcp_add_static_lease</Trans>
|
||||
</button>
|
||||
@@ -260,16 +267,17 @@ class Dhcp extends Component {
|
||||
}
|
||||
|
||||
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,
|
||||
dhcp: PropTypes.object.isRequired,
|
||||
toggleDhcp: PropTypes.func.isRequired,
|
||||
getDhcpStatus: PropTypes.func.isRequired,
|
||||
setDhcpConfig: PropTypes.func.isRequired,
|
||||
findActiveDhcp: PropTypes.func.isRequired,
|
||||
addStaticLease: PropTypes.func.isRequired,
|
||||
removeStaticLease: PropTypes.func.isRequired,
|
||||
toggleLeaseModal: PropTypes.func.isRequired,
|
||||
getDhcpInterfaces: PropTypes.func.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
resetDhcp: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default withNamespaces()(Dhcp);
|
||||
|
||||
153
client/src/components/Settings/Dns/Config/Form.js
Normal file
153
client/src/components/Settings/Dns/Config/Form.js
Normal file
@@ -0,0 +1,153 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { Field, reduxForm, formValueSelector } from 'redux-form';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
import flow from 'lodash/flow';
|
||||
|
||||
import {
|
||||
renderField,
|
||||
renderRadioField,
|
||||
renderSelectField,
|
||||
required,
|
||||
ipv4,
|
||||
ipv6,
|
||||
biggerOrEqualZero,
|
||||
toNumber,
|
||||
} from '../../../../helpers/form';
|
||||
import { BLOCKING_MODES } from '../../../../helpers/constants';
|
||||
|
||||
const getFields = (processing, t) => Object.values(BLOCKING_MODES).map(mode => (
|
||||
<Field
|
||||
key={mode}
|
||||
name="blocking_mode"
|
||||
type="radio"
|
||||
component={renderRadioField}
|
||||
value={mode}
|
||||
placeholder={t(mode)}
|
||||
disabled={processing}
|
||||
/>
|
||||
));
|
||||
|
||||
let Form = ({
|
||||
handleSubmit, submitting, invalid, processing, blockingMode, t,
|
||||
}) => (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="row">
|
||||
<div className="col-12 col-sm-6">
|
||||
<div className="form__group form__group--settings">
|
||||
<label htmlFor="ratelimit" className="form__label form__label--with-desc">
|
||||
<Trans>rate_limit</Trans>
|
||||
</label>
|
||||
<div className="form__desc form__desc--top">
|
||||
<Trans>rate_limit_desc</Trans>
|
||||
</div>
|
||||
<Field
|
||||
name="ratelimit"
|
||||
type="number"
|
||||
component={renderField}
|
||||
className="form-control"
|
||||
placeholder={t('form_enter_rate_limit')}
|
||||
normalize={toNumber}
|
||||
validate={[required, biggerOrEqualZero]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<div className="form__group form__group--settings">
|
||||
<Field
|
||||
name="edns_cs_enabled"
|
||||
type="checkbox"
|
||||
component={renderSelectField}
|
||||
placeholder={t('edns_enable')}
|
||||
disabled={processing}
|
||||
subtitle={t('edns_cs_desc')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<div className="form__group form__group--settings mb-4">
|
||||
<label className="form__label form__label--with-desc">
|
||||
<Trans>blocking_mode</Trans>
|
||||
</label>
|
||||
<div className="form__desc form__desc--top">
|
||||
<Trans components={[<div key="0">text</div>]}>blocking_mode_desc</Trans>
|
||||
</div>
|
||||
<div className="custom-controls-stacked">
|
||||
{getFields(processing, t)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{blockingMode === BLOCKING_MODES.custom_ip && (
|
||||
<Fragment>
|
||||
<div className="col-12 col-sm-6">
|
||||
<div className="form__group form__group--settings">
|
||||
<label htmlFor="blocking_ipv4" className="form__label form__label--with-desc">
|
||||
<Trans>blocking_ipv4</Trans>
|
||||
</label>
|
||||
<div className="form__desc form__desc--top">
|
||||
<Trans>blocking_ipv4_desc</Trans>
|
||||
</div>
|
||||
<Field
|
||||
name="blocking_ipv4"
|
||||
component={renderField}
|
||||
className="form-control"
|
||||
placeholder={t('form_enter_ip')}
|
||||
validate={[ipv4, required]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-12 col-sm-6">
|
||||
<div className="form__group form__group--settings">
|
||||
<label htmlFor="ip_address" className="form__label form__label--with-desc">
|
||||
<Trans>blocking_ipv6</Trans>
|
||||
</label>
|
||||
<div className="form__desc form__desc--top">
|
||||
<Trans>blocking_ipv6_desc</Trans>
|
||||
</div>
|
||||
<Field
|
||||
name="blocking_ipv6"
|
||||
component={renderField}
|
||||
className="form-control"
|
||||
placeholder={t('form_enter_ip')}
|
||||
validate={[ipv6, required]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-success btn-standard btn-large"
|
||||
disabled={submitting || invalid || processing}
|
||||
>
|
||||
<Trans>save_btn</Trans>
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
|
||||
Form.propTypes = {
|
||||
blockingMode: PropTypes.string.isRequired,
|
||||
handleSubmit: PropTypes.func.isRequired,
|
||||
submitting: PropTypes.bool.isRequired,
|
||||
invalid: PropTypes.bool.isRequired,
|
||||
processing: PropTypes.bool.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
const selector = formValueSelector('blockingModeForm');
|
||||
|
||||
Form = connect((state) => {
|
||||
const blockingMode = selector(state, 'blocking_mode');
|
||||
return {
|
||||
blockingMode,
|
||||
};
|
||||
})(Form);
|
||||
|
||||
export default flow([
|
||||
withNamespaces(),
|
||||
reduxForm({
|
||||
form: 'blockingModeForm',
|
||||
}),
|
||||
])(Form);
|
||||
51
client/src/components/Settings/Dns/Config/index.js
Normal file
51
client/src/components/Settings/Dns/Config/index.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withNamespaces } from 'react-i18next';
|
||||
|
||||
import Card from '../../../ui/Card';
|
||||
import Form from './Form';
|
||||
|
||||
const Config = ({ t, dnsConfig, setDnsConfig }) => {
|
||||
const handleFormSubmit = (values) => {
|
||||
setDnsConfig(values);
|
||||
};
|
||||
|
||||
const {
|
||||
blocking_mode,
|
||||
ratelimit,
|
||||
blocking_ipv4,
|
||||
blocking_ipv6,
|
||||
edns_cs_enabled,
|
||||
processingSetConfig,
|
||||
} = dnsConfig;
|
||||
|
||||
return (
|
||||
<Card
|
||||
title={t('dns_config')}
|
||||
bodyType="card-body box-body--settings"
|
||||
id="dns-config"
|
||||
>
|
||||
<div className="form">
|
||||
<Form
|
||||
initialValues={{
|
||||
ratelimit,
|
||||
blocking_mode,
|
||||
blocking_ipv4,
|
||||
blocking_ipv6,
|
||||
edns_cs_enabled,
|
||||
}}
|
||||
onSubmit={handleFormSubmit}
|
||||
processing={processingSetConfig}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
Config.propTypes = {
|
||||
dnsConfig: PropTypes.object.isRequired,
|
||||
setDnsConfig: PropTypes.func.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default withNamespaces()(Config);
|
||||
@@ -67,7 +67,7 @@ class Table extends Component {
|
||||
nextText={t('next_btn')}
|
||||
loadingText={t('loading_table_status')}
|
||||
pageText={t('page_table_footer_text')}
|
||||
ofText={t('of_table_footer_text')}
|
||||
ofText="/"
|
||||
rowsText={t('rows_table_footer_text')}
|
||||
noDataText={t('rewrite_not_found')}
|
||||
/>
|
||||
|
||||
@@ -23,10 +23,10 @@ const Examples = props => (
|
||||
<Trans>examples_title</Trans>:
|
||||
<ol className="leading-loose">
|
||||
<li>
|
||||
<code>1.1.1.1</code> - {props.t('example_upstream_regular')}
|
||||
<code>9.9.9.9</code> - {props.t('example_upstream_regular')}
|
||||
</li>
|
||||
<li>
|
||||
<code>tls://1dot1dot1dot1.cloudflare-dns.com</code> –
|
||||
<code>tls://dns.quad9.net</code> –
|
||||
<span>
|
||||
<Trans
|
||||
components={[
|
||||
@@ -45,7 +45,7 @@ const Examples = props => (
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<code>https://cloudflare-dns.com/dns-query</code> –
|
||||
<code>https://dns.quad9.net/dns-query</code> –
|
||||
<span>
|
||||
<Trans
|
||||
components={[
|
||||
@@ -64,7 +64,7 @@ const Examples = props => (
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<code>tcp://1.1.1.1</code> – <Trans>example_upstream_tcp</Trans>
|
||||
<code>tcp://9.9.9.9</code> – <Trans>example_upstream_tcp</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<code>sdns://...</code> –
|
||||
@@ -102,7 +102,7 @@ const Examples = props => (
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<code>[/example.local/]1.1.1.1</code> –
|
||||
<code>[/example.local/]9.9.9.9</code> –
|
||||
<span>
|
||||
<Trans
|
||||
components={[
|
||||
|
||||
@@ -5,6 +5,7 @@ import { withNamespaces } from 'react-i18next';
|
||||
import Upstream from './Upstream';
|
||||
import Access from './Access';
|
||||
import Rewrites from './Rewrites';
|
||||
import Config from './Config';
|
||||
import PageTitle from '../../ui/PageTitle';
|
||||
import Loading from '../../ui/Loading';
|
||||
|
||||
@@ -13,6 +14,7 @@ class Dns extends Component {
|
||||
this.props.getDnsSettings();
|
||||
this.props.getAccessList();
|
||||
this.props.getRewritesList();
|
||||
this.props.getDnsConfig();
|
||||
}
|
||||
|
||||
render() {
|
||||
@@ -29,12 +31,18 @@ class Dns extends Component {
|
||||
addRewrite,
|
||||
deleteRewrite,
|
||||
toggleRewritesModal,
|
||||
dnsConfig,
|
||||
setDnsConfig,
|
||||
} = this.props;
|
||||
|
||||
const isDataLoading =
|
||||
dashboard.processingDnsSettings || access.processing || rewrites.processing;
|
||||
const isDataReady =
|
||||
!dashboard.processingDnsSettings && !access.processing && !rewrites.processing;
|
||||
const isDataLoading = dashboard.processingDnsSettings
|
||||
|| access.processing
|
||||
|| rewrites.processing
|
||||
|| dnsConfig.processingGetConfig;
|
||||
const isDataReady = !dashboard.processingDnsSettings
|
||||
&& !access.processing
|
||||
&& !rewrites.processing
|
||||
&& !dnsConfig.processingGetConfig;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
@@ -42,6 +50,10 @@ class Dns extends Component {
|
||||
{isDataLoading && <Loading />}
|
||||
{isDataReady && (
|
||||
<Fragment>
|
||||
<Config
|
||||
dnsConfig={dnsConfig}
|
||||
setDnsConfig={setDnsConfig}
|
||||
/>
|
||||
<Upstream
|
||||
upstreamDns={dashboard.upstreamDns}
|
||||
bootstrapDns={dashboard.bootstrapDns}
|
||||
@@ -80,6 +92,9 @@ Dns.propTypes = {
|
||||
deleteRewrite: PropTypes.func.isRequired,
|
||||
toggleRewritesModal: PropTypes.func.isRequired,
|
||||
getDnsSettings: PropTypes.func.isRequired,
|
||||
dnsConfig: PropTypes.object.isRequired,
|
||||
setDnsConfig: PropTypes.func.isRequired,
|
||||
getDnsConfig: PropTypes.func.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
|
||||
@@ -222,6 +222,7 @@ let Form = (props) => {
|
||||
className="form-control mr-2"
|
||||
value="path"
|
||||
placeholder={t('encryption_certificates_source_path')}
|
||||
disabled={!isEnabled}
|
||||
/>
|
||||
<Field
|
||||
name="certificate_source"
|
||||
@@ -230,6 +231,7 @@ let Form = (props) => {
|
||||
className="form-control mr-2"
|
||||
value="content"
|
||||
placeholder={t('encryption_certificates_source_content')}
|
||||
disabled={!isEnabled}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -289,6 +291,7 @@ let Form = (props) => {
|
||||
className="form-control mr-2"
|
||||
value="path"
|
||||
placeholder={t('encryption_key_source_path')}
|
||||
disabled={!isEnabled}
|
||||
/>
|
||||
<Field
|
||||
name="key_source"
|
||||
@@ -297,6 +300,7 @@ let Form = (props) => {
|
||||
className="form-control mr-2"
|
||||
value="content"
|
||||
placeholder={t('encryption_key_source_content')}
|
||||
disabled={!isEnabled}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,7 @@ import Services from './Services';
|
||||
import StatsConfig from './StatsConfig';
|
||||
import LogsConfig from './LogsConfig';
|
||||
import FiltersConfig from './FiltersConfig';
|
||||
|
||||
import Checkbox from '../ui/Checkbox';
|
||||
import Loading from '../ui/Loading';
|
||||
import PageTitle from '../ui/PageTitle';
|
||||
|
||||
@@ -33,6 +33,36 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.card-body--loading {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.card-body--loading:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 100;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(255, 255, 255, 0.6);
|
||||
}
|
||||
|
||||
.card-body--loading:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
z-index: 101;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin-top: -20px;
|
||||
margin-left: -20px;
|
||||
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2047.6%2047.6%22%20height%3D%22100%25%22%20width%3D%22100%25%22%3E%3Cpath%20opacity%3D%22.235%22%20fill%3D%22%23979797%22%20d%3D%22M44.4%2011.9l-5.2%203c1.5%202.6%202.4%205.6%202.4%208.9%200%209.8-8%2017.8-17.8%2017.8-6.6%200-12.3-3.6-15.4-8.9l-5.2%203C7.3%2042.8%2015%2047.6%2023.8%2047.6c13.1%200%2023.8-10.7%2023.8-23.8%200-4.3-1.2-8.4-3.2-11.9z%22%2F%3E%3Cpath%20fill%3D%22%2366b574%22%20d%3D%22M3.2%2035.7C0%2030.2-.8%2023.8.8%2017.6%202.5%2011.5%206.4%206.4%2011.9%203.2%2017.4%200%2023.8-.8%2030%20.8c6.1%201.6%2011.3%205.6%2014.4%2011.1l-5.2%203c-2.4-4.1-6.2-7.1-10.8-8.3C23.8%205.4%2019%206%2014.9%208.4s-7.1%206.2-8.3%2010.8c-1.2%204.6-.6%209.4%201.8%2013.5l-5.2%203z%22%2F%3E%3C%2Fsvg%3E");
|
||||
will-change: transform;
|
||||
animation: clockwise 2s linear infinite;
|
||||
}
|
||||
|
||||
.card-title-stats {
|
||||
font-size: 13px;
|
||||
color: #9aa0ac;
|
||||
|
||||
@@ -3,3 +3,8 @@
|
||||
vertical-align: middle;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.icon--close {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
@@ -64,6 +64,14 @@ const Icons = () => (
|
||||
<path d="M15 3C10.57 3 6.701 5.419 4.623 9h2.39a10.063 10.063 0 0 1 4.05-3.19c-.524.89-.961 1.973-1.3 3.19h2.108c.79-2.459 1.998-4 3.129-4s2.339 1.541 3.129 4h2.107c-.338-1.217-.774-2.3-1.299-3.19A10.062 10.062 0 0 1 22.989 9h2.389C23.298 5.419 19.43 3 15 3zm7.035 9.129c-1.372 0-2.264.73-2.264 1.842 0 .896.538 1.463 1.579 1.66l.75.15c.65.13.898.3.898.615 0 .375-.37.635-.91.635-.6 0-1.014-.265-1.049-.68h-1.38c.023 1.097.93 1.776 2.37 1.776 1.491 0 2.399-.717 2.399-1.904 0-.903-.504-1.412-1.63-1.63l-.734-.142c-.6-.118-.851-.3-.851-.611 0-.378.336-.62.844-.62.509 0 .891.28.923.682h1.336c-.024-1.053-.948-1.773-2.28-1.773zm-16.185.148v5.696h2.39c1.712 0 2.662-1.033 2.662-2.903 0-1.779-.966-2.793-2.662-2.793H5.85zm6.933.004v5.692h1.373v-3.235h.076l2.377 3.235h1.149V12.28h-1.373v3.203h-.076l-2.372-3.203h-1.154zm-5.486 1.16h.682c.912 0 1.449.596 1.449 1.657 0 1.128-.51 1.713-1.45 1.713h-.681v-3.37zM4.623 21C6.701 24.581 10.57 27 15 27c4.43 0 8.299-2.419 10.377-6h-2.389a10.063 10.063 0 0 1-4.049 3.19c.524-.89.96-1.973 1.297-3.19H18.13c-.79 2.459-1.996 4-3.127 4-1.131 0-2.339-1.541-3.129-4h-2.11c.339 1.217.776 2.3 1.3 3.19A10.056 10.056 0 0 1 7.013 21h-2.39z"></path>
|
||||
</symbol>
|
||||
|
||||
<symbol id="service_amazon" viewBox="0 0 32 32" fill="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2">
|
||||
<path d="M16.2,4c-3.3,0-6.9,1.2-7.7,5.3C8.4,9.7,8.7,10,9,10l3.3,0.3c0.3,0,0.6-0.3,0.6-0.6c0.3-1.4,1.5-2.1,2.8-2.1c0.7,0,1.5,0.3,1.9,0.9c0.5,0.7,0.4,1.7,0.4,2.5v0.5c-2,0.2-4.6,0.4-6.5,1.2c-2.2,0.9-3.7,2.8-3.7,5.7c0,3.6,2.3,5.4,5.2,5.4c2.5,0,3.8-0.6,5.7-2.5c0.6,0.9,0.9,1.4,2,2.3c0.3,0.1,0.6,0.1,0.8-0.1v0c0.7-0.6,2-1.7,2.7-2.3c0.3-0.2,0.2-0.6,0-0.9c-0.6-0.9-1.3-1.6-1.3-3.2v-5.4c0-2.3,0.2-4.4-1.5-6C20.1,4.4,17.9,4,16.2,4z M17.1,14.3c0.3,0,0.6,0,0.9,0v0.8c0,1.3,0.1,2.5-0.6,3.7c-0.5,1-1.4,1.6-2.4,1.6c-1.3,0-2.1-1-2.1-2.5C12.9,15.2,14.9,14.5,17.1,14.3z M26.7,22.4c-0.9,0-1.9,0.2-2.7,0.8c-0.2,0.2-0.2,0.4,0.1,0.4c0.9-0.1,2.8-0.4,3.2,0.1s-0.4,2.3-0.7,3.1c-0.1,0.2,0.1,0.3,0.3,0.2c1.5-1.2,1.9-3.8,1.6-4.2C28.3,22.5,27.6,22.4,26.7,22.4z M3.7,22.8c-0.2,0-0.3,0.3-0.1,0.4c3.3,3,7.6,4.7,12.4,4.7c3.4,0,7.4-1.1,10.2-3.1c0.5-0.3,0.1-0.9-0.4-0.7c-3.1,1.3-6.4,1.9-9.5,1.9c-4.5,0-8.8-1.2-12.4-3.3C3.8,22.9,3.7,22.8,3.7,22.8z" />
|
||||
</symbol>
|
||||
|
||||
<symbol id="service_ebay" viewBox="0 0 128 128" fill="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2">
|
||||
<path d="M95.633 48.416h31.826v55.802H95.633z"/><path d="M64 0C45.644 0 30.717 14.927 30.717 33.283h7.734C38.452 19.194 49.911 7.734 64 7.734s25.548 11.46 25.548 25.549h7.734C97.283 14.927 82.356 0 64 0zm63.459 33.283v15.133H.541V33.283h126.918zm0 70.935V128H.541v-23.782h126.918z"/><path d="M.541 48.416h31.826v55.802H.541z"/><path d="M32.367 48.416h31.194v55.802H32.367z"/><path d="M63.562 48.416h32.071v55.802H63.562z" />
|
||||
</symbol>
|
||||
|
||||
<symbol id="service_youtube" viewBox="0 0 24 24" fill="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2">
|
||||
<path d="M19.695 4.04S15.348 3.2 12 3.2s-7.695.84-7.695.84L1.602 7.2v9.6l2.703 3.16s4.347.84 7.695.84 7.695-.84 7.695-.84l2.703-3.16V12 7.2zM9.602 15.68V8.32L16 12zm0 0"/><path d="M19.2 4a3.198 3.198 0 1 0 0 6.398c1.769 0 3.198-1.43 3.198-3.199C22.398 5.434 20.968 4 19.2 4zm0 9.602a3.198 3.198 0 1 0 0 6.398c1.769 0 3.198-1.434 3.198-3.2 0-1.769-1.43-3.198-3.199-3.198zM1.601 7.199c0 1.77 1.43 3.2 3.199 3.2 1.765 0 2.398-1.43 2.398-3.2C7.2 5.434 6.566 4 4.801 4 3.03 4 1.6 5.434 1.6 7.2zM4.8 13.602c-1.77 0-3.2 1.43-3.2 3.199A3.198 3.198 0 1 0 8 16.8c0-1.77-1.434-3.2-3.2-3.2zm0 0" />
|
||||
</symbol>
|
||||
@@ -104,6 +112,10 @@ const Icons = () => (
|
||||
<path d="M83.5 72.814V512l17.432-2.865a955.35 955.35 0 0 1 88.604-10.312l13.965-.966V338.206L83.5 72.814z"/><path d="M308.5 0L308.5 172.328 428.5 438.914 428.5 0z"/><path d="M308.5 245.415l-10.87-24.149L198.03 0H83.501l168.12 371.813 57.024 126.112 8.852.566a955.65 955.65 0 0 1 93.572 10.644L428.5 512l-120-266.585z"/>
|
||||
</symbol>
|
||||
|
||||
<symbol id="service_cloudflare" viewBox="0 0 50 50" fill="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2">
|
||||
<path d="M38 10.813l-.906 3.78-1.907-3.405v1.718c2.899 2.301 4.926 5.79 5.126 9.688.699-.2 1.3-.188 2-.188 1.374 0 2.667.297 3.812.875l-1.031-.593 3.812-.875-3.812-.907L48.5 19h-3.813l2.813-2.688-3.688 1.094 2-3.312-3.312 2 1.094-3.688-2.688 2.781.094-3.874-2 3.28zM27 11c-5 0-9.414 2.992-11.313 7.594-.699-.399-1.687-.688-2.687-.688-3.2 0-5.906 2.606-5.906 5.907v.5c-3.899.3-7.094 3.68-7.094 7.78 0 .802.113 1.52.313 2.22.101.398.5.687 1 .687h47c.398 0 .675-.195.874-.594.5-1.101.813-2.207.813-3.406 0-4.2-3.488-7.594-7.688-7.594-.8 0-1.511.082-2.312.282l4.906 6.625-5.5-4.5L22 29.593l15.094-4.905L28.5 21.5l10.688 1.813v-.125C39.188 16.488 33.699 11 27 11zm19.781 12.656c.434.274.844.586 1.219.938h.5z" />
|
||||
</symbol>
|
||||
|
||||
<symbol id="service_vk" viewBox="0 0 24 24" fill="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2">
|
||||
<path d="M12 .96C5.914.96.96 5.915.96 12c0 6.086 4.954 11.04 11.04 11.04 6.086 0 11.04-4.954 11.04-11.04C23.04 5.914 18.085.96 12 .96zm4.785 13.216c1.074.953 1.3 1.293 1.336 1.351.445.707-.492.793-.492.793h-1.98s-.481.004-.891-.27c-.672-.437-1.375-1.288-1.867-1.14-.414.125-.41.684-.41 1.16 0 .172-.149.25-.481.25h-.617c-1.086 0-2.262-.363-3.434-1.59-1.656-1.734-3.113-5.222-3.113-5.222s-.086-.176.008-.281c.105-.122.394-.106.394-.106h1.918s.18.031.309.125c.11.074.168.219.168.219s.32 1.062.734 1.742c.801 1.32 1.172 1.355 1.445 1.215.399-.207.266-1.617.266-1.617s.02-.602-.187-.871c-.16-.211-.465-.32-.598-.336-.11-.016.07-.203.3-.313.31-.137.727-.172 1.446-.164.563.004.723.04.941.09.665.152.5.555.5 1.969 0 .453-.062 1.09.278 1.3.148.09.652.204 1.55-1.257.43-.692.77-1.84.77-1.84s.067-.125.176-.188c.113-.066.11-.062.262-.062.152 0 1.683-.012 2.02-.012.335 0 .651-.004.702.191.078.282-.246 1.25-1.07 2.305-1.355 1.723-1.504 1.563-.383 2.559zm0 0" />
|
||||
</symbol>
|
||||
@@ -112,6 +124,10 @@ const Icons = () => (
|
||||
<path d="M50 28c-3.313 0-6 2.688-6 6 0 3.313 2.688 6 6 6 3.313 0 6-2.688 6-6 0-3.313-2.688-6-6-6zm0 0" /><path d="M50 4C24.637 4 4 24.637 4 50s20.637 46 46 46 46-20.637 46-46S75.363 4 50 4zm0 16c7.73 0 14 6.27 14 14s-6.27 14-14 14-14-6.27-14-14 6.27-14 14-14zm14.828 49.172A3.999 3.999 0 0 1 62 76a3.987 3.987 0 0 1-2.828-1.172L50 65.656l-9.172 9.172a3.999 3.999 0 0 1-5.656 0 3.999 3.999 0 0 1 0-5.656l6.43-6.43c-1.836-.539-3.618-1.207-5.29-2.066A4.302 4.302 0 0 1 34 56.859c0-2.98 3.172-4.761 5.809-3.375A21.767 21.767 0 0 0 50 56c3.684 0 7.148-.91 10.191-2.516C62.828 52.098 66 53.88 66 56.86c0 1.602-.89 3.078-2.313 3.813-1.671.863-3.453 1.531-5.289 2.07zm0 0" />
|
||||
</symbol>
|
||||
|
||||
<symbol id="service_origin" viewBox="0 0 24 24" fill="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2">
|
||||
<path d="M 12 4 C 11.539063 4 11.09375 4.046875 10.65625 4.121094 C 11.082031 3.183594 11.550781 2.445313 12 2 C 12.195313 1.804688 12.011719 1.484375 11.738281 1.539063 C 7.808594 2.359375 4 7.0625 4 12 C 4 16.417969 7.582031 20 12 20 C 12.460938 20 12.90625 19.953125 13.34375 19.878906 C 12.917969 20.816406 12.449219 21.554688 12 22 C 11.804688 22.195313 11.988281 22.515625 12.261719 22.460938 C 16.191406 21.640625 20 16.9375 20 12 C 20 7.582031 16.417969 4 12 4 Z M 12 15 C 10.34375 15 9 13.65625 9 12 C 9 10.34375 10.34375 9 12 9 C 13.65625 9 15 10.34375 15 12 C 15 13.65625 13.65625 15 12 15 Z" />
|
||||
</symbol>
|
||||
|
||||
<symbol id="service_steam" viewBox="0 0 22 22" fill="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2">
|
||||
<path d="M14.398 7.2a2.4 2.4 0 1 0 .003 4.799 2.4 2.4 0 0 0-.003-4.8zm0 0" fill="none" strokeWidth="1.6" stroke="currentColor" strokeMiterlimit="10"/><path d="M8 14c-.629 0-1.18.297-1.547.75l1.758.48c.426.114.68.555.562.98a.804.804 0 0 1-.984.563l-1.762-.48A1.998 1.998 0 0 0 10 16c0-1.105-.895-2-2-2zm0 0" /><path d="M19.2 3.2H4.8c-.886 0-1.6.714-1.6 1.6v9.063l2.027.551a3.213 3.213 0 0 1 2.289-1.566l2.136-2.567a4.799 4.799 0 1 1 4.066 4.066l-2.566 2.137A3.195 3.195 0 0 1 8 19.2 3.2 3.2 0 0 1 4.8 16c0-.016.005-.027.005-.043l-1.606-.437v3.68c0 .886.715 1.6 1.602 1.6h14.398c.887 0 1.602-.714 1.602-1.6V4.8c0-.886-.715-1.6-1.602-1.6zm0 0" />
|
||||
</symbol>
|
||||
@@ -120,6 +136,10 @@ const Icons = () => (
|
||||
<path d="M 10 3 C 6.69 3 4 5.69 4 9 L 4 41.240234 L 25 47.539062 L 46 41.240234 L 46 9 C 46 5.69 43.31 3 40 3 L 10 3 z M 11 8 L 15 8 L 15 11 L 11 11 L 11 18 L 14 18 L 14 21 L 11 21 L 11 28 L 15 28 L 15 31 L 11 31 C 9.34 31 8 29.66 8 28 L 8 11 C 8 9.34 9.34 8 11 8 z M 17 8 L 23 8 C 24.66 8 26 9.34 26 11 L 26 18 C 26 19.66 24.66 21 23 21 L 20 21 L 20 31 L 17 31 L 17 8 z M 28 8 L 31 8 L 31 31 L 28 31 L 28 8 z M 36 8 L 39 8 C 40.66 8 42 9.34 42 11 L 42 15 L 39 15 L 39 11 L 36 11 L 36 28 L 39 28 L 39 24 L 42 24 L 42 28 C 42 29.66 40.66 31 39 31 L 36 31 C 34.34 31 33 29.66 33 28 L 33 11 C 33 9.34 34.34 8 36 8 z M 20 11 L 20 18 L 23 18 L 23 11 L 20 11 z M 9 34 L 13 34 C 13.55 34 14 34.45 14 35 L 14 36 L 13 36 L 13 35.25 C 13 35.11 12.89 35 12.75 35 L 9.25 35 C 9.11 35 9 35.11 9 35.25 L 9 38.75 C 9 38.89 9.11 39 9.25 39 L 12.75 39 C 12.89 39 13 38.89 13 38.75 L 13 38 L 12 38 L 12 37 L 14 37 L 14 39 C 14 39.55 13.55 40 13 40 L 9 40 C 8.45 40 8 39.55 8 39 L 8 35 C 8 34.45 8.45 34 9 34 z M 18 34 L 19 34 L 22 40 L 21 40 L 20.5 39 L 16.5 39 L 16 40 L 15 40 L 18 34 z M 23 34 L 24 34 L 26 38 L 28 34 L 29 34 L 29 40 L 28 40 L 28 36 L 26.5 39 L 25.5 39 L 24 36 L 24 40 L 23 40 L 23 34 z M 30 34 L 35 34 L 35 35 L 31 35 L 31 36.5 L 33 36.5 L 33 37.5 L 31 37.5 L 31 39 L 35 39 L 35 40 L 30 40 L 30 34 z M 37 34 L 41 34 C 41.55 34 42 34.45 42 35 L 42 35.5 L 41 35.5 L 41 35.25 C 41 35.11 40.89 35 40.75 35 L 37.25 35 C 37.11 35 37 35.11 37 35.25 L 37 36.25 C 37 36.39 37.11 36.5 37.25 36.5 L 41 36.5 C 41.55 36.5 42 36.95 42 37.5 L 42 39 C 42 39.55 41.55 40 41 40 L 37 40 C 36.45 40 36 39.55 36 39 L 36 38.5 L 37 38.5 L 37 38.75 C 37 38.89 37.11 39 37.25 39 L 40.75 39 C 40.89 39 41 38.89 41 38.75 L 41 37.75 C 41 37.61 40.89 37.5 40.75 37.5 L 37 37.5 C 36.45 37.5 36 37.05 36 36.5 L 36 35 C 36 34.45 36.45 34 37 34 z M 18.5 35 L 17 38 L 20 38 L 18.5 35 z"></path>
|
||||
</symbol>
|
||||
|
||||
<symbol id="service_reddit" viewBox="0 0 24 24" fill="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2">
|
||||
<path d="M14.238 15.348c.085.084.085.221 0 .306-.465.462-1.194.687-2.231.687l-.008-.002-.008.002c-1.036 0-1.766-.225-2.231-.688-.085-.084-.085-.221 0-.305.084-.084.222-.084.307 0 .379.377 1.008.561 1.924.561l.008.002.008-.002c.915 0 1.544-.184 1.924-.561.085-.084.223-.084.307 0zm-3.44-2.418c0-.507-.414-.919-.922-.919-.509 0-.923.412-.923.919 0 .506.414.918.923.918.508.001.922-.411.922-.918zm13.202-.93c0 6.627-5.373 12-12 12s-12-5.373-12-12 5.373-12 12-12 12 5.373 12 12zm-5-.129c0-.851-.695-1.543-1.55-1.543-.417 0-.795.167-1.074.435-1.056-.695-2.485-1.137-4.066-1.194l.865-2.724 2.343.549-.003.034c0 .696.569 1.262 1.268 1.262.699 0 1.267-.566 1.267-1.262s-.568-1.262-1.267-1.262c-.537 0-.994.335-1.179.804l-2.525-.592c-.11-.027-.223.037-.257.145l-.965 3.038c-1.656.02-3.155.466-4.258 1.181-.277-.255-.644-.415-1.05-.415-.854.001-1.549.693-1.549 1.544 0 .566.311 1.056.768 1.325-.03.164-.05.331-.05.5 0 2.281 2.805 4.137 6.253 4.137s6.253-1.856 6.253-4.137c0-.16-.017-.317-.044-.472.486-.261.82-.766.82-1.353zm-4.872.141c-.509 0-.922.412-.922.919 0 .506.414.918.922.918s.922-.412.922-.918c0-.507-.413-.919-.922-.919z"/>
|
||||
</symbol>
|
||||
|
||||
<symbol id="service_skype" viewBox="0 0 26 26" fill="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2">
|
||||
<path d="M23.363 14.387c.153-.739.23-1.5.23-2.266C23.594 5.883 18.45.805 12.122.805c-.594 0-1.191.047-1.781.136A6.891 6.891 0 0 0 6.852 0C3.074 0 0 3.035 0 6.762c0 1.144.293 2.27.852 3.265-.133.688-.2 1.391-.2 2.094 0 6.238 5.149 11.316 11.47 11.316.648 0 1.3-.054 1.94-.164.95.477 2.012.727 3.086.727C20.926 24 24 20.969 24 17.238c0-1.004-.215-1.96-.637-2.851zM17.758 17.3c-.508.707-1.258 1.27-2.23 1.668-.966.394-2.122.593-3.434.593-1.578 0-2.903-.273-3.934-.812a5.074 5.074 0 0 1-1.808-1.582c-.47-.664-.707-1.324-.707-1.961 0-.395.156-.738.457-1.023.304-.278.687-.418 1.148-.418.379 0 .703.109.969.332.254.21.469.523.644.93.192.437.407.808.633 1.1.211.282.524.52.918.704.399.188.938.281 1.598.281.91 0 1.652-.191 2.215-.57.546-.367.812-.813.812-1.352 0-.43-.14-.765-.422-1.027-.3-.277-.699-.492-1.176-.637-.5-.152-1.18-.32-2.015-.496-1.14-.238-2.11-.523-2.88-.847-.788-.332-1.425-.79-1.89-1.364-.472-.582-.71-1.312-.71-2.172 0-.816.253-1.554.75-2.191.488-.633 1.206-1.125 2.132-1.46.91-.333 1.996-.5 3.223-.5.98 0 1.844.108 2.566.331.723.223 1.336.524 1.813.89.484.376.843.774 1.07 1.188.227.418.344.832.344 1.235 0 .386-.153.738-.453 1.046-.297.31-.68.465-1.125.465-.41 0-.727-.097-.95-.289-.207-.18-.418-.46-.656-.863-.273-.516-.605-.918-.984-1.203-.371-.277-.989-.418-1.836-.418-.79 0-1.43.156-1.902.465-.461.293-.684.633-.684 1.039 0 .246.07.449.219.629.156.187.379.351.656.488.289.145.586.258.883.34.308.082.82.207 1.523.367.887.191 1.707.398 2.43.625.73.234 1.363.516 1.879.848.527.34.941.773 1.238 1.293.297.52.445 1.16.445 1.91a4.07 4.07 0 0 1-.77 2.418zm0 0"/>
|
||||
</symbol>
|
||||
@@ -147,6 +167,14 @@ const Icons = () => (
|
||||
<symbol id="location" viewBox="0 0 24 24" fill="currentColor" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M12,2C8.134,2,5,5.134,5,9c0,5,7,13,7,13s7-8,7-13C19,5.134,15.866,2,12,2z M12,11.5c-1.381,0-2.5-1.119-2.5-2.5 c0-1.381,1.119-2.5,2.5-2.5s2.5,1.119,2.5,2.5C14.5,10.381,13.381,11.5,12,11.5z"/>
|
||||
</symbol>
|
||||
|
||||
<symbol id="cross" viewBox="0 0 24 24" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2">
|
||||
<line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line>
|
||||
</symbol>
|
||||
|
||||
<symbol id="plus" viewBox="0 0 24 24" stroke="currentColor" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2">
|
||||
<line x1="12" y1="5" x2="12" y2="19"></line><line x1="5" y1="12" x2="19" y2="12"></line>
|
||||
</symbol>
|
||||
</svg>
|
||||
);
|
||||
|
||||
|
||||
@@ -1,23 +1,27 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withNamespaces, Trans } from 'react-i18next';
|
||||
|
||||
import Card from '../ui/Card';
|
||||
|
||||
const Status = props => (
|
||||
const Status = ({ message, buttonMessage, reloadPage }) => (
|
||||
<div className="status">
|
||||
<Card bodyType="card-body card-body--status">
|
||||
<div className="h4 font-weight-light mb-4">
|
||||
You are currently not using AdGuard Home
|
||||
<Trans>{message}</Trans>
|
||||
</div>
|
||||
<button className="btn btn-success" onClick={props.handleStatusChange}>
|
||||
Enable AdGuard Home
|
||||
</button>
|
||||
{buttonMessage &&
|
||||
<button className="btn btn-success" onClick={reloadPage}>
|
||||
<Trans>{buttonMessage}</Trans>
|
||||
</button>}
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
|
||||
Status.propTypes = {
|
||||
handleStatusChange: PropTypes.func.isRequired,
|
||||
message: PropTypes.string.isRequired,
|
||||
buttonMessage: PropTypes.string,
|
||||
reloadPage: PropTypes.func,
|
||||
};
|
||||
|
||||
export default Status;
|
||||
export default withNamespaces()(Status);
|
||||
|
||||
@@ -64,6 +64,7 @@
|
||||
top: calc(100% + 10px);
|
||||
right: -10px;
|
||||
left: initial;
|
||||
width: 255px;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
toggleLeaseModal,
|
||||
addStaticLease,
|
||||
removeStaticLease,
|
||||
resetDhcp,
|
||||
} from '../actions';
|
||||
import Dhcp from '../components/Settings/Dhcp';
|
||||
|
||||
@@ -28,6 +29,7 @@ const mapDispatchToProps = {
|
||||
toggleLeaseModal,
|
||||
addStaticLease,
|
||||
removeStaticLease,
|
||||
resetDhcp,
|
||||
};
|
||||
|
||||
export default connect(
|
||||
|
||||
@@ -7,17 +7,19 @@ import {
|
||||
deleteRewrite,
|
||||
toggleRewritesModal,
|
||||
} from '../actions/rewrites';
|
||||
import { getDnsConfig, setDnsConfig } from '../actions/dnsConfig';
|
||||
import Dns from '../components/Settings/Dns';
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const {
|
||||
dashboard, settings, access, rewrites,
|
||||
dashboard, settings, access, rewrites, dnsConfig,
|
||||
} = state;
|
||||
const props = {
|
||||
dashboard,
|
||||
settings,
|
||||
access,
|
||||
rewrites,
|
||||
dnsConfig,
|
||||
};
|
||||
return props;
|
||||
};
|
||||
@@ -33,6 +35,8 @@ const mapDispatchToProps = {
|
||||
deleteRewrite,
|
||||
toggleRewritesModal,
|
||||
getDnsSettings,
|
||||
getDnsConfig,
|
||||
setDnsConfig,
|
||||
};
|
||||
|
||||
export default connect(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { addSuccessToast, getClients } from '../actions';
|
||||
import { getFilteringStatus, setRules } from '../actions/filtering';
|
||||
import { getLogs, getLogsConfig, setLogsPagination, setLogsFilter } from '../actions/queryLogs';
|
||||
import { getLogs, getLogsConfig, setLogsPagination, setLogsFilter, setLogsPage } from '../actions/queryLogs';
|
||||
import Logs from '../components/Logs';
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
@@ -19,6 +19,7 @@ const mapDispatchToProps = {
|
||||
getLogsConfig,
|
||||
setLogsPagination,
|
||||
setLogsFilter,
|
||||
setLogsPage,
|
||||
};
|
||||
|
||||
export default connect(
|
||||
|
||||
@@ -118,6 +118,22 @@ export const LANGUAGES = [
|
||||
key: 'zh-cn',
|
||||
name: '简体中文',
|
||||
},
|
||||
{
|
||||
key: 'no',
|
||||
name: 'Norsk',
|
||||
},
|
||||
{
|
||||
key: 'sr-cs',
|
||||
name: 'Srpski',
|
||||
},
|
||||
{
|
||||
key: 'hr',
|
||||
name: 'Hrvatski',
|
||||
},
|
||||
{
|
||||
key: 'fa',
|
||||
name: 'فارسی',
|
||||
},
|
||||
{
|
||||
key: 'ko',
|
||||
name: '한국어',
|
||||
@@ -141,6 +157,7 @@ export const STANDARD_HTTPS_PORT = 443;
|
||||
export const EMPTY_DATE = '0001-01-01T00:00:00Z';
|
||||
|
||||
export const DEBOUNCE_TIMEOUT = 300;
|
||||
export const DEBOUNCE_FILTER_TIMEOUT = 500;
|
||||
export const CHECK_TIMEOUT = 1000;
|
||||
export const STOP_TIMEOUT = 10000;
|
||||
|
||||
@@ -276,6 +293,22 @@ export const SERVICES = [
|
||||
id: 'skype',
|
||||
name: 'Skype',
|
||||
},
|
||||
{
|
||||
id: 'amazon',
|
||||
name: 'Amazon',
|
||||
},
|
||||
{
|
||||
id: 'ebay',
|
||||
name: 'eBay',
|
||||
},
|
||||
{
|
||||
id: 'origin',
|
||||
name: 'Origin',
|
||||
},
|
||||
{
|
||||
id: 'cloudflare',
|
||||
name: 'Cloudflare',
|
||||
},
|
||||
{
|
||||
id: 'steam',
|
||||
name: 'Steam',
|
||||
@@ -284,6 +317,10 @@ export const SERVICES = [
|
||||
id: 'epic_games',
|
||||
name: 'Epic Games',
|
||||
},
|
||||
{
|
||||
id: 'reddit',
|
||||
name: 'Reddit',
|
||||
},
|
||||
{
|
||||
id: 'ok',
|
||||
name: 'OK',
|
||||
@@ -320,6 +357,12 @@ export const QUERY_LOG_INTERVALS_DAYS = [1, 7, 30, 90];
|
||||
|
||||
export const FILTERS_INTERVALS_HOURS = [0, 1, 12, 24, 72, 168];
|
||||
|
||||
export const BLOCKING_MODES = {
|
||||
nxdomain: 'nxdomain',
|
||||
null_ip: 'null_ip',
|
||||
custom_ip: 'custom_ip',
|
||||
};
|
||||
|
||||
export const WHOIS_ICONS = {
|
||||
location: 'location',
|
||||
orgname: 'network',
|
||||
@@ -379,3 +422,12 @@ export const DEFAULT_LOGS_FILTER = {
|
||||
};
|
||||
|
||||
export const DEFAULT_LANGUAGE = 'en';
|
||||
|
||||
export const TABLE_DEFAULT_PAGE_SIZE = 100;
|
||||
|
||||
export const SMALL_TABLE_DEFAULT_PAGE_SIZE = 20;
|
||||
|
||||
export const RESPONSE_FILTER = {
|
||||
ALL: 'all',
|
||||
FILTERED: 'filtered',
|
||||
};
|
||||
|
||||
@@ -29,6 +29,50 @@ export const renderField = ({
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
export const renderGroupField = ({
|
||||
input,
|
||||
id,
|
||||
className,
|
||||
placeholder,
|
||||
type,
|
||||
disabled,
|
||||
autoComplete,
|
||||
isActionAvailable,
|
||||
removeField,
|
||||
meta: { touched, error },
|
||||
}) => (
|
||||
<Fragment>
|
||||
<div className="input-group">
|
||||
<input
|
||||
{...input}
|
||||
id={id}
|
||||
placeholder={placeholder}
|
||||
type={type}
|
||||
className={className}
|
||||
disabled={disabled}
|
||||
autoComplete={autoComplete}
|
||||
/>
|
||||
{isActionAvailable &&
|
||||
<span className="input-group-append">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary btn-icon"
|
||||
onClick={removeField}
|
||||
>
|
||||
<svg className="icon icon--close">
|
||||
<use xlinkHref="#cross" />
|
||||
</svg>
|
||||
</button>
|
||||
</span>
|
||||
}
|
||||
</div>
|
||||
|
||||
{!disabled &&
|
||||
touched &&
|
||||
(error && <span className="form__message form__message--error">{error}</span>)}
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
export const renderRadioField = ({
|
||||
input, placeholder, disabled, meta: { touched, error },
|
||||
}) => (
|
||||
@@ -102,6 +146,7 @@ export const renderServiceField = ({
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
// Validation functions
|
||||
export const required = (value) => {
|
||||
if (value || value === 0) {
|
||||
return false;
|
||||
@@ -111,6 +156,20 @@ export const required = (value) => {
|
||||
|
||||
export const ipv4 = (value) => {
|
||||
if (value && !new RegExp(R_IPV4).test(value)) {
|
||||
return <Trans>form_error_ip4_format</Trans>;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export const ipv6 = (value) => {
|
||||
if (value && !new RegExp(R_IPV6).test(value)) {
|
||||
return <Trans>form_error_ip6_format</Trans>;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export const ip = (value) => {
|
||||
if (value && !new RegExp(R_IPV4).test(value) && !new RegExp(R_IPV6).test(value)) {
|
||||
return <Trans>form_error_ip_format</Trans>;
|
||||
}
|
||||
return false;
|
||||
@@ -130,6 +189,13 @@ export const isPositive = (value) => {
|
||||
return false;
|
||||
};
|
||||
|
||||
export const biggerOrEqualZero = (value) => {
|
||||
if (value < 0) {
|
||||
return <Trans>form_error_negative</Trans>;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export const port = (value) => {
|
||||
if ((value || value === 0) && (value < 80 || value > 65535)) {
|
||||
return <Trans>form_error_port_range</Trans>;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { getClientInfo, normalizeWhois } from './helpers';
|
||||
import { normalizeWhois } from './helpers';
|
||||
import { WHOIS_ICONS } from './constants';
|
||||
|
||||
const getFormattedWhois = (whois, t) => {
|
||||
@@ -22,26 +22,29 @@ const getFormattedWhois = (whois, t) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const formatClientCell = (value, clients, autoClients, t) => {
|
||||
const clientInfo = getClientInfo(clients, value) || getClientInfo(autoClients, value);
|
||||
const { name, whois } = clientInfo;
|
||||
export const formatClientCell = (row, t) => {
|
||||
const { value, original: { info } } = row;
|
||||
let whoisContainer = '';
|
||||
let nameContainer = value;
|
||||
|
||||
if (name) {
|
||||
nameContainer = (
|
||||
<span className="logs__text logs__text--wrap" title={`${name} (${value})`}>
|
||||
{name} <small>({value})</small>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
if (info) {
|
||||
const { name, whois_info } = info;
|
||||
|
||||
if (whois) {
|
||||
whoisContainer = (
|
||||
<div className="logs__text logs__text--wrap logs__text--whois">
|
||||
{getFormattedWhois(whois, t)}
|
||||
</div>
|
||||
);
|
||||
if (name) {
|
||||
nameContainer = (
|
||||
<span className="logs__text logs__text--wrap" title={`${name} (${value})`}>
|
||||
{name} <small>({value})</small>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
if (whois_info) {
|
||||
whoisContainer = (
|
||||
<div className="logs__text logs__text--wrap logs__text--whois">
|
||||
{getFormattedWhois(whois_info, t)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -8,6 +8,7 @@ import subDays from 'date-fns/sub_days';
|
||||
import round from 'lodash/round';
|
||||
import axios from 'axios';
|
||||
import i18n from 'i18next';
|
||||
import uniqBy from 'lodash/uniqBy';
|
||||
import versionCompare from './versionCompare';
|
||||
|
||||
import {
|
||||
@@ -49,6 +50,7 @@ export const normalizeLogs = logs => logs.map((log) => {
|
||||
rule,
|
||||
service_name,
|
||||
status,
|
||||
original_answer,
|
||||
} = log;
|
||||
const { host: domain, type } = question;
|
||||
const responsesArray = response ? response.map((response) => {
|
||||
@@ -64,8 +66,9 @@ export const normalizeLogs = logs => logs.map((log) => {
|
||||
client,
|
||||
filterId,
|
||||
rule,
|
||||
serviceName: service_name,
|
||||
status,
|
||||
serviceName: service_name,
|
||||
originalAnswer: original_answer,
|
||||
};
|
||||
});
|
||||
|
||||
@@ -92,6 +95,17 @@ export const normalizeTopStats = stats => (
|
||||
}))
|
||||
);
|
||||
|
||||
export const addClientInfo = (data, clients, param) => (
|
||||
data.map((row) => {
|
||||
const clientIp = row[param];
|
||||
const info = clients.find(item => item[clientIp]) || '';
|
||||
return {
|
||||
...row,
|
||||
info: (info && info[clientIp]) || '',
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
export const normalizeFilteringStatus = (filteringStatus) => {
|
||||
const {
|
||||
enabled, filters, user_rules: userRules, interval,
|
||||
@@ -248,6 +262,20 @@ export const redirectToCurrentProtocol = (values, httpPort = 80) => {
|
||||
export const normalizeTextarea = text => text && text.replace(/[;, ]/g, '\n').split('\n').filter(n => n);
|
||||
|
||||
export const getClientInfo = (clients, ip) => {
|
||||
const client = clients
|
||||
.find(item => item.ip_addrs && item.ip_addrs.find(clientIp => clientIp === ip));
|
||||
|
||||
if (!client) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const { name, whois_info } = client;
|
||||
const whois = Object.keys(whois_info).length > 0 ? whois_info : '';
|
||||
|
||||
return { name, whois };
|
||||
};
|
||||
|
||||
export const getAutoClientInfo = (clients, ip) => {
|
||||
const client = clients.find(item => ip === item.ip);
|
||||
|
||||
if (!client) {
|
||||
@@ -328,3 +356,13 @@ export const getPathWithQueryString = (path, params) => {
|
||||
|
||||
return `${path}?${searchParams.toString()}`;
|
||||
};
|
||||
|
||||
export const getParamsForClientsSearch = (data, param) => {
|
||||
const uniqueClients = uniqBy(data, param);
|
||||
return uniqueClients
|
||||
.reduce((acc, item, idx) => {
|
||||
const key = `ip${idx}`;
|
||||
acc[key] = item[param];
|
||||
return acc;
|
||||
}, {});
|
||||
};
|
||||
|
||||
@@ -22,12 +22,16 @@ import de from './__locales/de.json';
|
||||
import id from './__locales/id.json';
|
||||
import it from './__locales/it.json';
|
||||
import ko from './__locales/ko.json';
|
||||
import no from './__locales/no.json';
|
||||
import nl from './__locales/nl.json';
|
||||
import pl from './__locales/pl.json';
|
||||
import ptPT from './__locales/pt-pt.json';
|
||||
import sk from './__locales/sk.json';
|
||||
import sl from './__locales/sl.json';
|
||||
import tr from './__locales/tr.json';
|
||||
import srCS from './__locales/sr-cs.json';
|
||||
import hr from './__locales/hr.json';
|
||||
import fa from './__locales/fa.json';
|
||||
|
||||
const resources = {
|
||||
en: {
|
||||
@@ -81,6 +85,9 @@ const resources = {
|
||||
ko: {
|
||||
translation: ko,
|
||||
},
|
||||
no: {
|
||||
translation: no,
|
||||
},
|
||||
nl: {
|
||||
translation: nl,
|
||||
},
|
||||
@@ -99,6 +106,15 @@ const resources = {
|
||||
tr: {
|
||||
translation: tr,
|
||||
},
|
||||
'sr-cs': {
|
||||
translation: srCS,
|
||||
},
|
||||
hr: {
|
||||
translation: hr,
|
||||
},
|
||||
fa: {
|
||||
translation: fa,
|
||||
},
|
||||
};
|
||||
|
||||
const availableLanguages = Object.keys(resources);
|
||||
|
||||
50
client/src/reducers/dnsConfig.js
Normal file
50
client/src/reducers/dnsConfig.js
Normal file
@@ -0,0 +1,50 @@
|
||||
import { handleActions } from 'redux-actions';
|
||||
|
||||
import * as actions from '../actions/dnsConfig';
|
||||
import { BLOCKING_MODES } from '../helpers/constants';
|
||||
|
||||
const DEFAULT_BLOCKING_IPV4 = '0.0.0.0';
|
||||
const DEFAULT_BLOCKING_IPV6 = '::';
|
||||
|
||||
const dnsConfig = handleActions(
|
||||
{
|
||||
[actions.getDnsConfigRequest]: state => ({ ...state, processingGetConfig: true }),
|
||||
[actions.getDnsConfigFailure]: state =>
|
||||
({ ...state, processingGetConfig: false }),
|
||||
[actions.getDnsConfigSuccess]: (state, { payload }) => {
|
||||
const {
|
||||
blocking_ipv4,
|
||||
blocking_ipv6,
|
||||
...values
|
||||
} = payload;
|
||||
|
||||
return {
|
||||
...state,
|
||||
...values,
|
||||
blocking_ipv4: blocking_ipv4 || DEFAULT_BLOCKING_IPV4,
|
||||
blocking_ipv6: blocking_ipv6 || DEFAULT_BLOCKING_IPV6,
|
||||
processingGetConfig: false,
|
||||
};
|
||||
},
|
||||
|
||||
[actions.setDnsConfigRequest]: state => ({ ...state, processingSetConfig: true }),
|
||||
[actions.setDnsConfigFailure]: state =>
|
||||
({ ...state, processingSetConfig: false }),
|
||||
[actions.setDnsConfigSuccess]: (state, { payload }) => ({
|
||||
...state,
|
||||
...payload,
|
||||
processingSetConfig: false,
|
||||
}),
|
||||
},
|
||||
{
|
||||
processingGetConfig: false,
|
||||
processingSetConfig: false,
|
||||
blocking_mode: BLOCKING_MODES.nxdomain,
|
||||
ratelimit: 20,
|
||||
blocking_ipv4: DEFAULT_BLOCKING_IPV4,
|
||||
blocking_ipv6: DEFAULT_BLOCKING_IPV6,
|
||||
edns_cs_enabled: false,
|
||||
},
|
||||
);
|
||||
|
||||
export default dnsConfig;
|
||||
@@ -13,6 +13,7 @@ import rewrites from './rewrites';
|
||||
import services from './services';
|
||||
import stats from './stats';
|
||||
import queryLogs from './queryLogs';
|
||||
import dnsConfig from './dnsConfig';
|
||||
import filtering from './filtering';
|
||||
|
||||
const settings = handleActions(
|
||||
@@ -57,12 +58,13 @@ const settings = handleActions(
|
||||
|
||||
const dashboard = handleActions(
|
||||
{
|
||||
[actions.setDnsRunningStatus]: (state, { payload }) =>
|
||||
({ ...state, isCoreRunning: payload }),
|
||||
[actions.dnsStatusRequest]: state => ({ ...state, processing: true }),
|
||||
[actions.dnsStatusFailure]: state => ({ ...state, processing: false }),
|
||||
[actions.dnsStatusSuccess]: (state, { payload }) => {
|
||||
const {
|
||||
version,
|
||||
running,
|
||||
dns_port: dnsPort,
|
||||
dns_addresses: dnsAddresses,
|
||||
upstream_dns: upstreamDns,
|
||||
@@ -74,7 +76,7 @@ const dashboard = handleActions(
|
||||
} = payload;
|
||||
const newState = {
|
||||
...state,
|
||||
isCoreRunning: running,
|
||||
isCoreRunning: true,
|
||||
processing: false,
|
||||
dnsVersion: version,
|
||||
dnsPort,
|
||||
@@ -89,20 +91,6 @@ const dashboard = handleActions(
|
||||
return newState;
|
||||
},
|
||||
|
||||
[actions.enableDnsRequest]: state => ({ ...state, processing: true }),
|
||||
[actions.enableDnsFailure]: state => ({ ...state, processing: false }),
|
||||
[actions.enableDnsSuccess]: (state) => {
|
||||
const newState = { ...state, isCoreRunning: !state.isCoreRunning, processing: false };
|
||||
return newState;
|
||||
},
|
||||
|
||||
[actions.disableDnsRequest]: state => ({ ...state, processing: true }),
|
||||
[actions.disableDnsFailure]: state => ({ ...state, processing: false }),
|
||||
[actions.disableDnsSuccess]: (state) => {
|
||||
const newState = { ...state, isCoreRunning: !state.isCoreRunning, processing: false };
|
||||
return newState;
|
||||
},
|
||||
|
||||
[actions.getVersionRequest]: state => ({ ...state, processingVersion: true }),
|
||||
[actions.getVersionFailure]: state => ({ ...state, processingVersion: false }),
|
||||
[actions.getVersionSuccess]: (state, { payload }) => {
|
||||
@@ -200,7 +188,7 @@ const dashboard = handleActions(
|
||||
},
|
||||
{
|
||||
processing: true,
|
||||
isCoreRunning: false,
|
||||
isCoreRunning: true,
|
||||
processingVersion: true,
|
||||
processingFiltering: true,
|
||||
processingClients: true,
|
||||
@@ -289,6 +277,16 @@ const dhcp = handleActions(
|
||||
return newState;
|
||||
},
|
||||
|
||||
[actions.resetDhcpRequest]: state => ({ ...state, processingReset: true }),
|
||||
[actions.resetDhcpFailure]: state => ({ ...state, processingReset: false }),
|
||||
[actions.resetDhcpSuccess]: state => ({
|
||||
...state,
|
||||
processingReset: false,
|
||||
config: {
|
||||
enabled: false,
|
||||
},
|
||||
}),
|
||||
|
||||
[actions.toggleLeaseModal]: (state) => {
|
||||
const newState = {
|
||||
...state,
|
||||
@@ -359,6 +357,7 @@ export default combineReducers({
|
||||
rewrites,
|
||||
services,
|
||||
stats,
|
||||
dnsConfig,
|
||||
loadingBar: loadingBarReducer,
|
||||
form: formReducer,
|
||||
});
|
||||
|
||||
@@ -20,25 +20,50 @@ const queryLogs = handleActions(
|
||||
};
|
||||
},
|
||||
|
||||
[actions.setLogsFilter]: (state, { payload }) => (
|
||||
{ ...state, filter: payload }
|
||||
),
|
||||
[actions.setLogsPage]: (state, { payload }) => ({
|
||||
...state,
|
||||
page: payload,
|
||||
}),
|
||||
|
||||
[actions.setLogsFilterRequest]: state => ({ ...state, processingGetLogs: true }),
|
||||
[actions.setLogsFilterFailure]: state => ({ ...state, processingGetLogs: false }),
|
||||
[actions.setLogsFilterSuccess]: (state, { payload }) => {
|
||||
const { logs, oldest, filter } = payload;
|
||||
const pageSize = 100;
|
||||
const page = 0;
|
||||
|
||||
const pages = Math.ceil(logs.length / pageSize);
|
||||
const total = logs.length;
|
||||
const rowsStart = pageSize * page;
|
||||
const rowsEnd = (pageSize * page) + pageSize;
|
||||
const logsSlice = logs.slice(rowsStart, rowsEnd);
|
||||
const isFiltered = Object.keys(filter).some(key => filter[key]);
|
||||
|
||||
return {
|
||||
...state,
|
||||
oldest,
|
||||
filter,
|
||||
isFiltered,
|
||||
pages,
|
||||
total,
|
||||
logs: logsSlice,
|
||||
allLogs: logs,
|
||||
processingGetLogs: false,
|
||||
};
|
||||
},
|
||||
|
||||
[actions.getLogsRequest]: state => ({ ...state, processingGetLogs: true }),
|
||||
[actions.getLogsFailure]: state => ({ ...state, processingGetLogs: false }),
|
||||
[actions.getLogsSuccess]: (state, { payload }) => {
|
||||
const {
|
||||
logs, lastRowTime, page, pageSize, filtered,
|
||||
logs, oldest, older_than, page, pageSize,
|
||||
} = payload;
|
||||
let logsWithOffset = state.allLogs.length > 0 ? state.allLogs : logs;
|
||||
let allLogs = logs;
|
||||
|
||||
if (lastRowTime) {
|
||||
if (older_than) {
|
||||
logsWithOffset = [...state.allLogs, ...logs];
|
||||
allLogs = [...state.allLogs, ...logs];
|
||||
} else if (filtered) {
|
||||
logsWithOffset = logs;
|
||||
allLogs = logs;
|
||||
}
|
||||
|
||||
const pages = Math.ceil(logsWithOffset.length / pageSize);
|
||||
@@ -49,6 +74,7 @@ const queryLogs = handleActions(
|
||||
|
||||
return {
|
||||
...state,
|
||||
oldest,
|
||||
pages,
|
||||
total,
|
||||
allLogs,
|
||||
@@ -81,20 +107,33 @@ const queryLogs = handleActions(
|
||||
...payload,
|
||||
processingSetConfig: false,
|
||||
}),
|
||||
|
||||
[actions.getAdditionalLogsRequest]: state => ({
|
||||
...state, processingAdditionalLogs: true, processingGetLogs: true,
|
||||
}),
|
||||
[actions.getAdditionalLogsFailure]: state => ({
|
||||
...state, processingAdditionalLogs: false, processingGetLogs: false,
|
||||
}),
|
||||
[actions.getAdditionalLogsSuccess]: state => ({
|
||||
...state, processingAdditionalLogs: false, processingGetLogs: false,
|
||||
}),
|
||||
},
|
||||
{
|
||||
processingGetLogs: true,
|
||||
processingClear: false,
|
||||
processingGetConfig: false,
|
||||
processingSetConfig: false,
|
||||
processingAdditionalLogs: false,
|
||||
logs: [],
|
||||
interval: 1,
|
||||
allLogs: [],
|
||||
page: 0,
|
||||
pages: 0,
|
||||
total: 0,
|
||||
enabled: true,
|
||||
older_than: '',
|
||||
oldest: '',
|
||||
filter: DEFAULT_LOGS_FILTER,
|
||||
isFiltered: false,
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
42
dhcpd/db.go
42
dhcpd/db.go
@@ -36,6 +36,8 @@ func ipInRange(start, stop, ip net.IP) bool {
|
||||
func (s *Server) dbLoad() {
|
||||
s.leases = nil
|
||||
s.IPpool = make(map[[4]byte]net.HardwareAddr)
|
||||
dynLeases := []*Lease{}
|
||||
staticLeases := []*Lease{}
|
||||
|
||||
data, err := ioutil.ReadFile(s.conf.DBFilePath)
|
||||
if err != nil {
|
||||
@@ -69,11 +71,47 @@ func (s *Server) dbLoad() {
|
||||
Expiry: time.Unix(obj[i].Expiry, 0),
|
||||
}
|
||||
|
||||
s.leases = append(s.leases, &lease)
|
||||
if obj[i].Expiry == leaseExpireStatic {
|
||||
staticLeases = append(staticLeases, &lease)
|
||||
} else {
|
||||
dynLeases = append(dynLeases, &lease)
|
||||
}
|
||||
}
|
||||
|
||||
s.leases = normalizeLeases(staticLeases, dynLeases)
|
||||
|
||||
for _, lease := range s.leases {
|
||||
s.reserveIP(lease.IP, lease.HWAddr)
|
||||
}
|
||||
log.Info("DHCP: loaded %d leases from DB", numLeases)
|
||||
|
||||
log.Info("DHCP: loaded %d (%d) leases from DB", len(s.leases), numLeases)
|
||||
}
|
||||
|
||||
// Skip duplicate leases
|
||||
// Static leases have a priority over dynamic leases
|
||||
func normalizeLeases(staticLeases, dynLeases []*Lease) []*Lease {
|
||||
leases := []*Lease{}
|
||||
index := map[string]int{}
|
||||
|
||||
for i, lease := range staticLeases {
|
||||
_, ok := index[lease.HWAddr.String()]
|
||||
if ok {
|
||||
continue // skip the lease with the same HW address
|
||||
}
|
||||
index[lease.HWAddr.String()] = i
|
||||
leases = append(leases, lease)
|
||||
}
|
||||
|
||||
for i, lease := range dynLeases {
|
||||
_, ok := index[lease.HWAddr.String()]
|
||||
if ok {
|
||||
continue // skip the lease with the same HW address
|
||||
}
|
||||
index[lease.HWAddr.String()] = i
|
||||
leases = append(leases, lease)
|
||||
}
|
||||
|
||||
return leases
|
||||
}
|
||||
|
||||
// Store lease table in DB
|
||||
|
||||
485
dhcpd/dhcp_http.go
Normal file
485
dhcpd/dhcp_http.go
Normal file
@@ -0,0 +1,485 @@
|
||||
package dhcpd
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/golibs/file"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
)
|
||||
|
||||
func httpError(r *http.Request, w http.ResponseWriter, code int, format string, args ...interface{}) {
|
||||
text := fmt.Sprintf(format, args...)
|
||||
log.Info("DHCP: %s %s: %s", r.Method, r.URL, text)
|
||||
http.Error(w, text, code)
|
||||
}
|
||||
|
||||
// []Lease -> JSON
|
||||
func convertLeases(inputLeases []Lease, includeExpires bool) []map[string]string {
|
||||
leases := []map[string]string{}
|
||||
for _, l := range inputLeases {
|
||||
lease := map[string]string{
|
||||
"mac": l.HWAddr.String(),
|
||||
"ip": l.IP.String(),
|
||||
"hostname": l.Hostname,
|
||||
}
|
||||
|
||||
if includeExpires {
|
||||
lease["expires"] = l.Expiry.Format(time.RFC3339)
|
||||
}
|
||||
|
||||
leases = append(leases, lease)
|
||||
}
|
||||
return leases
|
||||
}
|
||||
|
||||
func (s *Server) handleDHCPStatus(w http.ResponseWriter, r *http.Request) {
|
||||
leases := convertLeases(s.Leases(), true)
|
||||
staticLeases := convertLeases(s.StaticLeases(), false)
|
||||
status := map[string]interface{}{
|
||||
"config": s.conf,
|
||||
"leases": leases,
|
||||
"static_leases": staticLeases,
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
err := json.NewEncoder(w).Encode(status)
|
||||
if err != nil {
|
||||
httpError(r, w, http.StatusInternalServerError, "Unable to marshal DHCP status json: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
type staticLeaseJSON struct {
|
||||
HWAddr string `json:"mac"`
|
||||
IP string `json:"ip"`
|
||||
Hostname string `json:"hostname"`
|
||||
}
|
||||
|
||||
type dhcpServerConfigJSON struct {
|
||||
ServerConfig `json:",inline"`
|
||||
StaticLeases []staticLeaseJSON `json:"static_leases"`
|
||||
}
|
||||
|
||||
func (s *Server) handleDHCPSetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
newconfig := dhcpServerConfigJSON{}
|
||||
err := json.NewDecoder(r.Body).Decode(&newconfig)
|
||||
if err != nil {
|
||||
httpError(r, w, http.StatusBadRequest, "Failed to parse new DHCP config json: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = s.CheckConfig(newconfig.ServerConfig)
|
||||
if err != nil {
|
||||
httpError(r, w, http.StatusBadRequest, "Invalid DHCP configuration: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = s.Stop()
|
||||
if err != nil {
|
||||
log.Error("failed to stop the DHCP server: %s", err)
|
||||
}
|
||||
|
||||
err = s.Init(newconfig.ServerConfig)
|
||||
if err != nil {
|
||||
httpError(r, w, http.StatusBadRequest, "Invalid DHCP configuration: %s", err)
|
||||
return
|
||||
}
|
||||
s.conf.ConfigModified()
|
||||
|
||||
if newconfig.Enabled {
|
||||
|
||||
staticIP, err := hasStaticIP(newconfig.InterfaceName)
|
||||
if !staticIP && err == nil {
|
||||
err = setStaticIP(newconfig.InterfaceName)
|
||||
if err != nil {
|
||||
httpError(r, w, http.StatusInternalServerError, "Failed to configure static IP: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err = s.Start()
|
||||
if err != nil {
|
||||
httpError(r, w, http.StatusBadRequest, "Failed to start DHCP server: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type netInterface struct {
|
||||
Name string `json:"name"`
|
||||
MTU int `json:"mtu"`
|
||||
HardwareAddr string `json:"hardware_address"`
|
||||
Addresses []string `json:"ip_addresses"`
|
||||
Flags string `json:"flags"`
|
||||
}
|
||||
|
||||
// getValidNetInterfaces returns interfaces that are eligible for DNS and/or DHCP
|
||||
// invalid interface is a ppp interface or the one that doesn't allow broadcasts
|
||||
func getValidNetInterfaces() ([]net.Interface, error) {
|
||||
ifaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Couldn't get list of interfaces: %s", err)
|
||||
}
|
||||
|
||||
netIfaces := []net.Interface{}
|
||||
|
||||
for i := range ifaces {
|
||||
if ifaces[i].Flags&net.FlagPointToPoint != 0 {
|
||||
// this interface is ppp, we're not interested in this one
|
||||
continue
|
||||
}
|
||||
|
||||
iface := ifaces[i]
|
||||
netIfaces = append(netIfaces, iface)
|
||||
}
|
||||
|
||||
return netIfaces, nil
|
||||
}
|
||||
|
||||
func (s *Server) handleDHCPInterfaces(w http.ResponseWriter, r *http.Request) {
|
||||
response := map[string]interface{}{}
|
||||
|
||||
ifaces, err := getValidNetInterfaces()
|
||||
if err != nil {
|
||||
httpError(r, w, http.StatusInternalServerError, "Couldn't get interfaces: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, iface := range ifaces {
|
||||
if iface.Flags&net.FlagLoopback != 0 {
|
||||
// it's a loopback, skip it
|
||||
continue
|
||||
}
|
||||
if iface.Flags&net.FlagBroadcast == 0 {
|
||||
// this interface doesn't support broadcast, skip it
|
||||
continue
|
||||
}
|
||||
addrs, err := iface.Addrs()
|
||||
if err != nil {
|
||||
httpError(r, w, http.StatusInternalServerError, "Failed to get addresses for interface %s: %s", iface.Name, err)
|
||||
return
|
||||
}
|
||||
|
||||
jsonIface := netInterface{
|
||||
Name: iface.Name,
|
||||
MTU: iface.MTU,
|
||||
HardwareAddr: iface.HardwareAddr.String(),
|
||||
}
|
||||
|
||||
if iface.Flags != 0 {
|
||||
jsonIface.Flags = iface.Flags.String()
|
||||
}
|
||||
// we don't want link-local addresses in json, so skip them
|
||||
for _, addr := range addrs {
|
||||
ipnet, ok := addr.(*net.IPNet)
|
||||
if !ok {
|
||||
// not an IPNet, should not happen
|
||||
httpError(r, w, http.StatusInternalServerError, "SHOULD NOT HAPPEN: got iface.Addrs() element %s that is not net.IPNet, it is %T", addr, addr)
|
||||
return
|
||||
}
|
||||
// ignore link-local
|
||||
if ipnet.IP.IsLinkLocalUnicast() {
|
||||
continue
|
||||
}
|
||||
jsonIface.Addresses = append(jsonIface.Addresses, ipnet.IP.String())
|
||||
}
|
||||
if len(jsonIface.Addresses) != 0 {
|
||||
response[iface.Name] = jsonIface
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
err = json.NewEncoder(w).Encode(response)
|
||||
if err != nil {
|
||||
httpError(r, w, http.StatusInternalServerError, "Failed to marshal json with available interfaces: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Perform the following tasks:
|
||||
// . Search for another DHCP server running
|
||||
// . Check if a static IP is configured for the network interface
|
||||
// Respond with results
|
||||
func (s *Server) handleDHCPFindActiveServer(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
errorText := fmt.Sprintf("failed to read request body: %s", err)
|
||||
log.Error(errorText)
|
||||
http.Error(w, errorText, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
interfaceName := strings.TrimSpace(string(body))
|
||||
if interfaceName == "" {
|
||||
errorText := fmt.Sprintf("empty interface name specified")
|
||||
log.Error(errorText)
|
||||
http.Error(w, errorText, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
found, err := CheckIfOtherDHCPServersPresent(interfaceName)
|
||||
|
||||
othSrv := map[string]interface{}{}
|
||||
foundVal := "no"
|
||||
if found {
|
||||
foundVal = "yes"
|
||||
} else if err != nil {
|
||||
foundVal = "error"
|
||||
othSrv["error"] = err.Error()
|
||||
}
|
||||
othSrv["found"] = foundVal
|
||||
|
||||
staticIP := map[string]interface{}{}
|
||||
isStaticIP, err := hasStaticIP(interfaceName)
|
||||
staticIPStatus := "yes"
|
||||
if err != nil {
|
||||
staticIPStatus = "error"
|
||||
staticIP["error"] = err.Error()
|
||||
} else if !isStaticIP {
|
||||
staticIPStatus = "no"
|
||||
staticIP["ip"] = getFullIP(interfaceName)
|
||||
}
|
||||
staticIP["static"] = staticIPStatus
|
||||
|
||||
result := map[string]interface{}{}
|
||||
result["other_server"] = othSrv
|
||||
result["static_ip"] = staticIP
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
err = json.NewEncoder(w).Encode(result)
|
||||
if err != nil {
|
||||
httpError(r, w, http.StatusInternalServerError, "Failed to marshal DHCP found json: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Check if network interface has a static IP configured
|
||||
func hasStaticIP(ifaceName string) (bool, error) {
|
||||
if runtime.GOOS == "windows" {
|
||||
return false, errors.New("Can't detect static IP: not supported on Windows")
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadFile("/etc/dhcpcd.conf")
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
lines := strings.Split(string(body), "\n")
|
||||
nameLine := fmt.Sprintf("interface %s", ifaceName)
|
||||
withinInterfaceCtx := false
|
||||
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
|
||||
if withinInterfaceCtx && len(line) == 0 {
|
||||
// an empty line resets our state
|
||||
withinInterfaceCtx = false
|
||||
}
|
||||
|
||||
if len(line) == 0 || line[0] == '#' {
|
||||
continue
|
||||
}
|
||||
line = strings.TrimSpace(line)
|
||||
|
||||
if !withinInterfaceCtx {
|
||||
if line == nameLine {
|
||||
// we found our interface
|
||||
withinInterfaceCtx = true
|
||||
}
|
||||
|
||||
} else {
|
||||
if strings.HasPrefix(line, "interface ") {
|
||||
// we found another interface - reset our state
|
||||
withinInterfaceCtx = false
|
||||
continue
|
||||
}
|
||||
if strings.HasPrefix(line, "static ip_address=") {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Get IP address with netmask
|
||||
func getFullIP(ifaceName string) string {
|
||||
cmd := exec.Command("ip", "-oneline", "-family", "inet", "address", "show", ifaceName)
|
||||
log.Tracef("executing %s %v", cmd.Path, cmd.Args)
|
||||
d, err := cmd.Output()
|
||||
if err != nil || cmd.ProcessState.ExitCode() != 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
fields := strings.Fields(string(d))
|
||||
if len(fields) < 4 {
|
||||
return ""
|
||||
}
|
||||
_, _, err = net.ParseCIDR(fields[3])
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return fields[3]
|
||||
}
|
||||
|
||||
// Get gateway IP address
|
||||
func getGatewayIP(ifaceName string) string {
|
||||
cmd := exec.Command("ip", "route", "show", "dev", ifaceName)
|
||||
log.Tracef("executing %s %v", cmd.Path, cmd.Args)
|
||||
d, err := cmd.Output()
|
||||
if err != nil || cmd.ProcessState.ExitCode() != 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
fields := strings.Fields(string(d))
|
||||
if len(fields) < 3 || fields[0] != "default" {
|
||||
return ""
|
||||
}
|
||||
|
||||
ip := net.ParseIP(fields[2])
|
||||
if ip == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return fields[2]
|
||||
}
|
||||
|
||||
// Set a static IP for network interface
|
||||
func setStaticIP(ifaceName string) error {
|
||||
ip := getFullIP(ifaceName)
|
||||
if len(ip) == 0 {
|
||||
return errors.New("Can't get IP address")
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadFile("/etc/dhcpcd.conf")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ip4, _, err := net.ParseCIDR(ip)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
add := fmt.Sprintf("\ninterface %s\nstatic ip_address=%s\n",
|
||||
ifaceName, ip)
|
||||
body = append(body, []byte(add)...)
|
||||
|
||||
gatewayIP := getGatewayIP(ifaceName)
|
||||
if len(gatewayIP) != 0 {
|
||||
add = fmt.Sprintf("static routers=%s\n",
|
||||
gatewayIP)
|
||||
body = append(body, []byte(add)...)
|
||||
}
|
||||
|
||||
add = fmt.Sprintf("static domain_name_servers=%s\n\n",
|
||||
ip4)
|
||||
body = append(body, []byte(add)...)
|
||||
|
||||
err = file.SafeWrite("/etc/dhcpcd.conf", body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) handleDHCPAddStaticLease(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
lj := staticLeaseJSON{}
|
||||
err := json.NewDecoder(r.Body).Decode(&lj)
|
||||
if err != nil {
|
||||
httpError(r, w, http.StatusBadRequest, "json.Decode: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
ip, _ := parseIPv4(lj.IP)
|
||||
if ip == nil {
|
||||
httpError(r, w, http.StatusBadRequest, "invalid IP")
|
||||
return
|
||||
}
|
||||
|
||||
mac, _ := net.ParseMAC(lj.HWAddr)
|
||||
|
||||
lease := Lease{
|
||||
IP: ip,
|
||||
HWAddr: mac,
|
||||
Hostname: lj.Hostname,
|
||||
}
|
||||
err = s.AddStaticLease(lease)
|
||||
if err != nil {
|
||||
httpError(r, w, http.StatusBadRequest, "%s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) handleDHCPRemoveStaticLease(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
lj := staticLeaseJSON{}
|
||||
err := json.NewDecoder(r.Body).Decode(&lj)
|
||||
if err != nil {
|
||||
httpError(r, w, http.StatusBadRequest, "json.Decode: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
ip, _ := parseIPv4(lj.IP)
|
||||
if ip == nil {
|
||||
httpError(r, w, http.StatusBadRequest, "invalid IP")
|
||||
return
|
||||
}
|
||||
|
||||
mac, _ := net.ParseMAC(lj.HWAddr)
|
||||
|
||||
lease := Lease{
|
||||
IP: ip,
|
||||
HWAddr: mac,
|
||||
Hostname: lj.Hostname,
|
||||
}
|
||||
err = s.RemoveStaticLease(lease)
|
||||
if err != nil {
|
||||
httpError(r, w, http.StatusBadRequest, "%s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) handleReset(w http.ResponseWriter, r *http.Request) {
|
||||
err := s.Stop()
|
||||
if err != nil {
|
||||
log.Error("DHCP: Stop: %s", err)
|
||||
}
|
||||
|
||||
err = os.Remove(s.conf.DBFilePath)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
log.Error("DHCP: os.Remove: %s: %s", s.conf.DBFilePath, err)
|
||||
}
|
||||
|
||||
oldconf := s.conf
|
||||
s.conf = ServerConfig{}
|
||||
s.conf.LeaseDuration = 86400
|
||||
s.conf.ICMPTimeout = 1000
|
||||
s.conf.WorkDir = oldconf.WorkDir
|
||||
s.conf.HTTPRegister = oldconf.HTTPRegister
|
||||
s.conf.ConfigModified = oldconf.ConfigModified
|
||||
s.conf.DBFilePath = oldconf.DBFilePath
|
||||
s.conf.ConfigModified()
|
||||
}
|
||||
|
||||
func (s *Server) registerHandlers() {
|
||||
s.conf.HTTPRegister("GET", "/control/dhcp/status", s.handleDHCPStatus)
|
||||
s.conf.HTTPRegister("GET", "/control/dhcp/interfaces", s.handleDHCPInterfaces)
|
||||
s.conf.HTTPRegister("POST", "/control/dhcp/set_config", s.handleDHCPSetConfig)
|
||||
s.conf.HTTPRegister("POST", "/control/dhcp/find_active_dhcp", s.handleDHCPFindActiveServer)
|
||||
s.conf.HTTPRegister("POST", "/control/dhcp/add_static_lease", s.handleDHCPAddStaticLease)
|
||||
s.conf.HTTPRegister("POST", "/control/dhcp/remove_static_lease", s.handleDHCPRemoveStaticLease)
|
||||
s.conf.HTTPRegister("POST", "/control/dhcp/reset", s.handleReset)
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
@@ -38,13 +39,20 @@ type ServerConfig struct {
|
||||
SubnetMask string `json:"subnet_mask" yaml:"subnet_mask"`
|
||||
RangeStart string `json:"range_start" yaml:"range_start"`
|
||||
RangeEnd string `json:"range_end" yaml:"range_end"`
|
||||
LeaseDuration uint `json:"lease_duration" yaml:"lease_duration"` // in seconds
|
||||
WorkDir string `json:"-" yaml:"-"`
|
||||
DBFilePath string `json:"-" yaml:"-"` // path to DB file
|
||||
LeaseDuration uint32 `json:"lease_duration" yaml:"lease_duration"` // in seconds
|
||||
|
||||
// IP conflict detector: time (ms) to wait for ICMP reply.
|
||||
// 0: disable
|
||||
ICMPTimeout uint `json:"icmp_timeout_msec" yaml:"icmp_timeout_msec"`
|
||||
ICMPTimeout uint32 `json:"icmp_timeout_msec" yaml:"icmp_timeout_msec"`
|
||||
|
||||
WorkDir string `json:"-" yaml:"-"`
|
||||
DBFilePath string `json:"-" yaml:"-"` // path to DB file
|
||||
|
||||
// Called when the configuration is changed by HTTP request
|
||||
ConfigModified func() `json:"-" yaml:"-"`
|
||||
|
||||
// Register an HTTP handler
|
||||
HTTPRegister func(string, string, func(http.ResponseWriter, *http.Request)) `json:"-" yaml:"-"`
|
||||
}
|
||||
|
||||
// Server - the current state of the DHCP server
|
||||
@@ -88,6 +96,16 @@ func (s *Server) CheckConfig(config ServerConfig) error {
|
||||
return tmpServer.setConfig(config)
|
||||
}
|
||||
|
||||
// Create - create object
|
||||
func Create(config ServerConfig) *Server {
|
||||
s := Server{}
|
||||
s.conf = config
|
||||
if s.conf.HTTPRegister != nil {
|
||||
s.registerHandlers()
|
||||
}
|
||||
return &s
|
||||
}
|
||||
|
||||
// Init checks the configuration and initializes the server
|
||||
func (s *Server) Init(config ServerConfig) error {
|
||||
err := s.setConfig(config)
|
||||
@@ -98,10 +116,12 @@ func (s *Server) Init(config ServerConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) setConfig(config ServerConfig) error {
|
||||
s.conf = config
|
||||
s.conf.DBFilePath = filepath.Join(config.WorkDir, dbFilename)
|
||||
// WriteDiskConfig - write configuration
|
||||
func (s *Server) WriteDiskConfig(c *ServerConfig) {
|
||||
*c = s.conf
|
||||
}
|
||||
|
||||
func (s *Server) setConfig(config ServerConfig) error {
|
||||
iface, err := net.InterfaceByName(config.InterfaceName)
|
||||
if err != nil {
|
||||
printInterfaces()
|
||||
@@ -153,6 +173,12 @@ func (s *Server) setConfig(config ServerConfig) error {
|
||||
dhcp4.OptionDomainNameServer: s.ipnet.IP,
|
||||
}
|
||||
|
||||
oldconf := s.conf
|
||||
s.conf = config
|
||||
s.conf.WorkDir = oldconf.WorkDir
|
||||
s.conf.HTTPRegister = oldconf.HTTPRegister
|
||||
s.conf.ConfigModified = oldconf.ConfigModified
|
||||
s.conf.DBFilePath = filepath.Join(config.WorkDir, dbFilename)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -684,6 +710,21 @@ func (s *Server) FindIPbyMAC(mac net.HardwareAddr) net.IP {
|
||||
return nil
|
||||
}
|
||||
|
||||
// FindMACbyIP - find a MAC address by IP address in the currently active DHCP leases
|
||||
func (s *Server) FindMACbyIP(ip net.IP) net.HardwareAddr {
|
||||
now := time.Now().Unix()
|
||||
|
||||
s.leasesLock.RLock()
|
||||
defer s.leasesLock.RUnlock()
|
||||
|
||||
for _, l := range s.leases {
|
||||
if l.Expiry.Unix() > now && l.IP.Equal(ip) {
|
||||
return l.HWAddr
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reset internal state
|
||||
func (s *Server) reset() {
|
||||
s.leasesLock.Lock()
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/krolaw/dhcp4"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func check(t *testing.T, result bool, msg string) {
|
||||
@@ -211,3 +212,32 @@ func TestIsValidSubnetMask(t *testing.T) {
|
||||
t.Fatalf("isValidSubnetMask([]byte{255,255,253,0})")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeLeases(t *testing.T) {
|
||||
dynLeases := []*Lease{}
|
||||
staticLeases := []*Lease{}
|
||||
leases := []*Lease{}
|
||||
|
||||
lease := &Lease{}
|
||||
lease.HWAddr = []byte{1, 2, 3, 4}
|
||||
dynLeases = append(dynLeases, lease)
|
||||
lease = new(Lease)
|
||||
lease.HWAddr = []byte{1, 2, 3, 5}
|
||||
dynLeases = append(dynLeases, lease)
|
||||
|
||||
lease = new(Lease)
|
||||
lease.HWAddr = []byte{1, 2, 3, 4}
|
||||
lease.IP = []byte{0, 2, 3, 4}
|
||||
staticLeases = append(staticLeases, lease)
|
||||
lease = new(Lease)
|
||||
lease.HWAddr = []byte{2, 2, 3, 4}
|
||||
staticLeases = append(staticLeases, lease)
|
||||
|
||||
leases = normalizeLeases(staticLeases, dynLeases)
|
||||
|
||||
assert.True(t, len(leases) == 3)
|
||||
assert.True(t, bytes.Equal(leases[0].HWAddr, []byte{1, 2, 3, 4}))
|
||||
assert.True(t, bytes.Equal(leases[0].IP, []byte{0, 2, 3, 4}))
|
||||
assert.True(t, bytes.Equal(leases[1].HWAddr, []byte{2, 2, 3, 4}))
|
||||
assert.True(t, bytes.Equal(leases[2].HWAddr, []byte{1, 2, 3, 5}))
|
||||
}
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
package dnsfilter
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"encoding/gob"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
@@ -16,34 +10,20 @@ import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/joomcode/errorx"
|
||||
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/AdguardTeam/golibs/cache"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/urlfilter"
|
||||
"github.com/bluele/gcache"
|
||||
"github.com/AdguardTeam/urlfilter/filterlist"
|
||||
"github.com/AdguardTeam/urlfilter/rules"
|
||||
"github.com/miekg/dns"
|
||||
"golang.org/x/net/publicsuffix"
|
||||
)
|
||||
|
||||
const defaultHTTPTimeout = 5 * time.Minute
|
||||
const defaultHTTPMaxIdleConnections = 100
|
||||
|
||||
const defaultSafebrowsingServer = "sb.adtidy.org"
|
||||
const defaultSafebrowsingURL = "%s://%s/safebrowsing-lookup-hash.html?prefixes=%s"
|
||||
const defaultParentalServer = "pctrl.adguard.com"
|
||||
const defaultParentalURL = "%s://%s/check-parental-control-hash?prefixes=%s&sensitivity=%d"
|
||||
const defaultParentalSensitivity = 13 // use "TEEN" by default
|
||||
const maxDialCacheSize = 2 // the number of host names for safebrowsing and parental control
|
||||
|
||||
// ServiceEntry - blocked service array element
|
||||
type ServiceEntry struct {
|
||||
Name string
|
||||
Rules []*urlfilter.NetworkRule
|
||||
Rules []*rules.NetworkRule
|
||||
}
|
||||
|
||||
// RequestFilteringSettings is custom filtering settings
|
||||
@@ -65,7 +45,6 @@ type RewriteEntry struct {
|
||||
type Config struct {
|
||||
ParentalSensitivity int `yaml:"parental_sensitivity"` // must be either 3, 10, 13 or 17
|
||||
ParentalEnabled bool `yaml:"parental_enabled"`
|
||||
UsePlainHTTP bool `yaml:"-"` // use plain HTTP for requests to parental and safe browsing servers
|
||||
SafeSearchEnabled bool `yaml:"safesearch_enabled"`
|
||||
SafeBrowsingEnabled bool `yaml:"safebrowsing_enabled"`
|
||||
ResolverAddress string `yaml:"-"` // DNS server address
|
||||
@@ -106,16 +85,14 @@ type filtersInitializerParams struct {
|
||||
|
||||
// Dnsfilter holds added rules and performs hostname matches against the rules
|
||||
type Dnsfilter struct {
|
||||
rulesStorage *urlfilter.RuleStorage
|
||||
rulesStorage *filterlist.RuleStorage
|
||||
filteringEngine *urlfilter.DNSEngine
|
||||
engineLock sync.RWMutex
|
||||
|
||||
// HTTP lookups for safebrowsing and parental
|
||||
client http.Client // handle for http client -- single instance as recommended by docs
|
||||
transport *http.Transport // handle for http transport used by http client
|
||||
|
||||
parentalServer string // access via methods
|
||||
safeBrowsingServer string // access via methods
|
||||
parentalServer string // access via methods
|
||||
safeBrowsingServer string // access via methods
|
||||
parentalUpstream upstream.Upstream
|
||||
safeBrowsingUpstream upstream.Upstream
|
||||
|
||||
Config // for direct access by library users, even a = assignment
|
||||
confLock sync.RWMutex
|
||||
@@ -251,9 +228,6 @@ func (d *Dnsfilter) filtersInitializer() {
|
||||
|
||||
// Close - close the object
|
||||
func (d *Dnsfilter) Close() {
|
||||
if d != nil && d.transport != nil {
|
||||
d.transport.CloseIdleConnections()
|
||||
}
|
||||
if d.rulesStorage != nil {
|
||||
d.rulesStorage.Close()
|
||||
}
|
||||
@@ -261,7 +235,6 @@ func (d *Dnsfilter) Close() {
|
||||
|
||||
type dnsFilterContext struct {
|
||||
stats Stats
|
||||
dialCache gcache.Cache // "host" -> "IP" cache for safebrowsing and parental control servers
|
||||
safebrowsingCache cache.Cache
|
||||
parentalCache cache.Cache
|
||||
safeSearchCache cache.Cache
|
||||
@@ -297,10 +270,6 @@ func (d *Dnsfilter) CheckHost(host string, qtype uint16, setts *RequestFiltering
|
||||
return Result{Reason: NotFilteredNotFound}, nil
|
||||
}
|
||||
host = strings.ToLower(host)
|
||||
// prevent recursion
|
||||
if host == d.parentalServer || host == d.safeBrowsingServer {
|
||||
return Result{}, nil
|
||||
}
|
||||
|
||||
var result Result
|
||||
var err error
|
||||
@@ -328,11 +297,10 @@ func (d *Dnsfilter) CheckHost(host string, qtype uint16, setts *RequestFiltering
|
||||
}
|
||||
}
|
||||
|
||||
// check safeSearch if no match
|
||||
if setts.SafeSearchEnabled {
|
||||
result, err = d.checkSafeSearch(host)
|
||||
if err != nil {
|
||||
log.Printf("Failed to safesearch HTTP lookup, ignoring check: %v", err)
|
||||
log.Info("SafeSearch: failed: %v", err)
|
||||
return Result{}, nil
|
||||
}
|
||||
|
||||
@@ -341,12 +309,10 @@ func (d *Dnsfilter) CheckHost(host string, qtype uint16, setts *RequestFiltering
|
||||
}
|
||||
}
|
||||
|
||||
// check safebrowsing if no match
|
||||
if setts.SafeBrowsingEnabled {
|
||||
result, err = d.checkSafeBrowsing(host)
|
||||
if err != nil {
|
||||
// failed to do HTTP lookup -- treat it as if we got empty response, but don't save cache
|
||||
log.Printf("Failed to do safebrowsing HTTP lookup, ignoring check: %v", err)
|
||||
log.Info("SafeBrowsing: failed: %v", err)
|
||||
return Result{}, nil
|
||||
}
|
||||
if result.Reason.Matched() {
|
||||
@@ -354,12 +320,10 @@ func (d *Dnsfilter) CheckHost(host string, qtype uint16, setts *RequestFiltering
|
||||
}
|
||||
}
|
||||
|
||||
// check parental if no match
|
||||
if setts.ParentalEnabled {
|
||||
result, err = d.checkParental(host)
|
||||
if err != nil {
|
||||
// failed to do HTTP lookup -- treat it as if we got empty response, but don't save cache
|
||||
log.Printf("Failed to do parental HTTP lookup, ignoring check: %v", err)
|
||||
log.Printf("Parental: failed: %v", err)
|
||||
return Result{}, nil
|
||||
}
|
||||
if result.Reason.Matched() {
|
||||
@@ -367,7 +331,6 @@ func (d *Dnsfilter) CheckHost(host string, qtype uint16, setts *RequestFiltering
|
||||
}
|
||||
}
|
||||
|
||||
// nothing matched, return nothing
|
||||
return Result{}, nil
|
||||
}
|
||||
|
||||
@@ -426,7 +389,7 @@ func (d *Dnsfilter) processRewrites(host string, qtype uint16) Result {
|
||||
}
|
||||
|
||||
func matchBlockedServicesRules(host string, svcs []ServiceEntry) Result {
|
||||
req := urlfilter.NewRequestForHostname(host)
|
||||
req := rules.NewRequestForHostname(host)
|
||||
res := Result{}
|
||||
|
||||
for _, s := range svcs {
|
||||
@@ -445,311 +408,6 @@ func matchBlockedServicesRules(host string, svcs []ServiceEntry) Result {
|
||||
return res
|
||||
}
|
||||
|
||||
/*
|
||||
expire byte[4]
|
||||
res Result
|
||||
*/
|
||||
func (d *Dnsfilter) setCacheResult(cache cache.Cache, host string, res Result) {
|
||||
var buf bytes.Buffer
|
||||
|
||||
expire := uint(time.Now().Unix()) + d.Config.CacheTime*60
|
||||
var exp []byte
|
||||
exp = make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(exp, uint32(expire))
|
||||
_, _ = buf.Write(exp)
|
||||
|
||||
enc := gob.NewEncoder(&buf)
|
||||
err := enc.Encode(res)
|
||||
if err != nil {
|
||||
log.Error("gob.Encode(): %s", err)
|
||||
return
|
||||
}
|
||||
_ = cache.Set([]byte(host), buf.Bytes())
|
||||
log.Debug("Stored in cache %p: %s", cache, host)
|
||||
}
|
||||
|
||||
func getCachedResult(cache cache.Cache, host string) (Result, bool) {
|
||||
data := cache.Get([]byte(host))
|
||||
if data == nil {
|
||||
return Result{}, false
|
||||
}
|
||||
|
||||
exp := int(binary.BigEndian.Uint32(data[:4]))
|
||||
if exp <= int(time.Now().Unix()) {
|
||||
cache.Del([]byte(host))
|
||||
return Result{}, false
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
buf.Write(data[4:])
|
||||
dec := gob.NewDecoder(&buf)
|
||||
r := Result{}
|
||||
err := dec.Decode(&r)
|
||||
if err != nil {
|
||||
log.Debug("gob.Decode(): %s", err)
|
||||
return Result{}, false
|
||||
}
|
||||
|
||||
return r, true
|
||||
}
|
||||
|
||||
// for each dot, hash it and add it to string
|
||||
func hostnameToHashParam(host string, addslash bool) (string, map[string]bool) {
|
||||
var hashparam bytes.Buffer
|
||||
hashes := map[string]bool{}
|
||||
tld, icann := publicsuffix.PublicSuffix(host)
|
||||
if !icann {
|
||||
// private suffixes like cloudfront.net
|
||||
tld = ""
|
||||
}
|
||||
curhost := host
|
||||
for {
|
||||
if curhost == "" {
|
||||
// we've reached end of string
|
||||
break
|
||||
}
|
||||
if tld != "" && curhost == tld {
|
||||
// we've reached the TLD, don't hash it
|
||||
break
|
||||
}
|
||||
tohash := []byte(curhost)
|
||||
if addslash {
|
||||
tohash = append(tohash, '/')
|
||||
}
|
||||
sum := sha256.Sum256(tohash)
|
||||
hexhash := fmt.Sprintf("%X", sum)
|
||||
hashes[hexhash] = true
|
||||
hashparam.WriteString(fmt.Sprintf("%02X%02X%02X%02X/", sum[0], sum[1], sum[2], sum[3]))
|
||||
pos := strings.IndexByte(curhost, byte('.'))
|
||||
if pos < 0 {
|
||||
break
|
||||
}
|
||||
curhost = curhost[pos+1:]
|
||||
}
|
||||
return hashparam.String(), hashes
|
||||
}
|
||||
|
||||
func (d *Dnsfilter) checkSafeSearch(host string) (Result, error) {
|
||||
if log.GetLevel() >= log.DEBUG {
|
||||
timer := log.StartTimer()
|
||||
defer timer.LogElapsed("SafeSearch HTTP lookup for %s", host)
|
||||
}
|
||||
|
||||
// Check cache. Return cached result if it was found
|
||||
cachedValue, isFound := getCachedResult(gctx.safeSearchCache, host)
|
||||
if isFound {
|
||||
atomic.AddUint64(&gctx.stats.Safesearch.CacheHits, 1)
|
||||
log.Tracef("%s: found in SafeSearch cache", host)
|
||||
return cachedValue, nil
|
||||
}
|
||||
|
||||
safeHost, ok := d.SafeSearchDomain(host)
|
||||
if !ok {
|
||||
return Result{}, nil
|
||||
}
|
||||
|
||||
res := Result{IsFiltered: true, Reason: FilteredSafeSearch}
|
||||
if ip := net.ParseIP(safeHost); ip != nil {
|
||||
res.IP = ip
|
||||
d.setCacheResult(gctx.safeSearchCache, host, res)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// TODO this address should be resolved with upstream that was configured in dnsforward
|
||||
addrs, err := net.LookupIP(safeHost)
|
||||
if err != nil {
|
||||
log.Tracef("SafeSearchDomain for %s was found but failed to lookup for %s cause %s", host, safeHost, err)
|
||||
return Result{}, err
|
||||
}
|
||||
|
||||
for _, i := range addrs {
|
||||
if ipv4 := i.To4(); ipv4 != nil {
|
||||
res.IP = ipv4
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(res.IP) == 0 {
|
||||
return Result{}, fmt.Errorf("no ipv4 addresses in safe search response for %s", safeHost)
|
||||
}
|
||||
|
||||
// Cache result
|
||||
d.setCacheResult(gctx.safeSearchCache, host, res)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (d *Dnsfilter) checkSafeBrowsing(host string) (Result, error) {
|
||||
if log.GetLevel() >= log.DEBUG {
|
||||
timer := log.StartTimer()
|
||||
defer timer.LogElapsed("SafeBrowsing HTTP lookup for %s", host)
|
||||
}
|
||||
|
||||
format := func(hashparam string) string {
|
||||
schema := "https"
|
||||
if d.UsePlainHTTP {
|
||||
schema = "http"
|
||||
}
|
||||
url := fmt.Sprintf(defaultSafebrowsingURL, schema, d.safeBrowsingServer, hashparam)
|
||||
return url
|
||||
}
|
||||
handleBody := func(body []byte, hashes map[string]bool) (Result, error) {
|
||||
result := Result{}
|
||||
scanner := bufio.NewScanner(strings.NewReader(string(body)))
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
splitted := strings.Split(line, ":")
|
||||
if len(splitted) < 3 {
|
||||
continue
|
||||
}
|
||||
hash := splitted[2]
|
||||
if _, ok := hashes[hash]; ok {
|
||||
// it's in the hash
|
||||
result.IsFiltered = true
|
||||
result.Reason = FilteredSafeBrowsing
|
||||
result.Rule = splitted[0]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if err := scanner.Err(); err != nil {
|
||||
// error, don't save cache
|
||||
return Result{}, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// check cache
|
||||
cachedValue, isFound := getCachedResult(gctx.safebrowsingCache, host)
|
||||
if isFound {
|
||||
atomic.AddUint64(&gctx.stats.Safebrowsing.CacheHits, 1)
|
||||
log.Tracef("%s: found in the lookup cache %p", host, gctx.safebrowsingCache)
|
||||
return cachedValue, nil
|
||||
}
|
||||
|
||||
result, err := d.lookupCommon(host, &gctx.stats.Safebrowsing, true, format, handleBody)
|
||||
|
||||
if err == nil {
|
||||
d.setCacheResult(gctx.safebrowsingCache, host, result)
|
||||
}
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (d *Dnsfilter) checkParental(host string) (Result, error) {
|
||||
if log.GetLevel() >= log.DEBUG {
|
||||
timer := log.StartTimer()
|
||||
defer timer.LogElapsed("Parental HTTP lookup for %s", host)
|
||||
}
|
||||
|
||||
format := func(hashparam string) string {
|
||||
schema := "https"
|
||||
if d.UsePlainHTTP {
|
||||
schema = "http"
|
||||
}
|
||||
sensitivity := d.ParentalSensitivity
|
||||
if sensitivity == 0 {
|
||||
sensitivity = defaultParentalSensitivity
|
||||
}
|
||||
url := fmt.Sprintf(defaultParentalURL, schema, d.parentalServer, hashparam, sensitivity)
|
||||
return url
|
||||
}
|
||||
handleBody := func(body []byte, hashes map[string]bool) (Result, error) {
|
||||
// parse json
|
||||
var m []struct {
|
||||
Blocked bool `json:"blocked"`
|
||||
ClientTTL int `json:"clientTtl"`
|
||||
Reason string `json:"reason"`
|
||||
Hash string `json:"hash"`
|
||||
}
|
||||
err := json.Unmarshal(body, &m)
|
||||
if err != nil {
|
||||
// error, don't save cache
|
||||
log.Printf("Couldn't parse json '%s': %s", body, err)
|
||||
return Result{}, err
|
||||
}
|
||||
|
||||
result := Result{}
|
||||
|
||||
for i := range m {
|
||||
if !hashes[m[i].Hash] {
|
||||
continue
|
||||
}
|
||||
if m[i].Blocked {
|
||||
result.IsFiltered = true
|
||||
result.Reason = FilteredParental
|
||||
result.Rule = fmt.Sprintf("parental %s", m[i].Reason)
|
||||
break
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// check cache
|
||||
cachedValue, isFound := getCachedResult(gctx.parentalCache, host)
|
||||
if isFound {
|
||||
atomic.AddUint64(&gctx.stats.Parental.CacheHits, 1)
|
||||
log.Tracef("%s: found in the lookup cache %p", host, gctx.parentalCache)
|
||||
return cachedValue, nil
|
||||
}
|
||||
|
||||
result, err := d.lookupCommon(host, &gctx.stats.Parental, false, format, handleBody)
|
||||
|
||||
if err == nil {
|
||||
d.setCacheResult(gctx.parentalCache, host, result)
|
||||
}
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
type formatHandler func(hashparam string) string
|
||||
type bodyHandler func(body []byte, hashes map[string]bool) (Result, error)
|
||||
|
||||
// real implementation of lookup/check
|
||||
func (d *Dnsfilter) lookupCommon(host string, lookupstats *LookupStats, hashparamNeedSlash bool, format formatHandler, handleBody bodyHandler) (Result, error) {
|
||||
// convert hostname to hash parameters
|
||||
hashparam, hashes := hostnameToHashParam(host, hashparamNeedSlash)
|
||||
|
||||
// format URL with our hashes
|
||||
url := format(hashparam)
|
||||
|
||||
// do HTTP request
|
||||
atomic.AddUint64(&lookupstats.Requests, 1)
|
||||
atomic.AddInt64(&lookupstats.Pending, 1)
|
||||
updateMax(&lookupstats.Pending, &lookupstats.PendingMax)
|
||||
resp, err := d.client.Get(url)
|
||||
atomic.AddInt64(&lookupstats.Pending, -1)
|
||||
if resp != nil && resp.Body != nil {
|
||||
defer resp.Body.Close()
|
||||
}
|
||||
if err != nil {
|
||||
// error, don't save cache
|
||||
return Result{}, err
|
||||
}
|
||||
|
||||
// get body text
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
// error, don't save cache
|
||||
return Result{}, err
|
||||
}
|
||||
|
||||
// handle status code
|
||||
switch {
|
||||
case resp.StatusCode == 204:
|
||||
// empty result, save cache
|
||||
return Result{}, nil
|
||||
case resp.StatusCode != 200:
|
||||
return Result{}, fmt.Errorf("HTTP status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
result, err := handleBody(body, hashes)
|
||||
if err != nil {
|
||||
return Result{}, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
//
|
||||
// Adding rule and matching against the rules
|
||||
//
|
||||
@@ -765,19 +423,19 @@ func fileExists(fn string) bool {
|
||||
|
||||
// Initialize urlfilter objects
|
||||
func (d *Dnsfilter) initFiltering(filters map[int]string) error {
|
||||
listArray := []urlfilter.RuleList{}
|
||||
listArray := []filterlist.RuleList{}
|
||||
for id, dataOrFilePath := range filters {
|
||||
var list urlfilter.RuleList
|
||||
var list filterlist.RuleList
|
||||
|
||||
if id == 0 {
|
||||
list = &urlfilter.StringRuleList{
|
||||
list = &filterlist.StringRuleList{
|
||||
ID: 0,
|
||||
RulesText: dataOrFilePath,
|
||||
IgnoreCosmetic: true,
|
||||
}
|
||||
|
||||
} else if !fileExists(dataOrFilePath) {
|
||||
list = &urlfilter.StringRuleList{
|
||||
list = &filterlist.StringRuleList{
|
||||
ID: id,
|
||||
IgnoreCosmetic: true,
|
||||
}
|
||||
@@ -789,7 +447,7 @@ func (d *Dnsfilter) initFiltering(filters map[int]string) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("ioutil.ReadFile(): %s: %s", dataOrFilePath, err)
|
||||
}
|
||||
list = &urlfilter.StringRuleList{
|
||||
list = &filterlist.StringRuleList{
|
||||
ID: id,
|
||||
RulesText: string(data),
|
||||
IgnoreCosmetic: true,
|
||||
@@ -797,17 +455,17 @@ func (d *Dnsfilter) initFiltering(filters map[int]string) error {
|
||||
|
||||
} else {
|
||||
var err error
|
||||
list, err = urlfilter.NewFileRuleList(id, dataOrFilePath, true)
|
||||
list, err = filterlist.NewFileRuleList(id, dataOrFilePath, true)
|
||||
if err != nil {
|
||||
return fmt.Errorf("urlfilter.NewFileRuleList(): %s: %s", dataOrFilePath, err)
|
||||
return fmt.Errorf("filterlist.NewFileRuleList(): %s: %s", dataOrFilePath, err)
|
||||
}
|
||||
}
|
||||
listArray = append(listArray, list)
|
||||
}
|
||||
|
||||
rulesStorage, err := urlfilter.NewRuleStorage(listArray)
|
||||
rulesStorage, err := filterlist.NewRuleStorage(listArray)
|
||||
if err != nil {
|
||||
return fmt.Errorf("urlfilter.NewRuleStorage(): %s", err)
|
||||
return fmt.Errorf("filterlist.NewRuleStorage(): %s", err)
|
||||
}
|
||||
filteringEngine := urlfilter.NewDNSEngine(rulesStorage)
|
||||
|
||||
@@ -831,14 +489,14 @@ func (d *Dnsfilter) matchHost(host string, qtype uint16) (Result, error) {
|
||||
return Result{}, nil
|
||||
}
|
||||
|
||||
rules, ok := d.filteringEngine.Match(host)
|
||||
frules, ok := d.filteringEngine.Match(host)
|
||||
if !ok {
|
||||
return Result{}, nil
|
||||
}
|
||||
|
||||
log.Tracef("%d rules matched for host '%s'", len(rules), host)
|
||||
log.Tracef("%d rules matched for host '%s'", len(frules), host)
|
||||
|
||||
for _, rule := range rules {
|
||||
for _, rule := range frules {
|
||||
|
||||
log.Tracef("Found rule for host '%s': '%s' list_id: %d",
|
||||
host, rule.Text(), rule.GetFilterListID())
|
||||
@@ -849,7 +507,7 @@ func (d *Dnsfilter) matchHost(host string, qtype uint16) (Result, error) {
|
||||
res.FilterID = int64(rule.GetFilterListID())
|
||||
res.Rule = rule.Text()
|
||||
|
||||
if netRule, ok := rule.(*urlfilter.NetworkRule); ok {
|
||||
if netRule, ok := rule.(*rules.NetworkRule); ok {
|
||||
|
||||
if netRule.Whitelist {
|
||||
res.Reason = NotFilteredWhiteList
|
||||
@@ -857,7 +515,7 @@ func (d *Dnsfilter) matchHost(host string, qtype uint16) (Result, error) {
|
||||
}
|
||||
return res, nil
|
||||
|
||||
} else if hostRule, ok := rule.(*urlfilter.HostRule); ok {
|
||||
} else if hostRule, ok := rule.(*rules.HostRule); ok {
|
||||
|
||||
if qtype == dns.TypeA && hostRule.IP.To4() != nil {
|
||||
// either IPv4 or IPv4-mapped IPv6 address
|
||||
@@ -887,97 +545,6 @@ func (d *Dnsfilter) matchHost(host string, qtype uint16) (Result, error) {
|
||||
return Result{}, nil
|
||||
}
|
||||
|
||||
//
|
||||
// lifecycle helper functions
|
||||
//
|
||||
|
||||
// Return TRUE if this host's IP should be cached
|
||||
func (d *Dnsfilter) shouldBeInDialCache(host string) bool {
|
||||
return host == d.safeBrowsingServer ||
|
||||
host == d.parentalServer
|
||||
}
|
||||
|
||||
// Search for an IP address by host name
|
||||
func searchInDialCache(host string) string {
|
||||
rawValue, err := gctx.dialCache.Get(host)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
ip, _ := rawValue.(string)
|
||||
log.Debug("Found in cache: %s -> %s", host, ip)
|
||||
return ip
|
||||
}
|
||||
|
||||
// Add "hostname" -> "IP address" entry to cache
|
||||
func addToDialCache(host, ip string) {
|
||||
err := gctx.dialCache.Set(host, ip)
|
||||
if err != nil {
|
||||
log.Debug("dialCache.Set: %s", err)
|
||||
}
|
||||
log.Debug("Added to cache: %s -> %s", host, ip)
|
||||
}
|
||||
|
||||
type dialFunctionType func(ctx context.Context, network, addr string) (net.Conn, error)
|
||||
|
||||
// Connect to a remote server resolving hostname using our own DNS server
|
||||
func (d *Dnsfilter) createCustomDialContext(resolverAddr string) dialFunctionType {
|
||||
return func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
log.Tracef("network:%v addr:%v", network, addr)
|
||||
|
||||
host, port, err := net.SplitHostPort(addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dialer := &net.Dialer{
|
||||
Timeout: time.Minute * 5,
|
||||
}
|
||||
|
||||
if net.ParseIP(host) != nil {
|
||||
con, err := dialer.DialContext(ctx, network, addr)
|
||||
return con, err
|
||||
}
|
||||
|
||||
cache := d.shouldBeInDialCache(host)
|
||||
if cache {
|
||||
ip := searchInDialCache(host)
|
||||
if len(ip) != 0 {
|
||||
addr = fmt.Sprintf("%s:%s", ip, port)
|
||||
return dialer.DialContext(ctx, network, addr)
|
||||
}
|
||||
}
|
||||
|
||||
r := upstream.NewResolver(resolverAddr, 30*time.Second)
|
||||
addrs, e := r.LookupIPAddr(ctx, host)
|
||||
log.Tracef("LookupIPAddr: %s: %v", host, addrs)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
|
||||
if len(addrs) == 0 {
|
||||
return nil, fmt.Errorf("couldn't lookup host: %s", host)
|
||||
}
|
||||
|
||||
var dialErrs []error
|
||||
for _, a := range addrs {
|
||||
addr = fmt.Sprintf("%s:%s", a.String(), port)
|
||||
con, err := dialer.DialContext(ctx, network, addr)
|
||||
if err != nil {
|
||||
dialErrs = append(dialErrs, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if cache {
|
||||
addToDialCache(host, a.String())
|
||||
}
|
||||
|
||||
return con, err
|
||||
}
|
||||
return nil, errorx.DecorateMany(fmt.Sprintf("couldn't dial to %s", addr), dialErrs...)
|
||||
}
|
||||
}
|
||||
|
||||
// New creates properly initialized DNS Filter that is ready to be used
|
||||
func New(c *Config, filters map[int]string) *Dnsfilter {
|
||||
|
||||
@@ -1002,34 +569,16 @@ func New(c *Config, filters map[int]string) *Dnsfilter {
|
||||
cacheConf.MaxSize = c.ParentalCacheSize
|
||||
gctx.parentalCache = cache.New(cacheConf)
|
||||
}
|
||||
|
||||
if len(c.ResolverAddress) != 0 && gctx.dialCache == nil {
|
||||
dur := time.Duration(c.CacheTime) * time.Minute
|
||||
gctx.dialCache = gcache.New(maxDialCacheSize).LRU().Expiration(dur).Build()
|
||||
}
|
||||
}
|
||||
|
||||
d := new(Dnsfilter)
|
||||
|
||||
// Customize the Transport to have larger connection pool,
|
||||
// We are not (re)using http.DefaultTransport because of race conditions found by tests
|
||||
d.transport = &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
MaxIdleConns: defaultHTTPMaxIdleConnections, // default 100
|
||||
MaxIdleConnsPerHost: defaultHTTPMaxIdleConnections, // default 2
|
||||
IdleConnTimeout: 90 * time.Second,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
ExpectContinueTimeout: 1 * time.Second,
|
||||
err := d.initSecurityServices()
|
||||
if err != nil {
|
||||
log.Error("dnsfilter: initialize services: %s", err)
|
||||
return nil
|
||||
}
|
||||
if c != nil && len(c.ResolverAddress) != 0 {
|
||||
d.transport.DialContext = d.createCustomDialContext(c.ResolverAddress)
|
||||
}
|
||||
d.client = http.Client{
|
||||
Transport: d.transport,
|
||||
Timeout: defaultHTTPTimeout,
|
||||
}
|
||||
d.safeBrowsingServer = defaultSafebrowsingServer
|
||||
d.parentalServer = defaultParentalServer
|
||||
|
||||
if c != nil {
|
||||
d.Config = *c
|
||||
}
|
||||
@@ -1053,38 +602,6 @@ func New(c *Config, filters map[int]string) *Dnsfilter {
|
||||
return d
|
||||
}
|
||||
|
||||
//
|
||||
// config manipulation helpers
|
||||
//
|
||||
|
||||
// SetSafeBrowsingServer lets you optionally change hostname of safesearch lookup
|
||||
func (d *Dnsfilter) SetSafeBrowsingServer(host string) {
|
||||
if len(host) == 0 {
|
||||
d.safeBrowsingServer = defaultSafebrowsingServer
|
||||
} else {
|
||||
d.safeBrowsingServer = host
|
||||
}
|
||||
}
|
||||
|
||||
// SetHTTPTimeout lets you optionally change timeout during lookups
|
||||
func (d *Dnsfilter) SetHTTPTimeout(t time.Duration) {
|
||||
d.client.Timeout = t
|
||||
}
|
||||
|
||||
// ResetHTTPTimeout resets lookup timeouts
|
||||
func (d *Dnsfilter) ResetHTTPTimeout() {
|
||||
d.client.Timeout = defaultHTTPTimeout
|
||||
}
|
||||
|
||||
// SafeSearchDomain returns replacement address for search engine
|
||||
func (d *Dnsfilter) SafeSearchDomain(host string) (string, bool) {
|
||||
if d.SafeSearchEnabled {
|
||||
val, ok := safeSearchDomains[host]
|
||||
return val, ok
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
//
|
||||
// stats
|
||||
//
|
||||
|
||||
@@ -3,15 +3,11 @@ package dnsfilter
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"path"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/urlfilter"
|
||||
"github.com/bluele/gcache"
|
||||
"github.com/AdguardTeam/urlfilter/rules"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@@ -23,7 +19,6 @@ var setts RequestFilteringSettings
|
||||
// SAFE SEARCH
|
||||
// PARENTAL
|
||||
// FILTERING
|
||||
// CLIENTS SETTINGS
|
||||
// BENCHMARKS
|
||||
|
||||
// HELPERS
|
||||
@@ -126,34 +121,19 @@ func TestEtcHostsMatching(t *testing.T) {
|
||||
// SAFE BROWSING
|
||||
|
||||
func TestSafeBrowsing(t *testing.T) {
|
||||
testCases := []string{
|
||||
"",
|
||||
"sb.adtidy.org",
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(fmt.Sprintf("%s in %s", tc, _Func()), func(t *testing.T) {
|
||||
d := NewForTest(&Config{SafeBrowsingEnabled: true}, nil)
|
||||
defer d.Close()
|
||||
gctx.stats.Safebrowsing.Requests = 0
|
||||
d.checkMatch(t, "wmconvirus.narod.ru")
|
||||
d.checkMatch(t, "wmconvirus.narod.ru")
|
||||
if gctx.stats.Safebrowsing.Requests != 1 {
|
||||
t.Errorf("Safebrowsing lookup positive cache is not working: %v", gctx.stats.Safebrowsing.Requests)
|
||||
}
|
||||
d.checkMatch(t, "WMconvirus.narod.ru")
|
||||
if gctx.stats.Safebrowsing.Requests != 1 {
|
||||
t.Errorf("Safebrowsing lookup positive cache is not working: %v", gctx.stats.Safebrowsing.Requests)
|
||||
}
|
||||
d.checkMatch(t, "test.wmconvirus.narod.ru")
|
||||
d.checkMatchEmpty(t, "yandex.ru")
|
||||
d.checkMatchEmpty(t, "pornhub.com")
|
||||
l := gctx.stats.Safebrowsing.Requests
|
||||
d.checkMatchEmpty(t, "pornhub.com")
|
||||
if gctx.stats.Safebrowsing.Requests != l {
|
||||
t.Errorf("Safebrowsing lookup negative cache is not working: %v", gctx.stats.Safebrowsing.Requests)
|
||||
}
|
||||
})
|
||||
}
|
||||
d := NewForTest(&Config{SafeBrowsingEnabled: true}, nil)
|
||||
defer d.Close()
|
||||
gctx.stats.Safebrowsing.Requests = 0
|
||||
d.checkMatch(t, "wmconvirus.narod.ru")
|
||||
d.checkMatch(t, "test.wmconvirus.narod.ru")
|
||||
d.checkMatchEmpty(t, "yandex.ru")
|
||||
d.checkMatchEmpty(t, "pornhub.com")
|
||||
|
||||
// test cached result
|
||||
d.safeBrowsingServer = "127.0.0.1"
|
||||
d.checkMatch(t, "wmconvirus.narod.ru")
|
||||
d.checkMatchEmpty(t, "pornhub.com")
|
||||
d.safeBrowsingServer = defaultSafebrowsingServer
|
||||
}
|
||||
|
||||
func TestParallelSB(t *testing.T) {
|
||||
@@ -172,33 +152,10 @@ func TestParallelSB(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
// the only way to verify that custom server option is working is to point it at a server that does serve safebrowsing
|
||||
func TestSafeBrowsingCustomServerFail(t *testing.T) {
|
||||
d := NewForTest(&Config{SafeBrowsingEnabled: true}, nil)
|
||||
defer d.Close()
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// w.Write("Hello, client")
|
||||
fmt.Fprintln(w, "Hello, client")
|
||||
}))
|
||||
defer ts.Close()
|
||||
address := ts.Listener.Addr().String()
|
||||
|
||||
d.SetHTTPTimeout(time.Second * 5)
|
||||
d.SetSafeBrowsingServer(address) // this will ensure that test fails
|
||||
d.checkMatchEmpty(t, "wmconvirus.narod.ru")
|
||||
}
|
||||
|
||||
// SAFE SEARCH
|
||||
|
||||
func TestSafeSearch(t *testing.T) {
|
||||
d := NewForTest(nil, nil)
|
||||
defer d.Close()
|
||||
_, ok := d.SafeSearchDomain("www.google.com")
|
||||
if ok {
|
||||
t.Errorf("Expected safesearch to error when disabled")
|
||||
}
|
||||
|
||||
d = NewForTest(&Config{SafeSearchEnabled: true}, nil)
|
||||
d := NewForTest(&Config{SafeSearchEnabled: true}, nil)
|
||||
defer d.Close()
|
||||
val, ok := d.SafeSearchDomain("www.google.com")
|
||||
if !ok {
|
||||
@@ -355,24 +312,16 @@ func TestParentalControl(t *testing.T) {
|
||||
defer d.Close()
|
||||
d.ParentalSensitivity = 3
|
||||
d.checkMatch(t, "pornhub.com")
|
||||
d.checkMatch(t, "pornhub.com")
|
||||
if gctx.stats.Parental.Requests != 1 {
|
||||
t.Errorf("Parental lookup positive cache is not working")
|
||||
}
|
||||
d.checkMatch(t, "PORNhub.com")
|
||||
if gctx.stats.Parental.Requests != 1 {
|
||||
t.Errorf("Parental lookup positive cache is not working")
|
||||
}
|
||||
d.checkMatch(t, "www.pornhub.com")
|
||||
d.checkMatchEmpty(t, "www.yandex.ru")
|
||||
d.checkMatchEmpty(t, "yandex.ru")
|
||||
l := gctx.stats.Parental.Requests
|
||||
d.checkMatchEmpty(t, "yandex.ru")
|
||||
if gctx.stats.Parental.Requests != l {
|
||||
t.Errorf("Parental lookup negative cache is not working")
|
||||
}
|
||||
|
||||
d.checkMatchEmpty(t, "api.jquery.com")
|
||||
|
||||
// test cached result
|
||||
d.parentalServer = "127.0.0.1"
|
||||
d.checkMatch(t, "pornhub.com")
|
||||
d.checkMatchEmpty(t, "yandex.ru")
|
||||
d.parentalServer = defaultParentalServer
|
||||
}
|
||||
|
||||
// FILTERING
|
||||
@@ -458,10 +407,10 @@ func applyClientSettings(setts *RequestFilteringSettings) {
|
||||
setts.ParentalEnabled = false
|
||||
setts.SafeBrowsingEnabled = true
|
||||
|
||||
rule, _ := urlfilter.NewNetworkRule("||facebook.com^", 0)
|
||||
rule, _ := rules.NewNetworkRule("||facebook.com^", 0)
|
||||
s := ServiceEntry{}
|
||||
s.Name = "facebook"
|
||||
s.Rules = []*urlfilter.NetworkRule{rule}
|
||||
s.Rules = []*rules.NetworkRule{rule}
|
||||
setts.ServicesRules = append(setts.ServicesRules, s)
|
||||
}
|
||||
|
||||
@@ -588,17 +537,3 @@ func BenchmarkSafeSearchParallel(b *testing.B) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestDnsfilterDialCache(t *testing.T) {
|
||||
d := Dnsfilter{}
|
||||
gctx.dialCache = gcache.New(1).LRU().Expiration(30 * time.Minute).Build()
|
||||
|
||||
d.shouldBeInDialCache("hostname")
|
||||
if searchInDialCache("hostname") != "" {
|
||||
t.Errorf("searchInDialCache")
|
||||
}
|
||||
addToDialCache("hostname", "1.1.1.1")
|
||||
if searchInDialCache("hostname") != "1.1.1.1" {
|
||||
t.Errorf("searchInDialCache")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
package dnsfilter
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
func updateMax(valuePtr *int64, maxPtr *int64) {
|
||||
for {
|
||||
current := atomic.LoadInt64(valuePtr)
|
||||
max := atomic.LoadInt64(maxPtr)
|
||||
if current <= max {
|
||||
break
|
||||
}
|
||||
swapped := atomic.CompareAndSwapInt64(maxPtr, max, current)
|
||||
if swapped {
|
||||
break
|
||||
}
|
||||
// swapping failed because value has changed after reading, try again
|
||||
}
|
||||
}
|
||||
@@ -213,4 +213,6 @@ var safeSearchDomains = map[string]string{
|
||||
"youtubei.googleapis.com": "restrictmoderate.youtube.com",
|
||||
"youtube.googleapis.com": "restrictmoderate.youtube.com",
|
||||
"www.youtube-nocookie.com": "restrictmoderate.youtube.com",
|
||||
|
||||
"pixabay.com": "safesearch.pixabay.com",
|
||||
}
|
||||
|
||||
@@ -4,17 +4,294 @@ package dnsfilter
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"encoding/gob"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/AdguardTeam/golibs/cache"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/miekg/dns"
|
||||
"golang.org/x/net/publicsuffix"
|
||||
)
|
||||
|
||||
// Servers to use for resolution of SB/PC server name
|
||||
var bootstrapServers = []string{"176.103.130.130", "176.103.130.131"}
|
||||
|
||||
const dnsTimeout = 3 * time.Second
|
||||
const defaultSafebrowsingServer = "https://dns-family.adguard.com/dns-query"
|
||||
const defaultParentalServer = "https://dns-family.adguard.com/dns-query"
|
||||
const sbTXTSuffix = "sb.dns.adguard.com."
|
||||
const pcTXTSuffix = "pc.dns.adguard.com."
|
||||
|
||||
func (d *Dnsfilter) initSecurityServices() error {
|
||||
var err error
|
||||
d.safeBrowsingServer = defaultSafebrowsingServer
|
||||
d.parentalServer = defaultParentalServer
|
||||
opts := upstream.Options{Timeout: dnsTimeout, Bootstrap: bootstrapServers}
|
||||
|
||||
d.parentalUpstream, err = upstream.AddressToUpstream(d.parentalServer, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.safeBrowsingUpstream, err = upstream.AddressToUpstream(d.safeBrowsingServer, opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/*
|
||||
expire byte[4]
|
||||
res Result
|
||||
*/
|
||||
func (d *Dnsfilter) setCacheResult(cache cache.Cache, host string, res Result) int {
|
||||
var buf bytes.Buffer
|
||||
|
||||
expire := uint(time.Now().Unix()) + d.Config.CacheTime*60
|
||||
var exp []byte
|
||||
exp = make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(exp, uint32(expire))
|
||||
_, _ = buf.Write(exp)
|
||||
|
||||
enc := gob.NewEncoder(&buf)
|
||||
err := enc.Encode(res)
|
||||
if err != nil {
|
||||
log.Error("gob.Encode(): %s", err)
|
||||
return 0
|
||||
}
|
||||
val := buf.Bytes()
|
||||
_ = cache.Set([]byte(host), val)
|
||||
return len(val)
|
||||
}
|
||||
|
||||
func getCachedResult(cache cache.Cache, host string) (Result, bool) {
|
||||
data := cache.Get([]byte(host))
|
||||
if data == nil {
|
||||
return Result{}, false
|
||||
}
|
||||
|
||||
exp := int(binary.BigEndian.Uint32(data[:4]))
|
||||
if exp <= int(time.Now().Unix()) {
|
||||
cache.Del([]byte(host))
|
||||
return Result{}, false
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
buf.Write(data[4:])
|
||||
dec := gob.NewDecoder(&buf)
|
||||
r := Result{}
|
||||
err := dec.Decode(&r)
|
||||
if err != nil {
|
||||
log.Debug("gob.Decode(): %s", err)
|
||||
return Result{}, false
|
||||
}
|
||||
|
||||
return r, true
|
||||
}
|
||||
|
||||
// SafeSearchDomain returns replacement address for search engine
|
||||
func (d *Dnsfilter) SafeSearchDomain(host string) (string, bool) {
|
||||
val, ok := safeSearchDomains[host]
|
||||
return val, ok
|
||||
}
|
||||
|
||||
func (d *Dnsfilter) checkSafeSearch(host string) (Result, error) {
|
||||
if log.GetLevel() >= log.DEBUG {
|
||||
timer := log.StartTimer()
|
||||
defer timer.LogElapsed("SafeSearch: lookup for %s", host)
|
||||
}
|
||||
|
||||
// Check cache. Return cached result if it was found
|
||||
cachedValue, isFound := getCachedResult(gctx.safeSearchCache, host)
|
||||
if isFound {
|
||||
// atomic.AddUint64(&gctx.stats.Safesearch.CacheHits, 1)
|
||||
log.Tracef("SafeSearch: found in cache: %s", host)
|
||||
return cachedValue, nil
|
||||
}
|
||||
|
||||
safeHost, ok := d.SafeSearchDomain(host)
|
||||
if !ok {
|
||||
return Result{}, nil
|
||||
}
|
||||
|
||||
res := Result{IsFiltered: true, Reason: FilteredSafeSearch}
|
||||
if ip := net.ParseIP(safeHost); ip != nil {
|
||||
res.IP = ip
|
||||
len := d.setCacheResult(gctx.safeSearchCache, host, res)
|
||||
log.Debug("SafeSearch: stored in cache: %s (%d bytes)", host, len)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// TODO this address should be resolved with upstream that was configured in dnsforward
|
||||
addrs, err := net.LookupIP(safeHost)
|
||||
if err != nil {
|
||||
log.Tracef("SafeSearchDomain for %s was found but failed to lookup for %s cause %s", host, safeHost, err)
|
||||
return Result{}, err
|
||||
}
|
||||
|
||||
for _, i := range addrs {
|
||||
if ipv4 := i.To4(); ipv4 != nil {
|
||||
res.IP = ipv4
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(res.IP) == 0 {
|
||||
return Result{}, fmt.Errorf("no ipv4 addresses in safe search response for %s", safeHost)
|
||||
}
|
||||
|
||||
// Cache result
|
||||
len := d.setCacheResult(gctx.safeSearchCache, host, res)
|
||||
log.Debug("SafeSearch: stored in cache: %s (%d bytes)", host, len)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// for each dot, hash it and add it to string
|
||||
func hostnameToHashParam(host string) (string, map[string]bool) {
|
||||
var hashparam bytes.Buffer
|
||||
hashes := map[string]bool{}
|
||||
tld, icann := publicsuffix.PublicSuffix(host)
|
||||
if !icann {
|
||||
// private suffixes like cloudfront.net
|
||||
tld = ""
|
||||
}
|
||||
curhost := host
|
||||
for {
|
||||
if curhost == "" {
|
||||
// we've reached end of string
|
||||
break
|
||||
}
|
||||
if tld != "" && curhost == tld {
|
||||
// we've reached the TLD, don't hash it
|
||||
break
|
||||
}
|
||||
|
||||
sum := sha256.Sum256([]byte(curhost))
|
||||
hashes[hex.EncodeToString(sum[:])] = true
|
||||
hashparam.WriteString(fmt.Sprintf("%s.", hex.EncodeToString(sum[0:4])))
|
||||
|
||||
pos := strings.IndexByte(curhost, byte('.'))
|
||||
if pos < 0 {
|
||||
break
|
||||
}
|
||||
curhost = curhost[pos+1:]
|
||||
}
|
||||
return hashparam.String(), hashes
|
||||
}
|
||||
|
||||
// Find the target hash in TXT response
|
||||
func (d *Dnsfilter) processTXT(svc, host string, resp *dns.Msg, hashes map[string]bool) bool {
|
||||
for _, a := range resp.Answer {
|
||||
txt, ok := a.(*dns.TXT)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
log.Tracef("%s: hashes for %s: %v", svc, host, txt.Txt)
|
||||
for _, t := range txt.Txt {
|
||||
_, ok := hashes[t]
|
||||
if ok {
|
||||
log.Tracef("%s: matched %s by %s", svc, host, t)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Disabling "dupl": the algorithm of SB/PC is similar, but it uses different data
|
||||
// nolint:dupl
|
||||
func (d *Dnsfilter) checkSafeBrowsing(host string) (Result, error) {
|
||||
if log.GetLevel() >= log.DEBUG {
|
||||
timer := log.StartTimer()
|
||||
defer timer.LogElapsed("SafeBrowsing lookup for %s", host)
|
||||
}
|
||||
|
||||
// check cache
|
||||
cachedValue, isFound := getCachedResult(gctx.safebrowsingCache, host)
|
||||
if isFound {
|
||||
// atomic.AddUint64(&gctx.stats.Safebrowsing.CacheHits, 1)
|
||||
log.Tracef("SafeBrowsing: found in cache: %s", host)
|
||||
return cachedValue, nil
|
||||
}
|
||||
|
||||
result := Result{}
|
||||
question, hashes := hostnameToHashParam(host)
|
||||
question = question + sbTXTSuffix
|
||||
|
||||
log.Tracef("SafeBrowsing: checking %s: %s", host, question)
|
||||
|
||||
req := dns.Msg{}
|
||||
req.SetQuestion(question, dns.TypeTXT)
|
||||
resp, err := d.safeBrowsingUpstream.Exchange(&req)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
if d.processTXT("SafeBrowsing", host, resp, hashes) {
|
||||
result.IsFiltered = true
|
||||
result.Reason = FilteredSafeBrowsing
|
||||
result.Rule = "adguard-malware-shavar"
|
||||
}
|
||||
|
||||
len := d.setCacheResult(gctx.safebrowsingCache, host, result)
|
||||
log.Debug("SafeBrowsing: stored in cache: %s (%d bytes)", host, len)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Disabling "dupl": the algorithm of SB/PC is similar, but it uses different data
|
||||
// nolint:dupl
|
||||
func (d *Dnsfilter) checkParental(host string) (Result, error) {
|
||||
if log.GetLevel() >= log.DEBUG {
|
||||
timer := log.StartTimer()
|
||||
defer timer.LogElapsed("Parental lookup for %s", host)
|
||||
}
|
||||
|
||||
// check cache
|
||||
cachedValue, isFound := getCachedResult(gctx.parentalCache, host)
|
||||
if isFound {
|
||||
// atomic.AddUint64(&gctx.stats.Parental.CacheHits, 1)
|
||||
log.Tracef("Parental: found in cache: %s", host)
|
||||
return cachedValue, nil
|
||||
}
|
||||
|
||||
result := Result{}
|
||||
question, hashes := hostnameToHashParam(host)
|
||||
question = question + pcTXTSuffix
|
||||
|
||||
log.Tracef("Parental: checking %s: %s", host, question)
|
||||
|
||||
req := dns.Msg{}
|
||||
req.SetQuestion(question, dns.TypeTXT)
|
||||
resp, err := d.parentalUpstream.Exchange(&req)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
if d.processTXT("Parental", host, resp, hashes) {
|
||||
result.IsFiltered = true
|
||||
result.Reason = FilteredParental
|
||||
result.Rule = "parental CATEGORY_BLACKLISTED"
|
||||
}
|
||||
|
||||
len := d.setCacheResult(gctx.parentalCache, host, result)
|
||||
log.Debug("Parental: stored in cache: %s (%d bytes)", host, len)
|
||||
return result, err
|
||||
}
|
||||
|
||||
func httpError(r *http.Request, w http.ResponseWriter, code int, format string, args ...interface{}) {
|
||||
text := fmt.Sprintf(format, args...)
|
||||
log.Info("DNSFilter: %s %s: %s", r.Method, r.URL, text)
|
||||
@@ -170,9 +447,11 @@ func (d *Dnsfilter) registerSecurityHandlers() {
|
||||
d.Config.HTTPRegister("POST", "/control/safebrowsing/enable", d.handleSafeBrowsingEnable)
|
||||
d.Config.HTTPRegister("POST", "/control/safebrowsing/disable", d.handleSafeBrowsingDisable)
|
||||
d.Config.HTTPRegister("GET", "/control/safebrowsing/status", d.handleSafeBrowsingStatus)
|
||||
|
||||
d.Config.HTTPRegister("POST", "/control/parental/enable", d.handleParentalEnable)
|
||||
d.Config.HTTPRegister("POST", "/control/parental/disable", d.handleParentalDisable)
|
||||
d.Config.HTTPRegister("GET", "/control/parental/status", d.handleParentalStatus)
|
||||
|
||||
d.Config.HTTPRegister("POST", "/control/safesearch/enable", d.handleSafeSearchEnable)
|
||||
d.Config.HTTPRegister("POST", "/control/safesearch/disable", d.handleSafeSearchDisable)
|
||||
d.Config.HTTPRegister("GET", "/control/safesearch/status", d.handleSafeSearchStatus)
|
||||
|
||||
188
dnsforward/access.go
Normal file
188
dnsforward/access.go
Normal file
@@ -0,0 +1,188 @@
|
||||
package dnsforward
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
)
|
||||
|
||||
type accessCtx struct {
|
||||
lock sync.Mutex
|
||||
|
||||
allowedClients map[string]bool // IP addresses of whitelist clients
|
||||
disallowedClients map[string]bool // IP addresses of clients that should be blocked
|
||||
|
||||
allowedClientsIPNet []net.IPNet // CIDRs of whitelist clients
|
||||
disallowedClientsIPNet []net.IPNet // CIDRs of clients that should be blocked
|
||||
|
||||
blockedHosts map[string]bool // hosts that should be blocked
|
||||
}
|
||||
|
||||
func (a *accessCtx) Init(allowedClients, disallowedClients, blockedHosts []string) error {
|
||||
err := processIPCIDRArray(&a.allowedClients, &a.allowedClientsIPNet, allowedClients)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = processIPCIDRArray(&a.disallowedClients, &a.disallowedClientsIPNet, disallowedClients)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
convertArrayToMap(&a.blockedHosts, blockedHosts)
|
||||
return nil
|
||||
}
|
||||
|
||||
func convertArrayToMap(dst *map[string]bool, src []string) {
|
||||
*dst = make(map[string]bool)
|
||||
for _, s := range src {
|
||||
(*dst)[s] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Split array of IP or CIDR into 2 containers for fast search
|
||||
func processIPCIDRArray(dst *map[string]bool, dstIPNet *[]net.IPNet, src []string) error {
|
||||
*dst = make(map[string]bool)
|
||||
|
||||
for _, s := range src {
|
||||
ip := net.ParseIP(s)
|
||||
if ip != nil {
|
||||
(*dst)[s] = true
|
||||
continue
|
||||
}
|
||||
|
||||
_, ipnet, err := net.ParseCIDR(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*dstIPNet = append(*dstIPNet, *ipnet)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsBlockedIP - return TRUE if this client should be blocked
|
||||
func (a *accessCtx) IsBlockedIP(ip string) bool {
|
||||
a.lock.Lock()
|
||||
defer a.lock.Unlock()
|
||||
|
||||
if len(a.allowedClients) != 0 || len(a.allowedClientsIPNet) != 0 {
|
||||
_, ok := a.allowedClients[ip]
|
||||
if ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(a.allowedClientsIPNet) != 0 {
|
||||
ipAddr := net.ParseIP(ip)
|
||||
for _, ipnet := range a.allowedClientsIPNet {
|
||||
if ipnet.Contains(ipAddr) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
_, ok := a.disallowedClients[ip]
|
||||
if ok {
|
||||
return true
|
||||
}
|
||||
|
||||
if len(a.disallowedClientsIPNet) != 0 {
|
||||
ipAddr := net.ParseIP(ip)
|
||||
for _, ipnet := range a.disallowedClientsIPNet {
|
||||
if ipnet.Contains(ipAddr) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// IsBlockedDomain - return TRUE if this domain should be blocked
|
||||
func (a *accessCtx) IsBlockedDomain(host string) bool {
|
||||
a.lock.Lock()
|
||||
_, ok := a.blockedHosts[host]
|
||||
a.lock.Unlock()
|
||||
return ok
|
||||
}
|
||||
|
||||
type accessListJSON struct {
|
||||
AllowedClients []string `json:"allowed_clients"`
|
||||
DisallowedClients []string `json:"disallowed_clients"`
|
||||
BlockedHosts []string `json:"blocked_hosts"`
|
||||
}
|
||||
|
||||
func (s *Server) handleAccessList(w http.ResponseWriter, r *http.Request) {
|
||||
s.RLock()
|
||||
j := accessListJSON{
|
||||
AllowedClients: s.conf.AllowedClients,
|
||||
DisallowedClients: s.conf.DisallowedClients,
|
||||
BlockedHosts: s.conf.BlockedHosts,
|
||||
}
|
||||
s.RUnlock()
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
err := json.NewEncoder(w).Encode(j)
|
||||
if err != nil {
|
||||
httpError(r, w, http.StatusInternalServerError, "json.Encode: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func checkIPCIDRArray(src []string) error {
|
||||
for _, s := range src {
|
||||
ip := net.ParseIP(s)
|
||||
if ip != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
_, _, err := net.ParseCIDR(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) handleAccessSet(w http.ResponseWriter, r *http.Request) {
|
||||
j := accessListJSON{}
|
||||
err := json.NewDecoder(r.Body).Decode(&j)
|
||||
if err != nil {
|
||||
httpError(r, w, http.StatusBadRequest, "json.Decode: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = checkIPCIDRArray(j.AllowedClients)
|
||||
if err == nil {
|
||||
err = checkIPCIDRArray(j.DisallowedClients)
|
||||
}
|
||||
if err != nil {
|
||||
httpError(r, w, http.StatusBadRequest, "%s", err)
|
||||
return
|
||||
}
|
||||
|
||||
a := &accessCtx{}
|
||||
err = a.Init(j.AllowedClients, j.DisallowedClients, j.BlockedHosts)
|
||||
if err != nil {
|
||||
httpError(r, w, http.StatusBadRequest, "access.Init: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
s.Lock()
|
||||
s.conf.AllowedClients = j.AllowedClients
|
||||
s.conf.DisallowedClients = j.DisallowedClients
|
||||
s.conf.BlockedHosts = j.BlockedHosts
|
||||
s.access = a
|
||||
s.Unlock()
|
||||
s.conf.ConfigModified()
|
||||
|
||||
log.Debug("Access: updated lists: %d, %d, %d",
|
||||
len(j.AllowedClients), len(j.DisallowedClients), len(j.BlockedHosts))
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package dnsforward
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"runtime"
|
||||
@@ -28,6 +28,11 @@ const (
|
||||
parentalBlockHost = "family-block.dns.adguard.com"
|
||||
)
|
||||
|
||||
var defaultDNS = []string{
|
||||
"https://dns.quad9.net/dns-query",
|
||||
}
|
||||
var defaultBootstrap = []string{"9.9.9.9", "149.112.112.112"}
|
||||
|
||||
// Server is the main way to start a DNS server.
|
||||
//
|
||||
// Example:
|
||||
@@ -43,12 +48,14 @@ type Server struct {
|
||||
dnsFilter *dnsfilter.Dnsfilter // DNS filter instance
|
||||
queryLog querylog.QueryLog // Query log instance
|
||||
stats stats.Stats
|
||||
access *accessCtx
|
||||
|
||||
AllowedClients map[string]bool // IP addresses of whitelist clients
|
||||
DisallowedClients map[string]bool // IP addresses of clients that should be blocked
|
||||
AllowedClientsIPNet []net.IPNet // CIDRs of whitelist clients
|
||||
DisallowedClientsIPNet []net.IPNet // CIDRs of clients that should be blocked
|
||||
BlockedHosts map[string]bool // hosts that should be blocked
|
||||
// DNS proxy instance for internal usage
|
||||
// We don't Start() it and so no listen port is required.
|
||||
internalProxy *proxy.Proxy
|
||||
|
||||
webRegistered bool
|
||||
isRunning bool
|
||||
|
||||
sync.RWMutex
|
||||
conf ServerConfig
|
||||
@@ -61,37 +68,70 @@ func NewServer(dnsFilter *dnsfilter.Dnsfilter, stats stats.Stats, queryLog query
|
||||
s.dnsFilter = dnsFilter
|
||||
s.stats = stats
|
||||
s.queryLog = queryLog
|
||||
|
||||
if runtime.GOARCH == "mips" || runtime.GOARCH == "mipsle" {
|
||||
// Use plain DNS on MIPS, encryption is too slow
|
||||
defaultDNS = defaultBootstrap
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Close - close object
|
||||
func (s *Server) Close() {
|
||||
s.Lock()
|
||||
s.dnsFilter = nil
|
||||
s.stats = nil
|
||||
s.queryLog = nil
|
||||
s.dnsProxy = nil
|
||||
s.Unlock()
|
||||
}
|
||||
|
||||
func stringArrayDup(a []string) []string {
|
||||
a2 := make([]string, len(a))
|
||||
copy(a2, a)
|
||||
return a2
|
||||
}
|
||||
|
||||
// WriteDiskConfig - write configuration
|
||||
func (s *Server) WriteDiskConfig(c *FilteringConfig) {
|
||||
s.RLock()
|
||||
sc := s.conf.FilteringConfig
|
||||
*c = sc
|
||||
c.RatelimitWhitelist = stringArrayDup(sc.RatelimitWhitelist)
|
||||
c.BootstrapDNS = stringArrayDup(sc.BootstrapDNS)
|
||||
c.AllowedClients = stringArrayDup(sc.AllowedClients)
|
||||
c.DisallowedClients = stringArrayDup(sc.DisallowedClients)
|
||||
c.BlockedHosts = stringArrayDup(sc.BlockedHosts)
|
||||
c.UpstreamDNS = stringArrayDup(sc.UpstreamDNS)
|
||||
s.RUnlock()
|
||||
}
|
||||
|
||||
// FilteringConfig represents the DNS filtering configuration of AdGuard Home
|
||||
// The zero FilteringConfig is empty and ready for use.
|
||||
type FilteringConfig struct {
|
||||
// Filtering callback function
|
||||
FilterHandler func(clientAddr string, settings *dnsfilter.RequestFilteringSettings) `yaml:"-"`
|
||||
|
||||
ProtectionEnabled bool `yaml:"protection_enabled"` // whether or not use any of dnsfilter features
|
||||
FilteringEnabled bool `yaml:"filtering_enabled"` // whether or not use filter lists
|
||||
FiltersUpdateIntervalHours uint32 `yaml:"filters_update_interval"` // time period to update filters (in hours)
|
||||
// This callback function returns the list of upstream servers for a client specified by IP address
|
||||
GetUpstreamsByClient func(clientAddr string) []string `yaml:"-"`
|
||||
|
||||
ProtectionEnabled bool `yaml:"protection_enabled"` // whether or not use any of dnsfilter features
|
||||
|
||||
BlockingMode string `yaml:"blocking_mode"` // mode how to answer filtered requests
|
||||
BlockingIPv4 string `yaml:"blocking_ipv4"` // IP address to be returned for a blocked A request
|
||||
BlockingIPv6 string `yaml:"blocking_ipv6"` // IP address to be returned for a blocked AAAA request
|
||||
BlockingIPAddrv4 net.IP `yaml:"-"`
|
||||
BlockingIPAddrv6 net.IP `yaml:"-"`
|
||||
|
||||
BlockingMode string `yaml:"blocking_mode"` // mode how to answer filtered requests
|
||||
BlockedResponseTTL uint32 `yaml:"blocked_response_ttl"` // if 0, then default is used (3600)
|
||||
QueryLogEnabled bool `yaml:"querylog_enabled"` // if true, query log is enabled
|
||||
QueryLogInterval uint32 `yaml:"querylog_interval"` // time interval for query log (in days)
|
||||
Ratelimit int `yaml:"ratelimit"` // max number of requests per second from a given IP (0 to disable)
|
||||
Ratelimit uint32 `yaml:"ratelimit"` // max number of requests per second from a given IP (0 to disable)
|
||||
RatelimitWhitelist []string `yaml:"ratelimit_whitelist"` // a list of whitelisted client IP addresses
|
||||
RefuseAny bool `yaml:"refuse_any"` // if true, refuse ANY requests
|
||||
BootstrapDNS []string `yaml:"bootstrap_dns"` // a list of bootstrap DNS for DoH and DoT (plain DNS only)
|
||||
AllServers bool `yaml:"all_servers"` // if true, parallel queries to all configured upstream servers are enabled
|
||||
|
||||
EnableEDNSClientSubnet bool `yaml:"edns_client_subnet"` // Enable EDNS Client Subnet option
|
||||
|
||||
AllowedClients []string `yaml:"allowed_clients"` // IP addresses of whitelist clients
|
||||
DisallowedClients []string `yaml:"disallowed_clients"` // IP addresses of clients that should be blocked
|
||||
BlockedHosts []string `yaml:"blocked_hosts"` // hosts that should be blocked
|
||||
@@ -100,13 +140,8 @@ type FilteringConfig struct {
|
||||
ParentalBlockHost string `yaml:"parental_block_host"`
|
||||
SafeBrowsingBlockHost string `yaml:"safebrowsing_block_host"`
|
||||
|
||||
// Names of services to block (globally).
|
||||
// Per-client settings can override this configuration.
|
||||
BlockedServices []string `yaml:"blocked_services"`
|
||||
|
||||
CacheSize uint `yaml:"cache_size"` // DNS cache size (in bytes)
|
||||
|
||||
DnsfilterConf dnsfilter.Config `yaml:",inline"`
|
||||
CacheSize uint `yaml:"cache_size"` // DNS cache size (in bytes)
|
||||
UpstreamDNS []string `yaml:"upstream_dns"`
|
||||
}
|
||||
|
||||
// TLSConfig is the TLS configuration for HTTPS, DNS-over-HTTPS, and DNS-over-TLS
|
||||
@@ -133,6 +168,12 @@ type ServerConfig struct {
|
||||
|
||||
FilteringConfig
|
||||
TLSConfig
|
||||
|
||||
// Called when the configuration is changed by HTTP request
|
||||
ConfigModified func()
|
||||
|
||||
// Register an HTTP handler
|
||||
HTTPRegister func(string, string, func(http.ResponseWriter, *http.Request))
|
||||
}
|
||||
|
||||
// if any of ServerConfig values are zero, then default values from below are used
|
||||
@@ -142,74 +183,96 @@ var defaultValues = ServerConfig{
|
||||
FilteringConfig: FilteringConfig{BlockedResponseTTL: 3600},
|
||||
}
|
||||
|
||||
func init() {
|
||||
defaultDNS := []string{"8.8.8.8:53", "8.8.4.4:53"}
|
||||
// Resolve - get IP addresses by host name from an upstream server.
|
||||
// No request/response filtering is performed.
|
||||
// Query log and Stats are not updated.
|
||||
// This method may be called before Start().
|
||||
func (s *Server) Resolve(host string) ([]net.IPAddr, error) {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
return s.internalProxy.LookupIPAddr(host)
|
||||
}
|
||||
|
||||
defaultUpstreams := make([]upstream.Upstream, 0)
|
||||
for _, addr := range defaultDNS {
|
||||
u, err := upstream.AddressToUpstream(addr, upstream.Options{Timeout: DefaultTimeout})
|
||||
if err == nil {
|
||||
defaultUpstreams = append(defaultUpstreams, u)
|
||||
}
|
||||
// Exchange - send DNS request to an upstream server and receive response
|
||||
// No request/response filtering is performed.
|
||||
// Query log and Stats are not updated.
|
||||
// This method may be called before Start().
|
||||
func (s *Server) Exchange(req *dns.Msg) (*dns.Msg, error) {
|
||||
s.RLock()
|
||||
defer s.RUnlock()
|
||||
|
||||
ctx := &proxy.DNSContext{
|
||||
Proto: "udp",
|
||||
Req: req,
|
||||
StartTime: time.Now(),
|
||||
}
|
||||
defaultValues.Upstreams = defaultUpstreams
|
||||
err := s.internalProxy.Resolve(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ctx.Res, nil
|
||||
}
|
||||
|
||||
// Start starts the DNS server
|
||||
func (s *Server) Start(config *ServerConfig) error {
|
||||
func (s *Server) Start() error {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
return s.startInternal(config)
|
||||
}
|
||||
|
||||
func convertArrayToMap(dst *map[string]bool, src []string) {
|
||||
*dst = make(map[string]bool)
|
||||
for _, s := range src {
|
||||
(*dst)[s] = true
|
||||
}
|
||||
}
|
||||
|
||||
// Split array of IP or CIDR into 2 containers for fast search
|
||||
func processIPCIDRArray(dst *map[string]bool, dstIPNet *[]net.IPNet, src []string) error {
|
||||
*dst = make(map[string]bool)
|
||||
|
||||
for _, s := range src {
|
||||
ip := net.ParseIP(s)
|
||||
if ip != nil {
|
||||
(*dst)[s] = true
|
||||
continue
|
||||
}
|
||||
|
||||
_, ipnet, err := net.ParseCIDR(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*dstIPNet = append(*dstIPNet, *ipnet)
|
||||
}
|
||||
|
||||
return nil
|
||||
return s.startInternal()
|
||||
}
|
||||
|
||||
// startInternal starts without locking
|
||||
func (s *Server) startInternal(config *ServerConfig) error {
|
||||
if s.dnsProxy != nil {
|
||||
return errors.New("DNS server is already started")
|
||||
func (s *Server) startInternal() error {
|
||||
err := s.dnsProxy.Start()
|
||||
if err == nil {
|
||||
s.isRunning = true
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Prepare the object
|
||||
func (s *Server) Prepare(config *ServerConfig) error {
|
||||
if config != nil {
|
||||
s.conf = *config
|
||||
if s.conf.BlockingMode == "custom_ip" {
|
||||
s.conf.BlockingIPAddrv4 = net.ParseIP(s.conf.BlockingIPv4)
|
||||
s.conf.BlockingIPAddrv6 = net.ParseIP(s.conf.BlockingIPv6)
|
||||
if s.conf.BlockingIPAddrv4 == nil || s.conf.BlockingIPAddrv6 == nil {
|
||||
return fmt.Errorf("DNS: invalid custom blocking IP address specified")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(s.conf.UpstreamDNS) == 0 {
|
||||
s.conf.UpstreamDNS = defaultDNS
|
||||
}
|
||||
if len(s.conf.BootstrapDNS) == 0 {
|
||||
s.conf.BootstrapDNS = defaultBootstrap
|
||||
}
|
||||
|
||||
upstreamConfig, err := proxy.ParseUpstreamsConfig(s.conf.UpstreamDNS, s.conf.BootstrapDNS, DefaultTimeout)
|
||||
if err != nil {
|
||||
return fmt.Errorf("DNS: proxy.ParseUpstreamsConfig: %s", err)
|
||||
}
|
||||
s.conf.Upstreams = upstreamConfig.Upstreams
|
||||
s.conf.DomainsReservedUpstreams = upstreamConfig.DomainReservedUpstreams
|
||||
|
||||
if len(s.conf.ParentalBlockHost) == 0 {
|
||||
s.conf.ParentalBlockHost = parentalBlockHost
|
||||
}
|
||||
if len(s.conf.SafeBrowsingBlockHost) == 0 {
|
||||
s.conf.SafeBrowsingBlockHost = safeBrowsingBlockHost
|
||||
}
|
||||
if s.conf.UDPListenAddr == nil {
|
||||
s.conf.UDPListenAddr = defaultValues.UDPListenAddr
|
||||
}
|
||||
if s.conf.TCPListenAddr == nil {
|
||||
s.conf.TCPListenAddr = defaultValues.TCPListenAddr
|
||||
}
|
||||
|
||||
proxyConfig := proxy.Config{
|
||||
UDPListenAddr: s.conf.UDPListenAddr,
|
||||
TCPListenAddr: s.conf.TCPListenAddr,
|
||||
Ratelimit: s.conf.Ratelimit,
|
||||
Ratelimit: int(s.conf.Ratelimit),
|
||||
RatelimitWhitelist: s.conf.RatelimitWhitelist,
|
||||
RefuseAny: s.conf.RefuseAny,
|
||||
CacheEnabled: true,
|
||||
@@ -219,20 +282,23 @@ func (s *Server) startInternal(config *ServerConfig) error {
|
||||
BeforeRequestHandler: s.beforeRequestHandler,
|
||||
RequestHandler: s.handleDNSRequest,
|
||||
AllServers: s.conf.AllServers,
|
||||
EnableEDNSClientSubnet: s.conf.EnableEDNSClientSubnet,
|
||||
}
|
||||
|
||||
err := processIPCIDRArray(&s.AllowedClients, &s.AllowedClientsIPNet, s.conf.AllowedClients)
|
||||
intlProxyConfig := proxy.Config{
|
||||
CacheEnabled: true,
|
||||
CacheSizeBytes: 4096,
|
||||
Upstreams: s.conf.Upstreams,
|
||||
DomainsReservedUpstreams: s.conf.DomainsReservedUpstreams,
|
||||
}
|
||||
s.internalProxy = &proxy.Proxy{Config: intlProxyConfig}
|
||||
|
||||
s.access = &accessCtx{}
|
||||
err = s.access.Init(s.conf.AllowedClients, s.conf.DisallowedClients, s.conf.BlockedHosts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = processIPCIDRArray(&s.DisallowedClients, &s.DisallowedClientsIPNet, s.conf.DisallowedClients)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
convertArrayToMap(&s.BlockedHosts, s.conf.BlockedHosts)
|
||||
|
||||
if s.conf.TLSListenAddr != nil && len(s.conf.CertificateChainData) != 0 && len(s.conf.PrivateKeyData) != 0 {
|
||||
proxyConfig.TLSListenAddr = s.conf.TLSListenAddr
|
||||
keypair, err := tls.X509KeyPair(s.conf.CertificateChainData, s.conf.PrivateKeyData)
|
||||
@@ -245,21 +311,18 @@ func (s *Server) startInternal(config *ServerConfig) error {
|
||||
}
|
||||
}
|
||||
|
||||
if proxyConfig.UDPListenAddr == nil {
|
||||
proxyConfig.UDPListenAddr = defaultValues.UDPListenAddr
|
||||
}
|
||||
|
||||
if proxyConfig.TCPListenAddr == nil {
|
||||
proxyConfig.TCPListenAddr = defaultValues.TCPListenAddr
|
||||
}
|
||||
|
||||
if len(proxyConfig.Upstreams) == 0 {
|
||||
proxyConfig.Upstreams = defaultValues.Upstreams
|
||||
log.Fatal("len(proxyConfig.Upstreams) == 0")
|
||||
}
|
||||
|
||||
if !s.webRegistered && s.conf.HTTPRegister != nil {
|
||||
s.webRegistered = true
|
||||
s.registerHandlers()
|
||||
}
|
||||
|
||||
// Initialize and start the DNS proxy
|
||||
s.dnsProxy = &proxy.Proxy{Config: proxyConfig}
|
||||
return s.dnsProxy.Start()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Stop stops the DNS server
|
||||
@@ -273,24 +336,37 @@ func (s *Server) Stop() error {
|
||||
func (s *Server) stopInternal() error {
|
||||
if s.dnsProxy != nil {
|
||||
err := s.dnsProxy.Stop()
|
||||
s.dnsProxy = nil
|
||||
if err != nil {
|
||||
return errorx.Decorate(err, "could not stop the DNS server properly")
|
||||
}
|
||||
}
|
||||
|
||||
s.isRunning = false
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsRunning returns true if the DNS server is running
|
||||
func (s *Server) IsRunning() bool {
|
||||
s.RLock()
|
||||
isRunning := true
|
||||
if s.dnsProxy == nil {
|
||||
isRunning = false
|
||||
defer s.RUnlock()
|
||||
return s.isRunning
|
||||
}
|
||||
|
||||
// Restart - restart server
|
||||
func (s *Server) Restart() error {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
log.Print("Start reconfiguring the server")
|
||||
err := s.stopInternal()
|
||||
if err != nil {
|
||||
return errorx.Decorate(err, "could not reconfigure the server")
|
||||
}
|
||||
s.RUnlock()
|
||||
return isRunning
|
||||
err = s.startInternal()
|
||||
if err != nil {
|
||||
return errorx.Decorate(err, "could not reconfigure the server")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reconfigure applies the new configuration to the DNS server
|
||||
@@ -304,12 +380,16 @@ func (s *Server) Reconfigure(config *ServerConfig) error {
|
||||
return errorx.Decorate(err, "could not reconfigure the server")
|
||||
}
|
||||
|
||||
// On some Windows versions the UDP port we've just closed in proxy.Stop() doesn't get actually closed right away.
|
||||
if runtime.GOOS == "windows" {
|
||||
time.Sleep(1 * time.Second)
|
||||
// It seems that net.Listener.Close() doesn't close file descriptors right away.
|
||||
// We wait for some time and hope that this fd will be closed.
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
err = s.Prepare(config)
|
||||
if err != nil {
|
||||
return errorx.Decorate(err, "could not reconfigure the server")
|
||||
}
|
||||
|
||||
err = s.startInternal(config)
|
||||
err = s.startInternal()
|
||||
if err != nil {
|
||||
return errorx.Decorate(err, "could not reconfigure the server")
|
||||
}
|
||||
@@ -320,63 +400,36 @@ func (s *Server) Reconfigure(config *ServerConfig) error {
|
||||
// ServeHTTP is a HTTP handler method we use to provide DNS-over-HTTPS
|
||||
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
s.RLock()
|
||||
s.dnsProxy.ServeHTTP(w, r)
|
||||
p := s.dnsProxy
|
||||
s.RUnlock()
|
||||
if p != nil { // an attempt to protect against race in case we're here after Close() was called
|
||||
p.ServeHTTP(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
// Return TRUE if this client should be blocked
|
||||
func (s *Server) isBlockedIP(ip string) bool {
|
||||
if len(s.AllowedClients) != 0 || len(s.AllowedClientsIPNet) != 0 {
|
||||
_, ok := s.AllowedClients[ip]
|
||||
if ok {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(s.AllowedClientsIPNet) != 0 {
|
||||
ipAddr := net.ParseIP(ip)
|
||||
for _, ipnet := range s.AllowedClientsIPNet {
|
||||
if ipnet.Contains(ipAddr) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
// Get IP address from net.Addr object
|
||||
// Note: we can't use net.SplitHostPort(a.String()) because of IPv6 zone:
|
||||
// https://github.com/AdguardTeam/AdGuardHome/issues/1261
|
||||
func ipFromAddr(a net.Addr) string {
|
||||
switch addr := a.(type) {
|
||||
case *net.UDPAddr:
|
||||
return addr.IP.String()
|
||||
case *net.TCPAddr:
|
||||
return addr.IP.String()
|
||||
}
|
||||
|
||||
_, ok := s.DisallowedClients[ip]
|
||||
if ok {
|
||||
return true
|
||||
}
|
||||
|
||||
if len(s.DisallowedClientsIPNet) != 0 {
|
||||
ipAddr := net.ParseIP(ip)
|
||||
for _, ipnet := range s.DisallowedClientsIPNet {
|
||||
if ipnet.Contains(ipAddr) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// Return TRUE if this domain should be blocked
|
||||
func (s *Server) isBlockedDomain(host string) bool {
|
||||
_, ok := s.BlockedHosts[host]
|
||||
return ok
|
||||
return ""
|
||||
}
|
||||
|
||||
func (s *Server) beforeRequestHandler(p *proxy.Proxy, d *proxy.DNSContext) (bool, error) {
|
||||
ip, _, _ := net.SplitHostPort(d.Addr.String())
|
||||
if s.isBlockedIP(ip) {
|
||||
ip := ipFromAddr(d.Addr)
|
||||
if s.access.IsBlockedIP(ip) {
|
||||
log.Tracef("Client IP %s is blocked by settings", ip)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if len(d.Req.Question) == 1 {
|
||||
host := strings.TrimSuffix(d.Req.Question[0].Name, ".")
|
||||
if s.isBlockedDomain(host) {
|
||||
if s.access.IsBlockedDomain(host) {
|
||||
log.Tracef("Domain %s is blocked by settings", host)
|
||||
return false, nil
|
||||
}
|
||||
@@ -386,6 +439,7 @@ func (s *Server) beforeRequestHandler(p *proxy.Proxy, d *proxy.DNSContext) (bool
|
||||
}
|
||||
|
||||
// handleDNSRequest filters the incoming DNS requests and writes them to the query log
|
||||
// nolint (gocyclo)
|
||||
func (s *Server) handleDNSRequest(p *proxy.Proxy, d *proxy.DNSContext) error {
|
||||
start := time.Now()
|
||||
|
||||
@@ -414,6 +468,7 @@ func (s *Server) handleDNSRequest(p *proxy.Proxy, d *proxy.DNSContext) error {
|
||||
return err
|
||||
}
|
||||
|
||||
var origResp *dns.Msg
|
||||
if d.Res == nil {
|
||||
answer := []dns.RR{}
|
||||
originalQuestion := d.Req.Question[0]
|
||||
@@ -424,6 +479,19 @@ func (s *Server) handleDNSRequest(p *proxy.Proxy, d *proxy.DNSContext) error {
|
||||
d.Req.Question[0].Name = dns.Fqdn(res.CanonName)
|
||||
}
|
||||
|
||||
if d.Addr != nil && s.conf.GetUpstreamsByClient != nil {
|
||||
clientIP := ipFromAddr(d.Addr)
|
||||
upstreams := s.conf.GetUpstreamsByClient(clientIP)
|
||||
for _, us := range upstreams {
|
||||
u, err := upstream.AddressToUpstream(us, upstream.Options{Timeout: 30 * time.Second})
|
||||
if err != nil {
|
||||
log.Error("upstream.AddressToUpstream: %s: %s", us, err)
|
||||
continue
|
||||
}
|
||||
d.Upstreams = append(d.Upstreams, u)
|
||||
}
|
||||
}
|
||||
|
||||
// request was not filtered so let it be processed further
|
||||
err = p.Resolve(d)
|
||||
if err != nil {
|
||||
@@ -432,11 +500,24 @@ func (s *Server) handleDNSRequest(p *proxy.Proxy, d *proxy.DNSContext) error {
|
||||
|
||||
if res.Reason == dnsfilter.ReasonRewrite && len(res.CanonName) != 0 {
|
||||
d.Req.Question[0] = originalQuestion
|
||||
d.Res.Question[0] = originalQuestion
|
||||
|
||||
if len(d.Res.Answer) != 0 {
|
||||
answer = append(answer, d.Res.Answer...) // host -> IP
|
||||
d.Res.Answer = answer
|
||||
}
|
||||
|
||||
} else if res.Reason != dnsfilter.NotFilteredWhiteList {
|
||||
origResp2 := d.Res
|
||||
res, err = s.filterResponse(d)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if res != nil {
|
||||
origResp = origResp2 // matched by response
|
||||
} else {
|
||||
res = &dnsfilter.Result{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -457,11 +538,18 @@ func (s *Server) handleDNSRequest(p *proxy.Proxy, d *proxy.DNSContext) error {
|
||||
// Synchronize access to s.queryLog and s.stats so they won't be suddenly uninitialized while in use.
|
||||
// This can happen after proxy server has been stopped, but its workers haven't yet exited.
|
||||
if shouldLog && s.queryLog != nil {
|
||||
upstreamAddr := ""
|
||||
if d.Upstream != nil {
|
||||
upstreamAddr = d.Upstream.Address()
|
||||
p := querylog.AddParams{
|
||||
Question: msg,
|
||||
Answer: d.Res,
|
||||
OrigAnswer: origResp,
|
||||
Result: res,
|
||||
Elapsed: elapsed,
|
||||
ClientIP: getIP(d.Addr),
|
||||
}
|
||||
s.queryLog.Add(msg, d.Res, res, elapsed, d.Addr, upstreamAddr)
|
||||
if d.Upstream != nil {
|
||||
p.Upstream = d.Upstream.Address()
|
||||
}
|
||||
s.queryLog.Add(p)
|
||||
}
|
||||
|
||||
s.updateStats(d, elapsed, *res)
|
||||
@@ -470,6 +558,17 @@ func (s *Server) handleDNSRequest(p *proxy.Proxy, d *proxy.DNSContext) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get IP address from net.Addr
|
||||
func getIP(addr net.Addr) net.IP {
|
||||
switch addr := addr.(type) {
|
||||
case *net.UDPAddr:
|
||||
return addr.IP
|
||||
case *net.TCPAddr:
|
||||
return addr.IP
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) updateStats(d *proxy.DNSContext, elapsed time.Duration, res dnsfilter.Result) {
|
||||
if s.stats == nil {
|
||||
return
|
||||
@@ -517,14 +616,10 @@ func (s *Server) filterDNSRequest(d *proxy.DNSContext) (*dnsfilter.Result, error
|
||||
return &dnsfilter.Result{}, nil
|
||||
}
|
||||
|
||||
clientAddr := ""
|
||||
if d.Addr != nil {
|
||||
clientAddr, _, _ = net.SplitHostPort(d.Addr.String())
|
||||
}
|
||||
|
||||
setts := s.dnsFilter.GetConfig()
|
||||
setts.FilteringEnabled = true
|
||||
if s.conf.FilterHandler != nil {
|
||||
clientAddr := ipFromAddr(d.Addr)
|
||||
s.conf.FilterHandler(clientAddr, &setts)
|
||||
}
|
||||
|
||||
@@ -540,8 +635,7 @@ func (s *Server) filterDNSRequest(d *proxy.DNSContext) (*dnsfilter.Result, error
|
||||
d.Res = s.genDNSFilterMessage(d, &res)
|
||||
|
||||
} else if res.Reason == dnsfilter.ReasonRewrite && len(res.IPList) != 0 {
|
||||
resp := dns.Msg{}
|
||||
resp.SetReply(req)
|
||||
resp := s.makeResponse(req)
|
||||
|
||||
name := host
|
||||
if len(res.CanonName) != 0 {
|
||||
@@ -562,12 +656,69 @@ func (s *Server) filterDNSRequest(d *proxy.DNSContext) (*dnsfilter.Result, error
|
||||
}
|
||||
}
|
||||
|
||||
d.Res = &resp
|
||||
d.Res = resp
|
||||
}
|
||||
|
||||
return &res, err
|
||||
}
|
||||
|
||||
// If response contains CNAME, A or AAAA records, we apply filtering to each canonical host name or IP address.
|
||||
// If this is a match, we set a new response in d.Res and return.
|
||||
func (s *Server) filterResponse(d *proxy.DNSContext) (*dnsfilter.Result, error) {
|
||||
for _, a := range d.Res.Answer {
|
||||
host := ""
|
||||
|
||||
switch v := a.(type) {
|
||||
case *dns.CNAME:
|
||||
log.Debug("DNSFwd: Checking CNAME %s for %s", v.Target, v.Hdr.Name)
|
||||
host = strings.TrimSuffix(v.Target, ".")
|
||||
|
||||
case *dns.A:
|
||||
host = v.A.String()
|
||||
log.Debug("DNSFwd: Checking record A (%s) for %s", host, v.Hdr.Name)
|
||||
|
||||
case *dns.AAAA:
|
||||
host = v.AAAA.String()
|
||||
log.Debug("DNSFwd: Checking record AAAA (%s) for %s", host, v.Hdr.Name)
|
||||
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
s.RLock()
|
||||
// Synchronize access to s.dnsFilter so it won't be suddenly uninitialized while in use.
|
||||
// This could happen after proxy server has been stopped, but its workers are not yet exited.
|
||||
if !s.conf.ProtectionEnabled || s.dnsFilter == nil {
|
||||
s.RUnlock()
|
||||
continue
|
||||
}
|
||||
setts := dnsfilter.RequestFilteringSettings{}
|
||||
setts.FilteringEnabled = true
|
||||
res, err := s.dnsFilter.CheckHost(host, d.Req.Question[0].Qtype, &setts)
|
||||
s.RUnlock()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
} else if res.IsFiltered {
|
||||
d.Res = s.genDNSFilterMessage(d, &res)
|
||||
log.Debug("DNSFwd: Matched %s by response: %s", d.Req.Question[0].Name, host)
|
||||
return &res, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Create a DNS response by DNS request and set necessary flags
|
||||
func (s *Server) makeResponse(req *dns.Msg) *dns.Msg {
|
||||
resp := dns.Msg{}
|
||||
resp.SetReply(req)
|
||||
resp.RecursionAvailable = true
|
||||
resp.Compress = true
|
||||
return &resp
|
||||
}
|
||||
|
||||
// genDNSFilterMessage generates a DNS message corresponding to the filtering result
|
||||
func (s *Server) genDNSFilterMessage(d *proxy.DNSContext, result *dnsfilter.Result) *dns.Msg {
|
||||
m := d.Req
|
||||
@@ -593,6 +744,14 @@ func (s *Server) genDNSFilterMessage(d *proxy.DNSContext, result *dnsfilter.Resu
|
||||
case dns.TypeAAAA:
|
||||
return s.genAAAARecord(m, net.IPv6zero)
|
||||
}
|
||||
|
||||
} else if s.conf.BlockingMode == "custom_ip" {
|
||||
switch m.Question[0].Qtype {
|
||||
case dns.TypeA:
|
||||
return s.genARecord(m, s.conf.BlockingIPAddrv4)
|
||||
case dns.TypeAAAA:
|
||||
return s.genAAAARecord(m, s.conf.BlockingIPAddrv6)
|
||||
}
|
||||
}
|
||||
|
||||
return s.genNXDomain(m)
|
||||
@@ -607,17 +766,15 @@ func (s *Server) genServerFailure(request *dns.Msg) *dns.Msg {
|
||||
}
|
||||
|
||||
func (s *Server) genARecord(request *dns.Msg, ip net.IP) *dns.Msg {
|
||||
resp := dns.Msg{}
|
||||
resp.SetReply(request)
|
||||
resp := s.makeResponse(request)
|
||||
resp.Answer = append(resp.Answer, s.genAAnswer(request, ip))
|
||||
return &resp
|
||||
return resp
|
||||
}
|
||||
|
||||
func (s *Server) genAAAARecord(request *dns.Msg, ip net.IP) *dns.Msg {
|
||||
resp := dns.Msg{}
|
||||
resp.SetReply(request)
|
||||
resp := s.makeResponse(request)
|
||||
resp.Answer = append(resp.Answer, s.genAAAAAnswer(request, ip))
|
||||
return &resp
|
||||
return resp
|
||||
}
|
||||
|
||||
func (s *Server) genAAnswer(req *dns.Msg, ip net.IP) *dns.A {
|
||||
@@ -653,9 +810,8 @@ func (s *Server) genResponseWithIP(req *dns.Msg, ip net.IP) *dns.Msg {
|
||||
}
|
||||
|
||||
// empty response
|
||||
resp := dns.Msg{}
|
||||
resp.SetReply(req)
|
||||
return &resp
|
||||
resp := s.makeResponse(req)
|
||||
return resp
|
||||
}
|
||||
|
||||
func (s *Server) genBlockedHost(request *dns.Msg, newAddr string, d *proxy.DNSContext) *dns.Msg {
|
||||
@@ -683,9 +839,7 @@ func (s *Server) genBlockedHost(request *dns.Msg, newAddr string, d *proxy.DNSCo
|
||||
return s.genServerFailure(request)
|
||||
}
|
||||
|
||||
resp := dns.Msg{}
|
||||
resp.SetReply(request)
|
||||
resp.Authoritative, resp.RecursionAvailable = true, true
|
||||
resp := s.makeResponse(request)
|
||||
if newContext.Res != nil {
|
||||
for _, answer := range newContext.Res.Answer {
|
||||
answer.Header().Name = request.Question[0].Name
|
||||
@@ -693,7 +847,7 @@ func (s *Server) genBlockedHost(request *dns.Msg, newAddr string, d *proxy.DNSCo
|
||||
}
|
||||
}
|
||||
|
||||
return &resp
|
||||
return resp
|
||||
}
|
||||
|
||||
// Make a CNAME response
|
||||
|
||||
381
dnsforward/dnsforward_http.go
Normal file
381
dnsforward/dnsforward_http.go
Normal file
@@ -0,0 +1,381 @@
|
||||
package dnsforward
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/AdguardTeam/golibs/jsonutil"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/utils"
|
||||
"github.com/miekg/dns"
|
||||
)
|
||||
|
||||
func httpError(r *http.Request, w http.ResponseWriter, code int, format string, args ...interface{}) {
|
||||
text := fmt.Sprintf(format, args...)
|
||||
log.Info("DNS: %s %s: %s", r.Method, r.URL, text)
|
||||
http.Error(w, text, code)
|
||||
}
|
||||
|
||||
type dnsConfigJSON struct {
|
||||
ProtectionEnabled bool `json:"protection_enabled"`
|
||||
RateLimit uint32 `json:"ratelimit"`
|
||||
BlockingMode string `json:"blocking_mode"`
|
||||
BlockingIPv4 string `json:"blocking_ipv4"`
|
||||
BlockingIPv6 string `json:"blocking_ipv6"`
|
||||
EDNSCSEnabled bool `json:"edns_cs_enabled"`
|
||||
}
|
||||
|
||||
func (s *Server) handleGetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
resp := dnsConfigJSON{}
|
||||
s.RLock()
|
||||
resp.ProtectionEnabled = s.conf.ProtectionEnabled
|
||||
resp.BlockingMode = s.conf.BlockingMode
|
||||
resp.BlockingIPv4 = s.conf.BlockingIPv4
|
||||
resp.BlockingIPv6 = s.conf.BlockingIPv6
|
||||
resp.RateLimit = s.conf.Ratelimit
|
||||
resp.EDNSCSEnabled = s.conf.EnableEDNSClientSubnet
|
||||
s.RUnlock()
|
||||
|
||||
js, err := json.Marshal(resp)
|
||||
if err != nil {
|
||||
httpError(r, w, http.StatusInternalServerError, "json.Marshal: %s", err)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write(js)
|
||||
}
|
||||
|
||||
func checkBlockingMode(req dnsConfigJSON) bool {
|
||||
bm := req.BlockingMode
|
||||
if !(bm == "nxdomain" || bm == "null_ip" || bm == "custom_ip") {
|
||||
return false
|
||||
}
|
||||
|
||||
if bm == "custom_ip" {
|
||||
ip := net.ParseIP(req.BlockingIPv4)
|
||||
if ip == nil || ip.To4() == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
ip = net.ParseIP(req.BlockingIPv6)
|
||||
if ip == nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *Server) handleSetConfig(w http.ResponseWriter, r *http.Request) {
|
||||
req := dnsConfigJSON{}
|
||||
js, err := jsonutil.DecodeObject(&req, r.Body)
|
||||
if err != nil {
|
||||
httpError(r, w, http.StatusBadRequest, "json.Decode: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
if js.Exists("blocking_mode") && !checkBlockingMode(req) {
|
||||
httpError(r, w, http.StatusBadRequest, "blocking_mode: incorrect value")
|
||||
return
|
||||
}
|
||||
|
||||
restart := false
|
||||
s.Lock()
|
||||
|
||||
if js.Exists("protection_enabled") {
|
||||
s.conf.ProtectionEnabled = req.ProtectionEnabled
|
||||
}
|
||||
|
||||
if js.Exists("blocking_mode") {
|
||||
s.conf.BlockingMode = req.BlockingMode
|
||||
if req.BlockingMode == "custom_ip" {
|
||||
if js.Exists("blocking_ipv4") {
|
||||
s.conf.BlockingIPv4 = req.BlockingIPv4
|
||||
s.conf.BlockingIPAddrv4 = net.ParseIP(req.BlockingIPv4)
|
||||
}
|
||||
if js.Exists("blocking_ipv6") {
|
||||
s.conf.BlockingIPv6 = req.BlockingIPv6
|
||||
s.conf.BlockingIPAddrv6 = net.ParseIP(req.BlockingIPv6)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if js.Exists("ratelimit") {
|
||||
if s.conf.Ratelimit != req.RateLimit {
|
||||
restart = true
|
||||
}
|
||||
s.conf.Ratelimit = req.RateLimit
|
||||
}
|
||||
|
||||
if js.Exists("edns_cs_enabled") {
|
||||
s.conf.EnableEDNSClientSubnet = req.EDNSCSEnabled
|
||||
restart = true
|
||||
}
|
||||
|
||||
s.Unlock()
|
||||
s.conf.ConfigModified()
|
||||
|
||||
if restart {
|
||||
err = s.Restart()
|
||||
if err != nil {
|
||||
httpError(r, w, http.StatusInternalServerError, "%s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type upstreamJSON struct {
|
||||
Upstreams []string `json:"upstream_dns"` // Upstreams
|
||||
BootstrapDNS []string `json:"bootstrap_dns"` // Bootstrap DNS
|
||||
AllServers bool `json:"all_servers"` // --all-servers param for dnsproxy
|
||||
}
|
||||
|
||||
func (s *Server) handleSetUpstreamConfig(w http.ResponseWriter, r *http.Request) {
|
||||
req := upstreamJSON{}
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
httpError(r, w, http.StatusBadRequest, "Failed to parse new upstreams config json: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.Upstreams) != 0 {
|
||||
err = ValidateUpstreams(req.Upstreams)
|
||||
if err != nil {
|
||||
httpError(r, w, http.StatusBadRequest, "wrong upstreams specification: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
newconf := FilteringConfig{}
|
||||
newconf.UpstreamDNS = req.Upstreams
|
||||
|
||||
// bootstrap servers are plain DNS only
|
||||
for _, host := range req.BootstrapDNS {
|
||||
if err := checkPlainDNS(host); err != nil {
|
||||
httpError(r, w, http.StatusBadRequest, "%s can not be used as bootstrap dns cause: %s", host, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
newconf.BootstrapDNS = req.BootstrapDNS
|
||||
|
||||
newconf.AllServers = req.AllServers
|
||||
|
||||
s.Lock()
|
||||
s.conf.UpstreamDNS = newconf.UpstreamDNS
|
||||
s.conf.BootstrapDNS = newconf.BootstrapDNS
|
||||
s.conf.AllServers = newconf.AllServers
|
||||
s.Unlock()
|
||||
s.conf.ConfigModified()
|
||||
|
||||
err = s.Restart()
|
||||
if err != nil {
|
||||
httpError(r, w, http.StatusInternalServerError, "%s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateUpstreams validates each upstream and returns an error if any upstream is invalid or if there are no default upstreams specified
|
||||
func ValidateUpstreams(upstreams []string) error {
|
||||
var defaultUpstreamFound bool
|
||||
for _, u := range upstreams {
|
||||
d, err := validateUpstream(u)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check this flag until default upstream will not be found
|
||||
if !defaultUpstreamFound {
|
||||
defaultUpstreamFound = d
|
||||
}
|
||||
}
|
||||
|
||||
// Return error if there are no default upstreams
|
||||
if !defaultUpstreamFound {
|
||||
return fmt.Errorf("no default upstreams specified")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var protocols = []string{"tls://", "https://", "tcp://", "sdns://"}
|
||||
|
||||
func validateUpstream(u string) (bool, error) {
|
||||
// Check if user tries to specify upstream for domain
|
||||
u, defaultUpstream, err := separateUpstream(u)
|
||||
if err != nil {
|
||||
return defaultUpstream, err
|
||||
}
|
||||
|
||||
// The special server address '#' means "use the default servers"
|
||||
if u == "#" && !defaultUpstream {
|
||||
return defaultUpstream, nil
|
||||
}
|
||||
|
||||
// Check if the upstream has a valid protocol prefix
|
||||
for _, proto := range protocols {
|
||||
if strings.HasPrefix(u, proto) {
|
||||
return defaultUpstream, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Return error if the upstream contains '://' without any valid protocol
|
||||
if strings.Contains(u, "://") {
|
||||
return defaultUpstream, fmt.Errorf("wrong protocol")
|
||||
}
|
||||
|
||||
// Check if upstream is valid plain DNS
|
||||
return defaultUpstream, checkPlainDNS(u)
|
||||
}
|
||||
|
||||
// separateUpstream returns upstream without specified domains and a bool flag that indicates if no domains were specified
|
||||
// error will be returned if upstream per domain specification is invalid
|
||||
func separateUpstream(upstream string) (string, bool, error) {
|
||||
defaultUpstream := true
|
||||
if strings.HasPrefix(upstream, "[/") {
|
||||
defaultUpstream = false
|
||||
// split domains and upstream string
|
||||
domainsAndUpstream := strings.Split(strings.TrimPrefix(upstream, "[/"), "/]")
|
||||
if len(domainsAndUpstream) != 2 {
|
||||
return "", defaultUpstream, fmt.Errorf("wrong DNS upstream per domain specification: %s", upstream)
|
||||
}
|
||||
|
||||
// split domains list and validate each one
|
||||
for _, host := range strings.Split(domainsAndUpstream[0], "/") {
|
||||
if host != "" {
|
||||
if err := utils.IsValidHostname(host); err != nil {
|
||||
return "", defaultUpstream, err
|
||||
}
|
||||
}
|
||||
}
|
||||
upstream = domainsAndUpstream[1]
|
||||
}
|
||||
return upstream, defaultUpstream, nil
|
||||
}
|
||||
|
||||
// checkPlainDNS checks if host is plain DNS
|
||||
func checkPlainDNS(upstream string) error {
|
||||
// Check if host is ip without port
|
||||
if net.ParseIP(upstream) != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if host is ip with port
|
||||
ip, port, err := net.SplitHostPort(upstream)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if net.ParseIP(ip) == nil {
|
||||
return fmt.Errorf("%s is not a valid IP", ip)
|
||||
}
|
||||
|
||||
_, err = strconv.ParseInt(port, 0, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s is not a valid port: %s", port, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) handleTestUpstreamDNS(w http.ResponseWriter, r *http.Request) {
|
||||
req := upstreamJSON{}
|
||||
err := json.NewDecoder(r.Body).Decode(&req)
|
||||
if err != nil {
|
||||
httpError(r, w, http.StatusBadRequest, "Failed to read request body: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(req.Upstreams) == 0 {
|
||||
httpError(r, w, http.StatusBadRequest, "No servers specified")
|
||||
return
|
||||
}
|
||||
|
||||
result := map[string]string{}
|
||||
|
||||
for _, host := range req.Upstreams {
|
||||
err = checkDNS(host, req.BootstrapDNS)
|
||||
if err != nil {
|
||||
log.Info("%v", err)
|
||||
result[host] = err.Error()
|
||||
} else {
|
||||
result[host] = "OK"
|
||||
}
|
||||
}
|
||||
|
||||
jsonVal, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
httpError(r, w, http.StatusInternalServerError, "Unable to marshal status json: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, err = w.Write(jsonVal)
|
||||
if err != nil {
|
||||
httpError(r, w, http.StatusInternalServerError, "Couldn't write body: %s", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func checkDNS(input string, bootstrap []string) error {
|
||||
// separate upstream from domains list
|
||||
input, defaultUpstream, err := separateUpstream(input)
|
||||
if err != nil {
|
||||
return fmt.Errorf("wrong upstream format: %s", err)
|
||||
}
|
||||
|
||||
// No need to check this entrance
|
||||
if input == "#" && !defaultUpstream {
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, err := validateUpstream(input); err != nil {
|
||||
return fmt.Errorf("wrong upstream format: %s", err)
|
||||
}
|
||||
|
||||
if len(bootstrap) == 0 {
|
||||
bootstrap = defaultBootstrap
|
||||
}
|
||||
|
||||
log.Debug("Checking if DNS %s works...", input)
|
||||
u, err := upstream.AddressToUpstream(input, upstream.Options{Bootstrap: bootstrap, Timeout: DefaultTimeout})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to choose upstream for %s: %s", input, err)
|
||||
}
|
||||
|
||||
req := dns.Msg{}
|
||||
req.Id = dns.Id()
|
||||
req.RecursionDesired = true
|
||||
req.Question = []dns.Question{
|
||||
{Name: "google-public-dns-a.google.com.", Qtype: dns.TypeA, Qclass: dns.ClassINET},
|
||||
}
|
||||
reply, err := u.Exchange(&req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't communicate with DNS server %s: %s", input, err)
|
||||
}
|
||||
if len(reply.Answer) != 1 {
|
||||
return fmt.Errorf("DNS server %s returned wrong answer", input)
|
||||
}
|
||||
if t, ok := reply.Answer[0].(*dns.A); ok {
|
||||
if !net.IPv4(8, 8, 8, 8).Equal(t.A) {
|
||||
return fmt.Errorf("DNS server %s returned wrong answer: %v", input, t.A)
|
||||
}
|
||||
}
|
||||
|
||||
log.Debug("DNS %s works OK", input)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) registerHandlers() {
|
||||
s.conf.HTTPRegister("GET", "/control/dns_info", s.handleGetConfig)
|
||||
s.conf.HTTPRegister("POST", "/control/dns_config", s.handleSetConfig)
|
||||
s.conf.HTTPRegister("POST", "/control/set_upstreams_config", s.handleSetUpstreamConfig)
|
||||
s.conf.HTTPRegister("POST", "/control/test_upstream_dns", s.handleTestUpstreamDNS)
|
||||
|
||||
s.conf.HTTPRegister("GET", "/control/access/list", s.handleAccessList)
|
||||
s.conf.HTTPRegister("POST", "/control/access/set", s.handleAccessSet)
|
||||
}
|
||||
@@ -16,7 +16,9 @@ import (
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/dnsfilter"
|
||||
"github.com/AdguardTeam/dnsproxy/proxy"
|
||||
"github.com/AdguardTeam/dnsproxy/upstream"
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -26,7 +28,7 @@ const (
|
||||
|
||||
func TestServer(t *testing.T) {
|
||||
s := createTestServer(t)
|
||||
err := s.Start(nil)
|
||||
err := s.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to start server: %s", err)
|
||||
}
|
||||
@@ -60,7 +62,7 @@ func TestServer(t *testing.T) {
|
||||
func TestServerWithProtectionDisabled(t *testing.T) {
|
||||
s := createTestServer(t)
|
||||
s.conf.ProtectionEnabled = false
|
||||
err := s.Start(nil)
|
||||
err := s.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to start server: %s", err)
|
||||
}
|
||||
@@ -92,8 +94,9 @@ func TestDotServer(t *testing.T) {
|
||||
PrivateKeyData: keyPem,
|
||||
}
|
||||
|
||||
_ = s.Prepare(nil)
|
||||
// Starting the server
|
||||
err := s.Start(nil)
|
||||
err := s.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to start server: %s", err)
|
||||
}
|
||||
@@ -125,7 +128,7 @@ func TestDotServer(t *testing.T) {
|
||||
|
||||
func TestServerRace(t *testing.T) {
|
||||
s := createTestServer(t)
|
||||
err := s.Start(nil)
|
||||
err := s.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to start server: %s", err)
|
||||
}
|
||||
@@ -148,7 +151,7 @@ func TestServerRace(t *testing.T) {
|
||||
|
||||
func TestSafeSearch(t *testing.T) {
|
||||
s := createTestServer(t)
|
||||
err := s.Start(nil)
|
||||
err := s.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to start server: %s", err)
|
||||
}
|
||||
@@ -189,7 +192,7 @@ func TestSafeSearch(t *testing.T) {
|
||||
|
||||
func TestInvalidRequest(t *testing.T) {
|
||||
s := createTestServer(t)
|
||||
err := s.Start(nil)
|
||||
err := s.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to start server: %s", err)
|
||||
}
|
||||
@@ -215,7 +218,7 @@ func TestInvalidRequest(t *testing.T) {
|
||||
|
||||
func TestBlockedRequest(t *testing.T) {
|
||||
s := createTestServer(t)
|
||||
err := s.Start(nil)
|
||||
err := s.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to start server: %s", err)
|
||||
}
|
||||
@@ -245,10 +248,146 @@ func TestBlockedRequest(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// testUpstream is a mock of real upstream.
|
||||
// specify fields with necessary values to simulate real upstream behaviour
|
||||
type testUpstream struct {
|
||||
cn map[string]string // Map of [name]canonical_name
|
||||
ipv4 map[string][]net.IP // Map of [name]IPv4
|
||||
ipv6 map[string][]net.IP // Map of [name]IPv6
|
||||
}
|
||||
|
||||
func (u *testUpstream) Exchange(m *dns.Msg) (*dns.Msg, error) {
|
||||
resp := dns.Msg{}
|
||||
resp.SetReply(m)
|
||||
hasARecord := false
|
||||
hasAAAARecord := false
|
||||
|
||||
reqType := m.Question[0].Qtype
|
||||
name := m.Question[0].Name
|
||||
|
||||
// Let's check if we have any CNAME for given name
|
||||
if cname, ok := u.cn[name]; ok {
|
||||
cn := dns.CNAME{}
|
||||
cn.Hdr.Name = name
|
||||
cn.Hdr.Rrtype = dns.TypeCNAME
|
||||
cn.Target = cname
|
||||
resp.Answer = append(resp.Answer, &cn)
|
||||
}
|
||||
|
||||
// Let's check if we can add some A records to the answer
|
||||
if ipv4addr, ok := u.ipv4[name]; ok && reqType == dns.TypeA {
|
||||
hasARecord = true
|
||||
for _, ipv4 := range ipv4addr {
|
||||
respA := dns.A{}
|
||||
respA.Hdr.Rrtype = dns.TypeA
|
||||
respA.Hdr.Name = name
|
||||
respA.A = ipv4
|
||||
resp.Answer = append(resp.Answer, &respA)
|
||||
}
|
||||
}
|
||||
|
||||
// Let's check if we can add some AAAA records to the answer
|
||||
if u.ipv6 != nil {
|
||||
if ipv6addr, ok := u.ipv6[name]; ok && reqType == dns.TypeAAAA {
|
||||
hasAAAARecord = true
|
||||
for _, ipv6 := range ipv6addr {
|
||||
respAAAA := dns.A{}
|
||||
respAAAA.Hdr.Rrtype = dns.TypeAAAA
|
||||
respAAAA.Hdr.Name = name
|
||||
respAAAA.A = ipv6
|
||||
resp.Answer = append(resp.Answer, &respAAAA)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(resp.Answer) == 0 {
|
||||
if hasARecord || hasAAAARecord {
|
||||
// Set No Error RCode if there are some records for given Qname but we didn't apply them
|
||||
resp.SetRcode(m, dns.RcodeSuccess)
|
||||
} else {
|
||||
// Set NXDomain RCode otherwise
|
||||
resp.SetRcode(m, dns.RcodeNameError)
|
||||
}
|
||||
}
|
||||
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
func (u *testUpstream) Address() string {
|
||||
return "test"
|
||||
}
|
||||
|
||||
func (s *Server) startWithUpstream(u upstream.Upstream) error {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
err := s.Prepare(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.dnsProxy.Upstreams = []upstream.Upstream{u}
|
||||
return s.dnsProxy.Start()
|
||||
}
|
||||
|
||||
// testCNAMEs is a simple map of names and CNAMEs necessary for the testUpstream work
|
||||
var testCNAMEs = map[string]string{
|
||||
"badhost.": "null.example.org.",
|
||||
"whitelist.example.org.": "null.example.org.",
|
||||
}
|
||||
|
||||
// testIPv4 is a simple map of names and IPv4s necessary for the testUpstream work
|
||||
var testIPv4 = map[string][]net.IP{
|
||||
"null.example.org.": {{1, 2, 3, 4}},
|
||||
"example.org.": {{127, 0, 0, 255}},
|
||||
}
|
||||
|
||||
func TestBlockCNAME(t *testing.T) {
|
||||
s := createTestServer(t)
|
||||
testUpstm := &testUpstream{testCNAMEs, testIPv4, nil}
|
||||
err := s.startWithUpstream(testUpstm)
|
||||
assert.True(t, err == nil)
|
||||
addr := s.dnsProxy.Addr(proxy.ProtoUDP)
|
||||
|
||||
// 'badhost' has a canonical name 'null.example.org' which is blocked by filters:
|
||||
// response is blocked
|
||||
req := dns.Msg{}
|
||||
req.Id = dns.Id()
|
||||
req.Question = []dns.Question{
|
||||
{Name: "badhost.", Qtype: dns.TypeA, Qclass: dns.ClassINET},
|
||||
}
|
||||
reply, err := dns.Exchange(&req, addr.String())
|
||||
assert.True(t, err == nil)
|
||||
assert.True(t, reply.Rcode == dns.RcodeNameError)
|
||||
|
||||
// 'whitelist.example.org' has a canonical name 'null.example.org' which is blocked by filters
|
||||
// but 'whitelist.example.org' is in a whitelist:
|
||||
// response isn't blocked
|
||||
req = dns.Msg{}
|
||||
req.Id = dns.Id()
|
||||
req.Question = []dns.Question{
|
||||
{Name: "whitelist.example.org.", Qtype: dns.TypeA, Qclass: dns.ClassINET},
|
||||
}
|
||||
reply, err = dns.Exchange(&req, addr.String())
|
||||
assert.True(t, err == nil)
|
||||
assert.True(t, reply.Rcode == dns.RcodeSuccess)
|
||||
|
||||
// 'example.org' has a canonical name 'cname1' with IP 127.0.0.255 which is blocked by filters:
|
||||
// response is blocked
|
||||
req = dns.Msg{}
|
||||
req.Id = dns.Id()
|
||||
req.Question = []dns.Question{
|
||||
{Name: "example.org.", Qtype: dns.TypeA, Qclass: dns.ClassINET},
|
||||
}
|
||||
reply, err = dns.Exchange(&req, addr.String())
|
||||
assert.True(t, err == nil)
|
||||
assert.True(t, reply.Rcode == dns.RcodeNameError)
|
||||
|
||||
_ = s.Stop()
|
||||
}
|
||||
|
||||
func TestNullBlockedRequest(t *testing.T) {
|
||||
s := createTestServer(t)
|
||||
s.conf.FilteringConfig.BlockingMode = "null_ip"
|
||||
err := s.Start(nil)
|
||||
err := s.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to start server: %s", err)
|
||||
}
|
||||
@@ -285,9 +424,58 @@ func TestNullBlockedRequest(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlockedCustomIP(t *testing.T) {
|
||||
rules := "||nxdomain.example.org^\n||null.example.org^\n127.0.0.1 host.example.org\n@@||whitelist.example.org^\n||127.0.0.255\n"
|
||||
filters := map[int]string{}
|
||||
filters[0] = rules
|
||||
c := dnsfilter.Config{}
|
||||
|
||||
f := dnsfilter.New(&c, filters)
|
||||
s := NewServer(f, nil, nil)
|
||||
conf := ServerConfig{}
|
||||
conf.UDPListenAddr = &net.UDPAddr{Port: 0}
|
||||
conf.TCPListenAddr = &net.TCPAddr{Port: 0}
|
||||
conf.ProtectionEnabled = true
|
||||
conf.BlockingMode = "custom_ip"
|
||||
conf.BlockingIPv4 = "bad IP"
|
||||
conf.UpstreamDNS = []string{"8.8.8.8:53", "8.8.4.4:53"}
|
||||
err := s.Prepare(&conf)
|
||||
assert.True(t, err != nil) // invalid BlockingIPv4
|
||||
|
||||
conf.BlockingIPv4 = "0.0.0.1"
|
||||
conf.BlockingIPv6 = "::1"
|
||||
err = s.Prepare(&conf)
|
||||
assert.True(t, err == nil)
|
||||
err = s.Start()
|
||||
assert.True(t, err == nil, "%s", err)
|
||||
|
||||
addr := s.dnsProxy.Addr(proxy.ProtoUDP)
|
||||
|
||||
req := createTestMessageWithType("null.example.org.", dns.TypeA)
|
||||
reply, err := dns.Exchange(req, addr.String())
|
||||
assert.True(t, err == nil)
|
||||
assert.True(t, len(reply.Answer) == 1)
|
||||
a, ok := reply.Answer[0].(*dns.A)
|
||||
assert.True(t, ok)
|
||||
assert.True(t, a.A.String() == "0.0.0.1")
|
||||
|
||||
req = createTestMessageWithType("null.example.org.", dns.TypeAAAA)
|
||||
reply, err = dns.Exchange(req, addr.String())
|
||||
assert.True(t, err == nil)
|
||||
assert.True(t, len(reply.Answer) == 1)
|
||||
a6, ok := reply.Answer[0].(*dns.AAAA)
|
||||
assert.True(t, ok)
|
||||
assert.True(t, a6.AAAA.String() == "::1")
|
||||
|
||||
err = s.Stop()
|
||||
if err != nil {
|
||||
t.Fatalf("DNS server failed to stop: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBlockedByHosts(t *testing.T) {
|
||||
s := createTestServer(t)
|
||||
err := s.Start(nil)
|
||||
err := s.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to start server: %s", err)
|
||||
}
|
||||
@@ -326,7 +514,7 @@ func TestBlockedByHosts(t *testing.T) {
|
||||
|
||||
func TestBlockedBySafeBrowsing(t *testing.T) {
|
||||
s := createTestServer(t)
|
||||
err := s.Start(nil)
|
||||
err := s.Start()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to start server: %s", err)
|
||||
}
|
||||
@@ -375,7 +563,7 @@ func TestBlockedBySafeBrowsing(t *testing.T) {
|
||||
}
|
||||
|
||||
func createTestServer(t *testing.T) *Server {
|
||||
rules := "||nxdomain.example.org^\n||null.example.org^\n127.0.0.1 host.example.org\n"
|
||||
rules := "||nxdomain.example.org^\n||null.example.org^\n127.0.0.1 host.example.org\n@@||whitelist.example.org^\n||127.0.0.255\n"
|
||||
filters := map[int]string{}
|
||||
filters[0] = rules
|
||||
c := dnsfilter.Config{}
|
||||
@@ -390,9 +578,10 @@ func createTestServer(t *testing.T) *Server {
|
||||
s := NewServer(f, nil, nil)
|
||||
s.conf.UDPListenAddr = &net.UDPAddr{Port: 0}
|
||||
s.conf.TCPListenAddr = &net.TCPAddr{Port: 0}
|
||||
|
||||
s.conf.FilteringConfig.FilteringEnabled = true
|
||||
s.conf.UpstreamDNS = []string{"8.8.8.8:53", "8.8.4.4:53"}
|
||||
s.conf.FilteringConfig.ProtectionEnabled = true
|
||||
err := s.Prepare(nil)
|
||||
assert.True(t, err == nil)
|
||||
return s
|
||||
}
|
||||
|
||||
@@ -512,6 +701,16 @@ func createTestMessage(host string) *dns.Msg {
|
||||
return &req
|
||||
}
|
||||
|
||||
func createTestMessageWithType(host string, qtype uint16) *dns.Msg {
|
||||
req := dns.Msg{}
|
||||
req.Id = dns.Id()
|
||||
req.RecursionDesired = true
|
||||
req.Question = []dns.Question{
|
||||
{Name: host, Qtype: qtype, Qclass: dns.ClassINET},
|
||||
}
|
||||
return &req
|
||||
}
|
||||
|
||||
func assertGoogleAResponse(t *testing.T, reply *dns.Msg) {
|
||||
assertResponse(t, reply, "8.8.8.8")
|
||||
}
|
||||
@@ -541,67 +740,118 @@ func publicKey(priv interface{}) interface{} {
|
||||
}
|
||||
|
||||
func TestIsBlockedIPAllowed(t *testing.T) {
|
||||
s := createTestServer(t)
|
||||
s.conf.AllowedClients = []string{"1.1.1.1", "2.2.0.0/16"}
|
||||
a := &accessCtx{}
|
||||
assert.True(t, a.Init([]string{"1.1.1.1", "2.2.0.0/16"}, nil, nil) == nil)
|
||||
|
||||
err := s.Start(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to start server: %s", err)
|
||||
}
|
||||
|
||||
if s.isBlockedIP("1.1.1.1") {
|
||||
t.Fatalf("isBlockedIP")
|
||||
}
|
||||
if !s.isBlockedIP("1.1.1.2") {
|
||||
t.Fatalf("isBlockedIP")
|
||||
}
|
||||
if s.isBlockedIP("2.2.1.1") {
|
||||
t.Fatalf("isBlockedIP")
|
||||
}
|
||||
if !s.isBlockedIP("2.3.1.1") {
|
||||
t.Fatalf("isBlockedIP")
|
||||
}
|
||||
assert.True(t, !a.IsBlockedIP("1.1.1.1"))
|
||||
assert.True(t, a.IsBlockedIP("1.1.1.2"))
|
||||
assert.True(t, !a.IsBlockedIP("2.2.1.1"))
|
||||
assert.True(t, a.IsBlockedIP("2.3.1.1"))
|
||||
}
|
||||
|
||||
func TestIsBlockedIPDisallowed(t *testing.T) {
|
||||
s := createTestServer(t)
|
||||
s.conf.DisallowedClients = []string{"1.1.1.1", "2.2.0.0/16"}
|
||||
a := &accessCtx{}
|
||||
assert.True(t, a.Init(nil, []string{"1.1.1.1", "2.2.0.0/16"}, nil) == nil)
|
||||
|
||||
err := s.Start(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to start server: %s", err)
|
||||
}
|
||||
|
||||
if !s.isBlockedIP("1.1.1.1") {
|
||||
t.Fatalf("isBlockedIP")
|
||||
}
|
||||
if s.isBlockedIP("1.1.1.2") {
|
||||
t.Fatalf("isBlockedIP")
|
||||
}
|
||||
if !s.isBlockedIP("2.2.1.1") {
|
||||
t.Fatalf("isBlockedIP")
|
||||
}
|
||||
if s.isBlockedIP("2.3.1.1") {
|
||||
t.Fatalf("isBlockedIP")
|
||||
}
|
||||
assert.True(t, a.IsBlockedIP("1.1.1.1"))
|
||||
assert.True(t, !a.IsBlockedIP("1.1.1.2"))
|
||||
assert.True(t, a.IsBlockedIP("2.2.1.1"))
|
||||
assert.True(t, !a.IsBlockedIP("2.3.1.1"))
|
||||
}
|
||||
|
||||
func TestIsBlockedIPBlockedDomain(t *testing.T) {
|
||||
s := createTestServer(t)
|
||||
s.conf.BlockedHosts = []string{"host1", "host2"}
|
||||
a := &accessCtx{}
|
||||
assert.True(t, a.Init(nil, nil, []string{"host1", "host2"}) == nil)
|
||||
|
||||
err := s.Start(nil)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to start server: %s", err)
|
||||
assert.True(t, a.IsBlockedDomain("host1"))
|
||||
assert.True(t, a.IsBlockedDomain("host2"))
|
||||
assert.True(t, !a.IsBlockedDomain("host3"))
|
||||
}
|
||||
|
||||
func TestValidateUpstream(t *testing.T) {
|
||||
invalidUpstreams := []string{"1.2.3.4.5",
|
||||
"123.3.7m",
|
||||
"htttps://google.com/dns-query",
|
||||
"[/host.com]tls://dns.adguard.com",
|
||||
"[host.ru]#",
|
||||
}
|
||||
|
||||
if !s.isBlockedDomain("host1") {
|
||||
t.Fatalf("isBlockedDomain")
|
||||
validDefaultUpstreams := []string{"1.1.1.1",
|
||||
"tls://1.1.1.1",
|
||||
"https://dns.adguard.com/dns-query",
|
||||
"sdns://AQMAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20",
|
||||
}
|
||||
if !s.isBlockedDomain("host2") {
|
||||
t.Fatalf("isBlockedDomain")
|
||||
|
||||
validUpstreams := []string{"[/host.com/]1.1.1.1",
|
||||
"[//]tls://1.1.1.1",
|
||||
"[/www.host.com/]#",
|
||||
"[/host.com/google.com/]8.8.8.8",
|
||||
"[/host/]sdns://AQMAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20",
|
||||
}
|
||||
if s.isBlockedDomain("host3") {
|
||||
t.Fatalf("isBlockedDomain")
|
||||
for _, u := range invalidUpstreams {
|
||||
_, err := validateUpstream(u)
|
||||
if err == nil {
|
||||
t.Fatalf("upstream %s is invalid but it pass through validation", u)
|
||||
}
|
||||
}
|
||||
|
||||
for _, u := range validDefaultUpstreams {
|
||||
defaultUpstream, err := validateUpstream(u)
|
||||
if err != nil {
|
||||
t.Fatalf("upstream %s is valid but it doen't pass through validation cause: %s", u, err)
|
||||
}
|
||||
if !defaultUpstream {
|
||||
t.Fatalf("upstream %s is default one!", u)
|
||||
}
|
||||
}
|
||||
|
||||
for _, u := range validUpstreams {
|
||||
defaultUpstream, err := validateUpstream(u)
|
||||
if err != nil {
|
||||
t.Fatalf("upstream %s is valid but it doen't pass through validation cause: %s", u, err)
|
||||
}
|
||||
if defaultUpstream {
|
||||
t.Fatalf("upstream %s is default one!", u)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateUpstreamsSet(t *testing.T) {
|
||||
// Set of valid upstreams. There is no default upstream specified
|
||||
upstreamsSet := []string{"[/host.com/]1.1.1.1",
|
||||
"[//]tls://1.1.1.1",
|
||||
"[/www.host.com/]#",
|
||||
"[/host.com/google.com/]8.8.8.8",
|
||||
"[/host/]sdns://AQMAAAAAAAAAFDE3Ni4xMDMuMTMwLjEzMDo1NDQzINErR_JS3PLCu_iZEIbq95zkSV2LFsigxDIuUso_OQhzIjIuZG5zY3J5cHQuZGVmYXVsdC5uczEuYWRndWFyZC5jb20",
|
||||
}
|
||||
err := ValidateUpstreams(upstreamsSet)
|
||||
if err == nil {
|
||||
t.Fatalf("there is no default upstream")
|
||||
}
|
||||
|
||||
// Let's add default upstream
|
||||
upstreamsSet = append(upstreamsSet, "8.8.8.8")
|
||||
err = ValidateUpstreams(upstreamsSet)
|
||||
if err != nil {
|
||||
t.Fatalf("upstreams set is valid, but doesn't pass through validation cause: %s", err)
|
||||
}
|
||||
|
||||
// Let's add invalid upstream
|
||||
upstreamsSet = append(upstreamsSet, "dhcp://fake.dns")
|
||||
err = ValidateUpstreams(upstreamsSet)
|
||||
if err == nil {
|
||||
t.Fatalf("there is an invalid upstream in set, but it pass through validation")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIpFromAddr(t *testing.T) {
|
||||
addr := net.UDPAddr{}
|
||||
addr.IP = net.ParseIP("1:2:3::4")
|
||||
addr.Port = 12345
|
||||
addr.Zone = "eth0"
|
||||
a := ipFromAddr(&addr)
|
||||
assert.True(t, a == "1:2:3::4")
|
||||
|
||||
a = ipFromAddr(nil)
|
||||
assert.True(t, a == "")
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 84 KiB |
BIN
doc/agh-filtering.png
Normal file
BIN
doc/agh-filtering.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 67 KiB |
13
go.mod
13
go.mod
@@ -1,15 +1,14 @@
|
||||
module github.com/AdguardTeam/AdGuardHome
|
||||
|
||||
go 1.12
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/AdguardTeam/dnsproxy v0.19.6
|
||||
github.com/AdguardTeam/golibs v0.2.4
|
||||
github.com/AdguardTeam/urlfilter v0.6.1
|
||||
github.com/AdguardTeam/dnsproxy v0.23.3
|
||||
github.com/AdguardTeam/golibs v0.3.0
|
||||
github.com/AdguardTeam/urlfilter v0.7.0
|
||||
github.com/NYTimes/gziphandler v1.1.1
|
||||
github.com/bluele/gcache v0.0.0-20190518031135-bc40bd653833
|
||||
github.com/etcd-io/bbolt v1.3.3
|
||||
github.com/go-test/deep v1.0.4
|
||||
github.com/go-test/deep v1.0.4 // indirect
|
||||
github.com/gobuffalo/packr v1.19.0
|
||||
github.com/joomcode/errorx v1.0.0
|
||||
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 // indirect
|
||||
@@ -18,7 +17,7 @@ require (
|
||||
github.com/miekg/dns v1.1.19
|
||||
github.com/sparrc/go-ping v0.0.0-20181106165434-ef3ab45e41b0
|
||||
github.com/stretchr/testify v1.4.0
|
||||
go.etcd.io/bbolt v1.3.3
|
||||
go.etcd.io/bbolt v1.3.3 // indirect
|
||||
golang.org/x/crypto v0.0.0-20191001170739-f9e2070545dc
|
||||
golang.org/x/net v0.0.0-20191002035440-2ec189313ef0
|
||||
golang.org/x/sys v0.0.0-20191002091554-b397fe3ad8ed
|
||||
|
||||
69
go.sum
69
go.sum
@@ -1,11 +1,12 @@
|
||||
github.com/AdguardTeam/dnsproxy v0.19.6 h1:66hBrCMDIlaMXu8AiB4fHXxnX7Q6MDIuUn7ghxGR8gY=
|
||||
github.com/AdguardTeam/dnsproxy v0.19.6/go.mod h1:61N+71pTD0+nRQWBc0BNh3Ayl3NIW+KZ1sF2+ZObsSA=
|
||||
github.com/AdguardTeam/golibs v0.1.3 h1:hmapdTtMtIk3T8eQDwTOLdqZLGDKNKk9325uC8z12xg=
|
||||
github.com/AdguardTeam/golibs v0.1.3/go.mod h1:b0XkhgIcn2TxwX6C5AQMtpIFAgjPehNgxJErWkwA3ko=
|
||||
github.com/AdguardTeam/dnsproxy v0.23.3 h1:RzI9M0sX99t7qnlikvKTPW25sCFzgfBStxUJ+2z1KQI=
|
||||
github.com/AdguardTeam/dnsproxy v0.23.3/go.mod h1:2qy8rpdfBzKgMPxkHmPdaNK4XZJ322v4KtVGI8s8Bn0=
|
||||
github.com/AdguardTeam/golibs v0.2.4 h1:GUssokegKxKF13K67Pgl0ZGwqHjNN6X7sep5ik6ORdY=
|
||||
github.com/AdguardTeam/golibs v0.2.4/go.mod h1:R3M+mAg3nWG4X4Hsag5eef/TckHFH12ZYhK7AzJc8+U=
|
||||
github.com/AdguardTeam/urlfilter v0.6.1 h1:JX3gNYmgD9TCWE+G0C4MOn8WHYLAoVt0agltSvfldkY=
|
||||
github.com/AdguardTeam/urlfilter v0.6.1/go.mod h1:y+XdxBdbRG9v7pfjznlvv4Ufi2HTG8D0YMqR22OVy0Y=
|
||||
github.com/AdguardTeam/golibs v0.3.0 h1:1zO8ulGEOdXDDM++Ap4sYfTsT/Z4tZBZtiWSA4ykcOU=
|
||||
github.com/AdguardTeam/golibs v0.3.0/go.mod h1:R3M+mAg3nWG4X4Hsag5eef/TckHFH12ZYhK7AzJc8+U=
|
||||
github.com/AdguardTeam/gomitmproxy v0.1.2/go.mod h1:Mrt/3EfiXIYY2aZ7KsLuCUJzUARD/fWJ119IfzOB13M=
|
||||
github.com/AdguardTeam/urlfilter v0.7.0 h1:ffFLt4rA3GX8PJYGL3bGcT5bSxZlML5k6cKpSeN2UI8=
|
||||
github.com/AdguardTeam/urlfilter v0.7.0/go.mod h1:GHXPzEG59ezyff22lXSQ7dicj1kFZBrH5kmZ6EvQzfk=
|
||||
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
|
||||
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
|
||||
github.com/StackExchange/wmi v0.0.0-20181212234831-e0a55b97c705 h1:UUppSQnhf4Yc6xGxSkoQpPhb7RVzuv5Nb1mwJ5VId9s=
|
||||
@@ -16,26 +17,34 @@ github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmH
|
||||
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da/go.mod h1:eHEWzANqSiWQsof+nXEI9bUVUyV6F53Fp89EuCh2EAA=
|
||||
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635 h1:52m0LGchQBBVqJRyYYufQuIbVqRawmubW3OFGqK1ekw=
|
||||
github.com/aead/poly1305 v0.0.0-20180717145839-3fee0db0b635/go.mod h1:lmLxL+FV291OopO93Bwf9fQLQeLyt33VJRUg5VJ30us=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/ameshkov/dnscrypt v1.0.7 h1:7LS9wiC/6c00H3ZdZOlwQSYGTJvs12g5ui9D1VSZ2aQ=
|
||||
github.com/ameshkov/dnscrypt v1.0.7/go.mod h1:rA74ASZ0j4JqPWaiN64hN97QXJ/zu5Kb2xgn295VzWQ=
|
||||
github.com/ameshkov/dnsstamps v1.0.1 h1:LhGvgWDzhNJh+kBQd/AfUlq1vfVe109huiXw4JhnPug=
|
||||
github.com/ameshkov/dnsstamps v1.0.1/go.mod h1:Ii3eUu73dx4Vw5O4wjzmT5+lkCwovjzaEZZ4gKyIH5A=
|
||||
github.com/ameshkov/goproxy v0.0.0-20190328085534-e9f6fabc24d4/go.mod h1:tKA6C/1BQYejT7L6ZX0klDrqloYenYETv3BCk8xCbrQ=
|
||||
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf h1:eg0MeVzsP1G42dRafH3vf+al2vQIJU0YHX+1Tw87oco=
|
||||
github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA=
|
||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||
github.com/beefsack/go-rate v0.0.0-20180408011153-efa7637bb9b6 h1:KXlsf+qt/X5ttPGEjR0tPH1xaWWoKBEg9Q1THAj2h3I=
|
||||
github.com/beefsack/go-rate v0.0.0-20180408011153-efa7637bb9b6/go.mod h1:6YNgTHLutezwnBvyneBbwvB8C82y3dcoOj5EQJIdGXA=
|
||||
github.com/bluele/gcache v0.0.0-20190518031135-bc40bd653833 h1:yCfXxYaelOyqnia8F/Yng47qhmfC9nKTRIbYRrRueq4=
|
||||
github.com/bluele/gcache v0.0.0-20190518031135-bc40bd653833/go.mod h1:8c4/i2VlovMO2gBnHGQPN5EJw+H0lx1u/5p+cgsXtCk=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/etcd-io/bbolt v1.3.3 h1:gSJmxrs37LgTqR/oyJBWok6k6SvXEUerFTbltIhXkBM=
|
||||
github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-ole/go-ole v1.2.4 h1:nNBDSCOigTSiarFpYE9J/KtEA1IOW4CNeqT9TQDqCxI=
|
||||
github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-test/deep v1.0.1 h1:UQhStjbkDClarlmv0am7OXXO4/GaPdCGiUiMTvi28sg=
|
||||
github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||
github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho=
|
||||
@@ -46,9 +55,10 @@ github.com/gobuffalo/packd v0.0.0-20181031195726-c82734870264 h1:roWyi0eEdiFreSq
|
||||
github.com/gobuffalo/packd v0.0.0-20181031195726-c82734870264/go.mod h1:Yf2toFaISlyQrr5TfO3h6DB9pl9mZRmyvBGQb/aQ/pI=
|
||||
github.com/gobuffalo/packr v1.19.0 h1:3UDmBDxesCOPF8iZdMDBBWKfkBoYujIMIZePnobqIUI=
|
||||
github.com/gobuffalo/packr v1.19.0/go.mod h1:MstrNkfCQhd5o+Ct4IJ0skWlxN8emOq8DsoT1G98VIU=
|
||||
github.com/google/pprof v0.0.0-20190723021845-34ac40c74b70/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
|
||||
github.com/inconshreveable/go-vhost v0.0.0-20160627193104-06d84117953b/go.mod h1:aA6DnFhALT3zH0y+A39we+zbrdMC2N0X/q21e6FI0LU=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA=
|
||||
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||
@@ -58,10 +68,14 @@ github.com/joomcode/errorx v0.8.0 h1:GhAqPtcYuo1O7TOIbtzEIDzPGQ3SrKJ3tdjXNmUtDNo
|
||||
github.com/joomcode/errorx v0.8.0/go.mod h1:kgco15ekB6cs+4Xjzo7SPeXzx38PbJzBwbnu9qfVNHQ=
|
||||
github.com/joomcode/errorx v1.0.0 h1:RJAKLTy1Sv2Tszhu14m5RZP4VGRlhXutG/XlL1En5VM=
|
||||
github.com/joomcode/errorx v1.0.0/go.mod h1:kgco15ekB6cs+4Xjzo7SPeXzx38PbJzBwbnu9qfVNHQ=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1 h1:PJPDf8OUfOK1bb/NeTKd4f1QXZItOX389VN3B6qC8ro=
|
||||
github.com/kardianos/osext v0.0.0-20170510131534-ae77be60afb1/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
|
||||
github.com/kardianos/service v0.0.0-20181115005516-4c239ee84e7b h1:vfiqKno48aUndBMjTeWFpCExNnTf2Xnd6d228L4EfTQ=
|
||||
github.com/kardianos/service v0.0.0-20181115005516-4c239ee84e7b/go.mod h1:10UU/bEkzh2iEN6aYzbevY7J6p03KO5siTxQWXMEerg=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
@@ -71,23 +85,38 @@ github.com/krolaw/dhcp4 v0.0.0-20180925202202-7cead472c414 h1:6wnYc2S/lVM7BvR32B
|
||||
github.com/krolaw/dhcp4 v0.0.0-20180925202202-7cead472c414/go.mod h1:0AqAH3ZogsCrvrtUpvc6EtVKbc3w6xwZhkvGLuqyi3o=
|
||||
github.com/markbates/oncer v0.0.0-20181014194634-05fccaae8fc4 h1:Mlji5gkcpzkqTROyE4ZxZ8hN7osunMb2RuGVrbvMvCc=
|
||||
github.com/markbates/oncer v0.0.0-20181014194634-05fccaae8fc4/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/miekg/dns v1.1.8 h1:1QYRAKU3lN5cRfLCkPU08hwvLJFhvjP6MqNMmQz6ZVI=
|
||||
github.com/miekg/dns v1.1.8/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
|
||||
github.com/miekg/dns v1.1.19 h1:0ymbfaLG1/utH2+BydNiF+dx1jSEmdr/nylOtkGHZZg=
|
||||
github.com/miekg/dns v1.1.19/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-charset v0.0.0-20180617210344-2471d30d28b4/go.mod h1:qgYeAmZ5ZIpBWTGllZSQnw97Dj+woV0toclVaRGI8pc=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/shirou/gopsutil v2.18.12+incompatible h1:1eaJvGomDnH74/5cF4CTmTbLHAriGFsTZppLXDX93OM=
|
||||
github.com/shirou/gopsutil v2.18.12+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/shirou/gopsutil v2.19.9+incompatible h1:IrPVlK4nfwW10DF7pW+7YJKws9NkgNzWozwwWv9FsgY=
|
||||
github.com/shirou/gopsutil v2.19.9+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 h1:udFKJ0aHUL60LboW/A+DfgoHVedieIzIXE8uylPue0U=
|
||||
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sparrc/go-ping v0.0.0-20181106165434-ef3ab45e41b0 h1:mu7brOsdaH5Dqf93vdch+mr/0To8Sgc+yInt/jE/RJM=
|
||||
github.com/sparrc/go-ping v0.0.0-20181106165434-ef3ab45e41b0/go.mod h1:eMyUVp6f/5jnzM+3zahzl7q6UXLbgSc3MKg/+ow9QW0=
|
||||
github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
|
||||
@@ -95,6 +124,7 @@ github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3
|
||||
github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
|
||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
@@ -103,7 +133,7 @@ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJy
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk=
|
||||
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
|
||||
golang.org/x/arch v0.0.0-20190815191158-8a70ba74b3a1/go.mod h1:flIaEI6LNU6xOCD5PaJvn9wGP0agmIOqjrtsKGRguv4=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
|
||||
@@ -112,11 +142,11 @@ golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3
|
||||
golang.org/x/crypto v0.0.0-20191001170739-f9e2070545dc h1:KyTYo8xkh/2WdbFLUyQwBS0Jfn3qfZ9QmuPbok2oENE=
|
||||
golang.org/x/crypto v0.0.0-20191001170739-f9e2070545dc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/net v0.0.0-20181102091132-c10e9556a7bc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190119204137-ed066c81e75e h1:MDa3fSUp6MdYHouVmCCNz/zaH2a6CRcxY3VhT/K3C5Q=
|
||||
golang.org/x/net v0.0.0-20190119204137-ed066c81e75e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190313220215-9f648a60d977 h1:actzWV6iWn3GLqN8dZjzsB+CLt+gaV2+wsxroxiQI8I=
|
||||
golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
@@ -126,15 +156,19 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6Zh
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f h1:Bl/8QSvNqXvPGPGXa2z5xUTmV7VDcZyvRZ+QQXkXTZQ=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190122071731-054c452bb702 h1:Lk4tbZFnlyPgV+sLgTw5yGfzrlOn9kx4vSombi2FFlY=
|
||||
golang.org/x/sys v0.0.0-20190122071731-054c452bb702/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191002091554-b397fe3ad8ed h1:5TJcLJn2a55mJjzYk0yOoqN8X1OdvBDUnaZaKKyQtkY=
|
||||
@@ -146,11 +180,12 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3 h1:fvjTMHxHEw/mxHbtzPi3JCcKXQRAnQTBRo6YCJSVHKI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
|
||||
75
home/auth.go
75
home/auth.go
@@ -13,12 +13,12 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"go.etcd.io/bbolt"
|
||||
"github.com/etcd-io/bbolt"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
const cookieTTL = 365 * 24 // in hours
|
||||
const expireTime = 30 * 24 // in hours
|
||||
const sessionCookieName = "agh_session"
|
||||
|
||||
type session struct {
|
||||
userName string
|
||||
@@ -56,10 +56,11 @@ func (s *session) deserialize(data []byte) bool {
|
||||
|
||||
// Auth - global object
|
||||
type Auth struct {
|
||||
db *bbolt.DB
|
||||
sessions map[string]*session // session name -> session data
|
||||
lock sync.Mutex
|
||||
users []User
|
||||
db *bbolt.DB
|
||||
sessions map[string]*session // session name -> session data
|
||||
lock sync.Mutex
|
||||
users []User
|
||||
sessionTTL uint32 // in seconds
|
||||
}
|
||||
|
||||
// User object
|
||||
@@ -69,8 +70,9 @@ type User struct {
|
||||
}
|
||||
|
||||
// InitAuth - create a global object
|
||||
func InitAuth(dbFilename string, users []User) *Auth {
|
||||
func InitAuth(dbFilename string, users []User, sessionTTL uint32) *Auth {
|
||||
a := Auth{}
|
||||
a.sessionTTL = sessionTTL
|
||||
a.sessions = make(map[string]*session)
|
||||
rand.Seed(time.Now().UTC().Unix())
|
||||
var err error
|
||||
@@ -145,18 +147,21 @@ func (a *Auth) loadSessions() {
|
||||
|
||||
// store session data in file
|
||||
func (a *Auth) addSession(data []byte, s *session) {
|
||||
name := hex.EncodeToString(data)
|
||||
a.lock.Lock()
|
||||
a.sessions[hex.EncodeToString(data)] = s
|
||||
a.sessions[name] = s
|
||||
a.lock.Unlock()
|
||||
a.storeSession(data, s)
|
||||
if a.storeSession(data, s) {
|
||||
log.Info("Auth: created session %s: expire=%d", name, s.expire)
|
||||
}
|
||||
}
|
||||
|
||||
// store session data in file
|
||||
func (a *Auth) storeSession(data []byte, s *session) {
|
||||
func (a *Auth) storeSession(data []byte, s *session) bool {
|
||||
tx, err := a.db.Begin(true)
|
||||
if err != nil {
|
||||
log.Error("Auth: bbolt.Begin: %s", err)
|
||||
return
|
||||
return false
|
||||
}
|
||||
defer func() {
|
||||
_ = tx.Rollback()
|
||||
@@ -165,21 +170,20 @@ func (a *Auth) storeSession(data []byte, s *session) {
|
||||
bkt, err := tx.CreateBucketIfNotExists(bucketName())
|
||||
if err != nil {
|
||||
log.Error("Auth: bbolt.CreateBucketIfNotExists: %s", err)
|
||||
return
|
||||
return false
|
||||
}
|
||||
err = bkt.Put(data, s.serialize())
|
||||
if err != nil {
|
||||
log.Error("Auth: bbolt.Put: %s", err)
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
log.Error("Auth: bbolt.Commit: %s", err)
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
log.Debug("Auth: stored session in DB")
|
||||
return true
|
||||
}
|
||||
|
||||
// remove session from file
|
||||
@@ -233,7 +237,7 @@ func (a *Auth) CheckSession(sess string) int {
|
||||
return 1
|
||||
}
|
||||
|
||||
newExpire := now + expireTime*60*60
|
||||
newExpire := now + a.sessionTTL
|
||||
if s.expire/(24*60*60) != newExpire/(24*60*60) {
|
||||
// update expiration time once a day
|
||||
update = true
|
||||
@@ -244,7 +248,9 @@ func (a *Auth) CheckSession(sess string) int {
|
||||
|
||||
if update {
|
||||
key, _ := hex.DecodeString(sess)
|
||||
a.storeSession(key, s)
|
||||
if a.storeSession(key, s) {
|
||||
log.Debug("Auth: updated session %s: expire=%d", sess, s.expire)
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
@@ -270,8 +276,8 @@ func getSession(u *User) []byte {
|
||||
return hash[:]
|
||||
}
|
||||
|
||||
func httpCookie(req loginJSON) string {
|
||||
u := config.auth.UserFind(req.Name, req.Password)
|
||||
func (a *Auth) httpCookie(req loginJSON) string {
|
||||
u := a.UserFind(req.Name, req.Password)
|
||||
if len(u.Name) == 0 {
|
||||
return ""
|
||||
}
|
||||
@@ -286,10 +292,11 @@ func httpCookie(req loginJSON) string {
|
||||
|
||||
s := session{}
|
||||
s.userName = u.Name
|
||||
s.expire = uint32(now.Unix()) + expireTime*60*60
|
||||
config.auth.addSession(sess, &s)
|
||||
s.expire = uint32(now.Unix()) + a.sessionTTL
|
||||
a.addSession(sess, &s)
|
||||
|
||||
return fmt.Sprintf("session=%s; Path=/; HttpOnly; Expires=%s", hex.EncodeToString(sess), expstr)
|
||||
return fmt.Sprintf("%s=%s; Path=/; HttpOnly; Expires=%s",
|
||||
sessionCookieName, hex.EncodeToString(sess), expstr)
|
||||
}
|
||||
|
||||
func handleLogin(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -300,10 +307,11 @@ func handleLogin(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
cookie := httpCookie(req)
|
||||
cookie := config.auth.httpCookie(req)
|
||||
if len(cookie) == 0 {
|
||||
log.Info("Auth: invalid user name or password: name='%s'", req.Name)
|
||||
time.Sleep(1 * time.Second)
|
||||
httpError(w, http.StatusBadRequest, "invalid login or password")
|
||||
http.Error(w, "invalid user name or password", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -324,7 +332,8 @@ func handleLogout(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
w.Header().Set("Location", "/login.html")
|
||||
|
||||
s := fmt.Sprintf("session=; Path=/; HttpOnly; Expires=Thu, 01 Jan 1970 00:00:00 GMT")
|
||||
s := fmt.Sprintf("%s=; Path=/; HttpOnly; Expires=Thu, 01 Jan 1970 00:00:00 GMT",
|
||||
sessionCookieName)
|
||||
w.Header().Set("Set-Cookie", s)
|
||||
|
||||
w.WriteHeader(http.StatusFound)
|
||||
@@ -344,7 +353,7 @@ func parseCookie(cookie string) string {
|
||||
if len(kv) != 2 {
|
||||
continue
|
||||
}
|
||||
if kv[0] == "session" {
|
||||
if kv[0] == sessionCookieName {
|
||||
return kv[1]
|
||||
}
|
||||
}
|
||||
@@ -357,7 +366,7 @@ func optionalAuth(handler func(http.ResponseWriter, *http.Request)) func(http.Re
|
||||
if r.URL.Path == "/login.html" {
|
||||
// redirect to dashboard if already authenticated
|
||||
authRequired := config.auth != nil && config.auth.AuthRequired()
|
||||
cookie, err := r.Cookie("session")
|
||||
cookie, err := r.Cookie(sessionCookieName)
|
||||
if authRequired && err == nil {
|
||||
r := config.auth.CheckSession(cookie.Value)
|
||||
if r == 0 {
|
||||
@@ -365,7 +374,7 @@ func optionalAuth(handler func(http.ResponseWriter, *http.Request)) func(http.Re
|
||||
w.WriteHeader(http.StatusFound)
|
||||
return
|
||||
} else if r < 0 {
|
||||
log.Debug("Auth: invalid cookie value: %s", cookie)
|
||||
log.Info("Auth: invalid cookie value: %s", cookie)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -376,13 +385,13 @@ func optionalAuth(handler func(http.ResponseWriter, *http.Request)) func(http.Re
|
||||
} else if config.auth != nil && config.auth.AuthRequired() {
|
||||
// redirect to login page if not authenticated
|
||||
ok := false
|
||||
cookie, err := r.Cookie("session")
|
||||
cookie, err := r.Cookie(sessionCookieName)
|
||||
if err == nil {
|
||||
r := config.auth.CheckSession(cookie.Value)
|
||||
if r == 0 {
|
||||
ok = true
|
||||
} else if r < 0 {
|
||||
log.Debug("Auth: invalid cookie value: %s", cookie)
|
||||
log.Info("Auth: invalid cookie value: %s", cookie)
|
||||
}
|
||||
} else {
|
||||
// there's no Cookie, check Basic authentication
|
||||
@@ -391,6 +400,8 @@ func optionalAuth(handler func(http.ResponseWriter, *http.Request)) func(http.Re
|
||||
u := config.auth.UserFind(user, pass)
|
||||
if len(u.Name) != 0 {
|
||||
ok = true
|
||||
} else {
|
||||
log.Info("Auth: invalid Basic Authorization value")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -452,7 +463,7 @@ func (a *Auth) UserFind(login string, password string) User {
|
||||
|
||||
// GetCurrentUser - get the current user
|
||||
func (a *Auth) GetCurrentUser(r *http.Request) User {
|
||||
cookie, err := r.Cookie("session")
|
||||
cookie, err := r.Cookie(sessionCookieName)
|
||||
if err != nil {
|
||||
// there's no Cookie, check Basic authentication
|
||||
user, pass, ok := r.BasicAuth()
|
||||
|
||||
@@ -27,7 +27,7 @@ func TestAuth(t *testing.T) {
|
||||
users := []User{
|
||||
User{Name: "name", PasswordHash: "$2y$05$..vyzAECIhJPfaQiOK17IukcQnqEgKJHy0iETyYqxn3YXJl8yZuo2"},
|
||||
}
|
||||
a := InitAuth(fn, nil)
|
||||
a := InitAuth(fn, nil, 60)
|
||||
s := session{}
|
||||
|
||||
user := User{Name: "name"}
|
||||
@@ -54,7 +54,7 @@ func TestAuth(t *testing.T) {
|
||||
a.Close()
|
||||
|
||||
// load saved session
|
||||
a = InitAuth(fn, users)
|
||||
a = InitAuth(fn, users, 60)
|
||||
|
||||
// the session is still alive
|
||||
assert.True(t, a.CheckSession(sessStr) == 0)
|
||||
@@ -69,7 +69,7 @@ func TestAuth(t *testing.T) {
|
||||
time.Sleep(3 * time.Second)
|
||||
|
||||
// load and remove expired sessions
|
||||
a = InitAuth(fn, users)
|
||||
a = InitAuth(fn, users, 60)
|
||||
assert.True(t, a.CheckSession(sessStr) == -1)
|
||||
|
||||
a.Close()
|
||||
@@ -100,7 +100,7 @@ func TestAuthHTTP(t *testing.T) {
|
||||
users := []User{
|
||||
User{Name: "name", PasswordHash: "$2y$05$..vyzAECIhJPfaQiOK17IukcQnqEgKJHy0iETyYqxn3YXJl8yZuo2"},
|
||||
}
|
||||
config.auth = InitAuth(fn, users)
|
||||
config.auth = InitAuth(fn, users, 60)
|
||||
|
||||
handlerCalled := false
|
||||
handler := func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -129,7 +129,7 @@ func TestAuthHTTP(t *testing.T) {
|
||||
assert.True(t, handlerCalled)
|
||||
|
||||
// perform login
|
||||
cookie := httpCookie(loginJSON{Name: "name", Password: "password"})
|
||||
cookie := config.auth.httpCookie(loginJSON{Name: "name", Password: "password"})
|
||||
assert.True(t, cookie != "")
|
||||
|
||||
// get /
|
||||
|
||||
@@ -6,10 +6,10 @@ import (
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/dnsfilter"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/urlfilter"
|
||||
"github.com/AdguardTeam/urlfilter/rules"
|
||||
)
|
||||
|
||||
var serviceRules map[string][]*urlfilter.NetworkRule // service name -> filtering rules
|
||||
var serviceRules map[string][]*rules.NetworkRule // service name -> filtering rules
|
||||
|
||||
type svc struct {
|
||||
name string
|
||||
@@ -20,24 +20,88 @@ type svc struct {
|
||||
// client/src/helpers/constants.js
|
||||
// client/src/components/ui/Icons.js
|
||||
var serviceRulesArray = []svc{
|
||||
{"whatsapp", []string{"||whatsapp.net^"}},
|
||||
{"facebook", []string{"||facebook.com^"}},
|
||||
{"whatsapp", []string{"||whatsapp.net^", "||whatsapp.com^"}},
|
||||
{"facebook", []string{"||facebook.com^", "||facebook.net^", "||fbcdn.net^", "||fb.me^", "||fb.com^", "||fbsbx.com^"}},
|
||||
{"twitter", []string{"||twitter.com^", "||t.co^", "||twimg.com^"}},
|
||||
{"youtube", []string{"||youtube.com^", "||ytimg.com^"}},
|
||||
{"messenger", []string{"||fb.com^", "||facebook.com^"}},
|
||||
{"youtube", []string{"||youtube.com^", "||ytimg.com^", "||youtu.be^", "||googlevideo.com^", "||youtubei.googleapis.com^"}},
|
||||
{"messenger", []string{"||fb.com^", "||facebook.com^", "||messenger.com^"}},
|
||||
{"twitch", []string{"||twitch.tv^", "||ttvnw.net^"}},
|
||||
{"netflix", []string{"||nflxext.com^", "||netflix.com^"}},
|
||||
{"instagram", []string{"||instagram.com^"}},
|
||||
{"snapchat", []string{"||snapchat.com^"}},
|
||||
{"instagram", []string{"||instagram.com^", "||cdninstagram.com^"}},
|
||||
{"snapchat", []string{"||snapchat.com^", "||sc-cdn.net^", "||impala-media-production.s3.amazonaws.com^"}},
|
||||
{"discord", []string{"||discord.gg^", "||discordapp.net^", "||discordapp.com^"}},
|
||||
{"ok", []string{"||ok.ru^"}},
|
||||
{"skype", []string{"||skype.com^"}},
|
||||
{"vk", []string{"||vk.com^"}},
|
||||
{"origin", []string{"||origin.com^", "||signin.ea.com^", "||accounts.ea.com^"}},
|
||||
{"steam", []string{"||steam.com^"}},
|
||||
{"epic_games", []string{"||epicgames.com^"}},
|
||||
{"reddit", []string{"||reddit.com^", "||redditstatic.com^", "||redditmedia.com^", "||redd.it^"}},
|
||||
{"mail_ru", []string{"||mail.ru^"}},
|
||||
{"cloudflare", []string{
|
||||
"||cloudflare.com^",
|
||||
"||cloudflare-dns.com^",
|
||||
"||cloudflare.net^",
|
||||
"||cloudflareinsights.com^",
|
||||
"||cloudflarestream.com^",
|
||||
"||cloudflareresolve.com^",
|
||||
"||cloudflareclient.com^",
|
||||
"||cloudflarebolt.com^",
|
||||
"||cloudflarestatus.com^",
|
||||
"||cloudflare.cn^",
|
||||
"||one.one^",
|
||||
"||warp.plus^",
|
||||
}},
|
||||
{"amazon", []string{
|
||||
"||amazon.com^",
|
||||
"||media-amazon.com^",
|
||||
"||images-amazon.com^",
|
||||
"||a2z.com^",
|
||||
"||amazon.ae^",
|
||||
"||amazon.ca^",
|
||||
"||amazon.cn^",
|
||||
"||amazon.de^",
|
||||
"||amazon.es^",
|
||||
"||amazon.fr^",
|
||||
"||amazon.in^",
|
||||
"||amazon.it^",
|
||||
"||amazon.nl^",
|
||||
"||amazon.com.au^",
|
||||
"||amazon.com.br^",
|
||||
"||amazon.co.jp^",
|
||||
"||amazon.com.mx^",
|
||||
"||amazon.co.uk^",
|
||||
}},
|
||||
{"ebay", []string{
|
||||
"||ebay.com^",
|
||||
"||ebayimg.com^",
|
||||
"||ebaystatic.com^",
|
||||
"||ebaycdn.net^",
|
||||
"||ebayinc.com^",
|
||||
"||ebay.at^",
|
||||
"||ebay.be^",
|
||||
"||ebay.ca^",
|
||||
"||ebay.ch^",
|
||||
"||ebay.cn^",
|
||||
"||ebay.de^",
|
||||
"||ebay.es^",
|
||||
"||ebay.fr^",
|
||||
"||ebay.ie^",
|
||||
"||ebay.in^",
|
||||
"||ebay.it^",
|
||||
"||ebay.ph^",
|
||||
"||ebay.pl^",
|
||||
"||ebay.nl^",
|
||||
"||ebay.com.au^",
|
||||
"||ebay.com.cn^",
|
||||
"||ebay.com.hk^",
|
||||
"||ebay.com.my^",
|
||||
"||ebay.com.sg^",
|
||||
"||ebay.co.uk^",
|
||||
}},
|
||||
{"tiktok", []string{
|
||||
"||tiktok.com^",
|
||||
"||tiktokcdn.com^",
|
||||
"||snssdk.com^",
|
||||
"||amemv.com^",
|
||||
"||toutiao.com^",
|
||||
@@ -50,23 +114,25 @@ var serviceRulesArray = []svc{
|
||||
"||bytecdn.cn^",
|
||||
"||byteimg.com^",
|
||||
"||ixigua.com^",
|
||||
"||muscdn.com^",
|
||||
"||bytedance.map.fastly.net^",
|
||||
}},
|
||||
}
|
||||
|
||||
// convert array to map
|
||||
func initServices() {
|
||||
serviceRules = make(map[string][]*urlfilter.NetworkRule)
|
||||
serviceRules = make(map[string][]*rules.NetworkRule)
|
||||
for _, s := range serviceRulesArray {
|
||||
rules := []*urlfilter.NetworkRule{}
|
||||
netRules := []*rules.NetworkRule{}
|
||||
for _, text := range s.rules {
|
||||
rule, err := urlfilter.NewNetworkRule(text, 0)
|
||||
rule, err := rules.NewNetworkRule(text, 0)
|
||||
if err != nil {
|
||||
log.Error("urlfilter.NewNetworkRule: %s rule: %s", err, text)
|
||||
log.Error("rules.NewNetworkRule: %s rule: %s", err, text)
|
||||
continue
|
||||
}
|
||||
rules = append(rules, rule)
|
||||
netRules = append(netRules, rule)
|
||||
}
|
||||
serviceRules[s.name] = rules
|
||||
serviceRules[s.name] = netRules
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,7 +187,7 @@ func handleBlockedServicesSet(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
returnOK(w)
|
||||
httpOK(r, w)
|
||||
}
|
||||
|
||||
// RegisterBlockedServicesHandlers - register HTTP handlers
|
||||
|
||||
497
home/clients.go
497
home/clients.go
@@ -1,11 +1,10 @@
|
||||
package home
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"runtime"
|
||||
@@ -13,6 +12,8 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/AdGuardHome/dhcpd"
|
||||
"github.com/AdguardTeam/AdGuardHome/dnsforward"
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/AdguardTeam/golibs/utils"
|
||||
)
|
||||
@@ -23,8 +24,7 @@ const (
|
||||
|
||||
// Client information
|
||||
type Client struct {
|
||||
IP string
|
||||
MAC string
|
||||
IDs []string
|
||||
Name string
|
||||
UseOwnSettings bool // false: use global settings
|
||||
FilteringEnabled bool
|
||||
@@ -35,22 +35,8 @@ type Client struct {
|
||||
|
||||
UseOwnBlockedServices bool // false: use global settings
|
||||
BlockedServices []string
|
||||
}
|
||||
|
||||
type clientJSON struct {
|
||||
IP string `json:"ip"`
|
||||
MAC string `json:"mac"`
|
||||
Name string `json:"name"`
|
||||
UseGlobalSettings bool `json:"use_global_settings"`
|
||||
FilteringEnabled bool `json:"filtering_enabled"`
|
||||
ParentalEnabled bool `json:"parental_enabled"`
|
||||
SafeSearchEnabled bool `json:"safebrowsing_enabled"`
|
||||
SafeBrowsingEnabled bool `json:"safesearch_enabled"`
|
||||
|
||||
WhoisInfo map[string]interface{} `json:"whois_info"`
|
||||
|
||||
UseGlobalBlockedServices bool `json:"use_global_blocked_services"`
|
||||
BlockedServices []string `json:"blocked_services"`
|
||||
Upstreams []string // list of upstream servers to be used for the client's requests
|
||||
}
|
||||
|
||||
type clientSource uint
|
||||
@@ -74,22 +60,98 @@ type ClientHost struct {
|
||||
|
||||
type clientsContainer struct {
|
||||
list map[string]*Client // name -> client
|
||||
ipIndex map[string]*Client // IP -> client
|
||||
idIndex map[string]*Client // IP -> client
|
||||
ipHost map[string]*ClientHost // IP -> Hostname
|
||||
lock sync.Mutex
|
||||
|
||||
dhcpServer *dhcpd.Server
|
||||
|
||||
testing bool // if TRUE, this object is used for internal tests
|
||||
}
|
||||
|
||||
// Init initializes clients container
|
||||
// Note: this function must be called only once
|
||||
func (clients *clientsContainer) Init() {
|
||||
func (clients *clientsContainer) Init(objects []clientObject, dhcpServer *dhcpd.Server) {
|
||||
if clients.list != nil {
|
||||
log.Fatal("clients.list != nil")
|
||||
}
|
||||
clients.list = make(map[string]*Client)
|
||||
clients.ipIndex = make(map[string]*Client)
|
||||
clients.idIndex = make(map[string]*Client)
|
||||
clients.ipHost = make(map[string]*ClientHost)
|
||||
clients.dhcpServer = dhcpServer
|
||||
clients.addFromConfig(objects)
|
||||
|
||||
go clients.periodicUpdate()
|
||||
if !clients.testing {
|
||||
go clients.periodicUpdate()
|
||||
|
||||
clients.registerWebHandlers()
|
||||
}
|
||||
}
|
||||
|
||||
type clientObject struct {
|
||||
Name string `yaml:"name"`
|
||||
IDs []string `yaml:"ids"`
|
||||
UseGlobalSettings bool `yaml:"use_global_settings"`
|
||||
FilteringEnabled bool `yaml:"filtering_enabled"`
|
||||
ParentalEnabled bool `yaml:"parental_enabled"`
|
||||
SafeSearchEnabled bool `yaml:"safebrowsing_enabled"`
|
||||
SafeBrowsingEnabled bool `yaml:"safesearch_enabled"`
|
||||
|
||||
UseGlobalBlockedServices bool `yaml:"use_global_blocked_services"`
|
||||
BlockedServices []string `yaml:"blocked_services"`
|
||||
|
||||
Upstreams []string `yaml:"upstreams"`
|
||||
}
|
||||
|
||||
func (clients *clientsContainer) addFromConfig(objects []clientObject) {
|
||||
for _, cy := range objects {
|
||||
cli := Client{
|
||||
Name: cy.Name,
|
||||
IDs: cy.IDs,
|
||||
UseOwnSettings: !cy.UseGlobalSettings,
|
||||
FilteringEnabled: cy.FilteringEnabled,
|
||||
ParentalEnabled: cy.ParentalEnabled,
|
||||
SafeSearchEnabled: cy.SafeSearchEnabled,
|
||||
SafeBrowsingEnabled: cy.SafeBrowsingEnabled,
|
||||
|
||||
UseOwnBlockedServices: !cy.UseGlobalBlockedServices,
|
||||
BlockedServices: cy.BlockedServices,
|
||||
|
||||
Upstreams: cy.Upstreams,
|
||||
}
|
||||
_, err := clients.Add(cli)
|
||||
if err != nil {
|
||||
log.Tracef("clientAdd: %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// WriteDiskConfig - write configuration
|
||||
func (clients *clientsContainer) WriteDiskConfig(objects *[]clientObject) {
|
||||
clients.lock.Lock()
|
||||
for _, cli := range clients.list {
|
||||
cy := clientObject{
|
||||
Name: cli.Name,
|
||||
UseGlobalSettings: !cli.UseOwnSettings,
|
||||
FilteringEnabled: cli.FilteringEnabled,
|
||||
ParentalEnabled: cli.ParentalEnabled,
|
||||
SafeSearchEnabled: cli.SafeSearchEnabled,
|
||||
SafeBrowsingEnabled: cli.SafeBrowsingEnabled,
|
||||
UseGlobalBlockedServices: !cli.UseOwnBlockedServices,
|
||||
}
|
||||
|
||||
cy.IDs = make([]string, len(cli.IDs))
|
||||
copy(cy.IDs, cli.IDs)
|
||||
|
||||
cy.BlockedServices = make([]string, len(cli.BlockedServices))
|
||||
copy(cy.BlockedServices, cli.BlockedServices)
|
||||
|
||||
cy.Upstreams = make([]string, len(cli.Upstreams))
|
||||
copy(cy.Upstreams, cli.Upstreams)
|
||||
|
||||
*objects = append(*objects, cy)
|
||||
}
|
||||
clients.lock.Unlock()
|
||||
}
|
||||
|
||||
func (clients *clientsContainer) periodicUpdate() {
|
||||
@@ -101,17 +163,12 @@ func (clients *clientsContainer) periodicUpdate() {
|
||||
}
|
||||
}
|
||||
|
||||
// GetList returns the pointer to clients list
|
||||
func (clients *clientsContainer) GetList() map[string]*Client {
|
||||
return clients.list
|
||||
}
|
||||
|
||||
// Exists checks if client with this IP already exists
|
||||
func (clients *clientsContainer) Exists(ip string, source clientSource) bool {
|
||||
clients.lock.Lock()
|
||||
defer clients.lock.Unlock()
|
||||
|
||||
_, ok := clients.ipIndex[ip]
|
||||
_, ok := clients.idIndex[ip]
|
||||
if ok {
|
||||
return true
|
||||
}
|
||||
@@ -128,25 +185,45 @@ func (clients *clientsContainer) Exists(ip string, source clientSource) bool {
|
||||
|
||||
// Find searches for a client by IP
|
||||
func (clients *clientsContainer) Find(ip string) (Client, bool) {
|
||||
ipAddr := net.ParseIP(ip)
|
||||
if ipAddr == nil {
|
||||
return Client{}, false
|
||||
}
|
||||
|
||||
clients.lock.Lock()
|
||||
defer clients.lock.Unlock()
|
||||
|
||||
c, ok := clients.ipIndex[ip]
|
||||
c, ok := clients.idIndex[ip]
|
||||
if ok {
|
||||
return *c, true
|
||||
}
|
||||
|
||||
for _, c = range clients.list {
|
||||
if len(c.MAC) != 0 {
|
||||
mac, err := net.ParseMAC(c.MAC)
|
||||
for _, id := range c.IDs {
|
||||
_, ipnet, err := net.ParseCIDR(id)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
ipAddr := config.dhcpServer.FindIPbyMAC(mac)
|
||||
if ipAddr == nil {
|
||||
if ipnet.Contains(ipAddr) {
|
||||
return *c, true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if clients.dhcpServer == nil {
|
||||
return Client{}, false
|
||||
}
|
||||
macFound := clients.dhcpServer.FindMACbyIP(ipAddr)
|
||||
if macFound == nil {
|
||||
return Client{}, false
|
||||
}
|
||||
for _, c = range clients.list {
|
||||
for _, id := range c.IDs {
|
||||
hwAddr, err := net.ParseMAC(id)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if ip == ipAddr.String() {
|
||||
if bytes.Equal(hwAddr, macFound) {
|
||||
return *c, true
|
||||
}
|
||||
}
|
||||
@@ -155,29 +232,60 @@ func (clients *clientsContainer) Find(ip string) (Client, bool) {
|
||||
return Client{}, false
|
||||
}
|
||||
|
||||
// FindAutoClient - search for an auto-client by IP
|
||||
func (clients *clientsContainer) FindAutoClient(ip string) (ClientHost, bool) {
|
||||
ipAddr := net.ParseIP(ip)
|
||||
if ipAddr == nil {
|
||||
return ClientHost{}, false
|
||||
}
|
||||
|
||||
clients.lock.Lock()
|
||||
defer clients.lock.Unlock()
|
||||
|
||||
ch, ok := clients.ipHost[ip]
|
||||
if ok {
|
||||
return *ch, true
|
||||
}
|
||||
return ClientHost{}, false
|
||||
}
|
||||
|
||||
// Check if Client object's fields are correct
|
||||
func (c *Client) check() error {
|
||||
if len(c.Name) == 0 {
|
||||
return fmt.Errorf("Invalid Name")
|
||||
}
|
||||
|
||||
if (len(c.IP) == 0 && len(c.MAC) == 0) ||
|
||||
(len(c.IP) != 0 && len(c.MAC) != 0) {
|
||||
return fmt.Errorf("IP or MAC required")
|
||||
if len(c.IDs) == 0 {
|
||||
return fmt.Errorf("ID required")
|
||||
}
|
||||
|
||||
if len(c.IP) != 0 {
|
||||
ip := net.ParseIP(c.IP)
|
||||
if ip == nil {
|
||||
return fmt.Errorf("Invalid IP")
|
||||
for i, id := range c.IDs {
|
||||
ip := net.ParseIP(id)
|
||||
if ip != nil {
|
||||
c.IDs[i] = ip.String() // normalize IP address
|
||||
continue
|
||||
}
|
||||
c.IP = ip.String()
|
||||
} else {
|
||||
_, err := net.ParseMAC(c.MAC)
|
||||
|
||||
_, _, err := net.ParseCIDR(id)
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
_, err = net.ParseMAC(id)
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
return fmt.Errorf("Invalid ID: %s", id)
|
||||
}
|
||||
|
||||
if len(c.Upstreams) != 0 {
|
||||
err := dnsforward.ValidateUpstreams(c.Upstreams)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Invalid MAC: %s", err)
|
||||
return fmt.Errorf("Invalid upstream servers: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -198,26 +306,34 @@ func (clients *clientsContainer) Add(c Client) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// check IP index
|
||||
if len(c.IP) != 0 {
|
||||
c2, ok := clients.ipIndex[c.IP]
|
||||
// check ID index
|
||||
for _, id := range c.IDs {
|
||||
c2, ok := clients.idIndex[id]
|
||||
if ok {
|
||||
return false, fmt.Errorf("Another client uses the same IP address: %s", c2.Name)
|
||||
return false, fmt.Errorf("Another client uses the same ID (%s): %s", id, c2.Name)
|
||||
}
|
||||
}
|
||||
|
||||
ch, ok := clients.ipHost[c.IP]
|
||||
if ok {
|
||||
c.WhoisInfo = ch.WhoisInfo
|
||||
delete(clients.ipHost, c.IP)
|
||||
// remove auto-clients with the same IP address, keeping WHOIS info if possible
|
||||
for _, id := range c.IDs {
|
||||
ch, ok := clients.ipHost[id]
|
||||
if ok {
|
||||
if len(c.WhoisInfo) == 0 {
|
||||
c.WhoisInfo = ch.WhoisInfo
|
||||
}
|
||||
delete(clients.ipHost, id)
|
||||
}
|
||||
}
|
||||
|
||||
// update Name index
|
||||
clients.list[c.Name] = &c
|
||||
if len(c.IP) != 0 {
|
||||
clients.ipIndex[c.IP] = &c
|
||||
|
||||
// update ID index
|
||||
for _, id := range c.IDs {
|
||||
clients.idIndex[id] = &c
|
||||
}
|
||||
|
||||
log.Tracef("'%s': '%s' | '%s' -> [%d]", c.Name, c.IP, c.MAC, len(clients.list))
|
||||
log.Tracef("'%s': ID:%v [%d]", c.Name, c.IDs, len(clients.list))
|
||||
return true, nil
|
||||
}
|
||||
|
||||
@@ -231,8 +347,26 @@ func (clients *clientsContainer) Del(name string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// update Name index
|
||||
delete(clients.list, name)
|
||||
delete(clients.ipIndex, c.IP)
|
||||
|
||||
// update ID index
|
||||
for _, id := range c.IDs {
|
||||
delete(clients.idIndex, id)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Return TRUE if arrays are equal
|
||||
func arraysEqual(a, b []string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i := 0; i != len(a); i++ {
|
||||
if a[i] != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -260,27 +394,30 @@ func (clients *clientsContainer) Update(name string, c Client) error {
|
||||
}
|
||||
|
||||
// check IP index
|
||||
if old.IP != c.IP && len(c.IP) != 0 {
|
||||
c2, ok := clients.ipIndex[c.IP]
|
||||
if ok {
|
||||
return fmt.Errorf("Another client uses the same IP address: %s", c2.Name)
|
||||
if !arraysEqual(old.IDs, c.IDs) {
|
||||
for _, id := range c.IDs {
|
||||
c2, ok := clients.idIndex[id]
|
||||
if ok && c2 != old {
|
||||
return fmt.Errorf("Another client uses the same ID (%s): %s", id, c2.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// update ID index
|
||||
for _, id := range old.IDs {
|
||||
delete(clients.idIndex, id)
|
||||
}
|
||||
for _, id := range c.IDs {
|
||||
clients.idIndex[id] = old
|
||||
}
|
||||
}
|
||||
|
||||
// update Name index
|
||||
if old.Name != c.Name {
|
||||
delete(clients.list, old.Name)
|
||||
}
|
||||
clients.list[c.Name] = &c
|
||||
|
||||
// update IP index
|
||||
if old.IP != c.IP {
|
||||
delete(clients.ipIndex, old.IP)
|
||||
}
|
||||
if len(c.IP) != 0 {
|
||||
clients.ipIndex[c.IP] = &c
|
||||
clients.list[c.Name] = old
|
||||
}
|
||||
|
||||
*old = c
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -289,7 +426,7 @@ func (clients *clientsContainer) SetWhoisInfo(ip string, info [][]string) {
|
||||
clients.lock.Lock()
|
||||
defer clients.lock.Unlock()
|
||||
|
||||
c, ok := clients.ipIndex[ip]
|
||||
c, ok := clients.idIndex[ip]
|
||||
if ok {
|
||||
c.WhoisInfo = info
|
||||
log.Debug("Clients: set WHOIS info for client %s: %v", c.Name, c.WhoisInfo)
|
||||
@@ -319,7 +456,7 @@ func (clients *clientsContainer) AddHost(ip, host string, source clientSource) (
|
||||
defer clients.lock.Unlock()
|
||||
|
||||
// check index
|
||||
_, ok := clients.ipIndex[ip]
|
||||
_, ok := clients.idIndex[ip]
|
||||
if ok {
|
||||
return false, nil
|
||||
}
|
||||
@@ -427,223 +564,19 @@ func (clients *clientsContainer) addFromSystemARP() {
|
||||
|
||||
// add clients from DHCP that have non-empty Hostname property
|
||||
func (clients *clientsContainer) addFromDHCP() {
|
||||
leases := config.dhcpServer.Leases()
|
||||
if clients.dhcpServer == nil {
|
||||
return
|
||||
}
|
||||
leases := clients.dhcpServer.Leases()
|
||||
n := 0
|
||||
for _, l := range leases {
|
||||
if len(l.Hostname) == 0 {
|
||||
continue
|
||||
}
|
||||
ok, _ := config.clients.AddHost(l.IP.String(), l.Hostname, ClientSourceDHCP)
|
||||
ok, _ := clients.AddHost(l.IP.String(), l.Hostname, ClientSourceDHCP)
|
||||
if ok {
|
||||
n++
|
||||
}
|
||||
}
|
||||
log.Debug("Added %d client aliases from DHCP", n)
|
||||
}
|
||||
|
||||
type clientHostJSON struct {
|
||||
IP string `json:"ip"`
|
||||
Name string `json:"name"`
|
||||
Source string `json:"source"`
|
||||
|
||||
WhoisInfo map[string]interface{} `json:"whois_info"`
|
||||
}
|
||||
|
||||
type clientListJSON struct {
|
||||
Clients []clientJSON `json:"clients"`
|
||||
AutoClients []clientHostJSON `json:"auto_clients"`
|
||||
}
|
||||
|
||||
// respond with information about configured clients
|
||||
func handleGetClients(w http.ResponseWriter, r *http.Request) {
|
||||
data := clientListJSON{}
|
||||
|
||||
config.clients.lock.Lock()
|
||||
for _, c := range config.clients.list {
|
||||
cj := clientJSON{
|
||||
IP: c.IP,
|
||||
MAC: c.MAC,
|
||||
Name: c.Name,
|
||||
UseGlobalSettings: !c.UseOwnSettings,
|
||||
FilteringEnabled: c.FilteringEnabled,
|
||||
ParentalEnabled: c.ParentalEnabled,
|
||||
SafeSearchEnabled: c.SafeSearchEnabled,
|
||||
SafeBrowsingEnabled: c.SafeBrowsingEnabled,
|
||||
|
||||
UseGlobalBlockedServices: !c.UseOwnBlockedServices,
|
||||
BlockedServices: c.BlockedServices,
|
||||
}
|
||||
|
||||
if len(c.MAC) != 0 {
|
||||
hwAddr, _ := net.ParseMAC(c.MAC)
|
||||
ipAddr := config.dhcpServer.FindIPbyMAC(hwAddr)
|
||||
if ipAddr != nil {
|
||||
cj.IP = ipAddr.String()
|
||||
}
|
||||
}
|
||||
|
||||
cj.WhoisInfo = make(map[string]interface{})
|
||||
for _, wi := range c.WhoisInfo {
|
||||
cj.WhoisInfo[wi[0]] = wi[1]
|
||||
}
|
||||
|
||||
data.Clients = append(data.Clients, cj)
|
||||
}
|
||||
for ip, ch := range config.clients.ipHost {
|
||||
cj := clientHostJSON{
|
||||
IP: ip,
|
||||
Name: ch.Host,
|
||||
}
|
||||
|
||||
cj.Source = "etc/hosts"
|
||||
switch ch.Source {
|
||||
case ClientSourceDHCP:
|
||||
cj.Source = "DHCP"
|
||||
case ClientSourceRDNS:
|
||||
cj.Source = "rDNS"
|
||||
case ClientSourceARP:
|
||||
cj.Source = "ARP"
|
||||
case ClientSourceWHOIS:
|
||||
cj.Source = "WHOIS"
|
||||
}
|
||||
|
||||
cj.WhoisInfo = make(map[string]interface{})
|
||||
for _, wi := range ch.WhoisInfo {
|
||||
cj.WhoisInfo[wi[0]] = wi[1]
|
||||
}
|
||||
|
||||
data.AutoClients = append(data.AutoClients, cj)
|
||||
}
|
||||
config.clients.lock.Unlock()
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
e := json.NewEncoder(w).Encode(data)
|
||||
if e != nil {
|
||||
httpError(w, http.StatusInternalServerError, "Failed to encode to json: %v", e)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Convert JSON object to Client object
|
||||
func jsonToClient(cj clientJSON) (*Client, error) {
|
||||
c := Client{
|
||||
IP: cj.IP,
|
||||
MAC: cj.MAC,
|
||||
Name: cj.Name,
|
||||
UseOwnSettings: !cj.UseGlobalSettings,
|
||||
FilteringEnabled: cj.FilteringEnabled,
|
||||
ParentalEnabled: cj.ParentalEnabled,
|
||||
SafeSearchEnabled: cj.SafeSearchEnabled,
|
||||
SafeBrowsingEnabled: cj.SafeBrowsingEnabled,
|
||||
|
||||
UseOwnBlockedServices: !cj.UseGlobalBlockedServices,
|
||||
BlockedServices: cj.BlockedServices,
|
||||
}
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
// Add a new client
|
||||
func handleAddClient(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
httpError(w, http.StatusBadRequest, "failed to read request body: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
cj := clientJSON{}
|
||||
err = json.Unmarshal(body, &cj)
|
||||
if err != nil {
|
||||
httpError(w, http.StatusBadRequest, "JSON parse: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
c, err := jsonToClient(cj)
|
||||
if err != nil {
|
||||
httpError(w, http.StatusBadRequest, "%s", err)
|
||||
return
|
||||
}
|
||||
ok, err := config.clients.Add(*c)
|
||||
if err != nil {
|
||||
httpError(w, http.StatusBadRequest, "%s", err)
|
||||
return
|
||||
}
|
||||
if !ok {
|
||||
httpError(w, http.StatusBadRequest, "Client already exists")
|
||||
return
|
||||
}
|
||||
|
||||
_ = writeAllConfigsAndReloadDNS()
|
||||
returnOK(w)
|
||||
}
|
||||
|
||||
// Remove client
|
||||
func handleDelClient(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
httpError(w, http.StatusBadRequest, "failed to read request body: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
cj := clientJSON{}
|
||||
err = json.Unmarshal(body, &cj)
|
||||
if err != nil || len(cj.Name) == 0 {
|
||||
httpError(w, http.StatusBadRequest, "JSON parse: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !config.clients.Del(cj.Name) {
|
||||
httpError(w, http.StatusBadRequest, "Client not found")
|
||||
return
|
||||
}
|
||||
|
||||
_ = writeAllConfigsAndReloadDNS()
|
||||
returnOK(w)
|
||||
}
|
||||
|
||||
type updateJSON struct {
|
||||
Name string `json:"name"`
|
||||
Data clientJSON `json:"data"`
|
||||
}
|
||||
|
||||
// Update client's properties
|
||||
func handleUpdateClient(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
httpError(w, http.StatusBadRequest, "failed to read request body: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
var dj updateJSON
|
||||
err = json.Unmarshal(body, &dj)
|
||||
if err != nil {
|
||||
httpError(w, http.StatusBadRequest, "JSON parse: %s", err)
|
||||
return
|
||||
}
|
||||
if len(dj.Name) == 0 {
|
||||
httpError(w, http.StatusBadRequest, "Invalid request")
|
||||
return
|
||||
}
|
||||
|
||||
c, err := jsonToClient(dj.Data)
|
||||
if err != nil {
|
||||
httpError(w, http.StatusBadRequest, "%s", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = config.clients.Update(dj.Name, *c)
|
||||
if err != nil {
|
||||
httpError(w, http.StatusBadRequest, "%s", err)
|
||||
return
|
||||
}
|
||||
|
||||
_ = writeAllConfigsAndReloadDNS()
|
||||
returnOK(w)
|
||||
}
|
||||
|
||||
// RegisterClientsHandlers registers HTTP handlers
|
||||
func RegisterClientsHandlers() {
|
||||
httpRegister(http.MethodGet, "/control/clients", handleGetClients)
|
||||
httpRegister(http.MethodPost, "/control/clients/add", handleAddClient)
|
||||
httpRegister(http.MethodPost, "/control/clients/delete", handleDelClient)
|
||||
httpRegister(http.MethodPost, "/control/clients/update", handleUpdateClient)
|
||||
}
|
||||
|
||||
289
home/clients_http.go
Normal file
289
home/clients_http.go
Normal file
@@ -0,0 +1,289 @@
|
||||
package home
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type clientJSON struct {
|
||||
IDs []string `json:"ids"`
|
||||
Name string `json:"name"`
|
||||
UseGlobalSettings bool `json:"use_global_settings"`
|
||||
FilteringEnabled bool `json:"filtering_enabled"`
|
||||
ParentalEnabled bool `json:"parental_enabled"`
|
||||
SafeSearchEnabled bool `json:"safebrowsing_enabled"`
|
||||
SafeBrowsingEnabled bool `json:"safesearch_enabled"`
|
||||
|
||||
WhoisInfo map[string]interface{} `json:"whois_info"`
|
||||
|
||||
UseGlobalBlockedServices bool `json:"use_global_blocked_services"`
|
||||
BlockedServices []string `json:"blocked_services"`
|
||||
|
||||
Upstreams []string `json:"upstreams"`
|
||||
}
|
||||
|
||||
type clientHostJSON struct {
|
||||
IP string `json:"ip"`
|
||||
Name string `json:"name"`
|
||||
Source string `json:"source"`
|
||||
|
||||
WhoisInfo map[string]interface{} `json:"whois_info"`
|
||||
}
|
||||
|
||||
type clientListJSON struct {
|
||||
Clients []clientJSON `json:"clients"`
|
||||
AutoClients []clientHostJSON `json:"auto_clients"`
|
||||
}
|
||||
|
||||
// respond with information about configured clients
|
||||
func (clients *clientsContainer) handleGetClients(w http.ResponseWriter, r *http.Request) {
|
||||
data := clientListJSON{}
|
||||
|
||||
clients.lock.Lock()
|
||||
for _, c := range clients.list {
|
||||
cj := clientToJSON(c)
|
||||
data.Clients = append(data.Clients, cj)
|
||||
}
|
||||
for ip, ch := range clients.ipHost {
|
||||
cj := clientHostJSON{
|
||||
IP: ip,
|
||||
Name: ch.Host,
|
||||
}
|
||||
|
||||
cj.Source = "etc/hosts"
|
||||
switch ch.Source {
|
||||
case ClientSourceDHCP:
|
||||
cj.Source = "DHCP"
|
||||
case ClientSourceRDNS:
|
||||
cj.Source = "rDNS"
|
||||
case ClientSourceARP:
|
||||
cj.Source = "ARP"
|
||||
case ClientSourceWHOIS:
|
||||
cj.Source = "WHOIS"
|
||||
}
|
||||
|
||||
cj.WhoisInfo = make(map[string]interface{})
|
||||
for _, wi := range ch.WhoisInfo {
|
||||
cj.WhoisInfo[wi[0]] = wi[1]
|
||||
}
|
||||
|
||||
data.AutoClients = append(data.AutoClients, cj)
|
||||
}
|
||||
clients.lock.Unlock()
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
e := json.NewEncoder(w).Encode(data)
|
||||
if e != nil {
|
||||
httpError(w, http.StatusInternalServerError, "Failed to encode to json: %v", e)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Convert JSON object to Client object
|
||||
func jsonToClient(cj clientJSON) (*Client, error) {
|
||||
c := Client{
|
||||
Name: cj.Name,
|
||||
IDs: cj.IDs,
|
||||
UseOwnSettings: !cj.UseGlobalSettings,
|
||||
FilteringEnabled: cj.FilteringEnabled,
|
||||
ParentalEnabled: cj.ParentalEnabled,
|
||||
SafeSearchEnabled: cj.SafeSearchEnabled,
|
||||
SafeBrowsingEnabled: cj.SafeBrowsingEnabled,
|
||||
|
||||
UseOwnBlockedServices: !cj.UseGlobalBlockedServices,
|
||||
BlockedServices: cj.BlockedServices,
|
||||
|
||||
Upstreams: cj.Upstreams,
|
||||
}
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
// Convert Client object to JSON
|
||||
func clientToJSON(c *Client) clientJSON {
|
||||
cj := clientJSON{
|
||||
Name: c.Name,
|
||||
IDs: c.IDs,
|
||||
UseGlobalSettings: !c.UseOwnSettings,
|
||||
FilteringEnabled: c.FilteringEnabled,
|
||||
ParentalEnabled: c.ParentalEnabled,
|
||||
SafeSearchEnabled: c.SafeSearchEnabled,
|
||||
SafeBrowsingEnabled: c.SafeBrowsingEnabled,
|
||||
|
||||
UseGlobalBlockedServices: !c.UseOwnBlockedServices,
|
||||
BlockedServices: c.BlockedServices,
|
||||
|
||||
Upstreams: c.Upstreams,
|
||||
}
|
||||
|
||||
cj.WhoisInfo = make(map[string]interface{})
|
||||
for _, wi := range c.WhoisInfo {
|
||||
cj.WhoisInfo[wi[0]] = wi[1]
|
||||
}
|
||||
return cj
|
||||
}
|
||||
|
||||
type clientHostJSONWithID struct {
|
||||
IDs []string `json:"ids"`
|
||||
Name string `json:"name"`
|
||||
WhoisInfo map[string]interface{} `json:"whois_info"`
|
||||
}
|
||||
|
||||
// Convert ClientHost object to JSON
|
||||
func clientHostToJSON(ip string, ch ClientHost) clientHostJSONWithID {
|
||||
cj := clientHostJSONWithID{
|
||||
Name: ch.Host,
|
||||
IDs: []string{ip},
|
||||
}
|
||||
|
||||
cj.WhoisInfo = make(map[string]interface{})
|
||||
for _, wi := range ch.WhoisInfo {
|
||||
cj.WhoisInfo[wi[0]] = wi[1]
|
||||
}
|
||||
return cj
|
||||
}
|
||||
|
||||
// Add a new client
|
||||
func (clients *clientsContainer) handleAddClient(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
httpError(w, http.StatusBadRequest, "failed to read request body: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
cj := clientJSON{}
|
||||
err = json.Unmarshal(body, &cj)
|
||||
if err != nil {
|
||||
httpError(w, http.StatusBadRequest, "JSON parse: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
c, err := jsonToClient(cj)
|
||||
if err != nil {
|
||||
httpError(w, http.StatusBadRequest, "%s", err)
|
||||
return
|
||||
}
|
||||
ok, err := clients.Add(*c)
|
||||
if err != nil {
|
||||
httpError(w, http.StatusBadRequest, "%s", err)
|
||||
return
|
||||
}
|
||||
if !ok {
|
||||
httpError(w, http.StatusBadRequest, "Client already exists")
|
||||
return
|
||||
}
|
||||
|
||||
onConfigModified()
|
||||
}
|
||||
|
||||
// Remove client
|
||||
func (clients *clientsContainer) handleDelClient(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
httpError(w, http.StatusBadRequest, "failed to read request body: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
cj := clientJSON{}
|
||||
err = json.Unmarshal(body, &cj)
|
||||
if err != nil || len(cj.Name) == 0 {
|
||||
httpError(w, http.StatusBadRequest, "JSON parse: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
if !clients.Del(cj.Name) {
|
||||
httpError(w, http.StatusBadRequest, "Client not found")
|
||||
return
|
||||
}
|
||||
|
||||
onConfigModified()
|
||||
}
|
||||
|
||||
type updateJSON struct {
|
||||
Name string `json:"name"`
|
||||
Data clientJSON `json:"data"`
|
||||
}
|
||||
|
||||
// Update client's properties
|
||||
func (clients *clientsContainer) handleUpdateClient(w http.ResponseWriter, r *http.Request) {
|
||||
body, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
httpError(w, http.StatusBadRequest, "failed to read request body: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
var dj updateJSON
|
||||
err = json.Unmarshal(body, &dj)
|
||||
if err != nil {
|
||||
httpError(w, http.StatusBadRequest, "JSON parse: %s", err)
|
||||
return
|
||||
}
|
||||
if len(dj.Name) == 0 {
|
||||
httpError(w, http.StatusBadRequest, "Invalid request")
|
||||
return
|
||||
}
|
||||
|
||||
c, err := jsonToClient(dj.Data)
|
||||
if err != nil {
|
||||
httpError(w, http.StatusBadRequest, "%s", err)
|
||||
return
|
||||
}
|
||||
|
||||
err = clients.Update(dj.Name, *c)
|
||||
if err != nil {
|
||||
httpError(w, http.StatusBadRequest, "%s", err)
|
||||
return
|
||||
}
|
||||
|
||||
onConfigModified()
|
||||
}
|
||||
|
||||
// Get the list of clients by IP address list
|
||||
func (clients *clientsContainer) handleFindClient(w http.ResponseWriter, r *http.Request) {
|
||||
q := r.URL.Query()
|
||||
data := []map[string]interface{}{}
|
||||
for i := 0; ; i++ {
|
||||
ip := q.Get(fmt.Sprintf("ip%d", i))
|
||||
if len(ip) == 0 {
|
||||
break
|
||||
}
|
||||
el := map[string]interface{}{}
|
||||
c, ok := clients.Find(ip)
|
||||
if !ok {
|
||||
ch, ok := clients.FindAutoClient(ip)
|
||||
if !ok {
|
||||
continue // a client with this IP isn't found
|
||||
}
|
||||
cj := clientHostToJSON(ip, ch)
|
||||
el[ip] = cj
|
||||
|
||||
} else {
|
||||
cj := clientToJSON(&c)
|
||||
el[ip] = cj
|
||||
}
|
||||
|
||||
data = append(data, el)
|
||||
}
|
||||
|
||||
js, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
httpError(w, http.StatusInternalServerError, "json.Marshal: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_, err = w.Write(js)
|
||||
if err != nil {
|
||||
httpError(w, http.StatusInternalServerError, "Couldn't write response: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterClientsHandlers registers HTTP handlers
|
||||
func (clients *clientsContainer) registerWebHandlers() {
|
||||
httpRegister("GET", "/control/clients", clients.handleGetClients)
|
||||
httpRegister("POST", "/control/clients/add", clients.handleAddClient)
|
||||
httpRegister("POST", "/control/clients/delete", clients.handleDelClient)
|
||||
httpRegister("POST", "/control/clients/update", clients.handleUpdateClient)
|
||||
httpRegister("GET", "/control/clients/find", clients.handleFindClient)
|
||||
}
|
||||
@@ -11,12 +11,13 @@ func TestClients(t *testing.T) {
|
||||
var e error
|
||||
var b bool
|
||||
clients := clientsContainer{}
|
||||
clients.testing = true
|
||||
|
||||
clients.Init()
|
||||
clients.Init(nil, nil)
|
||||
|
||||
// add
|
||||
c = Client{
|
||||
IP: "1.1.1.1",
|
||||
IDs: []string{"1.1.1.1", "1:2:3::4", "aa:aa:aa:aa:aa:aa"},
|
||||
Name: "client1",
|
||||
}
|
||||
b, e = clients.Add(c)
|
||||
@@ -26,7 +27,7 @@ func TestClients(t *testing.T) {
|
||||
|
||||
// add #2
|
||||
c = Client{
|
||||
IP: "2.2.2.2",
|
||||
IDs: []string{"2.2.2.2"},
|
||||
Name: "client2",
|
||||
}
|
||||
b, e = clients.Add(c)
|
||||
@@ -35,18 +36,17 @@ func TestClients(t *testing.T) {
|
||||
}
|
||||
|
||||
c, b = clients.Find("1.1.1.1")
|
||||
if !b || c.Name != "client1" {
|
||||
t.Fatalf("Find #1")
|
||||
}
|
||||
assert.True(t, b && c.Name == "client1")
|
||||
|
||||
c, b = clients.Find("1:2:3::4")
|
||||
assert.True(t, b && c.Name == "client1")
|
||||
|
||||
c, b = clients.Find("2.2.2.2")
|
||||
if !b || c.Name != "client2" {
|
||||
t.Fatalf("Find #2")
|
||||
}
|
||||
assert.True(t, b && c.Name == "client2")
|
||||
|
||||
// failed add - name in use
|
||||
c = Client{
|
||||
IP: "1.2.3.5",
|
||||
IDs: []string{"1.2.3.5"},
|
||||
Name: "client1",
|
||||
}
|
||||
b, _ = clients.Add(c)
|
||||
@@ -56,7 +56,7 @@ func TestClients(t *testing.T) {
|
||||
|
||||
// failed add - ip in use
|
||||
c = Client{
|
||||
IP: "2.2.2.2",
|
||||
IDs: []string{"2.2.2.2"},
|
||||
Name: "client3",
|
||||
}
|
||||
b, e = clients.Add(c)
|
||||
@@ -70,35 +70,45 @@ func TestClients(t *testing.T) {
|
||||
assert.True(t, clients.Exists("2.2.2.2", ClientSourceHostsFile))
|
||||
|
||||
// failed update - no such name
|
||||
c.IP = "1.2.3.0"
|
||||
c.IDs = []string{"1.2.3.0"}
|
||||
c.Name = "client3"
|
||||
if clients.Update("client3", c) == nil {
|
||||
t.Fatalf("Update")
|
||||
}
|
||||
|
||||
// failed update - name in use
|
||||
c.IP = "1.2.3.0"
|
||||
c.IDs = []string{"1.2.3.0"}
|
||||
c.Name = "client2"
|
||||
if clients.Update("client1", c) == nil {
|
||||
t.Fatalf("Update - name in use")
|
||||
}
|
||||
|
||||
// failed update - ip in use
|
||||
c.IP = "2.2.2.2"
|
||||
c.IDs = []string{"2.2.2.2"}
|
||||
c.Name = "client1"
|
||||
if clients.Update("client1", c) == nil {
|
||||
t.Fatalf("Update - ip in use")
|
||||
}
|
||||
|
||||
// update
|
||||
c.IP = "1.1.1.2"
|
||||
c.IDs = []string{"1.1.1.2"}
|
||||
c.Name = "client1"
|
||||
if clients.Update("client1", c) != nil {
|
||||
t.Fatalf("Update")
|
||||
}
|
||||
|
||||
// get after update
|
||||
assert.True(t, !(clients.Exists("1.1.1.1", ClientSourceHostsFile) || !clients.Exists("1.1.1.2", ClientSourceHostsFile)))
|
||||
assert.True(t, !clients.Exists("1.1.1.1", ClientSourceHostsFile))
|
||||
assert.True(t, clients.Exists("1.1.1.2", ClientSourceHostsFile))
|
||||
|
||||
// update - rename
|
||||
c.IDs = []string{"1.1.1.2"}
|
||||
c.Name = "client1-renamed"
|
||||
c.UseOwnSettings = true
|
||||
assert.True(t, clients.Update("client1", c) == nil)
|
||||
c = Client{}
|
||||
c, b = clients.Find("1.1.1.2")
|
||||
assert.True(t, b && c.Name == "client1-renamed" && c.IDs[0] == "1.1.1.2" && c.UseOwnSettings)
|
||||
|
||||
// failed remove - no such name
|
||||
if clients.Del("client3") {
|
||||
@@ -106,7 +116,7 @@ func TestClients(t *testing.T) {
|
||||
}
|
||||
|
||||
// remove
|
||||
assert.True(t, !(!clients.Del("client1") || clients.Exists("1.1.1.2", ClientSourceHostsFile)))
|
||||
assert.True(t, !(!clients.Del("client1-renamed") || clients.Exists("1.1.1.2", ClientSourceHostsFile)))
|
||||
|
||||
// add host client
|
||||
b, e = clients.AddHost("1.1.1.1", "host", ClientSourceARP)
|
||||
@@ -139,7 +149,8 @@ func TestClients(t *testing.T) {
|
||||
func TestClientsWhois(t *testing.T) {
|
||||
var c Client
|
||||
clients := clientsContainer{}
|
||||
clients.Init()
|
||||
clients.testing = true
|
||||
clients.Init(nil, nil)
|
||||
|
||||
whois := [][]string{{"orgname", "orgname-val"}, {"country", "country-val"}}
|
||||
// set whois info on new client
|
||||
@@ -153,11 +164,11 @@ func TestClientsWhois(t *testing.T) {
|
||||
|
||||
// set whois info on existing client
|
||||
c = Client{
|
||||
IP: "1.1.1.2",
|
||||
IDs: []string{"1.1.1.2"},
|
||||
Name: "client1",
|
||||
}
|
||||
_, _ = clients.Add(c)
|
||||
clients.SetWhoisInfo("1.1.1.2", whois)
|
||||
assert.True(t, clients.ipIndex["1.1.1.2"].WhoisInfo[0][1] == "orgname-val")
|
||||
assert.True(t, clients.idIndex["1.1.1.2"].WhoisInfo[0][1] == "orgname-val")
|
||||
_ = clients.Del("client1")
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user