Compare commits
83 Commits
v0.103.0-b
...
1228-proxy
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f85de51452 | ||
|
|
c3123473cf | ||
|
|
8d0c8ad438 | ||
|
|
1e2e965ea7 | ||
|
|
0b539ced92 | ||
|
|
9e09dffbc3 | ||
|
|
473d881871 | ||
|
|
a1ca7862f8 | ||
|
|
8ea1e64c7b | ||
|
|
2f8e34e73b | ||
|
|
705a9d909d | ||
|
|
97df19898f | ||
|
|
de92c85256 | ||
|
|
d3f5b40700 | ||
|
|
20a0ba5f60 | ||
|
|
9b9902f004 | ||
|
|
020a30fb6d | ||
|
|
39f2d5c4ae | ||
|
|
6ce3c52456 | ||
|
|
5188da60cf | ||
|
|
e57cbc36d9 | ||
|
|
57e43a66c3 | ||
|
|
4f3d503916 | ||
|
|
6bb6c700d6 | ||
|
|
ed76a3cb8b | ||
|
|
335d62b08e | ||
|
|
99625da1e4 | ||
|
|
9fecab8675 | ||
|
|
b9aa969a56 | ||
|
|
ce21514246 | ||
|
|
3ff0c964dc | ||
|
|
552280e9a3 | ||
|
|
d154456ae5 | ||
|
|
3cecd6f090 | ||
|
|
dc1fc82b9e | ||
|
|
a033b68bfd | ||
|
|
4ca4fb8a11 | ||
|
|
8d10a269ed | ||
|
|
a536357427 | ||
|
|
cecf848364 | ||
|
|
ddb9a2e872 | ||
|
|
cfdfd250a0 | ||
|
|
cb01d05ef4 | ||
|
|
c7e7b76cec | ||
|
|
f5128d27f1 | ||
|
|
005f8fb279 | ||
|
|
244fe093cd | ||
|
|
ff9d1c234c | ||
|
|
1c9d3acaa8 | ||
|
|
0dab36a108 | ||
|
|
611ed94884 | ||
|
|
22935c5fed | ||
|
|
4a8dcbeeed | ||
|
|
1ab650bb86 | ||
|
|
4743743b1f | ||
|
|
dd2c9d96e7 | ||
|
|
946bda37a3 | ||
|
|
ad4e85d8f5 | ||
|
|
4b9ab97271 | ||
|
|
d2bf1e176e | ||
|
|
ffeb88ac0c | ||
|
|
c71b8d3ad2 | ||
|
|
01957bf503 | ||
|
|
1e5419714d | ||
|
|
4f4a688ee6 | ||
|
|
ccf5494f67 | ||
|
|
f2edcca54b | ||
|
|
b4aa791513 | ||
|
|
6d5d183311 | ||
|
|
e3ea2528be | ||
|
|
117ec4dd43 | ||
|
|
0cc0aec5b3 | ||
|
|
3c53a2162c | ||
|
|
1bb183c2aa | ||
|
|
62ccd3fb41 | ||
|
|
a409cdc2bb | ||
|
|
e0aa24e2b7 | ||
|
|
0662769696 | ||
|
|
dc237f10a8 | ||
|
|
4fef0c32cb | ||
|
|
c131ac445a | ||
|
|
87789679f5 | ||
|
|
793194db67 |
2
.github/workflows/lint.yml
vendored
@@ -44,4 +44,4 @@ jobs:
|
|||||||
fields: repo,message,commit,author
|
fields: repo,message,commit,author
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||||
|
|||||||
157
AGHTechDoc.md
@@ -22,7 +22,8 @@ Contents:
|
|||||||
* Update client
|
* Update client
|
||||||
* Delete client
|
* Delete client
|
||||||
* API: Find clients by IP
|
* API: Find clients by IP
|
||||||
* Enable DHCP server
|
* DHCP server
|
||||||
|
* DHCP server in DNS
|
||||||
* "Show DHCP status" command
|
* "Show DHCP status" command
|
||||||
* "Check DHCP" command
|
* "Check DHCP" command
|
||||||
* "Enable DHCP" command
|
* "Enable DHCP" command
|
||||||
@@ -51,19 +52,26 @@ Contents:
|
|||||||
* API: Get query log
|
* API: Get query log
|
||||||
* API: Set querylog parameters
|
* API: Set querylog parameters
|
||||||
* API: Get querylog parameters
|
* API: Get querylog parameters
|
||||||
* Filtering
|
* DNS Filtering
|
||||||
* Filters update mechanism
|
* Filters update mechanism
|
||||||
* API: Get filtering parameters
|
* API: Get filtering parameters
|
||||||
* API: Set filtering parameters
|
* API: Set filtering parameters
|
||||||
* API: Refresh filters
|
* API: Refresh filters
|
||||||
* API: Add Filter
|
* API: Add Filter
|
||||||
* API: Set URL parameters
|
* API: Set Filter parameters
|
||||||
* API: Delete URL
|
* API: Delete Filter
|
||||||
* API: Domain Check
|
* API: Domain Check
|
||||||
|
* HTTP Proxy
|
||||||
|
* API: Get Proxy settings
|
||||||
|
* API: Set Proxy settings
|
||||||
|
* API: Get Proxy filtering parameters
|
||||||
|
* API: Add Proxy Filter
|
||||||
|
* API: Delete Proxy Filter
|
||||||
* Log-in page
|
* Log-in page
|
||||||
* API: Log in
|
* API: Log in
|
||||||
* API: Log out
|
* API: Log out
|
||||||
* API: Get current user info
|
* API: Get current user info
|
||||||
|
* Safe services
|
||||||
|
|
||||||
|
|
||||||
## Relations between subsystems
|
## Relations between subsystems
|
||||||
@@ -374,9 +382,9 @@ Error response:
|
|||||||
UI shows error message "Auto-update has failed"
|
UI shows error message "Auto-update has failed"
|
||||||
|
|
||||||
|
|
||||||
## Enable DHCP server
|
## DHCP server
|
||||||
|
|
||||||
Algorithm:
|
Enable DHCP server algorithm:
|
||||||
|
|
||||||
* UI shows DHCP configuration screen with "Enabled DHCP" button disabled, and "Check DHCP" button enabled
|
* UI shows DHCP configuration screen with "Enabled DHCP" button disabled, and "Check DHCP" button enabled
|
||||||
* User clicks on "Check DHCP"; UI sends request to server
|
* User clicks on "Check DHCP"; UI sends request to server
|
||||||
@@ -388,6 +396,21 @@ Algorithm:
|
|||||||
* UI shows the status
|
* UI shows the status
|
||||||
|
|
||||||
|
|
||||||
|
### DHCP server in DNS
|
||||||
|
|
||||||
|
DHCP leases are used in several ways by DNS module.
|
||||||
|
|
||||||
|
* For "A" DNS reqeust we reply with an IP address leased by our DHCP server.
|
||||||
|
|
||||||
|
< A bills-notebook.lan.
|
||||||
|
> A bills-notebook.lan. = 192.168.1.100
|
||||||
|
|
||||||
|
* For "PTR" DNS request we reply with a hostname from an active DHCP lease.
|
||||||
|
|
||||||
|
< PTR 100.1.168.192.in-addr.arpa.
|
||||||
|
> PTR 100.1.168.192.in-addr.arpa. = bills-notebook.
|
||||||
|
|
||||||
|
|
||||||
### "Show DHCP status" command
|
### "Show DHCP status" command
|
||||||
|
|
||||||
Request:
|
Request:
|
||||||
@@ -1460,7 +1483,7 @@ Response:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
## Filtering
|
## DNS Filtering
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
@@ -1531,7 +1554,19 @@ Response:
|
|||||||
}
|
}
|
||||||
...
|
...
|
||||||
],
|
],
|
||||||
"user_rules":["...", ...]
|
"user_rules":["...", ...],
|
||||||
|
|
||||||
|
"proxy_filtering_enabled": true | false
|
||||||
|
"proxy_filters":[
|
||||||
|
{
|
||||||
|
"enabled":true,
|
||||||
|
"url":"https://...",
|
||||||
|
"name":"...",
|
||||||
|
"rules_count":1234,
|
||||||
|
"last_updated":"2019-09-04T18:29:30+00:00",
|
||||||
|
}
|
||||||
|
...
|
||||||
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
For both arrays `filters` and `whitelist_filters` there are unique values: id, url.
|
For both arrays `filters` and `whitelist_filters` there are unique values: id, url.
|
||||||
@@ -1546,6 +1581,7 @@ Request:
|
|||||||
|
|
||||||
{
|
{
|
||||||
"enabled": true | false
|
"enabled": true | false
|
||||||
|
"proxy_filtering_enabled": true | false
|
||||||
"interval": 0 | 1 | 12 | 1*24 || 3*24 || 7*24
|
"interval": 0 | 1 | 12 | 1*24 || 3*24 || 7*24
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1561,7 +1597,7 @@ Request:
|
|||||||
POST /control/filtering/refresh
|
POST /control/filtering/refresh
|
||||||
|
|
||||||
{
|
{
|
||||||
"whitelist": true
|
"type": blocklist | whitelist | proxylist
|
||||||
}
|
}
|
||||||
|
|
||||||
Response:
|
Response:
|
||||||
@@ -1582,7 +1618,7 @@ Request:
|
|||||||
{
|
{
|
||||||
"name": "..."
|
"name": "..."
|
||||||
"url": "..." // URL or an absolute file path
|
"url": "..." // URL or an absolute file path
|
||||||
"whitelist": true
|
"type": blocklist | whitelist | proxylist
|
||||||
}
|
}
|
||||||
|
|
||||||
Response:
|
Response:
|
||||||
@@ -1590,7 +1626,7 @@ Response:
|
|||||||
200 OK
|
200 OK
|
||||||
|
|
||||||
|
|
||||||
### API: Set URL parameters
|
### API: Set Filter parameters
|
||||||
|
|
||||||
Request:
|
Request:
|
||||||
|
|
||||||
@@ -1598,11 +1634,11 @@ Request:
|
|||||||
|
|
||||||
{
|
{
|
||||||
"url": "..."
|
"url": "..."
|
||||||
"whitelist": true
|
"type": blocklist | whitelist | proxylist
|
||||||
"data": {
|
"data": {
|
||||||
"name": "..."
|
"name": "..."
|
||||||
"url": "..."
|
"url": "..."
|
||||||
"enabled": true | false
|
"enabled": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1611,7 +1647,7 @@ Response:
|
|||||||
200 OK
|
200 OK
|
||||||
|
|
||||||
|
|
||||||
### API: Delete URL
|
### API: Delete Filter
|
||||||
|
|
||||||
Request:
|
Request:
|
||||||
|
|
||||||
@@ -1619,7 +1655,7 @@ Request:
|
|||||||
|
|
||||||
{
|
{
|
||||||
"url": "..."
|
"url": "..."
|
||||||
"whitelist": true
|
"type": blocklist | whitelist | proxylist
|
||||||
}
|
}
|
||||||
|
|
||||||
Response:
|
Response:
|
||||||
@@ -1651,6 +1687,60 @@ Response:
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
## HTTP Proxy
|
||||||
|
|
||||||
|
Browser <-(HTTP)-> AGH Proxy <-(HTTP)-> Internet Server
|
||||||
|
|
||||||
|
HTTPS MITM:
|
||||||
|
|
||||||
|
. Browser --(CONNECT...)-> AGH Proxy --(handshake)-> Internet Server
|
||||||
|
. Browser <-(handshake,cert/AGH)-- AGH Proxy <-(cert/issuer)-- Internet Server
|
||||||
|
. Browser <-(TLS/session2)-> AGH Proxy <-(TLS/session1)-> Internet Server
|
||||||
|
|
||||||
|
|
||||||
|
### API: Get Proxy settings
|
||||||
|
|
||||||
|
Request:
|
||||||
|
|
||||||
|
GET /control/proxy_info
|
||||||
|
|
||||||
|
Response:
|
||||||
|
|
||||||
|
200 OK
|
||||||
|
|
||||||
|
{
|
||||||
|
"enabled": true|false,
|
||||||
|
"listen_address": "ip",
|
||||||
|
"listen_port": 12345,
|
||||||
|
|
||||||
|
"auth_username": "",
|
||||||
|
"auth_password": ""
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
### API: Set Proxy settings
|
||||||
|
|
||||||
|
Request:
|
||||||
|
|
||||||
|
POST /control/proxy_config
|
||||||
|
|
||||||
|
{
|
||||||
|
"enabled": true|false,
|
||||||
|
"listen_address": "ip",
|
||||||
|
"listen_port": 12345,
|
||||||
|
|
||||||
|
"auth_username": "",
|
||||||
|
"auth_password": "",
|
||||||
|
|
||||||
|
"cert_data":"...", // user-specified certificate. "": generate new
|
||||||
|
"pkey_data":"...",
|
||||||
|
}
|
||||||
|
|
||||||
|
Response:
|
||||||
|
|
||||||
|
200 OK
|
||||||
|
|
||||||
|
|
||||||
## Log-in page
|
## Log-in page
|
||||||
|
|
||||||
After user completes the steps of installation wizard, he must log in into dashboard using his name and password. After user successfully logs in, he gets the Cookie which allows the server to authenticate him next time without password. After the Cookie is expired, user needs to perform log-in operation again.
|
After user completes the steps of installation wizard, he must log in into dashboard using his name and password. After user successfully logs in, he gets the Cookie which allows the server to authenticate him next time without password. After the Cookie is expired, user needs to perform log-in operation again.
|
||||||
@@ -1747,3 +1837,40 @@ Response:
|
|||||||
}
|
}
|
||||||
|
|
||||||
If no client is configured then authentication is disabled and server sends an empty response.
|
If no client is configured then authentication is disabled and server sends an empty response.
|
||||||
|
|
||||||
|
|
||||||
|
### Safe services
|
||||||
|
|
||||||
|
Check if host name is blocked by SB/PC service:
|
||||||
|
|
||||||
|
* For each host name component, search for the result in cache by the first 2 bytes of SHA-256 hashes of host name components (max. is 4, i.e. sub2.sub1.host.com), excluding TLD:
|
||||||
|
|
||||||
|
hashes[] = cache_search(sha256(host.com)[0..1])
|
||||||
|
...
|
||||||
|
|
||||||
|
If hash prefix is found, search for a full hash sum in the cached data.
|
||||||
|
If found, the host is blocked.
|
||||||
|
If not found, the host is not blocked - don't request data for this prefix from the Family server again.
|
||||||
|
If hash prefix is not found, request data for this prefix from the Family server.
|
||||||
|
|
||||||
|
* Prepare query string which is generated from the first 2 bytes (converted to a 4-character string) of SHA-256 hashes of host name components (max. is 4, i.e. sub2.sub1.host.com), excluding TLD:
|
||||||
|
|
||||||
|
qs = ... + string(sha256(sub.host.com)[0..1]) + "." + string(sha256(host.com)[0..1]) + ".sb.dns.adguard.com."
|
||||||
|
|
||||||
|
For PC `.pc.dns.adguard.com` suffix is used.
|
||||||
|
|
||||||
|
* Send TXT query to Family server, receive response which contains the array of complete hash sums of the blocked hosts
|
||||||
|
|
||||||
|
* Check if one of received hash sums (`hashes[]`) matches hash sums for our host name
|
||||||
|
|
||||||
|
hashes[0] <> sha256(host.com)
|
||||||
|
hashes[0] <> sha256(sub.host.com)
|
||||||
|
hashes[1] <> sha256(host.com)
|
||||||
|
hashes[1] <> sha256(sub.host.com)
|
||||||
|
...
|
||||||
|
|
||||||
|
* Store all received hash sums in cache:
|
||||||
|
|
||||||
|
sha256(host.com)[0..1] -> hashes[0],hashes[1],...
|
||||||
|
sha256(sub.host.com)[0..1] -> hashes[2],...
|
||||||
|
...
|
||||||
|
|||||||
120
Makefile
@@ -14,6 +14,13 @@
|
|||||||
# Building releases:
|
# Building releases:
|
||||||
#
|
#
|
||||||
# * release -- builds AdGuard Home distros. CHANNEL must be specified (edge, release or beta).
|
# * release -- builds AdGuard Home distros. CHANNEL must be specified (edge, release or beta).
|
||||||
|
# * release_and_sign -- builds AdGuard Home distros and signs the binary files.
|
||||||
|
# CHANNEL must be specified (edge, release or beta).
|
||||||
|
# * sign -- Repacks all release archive files and signs the binary files inside them.
|
||||||
|
# For signing to work, the public+private key pair for $(GPG_KEY) must be imported:
|
||||||
|
# gpg --import public.txt
|
||||||
|
# gpg --import private.txt
|
||||||
|
# GPG_KEY_PASSPHRASE must contain the GPG key passphrase
|
||||||
# * docker-multi-arch -- builds a multi-arch image. If you want it to be pushed to docker hub,
|
# * docker-multi-arch -- builds a multi-arch image. If you want it to be pushed to docker hub,
|
||||||
# you must specify:
|
# you must specify:
|
||||||
# * DOCKER_IMAGE_NAME - adguard/adguard-home
|
# * DOCKER_IMAGE_NAME - adguard/adguard-home
|
||||||
@@ -23,6 +30,9 @@ GOPATH := $(shell go env GOPATH)
|
|||||||
PWD := $(shell pwd)
|
PWD := $(shell pwd)
|
||||||
TARGET=AdGuardHome
|
TARGET=AdGuardHome
|
||||||
BASE_URL="https://static.adguard.com/adguardhome/$(CHANNEL)"
|
BASE_URL="https://static.adguard.com/adguardhome/$(CHANNEL)"
|
||||||
|
GPG_KEY := devteam@adguard.com
|
||||||
|
GPG_KEY_PASSPHRASE :=
|
||||||
|
GPG_CMD := gpg --detach-sig --default-key $(GPG_KEY) --pinentry-mode loopback --passphrase $(GPG_KEY_PASSPHRASE)
|
||||||
|
|
||||||
# See release target
|
# See release target
|
||||||
DIST_DIR=dist
|
DIST_DIR=dist
|
||||||
@@ -39,6 +49,12 @@ endif
|
|||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
# Version history URL (see
|
||||||
|
VERSION_HISTORY_URL="https://github.com/AdguardTeam/AdGuardHome/releases"
|
||||||
|
ifeq ($(CHANNEL),edge)
|
||||||
|
VERSION_HISTORY_URL="https://github.com/AdguardTeam/AdGuardHome/commits/master"
|
||||||
|
endif
|
||||||
|
|
||||||
# goreleaser command depends on the $CHANNEL
|
# goreleaser command depends on the $CHANNEL
|
||||||
GORELEASER_COMMAND=goreleaser release --rm-dist --skip-publish --snapshot
|
GORELEASER_COMMAND=goreleaser release --rm-dist --skip-publish --snapshot
|
||||||
ifneq ($(CHANNEL),edge)
|
ifneq ($(CHANNEL),edge)
|
||||||
@@ -55,7 +71,11 @@ SNAPSHOT_VERSION=$(RELEASE_VERSION)-SNAPSHOT-$(COMMIT)
|
|||||||
# Set proper version
|
# Set proper version
|
||||||
VERSION=
|
VERSION=
|
||||||
ifeq ($(TAG_NAME),$(shell git describe --abbrev=4))
|
ifeq ($(TAG_NAME),$(shell git describe --abbrev=4))
|
||||||
VERSION=$(RELEASE_VERSION)
|
ifeq ($(CHANNEL),edge)
|
||||||
|
VERSION=$(SNAPSHOT_VERSION)
|
||||||
|
else
|
||||||
|
VERSION=$(RELEASE_VERSION)
|
||||||
|
endif
|
||||||
else
|
else
|
||||||
VERSION=$(SNAPSHOT_VERSION)
|
VERSION=$(SNAPSHOT_VERSION)
|
||||||
endif
|
endif
|
||||||
@@ -94,7 +114,8 @@ all: build
|
|||||||
init:
|
init:
|
||||||
git config core.hooksPath .githooks
|
git config core.hooksPath .githooks
|
||||||
|
|
||||||
build: dependencies client
|
build: client_with_deps
|
||||||
|
go mod download
|
||||||
PATH=$(GOPATH)/bin:$(PATH) go generate ./...
|
PATH=$(GOPATH)/bin:$(PATH) go generate ./...
|
||||||
CGO_ENABLED=0 go build -ldflags="-s -w -X main.version=$(VERSION) -X main.channel=$(CHANNEL) -X main.goarm=$(GOARM)"
|
CGO_ENABLED=0 go build -ldflags="-s -w -X main.version=$(VERSION) -X main.channel=$(CHANNEL) -X main.goarm=$(GOARM)"
|
||||||
PATH=$(GOPATH)/bin:$(PATH) packr clean
|
PATH=$(GOPATH)/bin:$(PATH) packr clean
|
||||||
@@ -102,6 +123,10 @@ build: dependencies client
|
|||||||
client:
|
client:
|
||||||
npm --prefix client run build-prod
|
npm --prefix client run build-prod
|
||||||
|
|
||||||
|
client_with_deps:
|
||||||
|
npm --prefix client ci
|
||||||
|
npm --prefix client run build-prod
|
||||||
|
|
||||||
client-watch:
|
client-watch:
|
||||||
npm --prefix client run watch
|
npm --prefix client run watch
|
||||||
|
|
||||||
@@ -130,10 +155,14 @@ lint-go:
|
|||||||
golangci-lint run
|
golangci-lint run
|
||||||
|
|
||||||
test:
|
test:
|
||||||
@echo Running unit-tests
|
@echo Running JS unit-tests
|
||||||
|
npm run test --prefix client
|
||||||
|
@echo Running Go unit-tests
|
||||||
go test -race -v -bench=. -coverprofile=coverage.txt -covermode=atomic ./...
|
go test -race -v -bench=. -coverprofile=coverage.txt -covermode=atomic ./...
|
||||||
|
|
||||||
ci: dependencies client test
|
ci: client_with_deps
|
||||||
|
go mod download
|
||||||
|
$(MAKE) test
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
npm --prefix client ci
|
npm --prefix client ci
|
||||||
@@ -170,12 +199,20 @@ docker-multi-arch:
|
|||||||
@echo If the image was pushed to the registry, you can now run it:
|
@echo If the image was pushed to the registry, you can now run it:
|
||||||
@echo docker run --name "adguard-home" -p 53:53/tcp -p 53:53/udp -p 80:80/tcp -p 443:443/tcp -p 853:853/tcp -p 3000:3000/tcp $(DOCKER_IMAGE_NAME)
|
@echo docker run --name "adguard-home" -p 53:53/tcp -p 53:53/udp -p 80:80/tcp -p 443:443/tcp -p 853:853/tcp -p 3000:3000/tcp $(DOCKER_IMAGE_NAME)
|
||||||
|
|
||||||
release: dependencies client
|
release: client_with_deps
|
||||||
|
go mod download
|
||||||
@echo Starting release build: version $(VERSION), channel $(CHANNEL)
|
@echo Starting release build: version $(VERSION), channel $(CHANNEL)
|
||||||
CHANNEL=$(CHANNEL) $(GORELEASER_COMMAND)
|
CHANNEL=$(CHANNEL) $(GORELEASER_COMMAND)
|
||||||
$(call write_version_file,$(VERSION))
|
$(call write_version_file,$(VERSION))
|
||||||
PATH=$(GOPATH)/bin:$(PATH) packr clean
|
PATH=$(GOPATH)/bin:$(PATH) packr clean
|
||||||
|
|
||||||
|
release_and_sign: client_with_deps
|
||||||
|
$(MAKE) release
|
||||||
|
$(call repack_dist)
|
||||||
|
|
||||||
|
sign:
|
||||||
|
$(call repack_dist)
|
||||||
|
|
||||||
define write_version_file
|
define write_version_file
|
||||||
$(eval version := $(1))
|
$(eval version := $(1))
|
||||||
|
|
||||||
@@ -190,8 +227,8 @@ define write_version_file
|
|||||||
echo "{" >> $(DIST_DIR)/version.json
|
echo "{" >> $(DIST_DIR)/version.json
|
||||||
echo " \"version\": \"$(version)\"," >> $(DIST_DIR)/version.json
|
echo " \"version\": \"$(version)\"," >> $(DIST_DIR)/version.json
|
||||||
echo " \"announcement\": \"AdGuard Home $(version) is now available!\"," >> $(DIST_DIR)/version.json
|
echo " \"announcement\": \"AdGuard Home $(version) is now available!\"," >> $(DIST_DIR)/version.json
|
||||||
echo " \"announcement_url\": \"https://github.com/AdguardTeam/AdGuardHome/releases\"," >> $(DIST_DIR)/version.json
|
echo " \"announcement_url\": \"$(VERSION_HISTORY_URL)\"," >> $(DIST_DIR)/version.json
|
||||||
echo " \"selfupdate_min_version\": \"v0.0\"," >> $(DIST_DIR)/version.json
|
echo " \"selfupdate_min_version\": \"0.0\"," >> $(DIST_DIR)/version.json
|
||||||
|
|
||||||
# Windows builds
|
# Windows builds
|
||||||
echo " \"download_windows_amd64\": \"$(BASE_URL)/AdGuardHome_windows_amd64.zip\"," >> $(DIST_DIR)/version.json
|
echo " \"download_windows_amd64\": \"$(BASE_URL)/AdGuardHome_windows_amd64.zip\"," >> $(DIST_DIR)/version.json
|
||||||
@@ -232,3 +269,72 @@ define write_version_file
|
|||||||
# Finish
|
# Finish
|
||||||
echo "}" >> $(DIST_DIR)/version.json
|
echo "}" >> $(DIST_DIR)/version.json
|
||||||
endef
|
endef
|
||||||
|
|
||||||
|
define repack_dist
|
||||||
|
# Repack archive files
|
||||||
|
# A temporary solution for our auto-update code to be able to unpack these archive files
|
||||||
|
# The problem is that goreleaser doesn't add directory AdGuardHome/ to the archive file
|
||||||
|
# and we can't create it
|
||||||
|
rm -rf $(DIST_DIR)/AdGuardHome
|
||||||
|
|
||||||
|
# Windows builds
|
||||||
|
$(call zip_repack_windows,AdGuardHome_windows_amd64.zip)
|
||||||
|
$(call zip_repack_windows,AdGuardHome_windows_386.zip)
|
||||||
|
|
||||||
|
# MacOS builds
|
||||||
|
$(call zip_repack,AdGuardHome_darwin_amd64.zip)
|
||||||
|
$(call zip_repack,AdGuardHome_darwin_386.zip)
|
||||||
|
|
||||||
|
# Linux
|
||||||
|
$(call tar_repack,AdGuardHome_linux_amd64.tar.gz)
|
||||||
|
$(call tar_repack,AdGuardHome_linux_386.tar.gz)
|
||||||
|
|
||||||
|
# Linux, all kinds of ARM
|
||||||
|
$(call tar_repack,AdGuardHome_linux_armv5.tar.gz)
|
||||||
|
$(call tar_repack,AdGuardHome_linux_armv6.tar.gz)
|
||||||
|
$(call tar_repack,AdGuardHome_linux_armv7.tar.gz)
|
||||||
|
$(call tar_repack,AdGuardHome_linux_arm64.tar.gz)
|
||||||
|
|
||||||
|
# Linux, MIPS
|
||||||
|
$(call tar_repack,AdGuardHome_linux_mips_softfloat.tar.gz)
|
||||||
|
$(call tar_repack,AdGuardHome_linux_mipsle_softfloat.tar.gz)
|
||||||
|
$(call tar_repack,AdGuardHome_linux_mips64_softfloat.tar.gz)
|
||||||
|
$(call tar_repack,AdGuardHome_linux_mips64le_softfloat.tar.gz)
|
||||||
|
|
||||||
|
# FreeBSD
|
||||||
|
$(call tar_repack,AdGuardHome_freebsd_386.tar.gz)
|
||||||
|
$(call tar_repack,AdGuardHome_freebsd_amd64.tar.gz)
|
||||||
|
|
||||||
|
# FreeBSD, all kinds of ARM
|
||||||
|
$(call tar_repack,AdGuardHome_freebsd_armv5.tar.gz)
|
||||||
|
$(call tar_repack,AdGuardHome_freebsd_armv6.tar.gz)
|
||||||
|
$(call tar_repack,AdGuardHome_freebsd_armv7.tar.gz)
|
||||||
|
$(call tar_repack,AdGuardHome_freebsd_arm64.tar.gz)
|
||||||
|
endef
|
||||||
|
|
||||||
|
define zip_repack_windows
|
||||||
|
$(eval ARC := $(1))
|
||||||
|
cd $(DIST_DIR) && \
|
||||||
|
unzip $(ARC) && \
|
||||||
|
$(GPG_CMD) AdGuardHome/AdGuardHome.exe && \
|
||||||
|
zip -r $(ARC) AdGuardHome/ && \
|
||||||
|
rm -rf AdGuardHome
|
||||||
|
endef
|
||||||
|
|
||||||
|
define zip_repack
|
||||||
|
$(eval ARC := $(1))
|
||||||
|
cd $(DIST_DIR) && \
|
||||||
|
unzip $(ARC) && \
|
||||||
|
$(GPG_CMD) AdGuardHome/AdGuardHome && \
|
||||||
|
zip -r $(ARC) AdGuardHome/ && \
|
||||||
|
rm -rf AdGuardHome
|
||||||
|
endef
|
||||||
|
|
||||||
|
define tar_repack
|
||||||
|
$(eval ARC := $(1))
|
||||||
|
cd $(DIST_DIR) && \
|
||||||
|
tar xzf $(ARC) && \
|
||||||
|
$(GPG_CMD) AdGuardHome/AdGuardHome && \
|
||||||
|
tar czf $(ARC) AdGuardHome/ && \
|
||||||
|
rm -rf AdGuardHome
|
||||||
|
endef
|
||||||
|
|||||||
@@ -155,7 +155,10 @@ Run `make init` to prepare the development environment.
|
|||||||
You will need this to build AdGuard Home:
|
You will need this to build AdGuard Home:
|
||||||
|
|
||||||
* [go](https://golang.org/dl/) v1.14 or later.
|
* [go](https://golang.org/dl/) v1.14 or later.
|
||||||
* [node.js](https://nodejs.org/en/download/) v10 or later.
|
* [node.js](https://nodejs.org/en/download/) v10.16.2 or later.
|
||||||
|
* [npm](https://www.npmjs.com/) v6.14 or later.
|
||||||
|
|
||||||
|
Optionally, for Go devs:
|
||||||
* [golangci-lint](https://github.com/golangci/golangci-lint)
|
* [golangci-lint](https://github.com/golangci/golangci-lint)
|
||||||
|
|
||||||
### Building
|
### Building
|
||||||
@@ -214,6 +217,8 @@ You may need to prepare before using these builds:
|
|||||||
|
|
||||||
You are welcome to fork this repository, make your changes and submit a pull request — https://github.com/AdguardTeam/AdGuardHome/pulls
|
You are welcome to fork this repository, make your changes and submit a pull request — https://github.com/AdguardTeam/AdGuardHome/pulls
|
||||||
|
|
||||||
|
Please note, that we don't expect people to contribute to both UI and golang parts of the program simultaneously. Ideally, the golang part is implemented first, i.e. configuration, API, and the functionality itself. The UI part can be implemented later in a different pull request by a different person.
|
||||||
|
|
||||||
<a id="test-unstable-versions"></a>
|
<a id="test-unstable-versions"></a>
|
||||||
### Test unstable versions
|
### Test unstable versions
|
||||||
|
|
||||||
@@ -230,7 +235,7 @@ There are three options how you can install an unstable version:
|
|||||||
|
|
||||||
There are three options how you can install an unstable version.
|
There are three options how you can install an unstable version.
|
||||||
|
|
||||||
1. You can either install a beta version of AdGuard Home which we update periodically.
|
1. You can either install a beta or edge version of AdGuard Home which we update periodically. If you're already using stable version of AdGuard Home, just replace the executable with a new one.
|
||||||
2. You can use the Docker image from the `edge` tag, which is synced with the repo master branch.
|
2. You can use the Docker image from the `edge` tag, which is synced with the repo master branch.
|
||||||
3. You can install AdGuard Home from `beta` or `edge` channels on the Snap Store.
|
3. You can install AdGuard Home from `beta` or `edge` channels on the Snap Store.
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
"disableEmoji": true,
|
"disableEmoji": true,
|
||||||
"list": [
|
"list": [
|
||||||
"+",
|
"+ ",
|
||||||
"*",
|
"* ",
|
||||||
"-",
|
"- ",
|
||||||
],
|
],
|
||||||
"maxMessageLength": 64,
|
"maxMessageLength": 64,
|
||||||
"minMessageLength": 3,
|
"minMessageLength": 3,
|
||||||
@@ -12,7 +12,7 @@ module.exports = {
|
|||||||
"scope",
|
"scope",
|
||||||
"subject",
|
"subject",
|
||||||
"body",
|
"body",
|
||||||
"issues"
|
"issues",
|
||||||
],
|
],
|
||||||
"scopes": [
|
"scopes": [
|
||||||
"",
|
"",
|
||||||
@@ -26,20 +26,20 @@ module.exports = {
|
|||||||
"documentation",
|
"documentation",
|
||||||
],
|
],
|
||||||
"types": {
|
"types": {
|
||||||
"+": {
|
"+ ": {
|
||||||
"description": "A new feature",
|
"description": "A new feature",
|
||||||
"emoji": "",
|
"emoji": "",
|
||||||
"value": "+"
|
"value": "+ "
|
||||||
},
|
},
|
||||||
"*": {
|
"* ": {
|
||||||
"description": "A code change that neither fixes a bug or adds a feature",
|
"description": "A code change that neither fixes a bug or adds a feature",
|
||||||
"emoji": "",
|
"emoji": "",
|
||||||
"value": "*"
|
"value": "* "
|
||||||
},
|
},
|
||||||
"-": {
|
"- ": {
|
||||||
"description": "A bug fix",
|
"description": "A bug fix",
|
||||||
"emoji": "",
|
"emoji": "",
|
||||||
"value": "-"
|
"value": "- "
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
1
client/babel.config.js
vendored
@@ -11,6 +11,7 @@ module.exports = (api) => {
|
|||||||
'@babel/plugin-proposal-object-rest-spread',
|
'@babel/plugin-proposal-object-rest-spread',
|
||||||
'@babel/plugin-proposal-nullish-coalescing-operator',
|
'@babel/plugin-proposal-nullish-coalescing-operator',
|
||||||
'@babel/plugin-proposal-optional-chaining',
|
'@babel/plugin-proposal-optional-chaining',
|
||||||
|
'react-hot-loader/babel',
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
11
client/constants.js
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
const BUILD_ENVS = {
|
||||||
|
dev: 'development',
|
||||||
|
prod: 'production',
|
||||||
|
};
|
||||||
|
|
||||||
|
const BASE_URL = '/control';
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
BUILD_ENVS,
|
||||||
|
BASE_URL,
|
||||||
|
};
|
||||||
146
client/package-lock.json
generated
vendored
@@ -1356,6 +1356,17 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz",
|
"resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz",
|
||||||
"integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA=="
|
"integrity": "sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA=="
|
||||||
},
|
},
|
||||||
|
"@hot-loader/react-dom": {
|
||||||
|
"version": "16.13.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@hot-loader/react-dom/-/react-dom-16.13.0.tgz",
|
||||||
|
"integrity": "sha512-lJZrmkucz2MrQJTQtJobx5MICXcfQvKihszqv655p557HPi0hMOWxrNpiHv3DWD8ugNWjtWcVWqRnFvwsHq1mQ==",
|
||||||
|
"requires": {
|
||||||
|
"loose-envify": "^1.1.0",
|
||||||
|
"object-assign": "^4.1.1",
|
||||||
|
"prop-types": "^15.6.2",
|
||||||
|
"scheduler": "^0.19.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@istanbuljs/load-nyc-config": {
|
"@istanbuljs/load-nyc-config": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
|
||||||
@@ -2257,6 +2268,12 @@
|
|||||||
"@types/istanbul-lib-report": "*"
|
"@types/istanbul-lib-report": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/json-schema": {
|
||||||
|
"version": "7.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.5.tgz",
|
||||||
|
"integrity": "sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"@types/minimatch": {
|
"@types/minimatch": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
|
||||||
@@ -2728,7 +2745,6 @@
|
|||||||
"version": "1.0.10",
|
"version": "1.0.10",
|
||||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
||||||
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
|
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"sprintf-js": "~1.0.2"
|
"sprintf-js": "~1.0.2"
|
||||||
}
|
}
|
||||||
@@ -5108,6 +5124,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dom-walk": {
|
||||||
|
"version": "0.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz",
|
||||||
|
"integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"domain-browser": {
|
"domain-browser": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz",
|
||||||
@@ -5201,9 +5223,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"elliptic": {
|
"elliptic": {
|
||||||
"version": "6.5.2",
|
"version": "6.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz",
|
||||||
"integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==",
|
"integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"bn.js": "^4.4.0",
|
"bn.js": "^4.4.0",
|
||||||
@@ -5216,9 +5238,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"bn.js": {
|
"bn.js": {
|
||||||
"version": "4.11.8",
|
"version": "4.11.9",
|
||||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
|
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
|
||||||
"integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==",
|
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5887,8 +5909,7 @@
|
|||||||
"esprima": {
|
"esprima": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
||||||
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
|
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"esquery": {
|
"esquery": {
|
||||||
"version": "1.3.1",
|
"version": "1.3.1",
|
||||||
@@ -6844,6 +6865,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"global": {
|
||||||
|
"version": "4.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz",
|
||||||
|
"integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"min-document": "^2.19.0",
|
||||||
|
"process": "^0.11.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"global-modules": {
|
"global-modules": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz",
|
||||||
@@ -7357,18 +7388,6 @@
|
|||||||
"requires-port": "^1.0.0"
|
"requires-port": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"http-proxy-middleware": {
|
|
||||||
"version": "0.19.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz",
|
|
||||||
"integrity": "sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"http-proxy": "^1.17.0",
|
|
||||||
"is-glob": "^4.0.0",
|
|
||||||
"lodash": "^4.17.11",
|
|
||||||
"micromatch": "^3.1.10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"http-signature": {
|
"http-signature": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
|
||||||
@@ -9898,10 +9917,9 @@
|
|||||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
|
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
|
||||||
},
|
},
|
||||||
"js-yaml": {
|
"js-yaml": {
|
||||||
"version": "3.13.1",
|
"version": "3.14.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz",
|
||||||
"integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
|
"integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"argparse": "^1.0.7",
|
"argparse": "^1.0.7",
|
||||||
"esprima": "^4.0.0"
|
"esprima": "^4.0.0"
|
||||||
@@ -10168,9 +10186,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lodash": {
|
"lodash": {
|
||||||
"version": "4.17.15",
|
"version": "4.17.19",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
|
||||||
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
|
"integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ=="
|
||||||
},
|
},
|
||||||
"lodash-es": {
|
"lodash-es": {
|
||||||
"version": "4.17.15",
|
"version": "4.17.15",
|
||||||
@@ -10628,6 +10646,15 @@
|
|||||||
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
|
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"min-document": {
|
||||||
|
"version": "2.19.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz",
|
||||||
|
"integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"dom-walk": "^0.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"min-indent": {
|
"min-indent": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.0.tgz",
|
||||||
@@ -12287,6 +12314,39 @@
|
|||||||
"scheduler": "^0.19.1"
|
"scheduler": "^0.19.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-hot-loader": {
|
||||||
|
"version": "4.12.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-hot-loader/-/react-hot-loader-4.12.21.tgz",
|
||||||
|
"integrity": "sha512-Ynxa6ROfWUeKWsTHxsrL2KMzujxJVPjs385lmB2t5cHUxdoRPGind9F00tOkdc1l5WBleOF4XEAMILY1KPIIDA==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"fast-levenshtein": "^2.0.6",
|
||||||
|
"global": "^4.3.0",
|
||||||
|
"hoist-non-react-statics": "^3.3.0",
|
||||||
|
"loader-utils": "^1.1.0",
|
||||||
|
"prop-types": "^15.6.1",
|
||||||
|
"react-lifecycles-compat": "^3.0.4",
|
||||||
|
"shallowequal": "^1.1.0",
|
||||||
|
"source-map": "^0.7.3"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"hoist-non-react-statics": {
|
||||||
|
"version": "3.3.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
||||||
|
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"react-is": "^16.7.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"source-map": {
|
||||||
|
"version": "0.7.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
|
||||||
|
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-i18next": {
|
"react-i18next": {
|
||||||
"version": "11.4.0",
|
"version": "11.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-11.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-11.4.0.tgz",
|
||||||
@@ -13328,6 +13388,12 @@
|
|||||||
"safe-buffer": "^5.0.1"
|
"safe-buffer": "^5.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"shallowequal": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"shebang-command": {
|
"shebang-command": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||||
@@ -13752,8 +13818,7 @@
|
|||||||
"sprintf-js": {
|
"sprintf-js": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||||
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
|
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"sshpk": {
|
"sshpk": {
|
||||||
"version": "1.16.1",
|
"version": "1.16.1",
|
||||||
@@ -14080,12 +14145,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"schema-utils": {
|
"schema-utils": {
|
||||||
"version": "2.6.6",
|
"version": "2.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.6.tgz",
|
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz",
|
||||||
"integrity": "sha512-wHutF/WPSbIi9x6ctjGGk2Hvl0VOz5l3EKEuKbjPlB30mKZUzb9A5k9yEXRX3pwyqVLPvpfZZEllaFq/M718hA==",
|
"integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"ajv": "^6.12.0",
|
"@types/json-schema": "^7.0.4",
|
||||||
|
"ajv": "^6.12.2",
|
||||||
"ajv-keywords": "^3.4.1"
|
"ajv-keywords": "^3.4.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -15931,6 +15997,18 @@
|
|||||||
"ms": "^2.1.1"
|
"ms": "^2.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"http-proxy-middleware": {
|
||||||
|
"version": "0.19.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz",
|
||||||
|
"integrity": "sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"http-proxy": "^1.17.0",
|
||||||
|
"is-glob": "^4.0.0",
|
||||||
|
"lodash": "^4.17.11",
|
||||||
|
"micromatch": "^3.1.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"ms": {
|
"ms": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||||
|
|||||||
8
client/package.json
vendored
@@ -4,14 +4,16 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build-dev": "cross-env BUILD_ENV=dev webpack --config webpack.dev.js",
|
"build-dev": "cross-env BUILD_ENV=dev webpack --config webpack.dev.js",
|
||||||
"watch": "cross-env BUILD_ENV=dev webpack --config webpack.dev.js --watch",
|
|
||||||
"build-prod": "cross-env BUILD_ENV=prod webpack --config webpack.prod.js",
|
"build-prod": "cross-env BUILD_ENV=prod webpack --config webpack.prod.js",
|
||||||
|
"watch": "cross-env BUILD_ENV=dev webpack --config webpack.dev.js --watch",
|
||||||
|
"watch:hot": "cross-env BUILD_ENV=dev webpack-dev-server --config webpack.dev.js",
|
||||||
"lint": "eslint src",
|
"lint": "eslint src",
|
||||||
"lint:fix": "eslint src --fix",
|
"lint:fix": "eslint src --fix",
|
||||||
"test": "jest",
|
"test": "jest",
|
||||||
"test:watch": "jest --watch"
|
"test:watch": "jest --watch"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@hot-loader/react-dom": "^16.13.0",
|
||||||
"@nivo/line": "^0.49.1",
|
"@nivo/line": "^0.49.1",
|
||||||
"axios": "^0.19.2",
|
"axios": "^0.19.2",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
@@ -19,7 +21,8 @@
|
|||||||
"i18next": "^19.4.4",
|
"i18next": "^19.4.4",
|
||||||
"i18next-browser-languagedetector": "^4.2.0",
|
"i18next-browser-languagedetector": "^4.2.0",
|
||||||
"ipaddr.js": "^1.9.1",
|
"ipaddr.js": "^1.9.1",
|
||||||
"lodash": "^4.17.15",
|
"js-yaml": "^3.14.0",
|
||||||
|
"lodash": "^4.17.19",
|
||||||
"nanoid": "^3.1.9",
|
"nanoid": "^3.1.9",
|
||||||
"prop-types": "^15.7.2",
|
"prop-types": "^15.7.2",
|
||||||
"query-string": "^6.13.1",
|
"query-string": "^6.13.1",
|
||||||
@@ -73,6 +76,7 @@
|
|||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
"postcss-flexbugs-fixes": "4.2.1",
|
"postcss-flexbugs-fixes": "4.2.1",
|
||||||
"postcss-loader": "^3.0.0",
|
"postcss-loader": "^3.0.0",
|
||||||
|
"react-hot-loader": "^4.12.21",
|
||||||
"style-loader": "^1.2.1",
|
"style-loader": "^1.2.1",
|
||||||
"stylelint": "^13.5.0",
|
"stylelint": "^13.5.0",
|
||||||
"stylelint-webpack-plugin": "2.0.0",
|
"stylelint-webpack-plugin": "2.0.0",
|
||||||
|
|||||||
@@ -236,5 +236,6 @@
|
|||||||
"reset_settings": "Изтрий всички настройки",
|
"reset_settings": "Изтрий всички настройки",
|
||||||
"update_announcement": "Има нова AdGuard Home {{version}}! <0>Цъкни тук</0> за повече информация.",
|
"update_announcement": "Има нова AdGuard Home {{version}}! <0>Цъкни тук</0> за повече информация.",
|
||||||
"disable_ipv6": "Изключете IPv6 протокола",
|
"disable_ipv6": "Изключете IPv6 протокола",
|
||||||
"show_blocked_responses": "Блокирано"
|
"show_blocked_responses": "Блокирано",
|
||||||
|
"port_53_faq_link": "Порт 53 често е зает от \"DNSStubListener\" или \"systemd-resolved\" услуги. Моля, прочетете <0>тази инструкция</0> как да решите това."
|
||||||
}
|
}
|
||||||
@@ -562,5 +562,6 @@
|
|||||||
"filter_category_regional_desc": "Seznamy, které jsou zaměřené na regionální reklamy a sledovací servery",
|
"filter_category_regional_desc": "Seznamy, které jsou zaměřené na regionální reklamy a sledovací servery",
|
||||||
"filter_category_other_desc": "Další seznamy zakázaných",
|
"filter_category_other_desc": "Další seznamy zakázaných",
|
||||||
"original_response": "Původní odezva",
|
"original_response": "Původní odezva",
|
||||||
"click_to_view_queries": "Klikněte pro zobrazení dotazů"
|
"click_to_view_queries": "Klikněte pro zobrazení dotazů",
|
||||||
|
"port_53_faq_link": "Port 53 je často obsazen službami \"DNSStubListener\" nebo \"systemd-resolved\". Přečtěte si <0>tento návod</0> o tom, jak to vyřešit."
|
||||||
}
|
}
|
||||||
@@ -562,5 +562,6 @@
|
|||||||
"filter_category_regional_desc": "Lister, der fokuserer på regionale annoncer og tracking-servere",
|
"filter_category_regional_desc": "Lister, der fokuserer på regionale annoncer og tracking-servere",
|
||||||
"filter_category_other_desc": "Andre blokeringslister",
|
"filter_category_other_desc": "Andre blokeringslister",
|
||||||
"original_response": "Oprindeligt svar",
|
"original_response": "Oprindeligt svar",
|
||||||
"click_to_view_queries": "Klik for at se forespørgsler"
|
"click_to_view_queries": "Klik for at se forespørgsler",
|
||||||
|
"port_53_faq_link": "Port 53 optages ofte af \"DNSStubListener\" eller \"systemd-resolved\" tjenester. Læs <0>denne instruktion</0> om, hvordan du løser dette."
|
||||||
}
|
}
|
||||||
@@ -562,5 +562,6 @@
|
|||||||
"filter_category_regional_desc": "Listen, die sich auf regionale Werbeanzeigen und Tracking-Server konzentrieren",
|
"filter_category_regional_desc": "Listen, die sich auf regionale Werbeanzeigen und Tracking-Server konzentrieren",
|
||||||
"filter_category_other_desc": "Weitere Sperrlisten",
|
"filter_category_other_desc": "Weitere Sperrlisten",
|
||||||
"original_response": "Ursprüngliche Antwort",
|
"original_response": "Ursprüngliche Antwort",
|
||||||
"click_to_view_queries": "Anklicken, um Abfragen anzuzeigen"
|
"click_to_view_queries": "Anklicken, um Abfragen anzuzeigen",
|
||||||
|
"port_53_faq_link": "Port 53 wird oft von Diensten wie „DNSStubListener” oder „systemresolved” belegt. Bitte lesen Sie <0>diese Anweisung</0>, wie dies behoben werden kann."
|
||||||
}
|
}
|
||||||
@@ -542,7 +542,6 @@
|
|||||||
"safe_search": "Safe search",
|
"safe_search": "Safe search",
|
||||||
"blocklist": "Blocklist",
|
"blocklist": "Blocklist",
|
||||||
"milliseconds_abbreviation": "ms",
|
"milliseconds_abbreviation": "ms",
|
||||||
"dnssec_enable_desc": "Set DNSSEC flag in the outcoming DNS queries and check the result (DNSSEC-enabled resolver is required)",
|
|
||||||
"cache_size": "Cache size",
|
"cache_size": "Cache size",
|
||||||
"cache_size_desc": "DNS cache size (in bytes)",
|
"cache_size_desc": "DNS cache size (in bytes)",
|
||||||
"cache_ttl_min_override": "Override minimum TTL",
|
"cache_ttl_min_override": "Override minimum TTL",
|
||||||
@@ -565,4 +564,4 @@
|
|||||||
"original_response": "Original response",
|
"original_response": "Original response",
|
||||||
"click_to_view_queries": "Click to view queries",
|
"click_to_view_queries": "Click to view queries",
|
||||||
"port_53_faq_link": "Port 53 is often occupied by \"DNSStubListener\" or \"systemd-resolved\" services. Please read <0>this instruction</0> on how to resolve this."
|
"port_53_faq_link": "Port 53 is often occupied by \"DNSStubListener\" or \"systemd-resolved\" services. Please read <0>this instruction</0> on how to resolve this."
|
||||||
}
|
}
|
||||||
@@ -354,8 +354,8 @@
|
|||||||
"fix": "Corregir",
|
"fix": "Corregir",
|
||||||
"dns_providers": "Aquí hay una <0>lista de proveedores DNS</0> conocidos para elegir.",
|
"dns_providers": "Aquí hay una <0>lista de proveedores DNS</0> conocidos para elegir.",
|
||||||
"update_now": "Actualizar ahora",
|
"update_now": "Actualizar ahora",
|
||||||
"update_failed": "Error en la actualización automática. Por favor <a href='https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started#update'>siga los pasos</a> para actualizar manualmente.",
|
"update_failed": "Error en la actualización automática. Por favor <a href='https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started#update'>sigue los pasos</a> para actualizar manualmente.",
|
||||||
"processing_update": "Por favor espere, AdGuard Home se está actualizando",
|
"processing_update": "Por favor espera, AdGuard Home se está actualizando",
|
||||||
"clients_title": "Clientes",
|
"clients_title": "Clientes",
|
||||||
"clients_desc": "Configurar dispositivos conectados con AdGuard Home",
|
"clients_desc": "Configurar dispositivos conectados con AdGuard Home",
|
||||||
"settings_global": "Global",
|
"settings_global": "Global",
|
||||||
@@ -562,5 +562,6 @@
|
|||||||
"filter_category_regional_desc": "Listas que se centran en anuncios regionales y servidores de rastreo",
|
"filter_category_regional_desc": "Listas que se centran en anuncios regionales y servidores de rastreo",
|
||||||
"filter_category_other_desc": "Otras listas de bloqueo",
|
"filter_category_other_desc": "Otras listas de bloqueo",
|
||||||
"original_response": "Respuesta original",
|
"original_response": "Respuesta original",
|
||||||
"click_to_view_queries": "Clic para ver las consultas"
|
"click_to_view_queries": "Clic para ver las consultas",
|
||||||
|
"port_53_faq_link": "El puerto 53 suele estar ocupado por los servicios \"DNSStubListener\" o \"systemd-resolved\". Por favor lee <0>esta instrucción</0> sobre cómo resolver esto."
|
||||||
}
|
}
|
||||||
@@ -559,5 +559,6 @@
|
|||||||
"filter_category_security_desc": "Listes spécialisées dans le blocage de logiciels malveillants, d’hameçonnage ou de domaines frauduleux",
|
"filter_category_security_desc": "Listes spécialisées dans le blocage de logiciels malveillants, d’hameçonnage ou de domaines frauduleux",
|
||||||
"filter_category_regional_desc": "Listes axées sur les annonces régionales et les serveurs de pistage",
|
"filter_category_regional_desc": "Listes axées sur les annonces régionales et les serveurs de pistage",
|
||||||
"filter_category_other_desc": "Autres listes noires",
|
"filter_category_other_desc": "Autres listes noires",
|
||||||
"click_to_view_queries": "Cliquez pour voir les requêtes"
|
"click_to_view_queries": "Cliquez pour voir les requêtes",
|
||||||
|
"port_53_faq_link": "Le port 53 est souvent occupé par les services « DNSStubListener » ou « systemd-resolved ». Veuillez lire <0>cette instruction</0> pour savoir comment résoudre ce problème."
|
||||||
}
|
}
|
||||||
@@ -84,7 +84,7 @@
|
|||||||
"disable_protection": "Onemogući zaštitu",
|
"disable_protection": "Onemogući zaštitu",
|
||||||
"disabled_protection": "Onemogućena zaštita",
|
"disabled_protection": "Onemogućena zaštita",
|
||||||
"refresh_statics": "Osvježi statistiku",
|
"refresh_statics": "Osvježi statistiku",
|
||||||
"dns_query": "DNS Upiti",
|
"dns_query": "DNS upiti",
|
||||||
"blocked_by": "<0>Blokirano filtrima</0>",
|
"blocked_by": "<0>Blokirano filtrima</0>",
|
||||||
"stats_malware_phishing": "Blokiran zločudni program/krađe identiteta",
|
"stats_malware_phishing": "Blokiran zločudni program/krađe identiteta",
|
||||||
"stats_adult": "Blokirane web stranice za odrasle",
|
"stats_adult": "Blokirane web stranice za odrasle",
|
||||||
@@ -111,7 +111,7 @@
|
|||||||
"block_domain_use_filters_and_hosts": "Blokiraj domene koristeći filtre ili hosts datoteke",
|
"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>.",
|
"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": "Koristi AdGuard uslugu zaštite pregledavanja",
|
||||||
"use_adguard_browsing_sec_hint": "AdGuard Home će provjeriti nalazi li se domena na popisu neželjenih domena od usluge zaštite pregledavanja. Za provjeru će se koristiti API za provjeru koji poštuje vašu privatnost. Samo mali dio SHA256 hash-a odnaziva domene se šalje poslužitelju.",
|
"use_adguard_browsing_sec_hint": "AdGuard Home će provjeriti nalazi li se domena na popisu neželjenih domena od usluge zaštite pregledavanja. Za provjeru će se koristiti API za provjeru koji poštuje vašu privatnost. Samo mali dio SHA256 hash-a od naziva domene se šalje poslužitelju.",
|
||||||
"use_adguard_parental": "Koristi web uslugu AdGuard roditeljske zaštite",
|
"use_adguard_parental": "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.",
|
"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_safe_search": "Omogući sigurno pretraživanje",
|
||||||
@@ -562,5 +562,6 @@
|
|||||||
"filter_category_regional_desc": "Popisi koji se fokusiraju na regionalne oglase i poslužitelje za praćenje",
|
"filter_category_regional_desc": "Popisi koji se fokusiraju na regionalne oglase i poslužitelje za praćenje",
|
||||||
"filter_category_other_desc": "Ostali popisi blokiranih",
|
"filter_category_other_desc": "Ostali popisi blokiranih",
|
||||||
"original_response": "Originalni odgovor",
|
"original_response": "Originalni odgovor",
|
||||||
"click_to_view_queries": "Kliknite za pregled upita"
|
"click_to_view_queries": "Kliknite za pregled upita",
|
||||||
|
"port_53_faq_link": "Port 53 često zauzimaju usluge \"DNSStubListener\" ili \"systemd-resolved\". Molimo pročitajte <0>ove upute</0> o tome kako to riješiti."
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,8 @@
|
|||||||
"example_upstream_reserved": "Anda dapat menetapkan DNS upstream <0>untuk domain spesifik</0>",
|
"example_upstream_reserved": "Anda dapat menetapkan DNS upstream <0>untuk domain spesifik</0>",
|
||||||
"upstream_parallel": "Gunakan kueri paralel untuk mempercepat resoluasi dengan menanyakan semua server upstream secara bersamaan",
|
"upstream_parallel": "Gunakan kueri paralel untuk mempercepat resoluasi dengan menanyakan semua server upstream secara bersamaan",
|
||||||
"parallel_requests": "Permintaan paralel",
|
"parallel_requests": "Permintaan paralel",
|
||||||
|
"load_balancing": "Penyeimbang beban",
|
||||||
|
"load_balancing_desc": "Permintaan satu server pada satu waktu. AdGuard Home akan menggunakan algoritma acak tertimbang untuk memilih server sehingga server tercepat akan lebih sering digunakan.",
|
||||||
"bootstrap_dns": "Server DNS bootstrap",
|
"bootstrap_dns": "Server DNS bootstrap",
|
||||||
"bootstrap_dns_desc": "Server Bootstrap DNS dapat digunakan untuk meresolve alamat IP pada DoH/DoT resolvers yang Anda tentukan sebagai upstreams.",
|
"bootstrap_dns_desc": "Server Bootstrap DNS dapat digunakan untuk meresolve alamat IP pada DoH/DoT resolvers yang Anda tentukan sebagai upstreams.",
|
||||||
"check_dhcp_servers": "Cek untuk server DHCP",
|
"check_dhcp_servers": "Cek untuk server DHCP",
|
||||||
@@ -51,9 +53,11 @@
|
|||||||
"dhcp_add_static_lease": "Tambah static lease",
|
"dhcp_add_static_lease": "Tambah static lease",
|
||||||
"dhcp_reset": "Apakah anda yakin ingin mengatur ulang konfigurasi DHCP anda?",
|
"dhcp_reset": "Apakah anda yakin ingin mengatur ulang konfigurasi DHCP anda?",
|
||||||
"country": "Negara",
|
"country": "Negara",
|
||||||
|
"city": "Kota",
|
||||||
"delete_confirm": "Apakah anda yakin ingin menghapus \"{{key}}\"?",
|
"delete_confirm": "Apakah anda yakin ingin menghapus \"{{key}}\"?",
|
||||||
"form_enter_hostname": "Masukkan hostname",
|
"form_enter_hostname": "Masukkan hostname",
|
||||||
"error_details": "Detail kesalahan",
|
"error_details": "Detail kesalahan",
|
||||||
|
"response_details": "Detail respon",
|
||||||
"request_details": "Detai permintaan",
|
"request_details": "Detai permintaan",
|
||||||
"client_details": "Detail klien",
|
"client_details": "Detail klien",
|
||||||
"details": "Detail",
|
"details": "Detail",
|
||||||
@@ -63,6 +67,8 @@
|
|||||||
"filters": "Penyaring",
|
"filters": "Penyaring",
|
||||||
"filter": "Filter",
|
"filter": "Filter",
|
||||||
"query_log": "Catatan Kueri",
|
"query_log": "Catatan Kueri",
|
||||||
|
"compact": "Rapat",
|
||||||
|
"nothing_found": "Tidak ditemukan",
|
||||||
"faq": "Tanya Jawab",
|
"faq": "Tanya Jawab",
|
||||||
"version": "versi",
|
"version": "versi",
|
||||||
"address": "Alamat",
|
"address": "Alamat",
|
||||||
@@ -114,12 +120,16 @@
|
|||||||
"general_settings": "Pengaturan umum",
|
"general_settings": "Pengaturan umum",
|
||||||
"dns_settings": "Pengaturan DNS",
|
"dns_settings": "Pengaturan DNS",
|
||||||
"dns_blocklists": "Daftar blokir DNS",
|
"dns_blocklists": "Daftar blokir DNS",
|
||||||
|
"dns_allowlists": "Daftar putih DNS",
|
||||||
|
"dns_blocklists_desc": "AdGuard Home akan memblokir domain yang cocok dengan daftar hitam.",
|
||||||
|
"dns_allowlists_desc": "Domain dari daftar putih DNS akan diizinkan bahkan jika mereka ada juga di daftar hitam.",
|
||||||
"custom_filtering_rules": "Aturan penyaringan khusus",
|
"custom_filtering_rules": "Aturan penyaringan khusus",
|
||||||
"encryption_settings": "Pengaturan enkripsi",
|
"encryption_settings": "Pengaturan enkripsi",
|
||||||
"dhcp_settings": "Pengaturan DHCP",
|
"dhcp_settings": "Pengaturan DHCP",
|
||||||
"upstream_dns": "Server DNS hulu",
|
"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.",
|
"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",
|
"test_upstream_btn": "Uji hulu",
|
||||||
|
"upstreams": "Upstream",
|
||||||
"apply_btn": "Terapkan",
|
"apply_btn": "Terapkan",
|
||||||
"disabled_filtering_toast": "Penyaringan nonaktif",
|
"disabled_filtering_toast": "Penyaringan nonaktif",
|
||||||
"enabled_filtering_toast": "Penyaringan aktif",
|
"enabled_filtering_toast": "Penyaringan aktif",
|
||||||
@@ -131,6 +141,7 @@
|
|||||||
"enabled_save_search_toast": "Pencarian aman diaktifkan",
|
"enabled_save_search_toast": "Pencarian aman diaktifkan",
|
||||||
"enabled_table_header": "Diaktifkan",
|
"enabled_table_header": "Diaktifkan",
|
||||||
"name_table_header": "Nama",
|
"name_table_header": "Nama",
|
||||||
|
"list_url_table_header": "Daftar URL",
|
||||||
"rules_count_table_header": "Jumlah Aturan",
|
"rules_count_table_header": "Jumlah Aturan",
|
||||||
"last_time_updated_table_header": "Terakhir diperbaharui",
|
"last_time_updated_table_header": "Terakhir diperbaharui",
|
||||||
"actions_table_header": "Aksi",
|
"actions_table_header": "Aksi",
|
||||||
@@ -139,10 +150,23 @@
|
|||||||
"delete_table_action": "Hapus",
|
"delete_table_action": "Hapus",
|
||||||
"elapsed": "Berlalu",
|
"elapsed": "Berlalu",
|
||||||
"filters_and_hosts_hint": "AdGuard Home memahami aturan dasar adblock dan sintak file hosts.",
|
"filters_and_hosts_hint": "AdGuard Home memahami aturan dasar adblock dan sintak file hosts.",
|
||||||
|
"no_blocklist_added": "Tiada daftar hitam ditambahkan",
|
||||||
|
"no_whitelist_added": "Tiada daftar putih ditambahkan",
|
||||||
|
"add_blocklist": "Tambah daftar hitam",
|
||||||
|
"add_allowlist": "Tambah daftar putih",
|
||||||
"cancel_btn": "Batal",
|
"cancel_btn": "Batal",
|
||||||
"enter_name_hint": "Masukkan nama",
|
"enter_name_hint": "Masukkan nama",
|
||||||
"enter_url_or_path_hint": "Masukan sebuah URL atau jalur absolut dari daftar",
|
"enter_url_or_path_hint": "Masukan sebuah URL atau jalur absolut dari daftar",
|
||||||
"check_updates_btn": "Cek pembaruan",
|
"check_updates_btn": "Cek pembaruan",
|
||||||
|
"new_blocklist": "Daftar hitam baru",
|
||||||
|
"new_allowlist": "Daftar putih baru",
|
||||||
|
"edit_blocklist": "Edit daftar hitam",
|
||||||
|
"edit_allowlist": "Edit daftar putih",
|
||||||
|
"choose_blocklist": "Pilih daftar hitam",
|
||||||
|
"choose_allowlist": "Pilih daftar putih",
|
||||||
|
"enter_valid_blocklist": "Masukkan valid URL ke daftar hitam.",
|
||||||
|
"enter_valid_allowlist": "Masukkan valid URL ke daftar putih.",
|
||||||
|
"form_error_url_format": "Format URL tidak valid",
|
||||||
"form_error_url_or_path_format": "URL atau jalur absolut dari daftar tidak valid",
|
"form_error_url_or_path_format": "URL atau jalur absolut dari daftar tidak valid",
|
||||||
"custom_filter_rules": "Aturan penyaringan khusus",
|
"custom_filter_rules": "Aturan penyaringan khusus",
|
||||||
"custom_filter_rules_hint": "Masukkan satu aturan dalam sebuah baris. Anda dapat menggunakan baik aturan adblock maupun sintaks file hosts.",
|
"custom_filter_rules_hint": "Masukkan satu aturan dalam sebuah baris. Anda dapat menggunakan baik aturan adblock maupun sintaks file hosts.",
|
||||||
@@ -159,6 +183,7 @@
|
|||||||
"example_upstream_doh": "terenkripsi <0>DNS-over-HTTPS</0>",
|
"example_upstream_doh": "terenkripsi <0>DNS-over-HTTPS</0>",
|
||||||
"example_upstream_sdns": "anda bisa menggunakan <0>Stempel DNS</0> untuk <1>DNSCrypt</1> atau pengarah <2>DNS-over-HTTPS</2>",
|
"example_upstream_sdns": "anda bisa menggunakan <0>Stempel DNS</0> untuk <1>DNSCrypt</1> atau pengarah <2>DNS-over-HTTPS</2>",
|
||||||
"example_upstream_tcp": "DNS reguler (melalui TCP)",
|
"example_upstream_tcp": "DNS reguler (melalui TCP)",
|
||||||
|
"all_lists_up_to_date_toast": "Semua daftar sudah diperbarui",
|
||||||
"updated_upstream_dns_toast": "Server DNS hulu terbarui",
|
"updated_upstream_dns_toast": "Server DNS hulu terbarui",
|
||||||
"dns_test_ok_toast": "Server DNS yang ditentukan bekerja dengan benar",
|
"dns_test_ok_toast": "Server DNS yang ditentukan bekerja dengan benar",
|
||||||
"dns_test_not_ok_toast": "Server \"{{key}}\": tidak dapat digunakan, mohon cek bahwa Anda telah menulisnya dengan benar",
|
"dns_test_not_ok_toast": "Server \"{{key}}\": tidak dapat digunakan, mohon cek bahwa Anda telah menulisnya dengan benar",
|
||||||
@@ -170,6 +195,7 @@
|
|||||||
"domain_or_client": "Domain atau klien",
|
"domain_or_client": "Domain atau klien",
|
||||||
"type_table_header": "Tipe",
|
"type_table_header": "Tipe",
|
||||||
"response_table_header": "Respon",
|
"response_table_header": "Respon",
|
||||||
|
"response_code": "Kode respon",
|
||||||
"client_table_header": "Klien",
|
"client_table_header": "Klien",
|
||||||
"empty_response_status": "Kosong",
|
"empty_response_status": "Kosong",
|
||||||
"show_all_filter_type": "Tampilkan semua",
|
"show_all_filter_type": "Tampilkan semua",
|
||||||
@@ -188,6 +214,7 @@
|
|||||||
"query_log_filtered": "Difilter oleh {{filter}}",
|
"query_log_filtered": "Difilter oleh {{filter}}",
|
||||||
"query_log_confirm_clear": "Apakah Anda yakin ingin menghapus seluruh kueri log?",
|
"query_log_confirm_clear": "Apakah Anda yakin ingin menghapus seluruh kueri log?",
|
||||||
"query_log_cleared": "Kueri log telah berhasil dihapus",
|
"query_log_cleared": "Kueri log telah berhasil dihapus",
|
||||||
|
"query_log_updated": "Log permintaan telah berhasil diperbarui",
|
||||||
"query_log_clear": "Hapus kueri log",
|
"query_log_clear": "Hapus kueri log",
|
||||||
"query_log_retention": "Retensi kueri log",
|
"query_log_retention": "Retensi kueri log",
|
||||||
"query_log_enable": "Aktifkan log",
|
"query_log_enable": "Aktifkan log",
|
||||||
@@ -195,19 +222,39 @@
|
|||||||
"query_log_disabled": "Kueri log dinonaktifkan dan dapat dikonfigurasi di <0>pengaturan</0>",
|
"query_log_disabled": "Kueri log dinonaktifkan dan dapat dikonfigurasi di <0>pengaturan</0>",
|
||||||
"query_log_strict_search": "Gunakan tanda kutip ganda untuk pencarian ketat",
|
"query_log_strict_search": "Gunakan tanda kutip ganda untuk pencarian ketat",
|
||||||
"query_log_retention_confirm": "Apakah Anda yakin ingin mengubah retensi kueri log? Jika Anda menurunkan nilai interval, beberapa data akan hilang",
|
"query_log_retention_confirm": "Apakah Anda yakin ingin mengubah retensi kueri log? Jika Anda menurunkan nilai interval, beberapa data akan hilang",
|
||||||
|
"anonymize_client_ip": "Anonim IP klien",
|
||||||
|
"anonymize_client_ip_desc": "Jangan simpan alamat lengkap IP klien dalam log dan statistik",
|
||||||
|
"dns_config": "Konfigurasi server DNS",
|
||||||
|
"dns_cache_config": "Konfigurasi cache DNS",
|
||||||
|
"dns_cache_config_desc": "Disini Anda bisa mengonfigurasi cache DNS",
|
||||||
"blocking_mode": "Mode blokir",
|
"blocking_mode": "Mode blokir",
|
||||||
"default": "Standar",
|
"default": "Standar",
|
||||||
|
"nxdomain": "NXDOMAIN",
|
||||||
|
"null_ip": "Null IP",
|
||||||
|
"custom_ip": "Custom IP",
|
||||||
"blocking_ipv4": "Blokiran IPv4",
|
"blocking_ipv4": "Blokiran IPv4",
|
||||||
"blocking_ipv6": "Blokiran IPv6",
|
"blocking_ipv6": "Blokiran IPv6",
|
||||||
"dns_over_https": "DNS-over-HTTPS",
|
"dns_over_https": "DNS-over-HTTPS",
|
||||||
"dns_over_tls": "DNS-over-TLS",
|
"dns_over_tls": "DNS-over-TLS",
|
||||||
|
"plain_dns": "Plain DNS",
|
||||||
|
"form_enter_rate_limit": "Masukkan batas nilai",
|
||||||
|
"rate_limit": "Batas nilai",
|
||||||
|
"edns_enable": "Aktifkan EDNS Klien Subnet",
|
||||||
"edns_cs_desc": "Apabila dinyalakan, AdGuard Home akan mengirim subnet klien ke server-server DNS.",
|
"edns_cs_desc": "Apabila dinyalakan, AdGuard Home akan mengirim subnet klien ke server-server DNS.",
|
||||||
"rate_limit_desc": "Jumlah permintaan per detik yang diperbolehkan untuk satu klien (0: tidak terbatas)",
|
"rate_limit_desc": "Jumlah permintaan per detik yang diperbolehkan untuk satu klien (0: tidak terbatas)",
|
||||||
|
"blocking_ipv4_desc": "Alamat IP akan dikembalikan untuk permintaan A yang diblokir",
|
||||||
|
"blocking_ipv6_desc": "Alamat IP akan dipulihkan untuk permintaan AAAA yang diblokir",
|
||||||
|
"blocking_mode_default": "Standar: Respon pakai NXDOMAIN saat diblokir oleh aturan gaya Adblock; membalas dengan alamat IP yang ditentukan dalam aturan ketika diblokir oleh /et /aturan hosts-style",
|
||||||
|
"blocking_mode_nxdomain": "NXDOMAIN: Respon pakai kode NXDOMAIN",
|
||||||
|
"blocking_mode_null_ip": "Null IP: Respon pakai alamat IP kosong (0.0.0.0 untuk A; :: untuk AAAA)",
|
||||||
"blocking_mode_custom_ip": "IP kustom: respon dengan alamat IP yang diset secara manual",
|
"blocking_mode_custom_ip": "IP kustom: respon dengan alamat IP yang diset secara manual",
|
||||||
|
"upstream_dns_client_desc": "Jika Anda biarkan bidang ini kosong, AdGuard Home akan memakai server yang dikonfigurasi di<0>Pengaturan DNS</0>.",
|
||||||
|
"tracker_source": "Sumber pelacak",
|
||||||
"source_label": "Sumber",
|
"source_label": "Sumber",
|
||||||
"found_in_known_domain_db": "Ditemukan di database domain dikenal",
|
"found_in_known_domain_db": "Ditemukan di database domain dikenal",
|
||||||
"category_label": "Kategori",
|
"category_label": "Kategori",
|
||||||
"rule_label": "Aturan",
|
"rule_label": "Aturan",
|
||||||
|
"list_label": "Daftar",
|
||||||
"unknown_filter": "Penyaringan {{filterId}} tidak dikenal",
|
"unknown_filter": "Penyaringan {{filterId}} tidak dikenal",
|
||||||
"known_tracker": "Pelacak yang dikenal",
|
"known_tracker": "Pelacak yang dikenal",
|
||||||
"install_welcome_title": "Selamat datang di AdGuard Home!",
|
"install_welcome_title": "Selamat datang di AdGuard Home!",
|
||||||
@@ -324,6 +371,8 @@
|
|||||||
"client_identifier_desc": "Klien dapat diidentifikasi dengan alamat IP atau alamat MAC. Harap dicatat bahwa menggunakan MAC sebagai pengidentifikasi hanya dimungkinkan jika AdGuard Home juga merupakan <0>server DHCP</0>",
|
"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_ip": "Masukkan IP",
|
||||||
"form_enter_mac": "Masukkan MAC",
|
"form_enter_mac": "Masukkan MAC",
|
||||||
|
"form_enter_id": "Masukkan pengenal",
|
||||||
|
"form_add_id": "Tambah pengenal",
|
||||||
"form_client_name": "Masukkan nama klien",
|
"form_client_name": "Masukkan nama klien",
|
||||||
"name": "Nama",
|
"name": "Nama",
|
||||||
"client_global_settings": "Gunakan pengaturan global",
|
"client_global_settings": "Gunakan pengaturan global",
|
||||||
@@ -332,6 +381,7 @@
|
|||||||
"client_updated": "Klien \"{{key}}\" berhasil diperbarui",
|
"client_updated": "Klien \"{{key}}\" berhasil diperbarui",
|
||||||
"clients_not_found": "Tidak ada klien ditemukan",
|
"clients_not_found": "Tidak ada klien ditemukan",
|
||||||
"client_confirm_delete": "Apakah anda yakin ingin menghapus klien \"{{key}}\"?",
|
"client_confirm_delete": "Apakah anda yakin ingin menghapus klien \"{{key}}\"?",
|
||||||
|
"list_confirm_delete": "Anda yakin ingin menghapus daftar ini?",
|
||||||
"auto_clients_title": "Klien (waktu berjalan)",
|
"auto_clients_title": "Klien (waktu berjalan)",
|
||||||
"auto_clients_desc": "Data pada klien yang menggunakan AdGuard Home, tetapi tidak disimpan dalam konfigurasi",
|
"auto_clients_desc": "Data pada klien yang menggunakan AdGuard Home, tetapi tidak disimpan dalam konfigurasi",
|
||||||
"access_title": "Pengaturan akses",
|
"access_title": "Pengaturan akses",
|
||||||
@@ -399,6 +449,8 @@
|
|||||||
"domain": "Domain",
|
"domain": "Domain",
|
||||||
"answer": "Jawab",
|
"answer": "Jawab",
|
||||||
"filter_added_successfully": "Filter telah berhasil ditambahkan",
|
"filter_added_successfully": "Filter telah berhasil ditambahkan",
|
||||||
|
"filter_removed_successfully": "Daftar ini telah sukses dihapus",
|
||||||
|
"filter_updated": "Daftar telah sukses diperbarui",
|
||||||
"statistics_configuration": "Konfigurasi statistik",
|
"statistics_configuration": "Konfigurasi statistik",
|
||||||
"statistics_retention": "Statistik disimpan",
|
"statistics_retention": "Statistik disimpan",
|
||||||
"statistics_retention_desc": "Jika Anda menurunkan nilai interval, beberapa data akan hilang",
|
"statistics_retention_desc": "Jika Anda menurunkan nilai interval, beberapa data akan hilang",
|
||||||
@@ -428,13 +480,33 @@
|
|||||||
"whois": "Whois",
|
"whois": "Whois",
|
||||||
"filtering_rules_learn_more": "<0>Pelajari lebih lanjut</0> tentang membuat daftar hitam host Anda sendiri.",
|
"filtering_rules_learn_more": "<0>Pelajari lebih lanjut</0> tentang membuat daftar hitam host Anda sendiri.",
|
||||||
"blocked_by_response": "Diblokir oleh CNAME atau IP sebagai respon",
|
"blocked_by_response": "Diblokir oleh CNAME atau IP sebagai respon",
|
||||||
|
"blocked_by_cname_or_ip": "Diblokir oleh CNAME atau IP",
|
||||||
"try_again": "Coba lagi",
|
"try_again": "Coba lagi",
|
||||||
|
"domain_desc": "Masukkan nama domain atau wildcard yang ingin Anda tulis ulang.",
|
||||||
|
"example_rewrite_domain": "tulis ulang respon hanya untuk domain ini saja.",
|
||||||
|
"example_rewrite_wildcard": "tulis ulang respon untuk semua subdomain <0>contoh.org</0>.",
|
||||||
|
"rewrite_ip_address": "Alamat IP: pakai IP ini dalam respons A atau AAAA",
|
||||||
|
"rewrite_domain_name": "Nama domain: tambah ke rekaman CNAME",
|
||||||
|
"rewrite_A": "<0>A</0>: nilai khusus, biarkan <0>A</0> merekam dari upstream",
|
||||||
|
"rewrite_AAAA": "<0>AAAA</0>: nilai khusus, biarkan <0>AAAA</0> merekam dari upstream",
|
||||||
"disable_ipv6": "Matikan IPv6",
|
"disable_ipv6": "Matikan IPv6",
|
||||||
"disable_ipv6_desc": "Apabila fitur ini dinyalakan, semua permintaan DNS untuk alamat-alamat IPv6 (tipe AAAA) akan diputus.",
|
"disable_ipv6_desc": "Apabila fitur ini dinyalakan, semua permintaan DNS untuk alamat-alamat IPv6 (tipe AAAA) akan diputus.",
|
||||||
"fastest_addr": "Alamat IP tercepat",
|
"fastest_addr": "Alamat IP tercepat",
|
||||||
"fastest_addr_desc": "Permintaan semua server DNS dan kembalinya alamat IP tercepat di antara semua respons",
|
"fastest_addr_desc": "Permintaan semua server DNS dan kembalinya alamat IP tercepat di antara semua respons",
|
||||||
"autofix_warning_text": "Apabila anda menekan \"Perbaiki\", AdGuardHome akan mengatur sistem anda untuk menggunakan server DNS AdGuardHome.",
|
"autofix_warning_text": "Apabila anda menekan \"Perbaiki\", AdGuardHome akan mengatur sistem anda untuk menggunakan server DNS AdGuardHome.",
|
||||||
|
"autofix_warning_list": "Ini akan melakukan tugas berikut: <0>Nonaktifkan sistem DNSStubListener</0> <0> Atur alamat server DNS ke 127.0.0.1</0> <0>Ganti target tautan simbolis /etc/resolv.conf pakai /run/systemd/resolve/resolv.conf</0> <0>Hentikan DNSStubListener (muat ulang layanan sistemd-resolve service)</0>",
|
||||||
"autofix_warning_result": "Hasilnya, semua permintaan DNS dari sistem anda akan diproses oleh AdGuardHome secara standar.",
|
"autofix_warning_result": "Hasilnya, semua permintaan DNS dari sistem anda akan diproses oleh AdGuardHome secara standar.",
|
||||||
|
"tags_title": "Tag",
|
||||||
|
"tags_desc": "Anda dapat memilih tag sesuai dengan klien. Tag dapat dimasukkan dalam aturan pemfilteran dan memungkinkan Anda untuk menerapkannya lebih akurat. <0>Pelajari lebih</0>",
|
||||||
|
"form_select_tags": "Pilih tag klien",
|
||||||
|
"check_title": "Periksa penyaringan",
|
||||||
|
"check_desc": "Periksa apakah nama host telah tersaring",
|
||||||
|
"check": "Periksa",
|
||||||
|
"form_enter_host": "Masukkan nama host",
|
||||||
|
"filtered_custom_rules": "Tersaring oleh aturan penyaring Buatan",
|
||||||
|
"choose_from_list": "Pilih dari daftar",
|
||||||
|
"add_custom_list": "Tambah daftar buatan",
|
||||||
|
"host_whitelisted": "Host didaftar putihkan",
|
||||||
"check_ip": "Alamat IP: {{ip}}",
|
"check_ip": "Alamat IP: {{ip}}",
|
||||||
"check_cname": "CNAME: {{cname}}",
|
"check_cname": "CNAME: {{cname}}",
|
||||||
"check_reason": "Alasan: {{reason}}",
|
"check_reason": "Alasan: {{reason}}",
|
||||||
@@ -446,11 +518,50 @@
|
|||||||
"client_blocked": "Klien \"{{ip}}\" sukses di blokir",
|
"client_blocked": "Klien \"{{ip}}\" sukses di blokir",
|
||||||
"client_unblocked": "Klien \"{{ip}}\" sukses di unblock",
|
"client_unblocked": "Klien \"{{ip}}\" sukses di unblock",
|
||||||
"static_ip": "Alamat IP statis",
|
"static_ip": "Alamat IP statis",
|
||||||
|
"static_ip_desc": "AdGuard Home adalah server jadi perlu alamat IP statis agar berfungsi dengan benar. Jika tidak, pada titik tertentu, router Anda dapat menetapkan alamat IP yang berbeda untuk perangkat ini.",
|
||||||
|
"set_static_ip": "Atur alamat IP statik",
|
||||||
|
"install_static_ok": "Kabar baik! Alamat IP statis sudah dikonfigurasi",
|
||||||
|
"install_static_error": "AdGuard Home tidak dapat mengonfigurasinya secara otomatis untuk antarmuka jaringan ini. Silakan cari instruksi tentang cara melakukan ini secara manual.",
|
||||||
|
"install_static_configure": "Kami mendeteksi alamat IP dinamis digunakan - <0>{{ip}}</0>. Anda ingin menggunakannya sebagai alamat statis Anda?",
|
||||||
|
"confirm_static_ip": "AdGuard Home akan mengonfigurasi {{ip}} menjadi alamat IP statis Anda. Anda ingin melanjutkan?",
|
||||||
|
"list_updated": "{{count}} daftar terbarui",
|
||||||
|
"list_updated_plural": "{{count}} daftar terbarui",
|
||||||
|
"dnssec_enable": "Aktifkan DNSSEC",
|
||||||
|
"dnssec_enable_desc": "Atur bendera DNSSEC di permintaan keluar DNS dan periksa hasilnya (resolver berkemampuan DNSSEC diperlukan)",
|
||||||
"validated_with_dnssec": "Tervalidasi dengan DNSSEC",
|
"validated_with_dnssec": "Tervalidasi dengan DNSSEC",
|
||||||
|
"all_queries": "Semua permintaan",
|
||||||
"show_blocked_responses": "Diblokir",
|
"show_blocked_responses": "Diblokir",
|
||||||
"show_whitelisted_responses": "Dalam Daftar Putih",
|
"show_whitelisted_responses": "Dalam Daftar Putih",
|
||||||
"show_processed_responses": "Terproses",
|
"show_processed_responses": "Terproses",
|
||||||
|
"blocked_safebrowsing": "Terblokir oleh Safebrowsing",
|
||||||
|
"blocked_adult_websites": "Situs Dewasa Terblokir",
|
||||||
|
"blocked_threats": "Blokir Ancaman",
|
||||||
|
"allowed": "Dibolehkan",
|
||||||
|
"filtered": "Tersaring",
|
||||||
|
"rewritten": "Tulis ulang",
|
||||||
"safe_search": "Pencarian aman",
|
"safe_search": "Pencarian aman",
|
||||||
"blocklist": "Daftar blokir",
|
"blocklist": "Daftar blokir",
|
||||||
"milliseconds_abbreviation": "ms"
|
"milliseconds_abbreviation": "ms",
|
||||||
|
"cache_size": "Ukuran cache",
|
||||||
|
"cache_size_desc": "Ukuran DNS cache (dalam bit)",
|
||||||
|
"cache_ttl_min_override": "Tumpuk TTL minimum",
|
||||||
|
"cache_ttl_max_override": "Tumpuk TTL maksimum",
|
||||||
|
"enter_cache_size": "Masukkan ukuran cache",
|
||||||
|
"enter_cache_ttl_min_override": "Masukkan TTL minimum",
|
||||||
|
"enter_cache_ttl_max_override": "Masukkan TTL maksimum",
|
||||||
|
"cache_ttl_min_override_desc": "Ganti nilai TTL (minimum) yang diterima dari server upstream. Nilai ini tidak boleh lebih dari 3600 (1 jam)",
|
||||||
|
"cache_ttl_max_override_desc": "Ganti nilai TTL (maksimum) yang diterima dari server upstream",
|
||||||
|
"min_exceeds_max_value": "Nilai minimum melebihi nilai maksimum",
|
||||||
|
"value_not_larger_than": "Nilai tidak bisa lebih dari {{maximum}}",
|
||||||
|
"filter_category_general": "Umum",
|
||||||
|
"filter_category_security": "Keamanan",
|
||||||
|
"filter_category_regional": "Wilayah",
|
||||||
|
"filter_category_other": "Lainnya",
|
||||||
|
"filter_category_general_desc": "Daftar yang memblokir pelacakan dan iklan di sebagian besar perangkat",
|
||||||
|
"filter_category_security_desc": "Daftar yang khusus pada pemblokiran malware, phishing, atau domain penipuan",
|
||||||
|
"filter_category_regional_desc": "Daftar yang berfokus pada iklan regional dan server pelacakan",
|
||||||
|
"filter_category_other_desc": "Daftar hitam lain",
|
||||||
|
"original_response": "Respon asli",
|
||||||
|
"click_to_view_queries": "Klik untuk lihat permintaan",
|
||||||
|
"port_53_faq_link": "Port 53 sering ditempati oleh layanan \"DNSStubListener\" atau \"systemd-resolved\". Silakan baca <0>instruksi ini</0> tentang cara menyelesaikan ini."
|
||||||
}
|
}
|
||||||
@@ -562,5 +562,6 @@
|
|||||||
"filter_category_regional_desc": "それぞれの地域の広告と追跡サーバをターゲットするリストです。",
|
"filter_category_regional_desc": "それぞれの地域の広告と追跡サーバをターゲットするリストです。",
|
||||||
"filter_category_other_desc": "その他のブロックリストです。",
|
"filter_category_other_desc": "その他のブロックリストです。",
|
||||||
"original_response": "当初の応答",
|
"original_response": "当初の応答",
|
||||||
"click_to_view_queries": "クエリを表示するにはクリックしてください"
|
"click_to_view_queries": "クエリを表示するにはクリックしてください",
|
||||||
|
"port_53_faq_link": "多くの場合、ポート53は \"DNSStubListener\" または \"systemd-resolved\" サービスによって利用されています。これを解決する方法については、<0>この手順</0>をお読みください。"
|
||||||
}
|
}
|
||||||
@@ -562,5 +562,6 @@
|
|||||||
"filter_category_regional_desc": "Lijsten die focussen op regionale ads en tracking servers",
|
"filter_category_regional_desc": "Lijsten die focussen op regionale ads en tracking servers",
|
||||||
"filter_category_other_desc": "Overige blokkeerlijsten",
|
"filter_category_other_desc": "Overige blokkeerlijsten",
|
||||||
"original_response": "Oorspronkelijke reactie",
|
"original_response": "Oorspronkelijke reactie",
|
||||||
"click_to_view_queries": "Klik om queries te bekijken"
|
"click_to_view_queries": "Klik om queries te bekijken",
|
||||||
|
"port_53_faq_link": "Poort 53 wordt vaak gebruikt door services als DNSStubListener- of de systeem DNS-resolver. Lees a.u.b. <0>deze instructie</0> hoe dit is op te lossen."
|
||||||
}
|
}
|
||||||
@@ -108,7 +108,7 @@
|
|||||||
"number_of_dns_query_to_safe_search": "Liczba żądań DNS do wyszukiwarek, dla których zastosowano wymuszenie bezpiecznego wyszukiwania",
|
"number_of_dns_query_to_safe_search": "Liczba żądań DNS do wyszukiwarek, dla których zastosowano wymuszenie bezpiecznego wyszukiwania",
|
||||||
"average_processing_time": "Średni czas przetwarzania",
|
"average_processing_time": "Średni czas przetwarzania",
|
||||||
"average_processing_time_hint": "Średni czas przetwarzania żądania DNS liczony w milisekundach",
|
"average_processing_time_hint": "Średni czas przetwarzania żądania DNS liczony w milisekundach",
|
||||||
"block_domain_use_filters_and_hosts": "Blokuj domeny za pomocą filtrów i plików host",
|
"block_domain_use_filters_and_hosts": "Zablokuj domeny za pomocą filtrów i plików host",
|
||||||
"filters_block_toggle_hint": "Możesz skonfigurować reguły blokowania w ustawieniach <a href='#filters'>Filtry</a> ",
|
"filters_block_toggle_hint": "Możesz skonfigurować reguły blokowania w ustawieniach <a href='#filters'>Filtry</a> ",
|
||||||
"use_adguard_browsing_sec": "Użyj usługi sieciowej Bezpieczne Przeglądanie AdGuard",
|
"use_adguard_browsing_sec": "Użyj usługi sieciowej Bezpieczne Przeglądanie AdGuard",
|
||||||
"use_adguard_browsing_sec_hint": "AdGuard Home sprawdzi, czy domena jest na czarnej liście przez serwis internetowy Bezpieczne Przeglądanie. Będzie korzystać z interfejsu API przyjaznego dla prywatności w celu przeprowadzenia kontroli: na serwer wysyłany jest tylko krótki prefiks nazwy domeny SHA256.",
|
"use_adguard_browsing_sec_hint": "AdGuard Home sprawdzi, czy domena jest na czarnej liście przez serwis internetowy Bezpieczne Przeglądanie. Będzie korzystać z interfejsu API przyjaznego dla prywatności w celu przeprowadzenia kontroli: na serwer wysyłany jest tylko krótki prefiks nazwy domeny SHA256.",
|
||||||
@@ -177,7 +177,7 @@
|
|||||||
"example_comment": "! Tutaj jest komentarz",
|
"example_comment": "! Tutaj jest komentarz",
|
||||||
"example_comment_meaning": "komentarz",
|
"example_comment_meaning": "komentarz",
|
||||||
"example_comment_hash": "# Również komentarz",
|
"example_comment_hash": "# Również komentarz",
|
||||||
"example_regex_meaning": "blokuj dostęp do domen pasujących do określonego wyrażenia regularnego",
|
"example_regex_meaning": "zablokuj dostęp do domen pasujących do określonego wyrażenia regularnego",
|
||||||
"example_upstream_regular": "normalny DNS (przez UDP)",
|
"example_upstream_regular": "normalny DNS (przez UDP)",
|
||||||
"example_upstream_dot": "zaszyfrowany <0>DNS-over-TLS</0>",
|
"example_upstream_dot": "zaszyfrowany <0>DNS-over-TLS</0>",
|
||||||
"example_upstream_doh": "zaszyfrowany <0>DNS-over-HTTPS</0>",
|
"example_upstream_doh": "zaszyfrowany <0>DNS-over-HTTPS</0>",
|
||||||
@@ -427,7 +427,7 @@
|
|||||||
"form_error_answer_format": "Nieprawidłowy format odpowiedzi",
|
"form_error_answer_format": "Nieprawidłowy format odpowiedzi",
|
||||||
"configure": "Skonfiguruj",
|
"configure": "Skonfiguruj",
|
||||||
"main_settings": "Ustawienia główne",
|
"main_settings": "Ustawienia główne",
|
||||||
"block_services": "Blokuj określone usługi",
|
"block_services": "Zablokuj określone usługi",
|
||||||
"blocked_services": "Zablokowane usługi",
|
"blocked_services": "Zablokowane usługi",
|
||||||
"blocked_services_desc": "Pozwala szybko zablokować popularne witryny i usługi.",
|
"blocked_services_desc": "Pozwala szybko zablokować popularne witryny i usługi.",
|
||||||
"blocked_services_saved": "Zablokowane usługi zostały pomyślnie zapisane",
|
"blocked_services_saved": "Zablokowane usługi zostały pomyślnie zapisane",
|
||||||
@@ -444,7 +444,7 @@
|
|||||||
"stats_params": "Konfiguracja statystyk",
|
"stats_params": "Konfiguracja statystyk",
|
||||||
"config_successfully_saved": "Konfiguracja została pomyślnie zapisana",
|
"config_successfully_saved": "Konfiguracja została pomyślnie zapisana",
|
||||||
"interval_24_hour": "24 godziny",
|
"interval_24_hour": "24 godziny",
|
||||||
"interval_days": "{{count}} dzień",
|
"interval_days": "{{count}} dni",
|
||||||
"interval_days_plural": "{{count}} dni",
|
"interval_days_plural": "{{count}} dni",
|
||||||
"domain": "Domena",
|
"domain": "Domena",
|
||||||
"answer": "Odpowiedź",
|
"answer": "Odpowiedź",
|
||||||
@@ -562,5 +562,6 @@
|
|||||||
"filter_category_regional_desc": "Listy, które koncentrują się na reklamach regionalnych i serwerach ze skryptami śledzącymi",
|
"filter_category_regional_desc": "Listy, które koncentrują się na reklamach regionalnych i serwerach ze skryptami śledzącymi",
|
||||||
"filter_category_other_desc": "Inne listy zablokowanych",
|
"filter_category_other_desc": "Inne listy zablokowanych",
|
||||||
"original_response": "Oryginalna odpowiedź",
|
"original_response": "Oryginalna odpowiedź",
|
||||||
"click_to_view_queries": "Kliknij, aby wyświetlić zapytania"
|
"click_to_view_queries": "Kliknij, aby wyświetlić zapytania",
|
||||||
|
"port_53_faq_link": "Port 53 jest często zajęty przez usługi \"DNSStubListener\" lub \"systemd-resolved\". Przeczytaj <0>tę instrukcję</0> jak to rozwiązać."
|
||||||
}
|
}
|
||||||
@@ -562,5 +562,6 @@
|
|||||||
"filter_category_regional_desc": "Liste focalizate pe reclame regionale și servere de urmărire",
|
"filter_category_regional_desc": "Liste focalizate pe reclame regionale și servere de urmărire",
|
||||||
"filter_category_other_desc": "Alte liste de blocări",
|
"filter_category_other_desc": "Alte liste de blocări",
|
||||||
"original_response": "Răspuns original",
|
"original_response": "Răspuns original",
|
||||||
"click_to_view_queries": "Clicați pentru a vizualiza interogări"
|
"click_to_view_queries": "Clicați pentru a vizualiza interogări",
|
||||||
|
"port_53_faq_link": "Portul 53 este adesea ocupat de serviciile \"DNSStubListener\" sau \"systemd-resolved\". Vă rugăm să citiți <0>această instrucțiune</0> despre cum să rezolvați aceasta."
|
||||||
}
|
}
|
||||||
@@ -530,7 +530,7 @@
|
|||||||
"dnssec_enable_desc": "Установите флаг DNSSEC в исходящих DNS-запросах и проверьте результат (требуется резолвер с поддержкой DNSSEC)",
|
"dnssec_enable_desc": "Установите флаг DNSSEC в исходящих DNS-запросах и проверьте результат (требуется резолвер с поддержкой DNSSEC)",
|
||||||
"validated_with_dnssec": "Подтверждено с помощью DNSSEC",
|
"validated_with_dnssec": "Подтверждено с помощью DNSSEC",
|
||||||
"all_queries": "Все запросы",
|
"all_queries": "Все запросы",
|
||||||
"show_blocked_responses": "Blocked",
|
"show_blocked_responses": "Заблокировано",
|
||||||
"show_whitelisted_responses": "В белом списке",
|
"show_whitelisted_responses": "В белом списке",
|
||||||
"show_processed_responses": "Обработан",
|
"show_processed_responses": "Обработан",
|
||||||
"blocked_safebrowsing": "Заблокировано согласно базе данных Safebrowsing",
|
"blocked_safebrowsing": "Заблокировано согласно базе данных Safebrowsing",
|
||||||
@@ -562,5 +562,6 @@
|
|||||||
"filter_category_regional_desc": "Списки, которые фокусируются на региональной рекламе и серверах отслеживания",
|
"filter_category_regional_desc": "Списки, которые фокусируются на региональной рекламе и серверах отслеживания",
|
||||||
"filter_category_other_desc": "Другие списки блокировки",
|
"filter_category_other_desc": "Другие списки блокировки",
|
||||||
"original_response": "Первоначальный ответ",
|
"original_response": "Первоначальный ответ",
|
||||||
"click_to_view_queries": "Нажмите, чтобы просмотреть запросы"
|
"click_to_view_queries": "Нажмите, чтобы просмотреть запросы",
|
||||||
|
"port_53_faq_link": "Порт 53 часто занят службами \"DNSStubListener\" или \"systemd-resolved\". Ознакомьтесь с <0>инструкцией</0> о том, как это разрешить."
|
||||||
}
|
}
|
||||||
@@ -562,5 +562,6 @@
|
|||||||
"filter_category_regional_desc": "Zoznamy zamerané na regionálne reklamy a sledovacie servery",
|
"filter_category_regional_desc": "Zoznamy zamerané na regionálne reklamy a sledovacie servery",
|
||||||
"filter_category_other_desc": "Iné blokovacie zoznamy",
|
"filter_category_other_desc": "Iné blokovacie zoznamy",
|
||||||
"original_response": "Pôvodná odozva",
|
"original_response": "Pôvodná odozva",
|
||||||
"click_to_view_queries": "Kliknite pre zobrazenie dopytov"
|
"click_to_view_queries": "Kliknite pre zobrazenie dopytov",
|
||||||
|
"port_53_faq_link": "Port 53 je často obsadený službami \"DNSStubListener\" alebo \"systemd-resolved\". Prečítajte si <0>tento návod</0> o tom, ako to vyriešiť."
|
||||||
}
|
}
|
||||||
@@ -562,5 +562,6 @@
|
|||||||
"filter_category_regional_desc": "Seznami, ki so osredotočeni na področne oglase in strežnike za sledenje",
|
"filter_category_regional_desc": "Seznami, ki so osredotočeni na področne oglase in strežnike za sledenje",
|
||||||
"filter_category_other_desc": "Drugi seznami za zaviranje",
|
"filter_category_other_desc": "Drugi seznami za zaviranje",
|
||||||
"original_response": "Izviren odgovor",
|
"original_response": "Izviren odgovor",
|
||||||
"click_to_view_queries": "Kliknite za prikaz poizvedb"
|
"click_to_view_queries": "Kliknite za prikaz poizvedb",
|
||||||
|
"port_53_faq_link": "Vrata 53 pogosto zasedajo storitve 'DNSStubListener' ali 'Sistemsko razrešene storitve'. Preberite <0>to navodilo</0> o tem, kako to rešiti."
|
||||||
}
|
}
|
||||||
@@ -562,5 +562,6 @@
|
|||||||
"filter_category_regional_desc": "Lista koja se usredsređuje na regionalne reklame i servere praćenja",
|
"filter_category_regional_desc": "Lista koja se usredsređuje na regionalne reklame i servere praćenja",
|
||||||
"filter_category_other_desc": "Ostale liste blokiranja",
|
"filter_category_other_desc": "Ostale liste blokiranja",
|
||||||
"original_response": "Izvorni odgovor",
|
"original_response": "Izvorni odgovor",
|
||||||
"click_to_view_queries": "Kliknite da pogledate zahteve"
|
"click_to_view_queries": "Kliknite da pogledate zahteve",
|
||||||
|
"port_53_faq_link": "Port 53 je najčešće zauzet od \"DNSStubListener\" ili \"systemd-resolved\" usluga. Pročitajte <0>ovo uputstvo</0> kako da to rešite."
|
||||||
}
|
}
|
||||||
@@ -239,7 +239,7 @@
|
|||||||
"rule_label": "Kural",
|
"rule_label": "Kural",
|
||||||
"list_label": "Liste",
|
"list_label": "Liste",
|
||||||
"unknown_filter": "Bilinmeyen filtre {{filterId}}",
|
"unknown_filter": "Bilinmeyen filtre {{filterId}}",
|
||||||
"install_welcome_title": "AdGuard Home'a hoşgeldiniz!",
|
"install_welcome_title": "AdGuard Home'a hoş geldiniz!",
|
||||||
"install_welcome_desc": "AdGuard Home, ağ genelinde reklam ve izleyicileri engelleyen bir DNS sunucusudur. Tüm ağınızı ve tüm cihazlarınızı kontrol etmenize yarayan bir araçtır, istemci tarafında bir program kullanmanıza gerek duymaz.",
|
"install_welcome_desc": "AdGuard Home, ağ genelinde reklam ve izleyicileri engelleyen bir DNS sunucusudur. Tüm ağınızı ve tüm cihazlarınızı kontrol etmenize yarayan bir araçtır, istemci tarafında bir program kullanmanıza gerek duymaz.",
|
||||||
"install_settings_title": "Yönetici Web Arayüzü",
|
"install_settings_title": "Yönetici Web Arayüzü",
|
||||||
"install_settings_listen": "Dinleme arayüzü",
|
"install_settings_listen": "Dinleme arayüzü",
|
||||||
@@ -505,5 +505,6 @@
|
|||||||
"blocked_adult_websites": "Yetişkin içerikli site engellendi",
|
"blocked_adult_websites": "Yetişkin içerikli site engellendi",
|
||||||
"blocked_threats": "Engellenen Tehditler",
|
"blocked_threats": "Engellenen Tehditler",
|
||||||
"allowed": "İzin verildi",
|
"allowed": "İzin verildi",
|
||||||
"blocklist": "Engellenen listesi"
|
"blocklist": "Engellenen listesi",
|
||||||
|
"port_53_faq_link": "Port 53 genellikle \"DNSStubListener\" veya \"sistemd-resolved\" hizmetler tarafından kullanılır. Lütfen problemin nasıl çözüleceğine ilişkin <0>bu talimatı</0> okuyun."
|
||||||
}
|
}
|
||||||
@@ -562,5 +562,6 @@
|
|||||||
"filter_category_regional_desc": "专注于区域广告和跟踪服务器的列表",
|
"filter_category_regional_desc": "专注于区域广告和跟踪服务器的列表",
|
||||||
"filter_category_other_desc": "其他阻止列表",
|
"filter_category_other_desc": "其他阻止列表",
|
||||||
"original_response": "原始响应",
|
"original_response": "原始响应",
|
||||||
"click_to_view_queries": "点击查看查询"
|
"click_to_view_queries": "点击查看查询",
|
||||||
|
"port_53_faq_link": "53端口常被DNSStubListener或systemdn解析的服务占用。请阅读<0>这份关于如何解决这一问题的说明</0>"
|
||||||
}
|
}
|
||||||
@@ -544,8 +544,8 @@
|
|||||||
"milliseconds_abbreviation": "ms",
|
"milliseconds_abbreviation": "ms",
|
||||||
"cache_size": "快取大小",
|
"cache_size": "快取大小",
|
||||||
"cache_size_desc": "DNS 快取大小(以位元組)",
|
"cache_size_desc": "DNS 快取大小(以位元組)",
|
||||||
"cache_ttl_min_override": "覆寫最小的存活時間(TTL)",
|
"cache_ttl_min_override": "覆寫最小的存活時間(TTL)(以秒數)",
|
||||||
"cache_ttl_max_override": "覆寫最大的存活時間(TTL)",
|
"cache_ttl_max_override": "覆寫最大的存活時間(TTL)(以秒數)",
|
||||||
"enter_cache_size": "輸入快取大小",
|
"enter_cache_size": "輸入快取大小",
|
||||||
"enter_cache_ttl_min_override": "輸入最小的存活時間(TTL)",
|
"enter_cache_ttl_min_override": "輸入最小的存活時間(TTL)",
|
||||||
"enter_cache_ttl_max_override": "輸入最大的存活時間(TTL)",
|
"enter_cache_ttl_max_override": "輸入最大的存活時間(TTL)",
|
||||||
@@ -562,5 +562,6 @@
|
|||||||
"filter_category_regional_desc": "專注於區域性的廣告和追蹤伺服器之清單",
|
"filter_category_regional_desc": "專注於區域性的廣告和追蹤伺服器之清單",
|
||||||
"filter_category_other_desc": "其它的封鎖清單",
|
"filter_category_other_desc": "其它的封鎖清單",
|
||||||
"original_response": "原始的回應",
|
"original_response": "原始的回應",
|
||||||
"click_to_view_queries": "點擊以檢視查詢"
|
"click_to_view_queries": "點擊以檢視查詢",
|
||||||
|
"port_53_faq_link": "連接埠 53 常被 \"DNSStubListener\" 或 \"systemd-resolved\" 服務佔用。請閱讀有關如何解決這個的<0>用法說明</0>。"
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { getIpMatchListStatus } from '../helpers/helpers';
|
import { getIpMatchListStatus, sortIp } from '../helpers/helpers';
|
||||||
import { IP_MATCH_LIST_STATUS } from '../helpers/constants';
|
import { IP_MATCH_LIST_STATUS } from '../helpers/constants';
|
||||||
|
|
||||||
describe('getIpMatchListStatus', () => {
|
describe('getIpMatchListStatus', () => {
|
||||||
@@ -129,3 +129,356 @@ describe('getIpMatchListStatus', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('sortIp', () => {
|
||||||
|
describe('ipv4', () => {
|
||||||
|
test('one octet differ', () => {
|
||||||
|
const arr = [
|
||||||
|
'127.0.2.0',
|
||||||
|
'127.0.3.0',
|
||||||
|
'127.0.1.0',
|
||||||
|
];
|
||||||
|
const sortedArr = [
|
||||||
|
'127.0.1.0',
|
||||||
|
'127.0.2.0',
|
||||||
|
'127.0.3.0',
|
||||||
|
];
|
||||||
|
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
|
||||||
|
});
|
||||||
|
test('few octets differ', () => {
|
||||||
|
const arr = [
|
||||||
|
'192.168.11.10',
|
||||||
|
'192.168.10.0',
|
||||||
|
'192.168.11.11',
|
||||||
|
'192.168.10.10',
|
||||||
|
'192.168.1.10',
|
||||||
|
'192.168.0.1',
|
||||||
|
'192.168.1.0',
|
||||||
|
'192.168.1.1',
|
||||||
|
'192.168.11.0',
|
||||||
|
'192.168.0.10',
|
||||||
|
'192.168.10.11',
|
||||||
|
'192.168.0.11',
|
||||||
|
'192.168.1.11',
|
||||||
|
'192.168.0.0',
|
||||||
|
'192.168.10.1',
|
||||||
|
'192.168.11.1',
|
||||||
|
];
|
||||||
|
const sortedArr = [
|
||||||
|
'192.168.0.0',
|
||||||
|
'192.168.0.1',
|
||||||
|
'192.168.0.10',
|
||||||
|
'192.168.0.11',
|
||||||
|
'192.168.1.0',
|
||||||
|
'192.168.1.1',
|
||||||
|
'192.168.1.10',
|
||||||
|
'192.168.1.11',
|
||||||
|
'192.168.10.0',
|
||||||
|
'192.168.10.1',
|
||||||
|
'192.168.10.10',
|
||||||
|
'192.168.10.11',
|
||||||
|
'192.168.11.0',
|
||||||
|
'192.168.11.1',
|
||||||
|
'192.168.11.10',
|
||||||
|
'192.168.11.11',
|
||||||
|
];
|
||||||
|
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
|
||||||
|
|
||||||
|
// Example from issue https://github.com/AdguardTeam/AdGuardHome/issues/1778#issuecomment-640937599
|
||||||
|
const arr2 = [
|
||||||
|
'192.168.2.11',
|
||||||
|
'192.168.3.1',
|
||||||
|
'192.168.2.100',
|
||||||
|
'192.168.2.2',
|
||||||
|
'192.168.2.1',
|
||||||
|
'192.168.2.10',
|
||||||
|
'192.168.2.99',
|
||||||
|
'192.168.2.200',
|
||||||
|
'192.168.2.199',
|
||||||
|
];
|
||||||
|
const sortedArr2 = [
|
||||||
|
'192.168.2.1',
|
||||||
|
'192.168.2.2',
|
||||||
|
'192.168.2.10',
|
||||||
|
'192.168.2.11',
|
||||||
|
'192.168.2.99',
|
||||||
|
'192.168.2.100',
|
||||||
|
'192.168.2.199',
|
||||||
|
'192.168.2.200',
|
||||||
|
'192.168.3.1',
|
||||||
|
];
|
||||||
|
expect(arr2.sort(sortIp)).toStrictEqual(sortedArr2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('ipv6', () => {
|
||||||
|
test('only long form', () => {
|
||||||
|
const arr = [
|
||||||
|
'2001:db8:11a3:9d7:0:0:0:2',
|
||||||
|
'2001:db8:11a3:9d7:0:0:0:3',
|
||||||
|
'2001:db8:11a3:9d7:0:0:0:1',
|
||||||
|
];
|
||||||
|
const sortedArr = [
|
||||||
|
'2001:db8:11a3:9d7:0:0:0:1',
|
||||||
|
'2001:db8:11a3:9d7:0:0:0:2',
|
||||||
|
'2001:db8:11a3:9d7:0:0:0:3',
|
||||||
|
];
|
||||||
|
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
|
||||||
|
});
|
||||||
|
test('only short form', () => {
|
||||||
|
const arr = [
|
||||||
|
'2001:db8::',
|
||||||
|
'2001:db7::',
|
||||||
|
'2001:db9::',
|
||||||
|
];
|
||||||
|
const sortedArr = [
|
||||||
|
'2001:db7::',
|
||||||
|
'2001:db8::',
|
||||||
|
'2001:db9::',
|
||||||
|
];
|
||||||
|
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
|
||||||
|
});
|
||||||
|
test('long and short forms', () => {
|
||||||
|
const arr = [
|
||||||
|
'2001:db8::',
|
||||||
|
'2001:db7:11a3:9d7:0:0:0:2',
|
||||||
|
'2001:db6:11a3:9d7:0:0:0:1',
|
||||||
|
'2001:db6::',
|
||||||
|
'2001:db7:11a3:9d7:0:0:0:1',
|
||||||
|
'2001:db7::',
|
||||||
|
];
|
||||||
|
const sortedArr = [
|
||||||
|
'2001:db6::',
|
||||||
|
'2001:db6:11a3:9d7:0:0:0:1',
|
||||||
|
'2001:db7::',
|
||||||
|
'2001:db7:11a3:9d7:0:0:0:1',
|
||||||
|
'2001:db7:11a3:9d7:0:0:0:2',
|
||||||
|
'2001:db8::',
|
||||||
|
];
|
||||||
|
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('ipv4 and ipv6', () => {
|
||||||
|
test('ipv6 long form', () => {
|
||||||
|
const arr = [
|
||||||
|
'127.0.0.3',
|
||||||
|
'2001:db8:11a3:9d7:0:0:0:1',
|
||||||
|
'2001:db8:11a3:9d7:0:0:0:3',
|
||||||
|
'127.0.0.1',
|
||||||
|
'2001:db8:11a3:9d7:0:0:0:2',
|
||||||
|
'127.0.0.2',
|
||||||
|
];
|
||||||
|
const sortedArr = [
|
||||||
|
'127.0.0.1',
|
||||||
|
'127.0.0.2',
|
||||||
|
'127.0.0.3',
|
||||||
|
'2001:db8:11a3:9d7:0:0:0:1',
|
||||||
|
'2001:db8:11a3:9d7:0:0:0:2',
|
||||||
|
'2001:db8:11a3:9d7:0:0:0:3',
|
||||||
|
];
|
||||||
|
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
|
||||||
|
});
|
||||||
|
test('ipv6 short form', () => {
|
||||||
|
const arr = [
|
||||||
|
'2001:db8:11a3:9d7::1',
|
||||||
|
'127.0.0.3',
|
||||||
|
'2001:db8:11a3:9d7::3',
|
||||||
|
'127.0.0.1',
|
||||||
|
'2001:db8:11a3:9d7::2',
|
||||||
|
'127.0.0.2',
|
||||||
|
];
|
||||||
|
const sortedArr = [
|
||||||
|
'127.0.0.1',
|
||||||
|
'127.0.0.2',
|
||||||
|
'127.0.0.3',
|
||||||
|
'2001:db8:11a3:9d7::1',
|
||||||
|
'2001:db8:11a3:9d7::2',
|
||||||
|
'2001:db8:11a3:9d7::3',
|
||||||
|
];
|
||||||
|
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
|
||||||
|
});
|
||||||
|
test('ipv6 long and short forms', () => {
|
||||||
|
const arr = [
|
||||||
|
'2001:db8:11a3:9d7::1',
|
||||||
|
'127.0.0.3',
|
||||||
|
'2001:db8:11a3:9d7:0:0:0:2',
|
||||||
|
'127.0.0.1',
|
||||||
|
'2001:db8:11a3:9d7::3',
|
||||||
|
'127.0.0.2',
|
||||||
|
];
|
||||||
|
const sortedArr = [
|
||||||
|
'127.0.0.1',
|
||||||
|
'127.0.0.2',
|
||||||
|
'127.0.0.3',
|
||||||
|
'2001:db8:11a3:9d7::1',
|
||||||
|
'2001:db8:11a3:9d7:0:0:0:2',
|
||||||
|
'2001:db8:11a3:9d7::3',
|
||||||
|
];
|
||||||
|
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
|
||||||
|
});
|
||||||
|
test('always put ipv4 before ipv6', () => {
|
||||||
|
const arr = [
|
||||||
|
'::1',
|
||||||
|
'0.0.0.2',
|
||||||
|
'127.0.0.1',
|
||||||
|
'::2',
|
||||||
|
'2001:db8:11a3:9d7:0:0:0:2',
|
||||||
|
'0.0.0.1',
|
||||||
|
'2001:db8:11a3:9d7::1',
|
||||||
|
];
|
||||||
|
const sortedArr = [
|
||||||
|
'0.0.0.1',
|
||||||
|
'0.0.0.2',
|
||||||
|
'127.0.0.1',
|
||||||
|
'::1',
|
||||||
|
'::2',
|
||||||
|
'2001:db8:11a3:9d7::1',
|
||||||
|
'2001:db8:11a3:9d7:0:0:0:2',
|
||||||
|
];
|
||||||
|
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('cidr', () => {
|
||||||
|
test('only ipv4 cidr', () => {
|
||||||
|
const arr = [
|
||||||
|
'192.168.0.1/9',
|
||||||
|
'192.168.0.1/7',
|
||||||
|
'192.168.0.1/8',
|
||||||
|
];
|
||||||
|
const sortedArr = [
|
||||||
|
'192.168.0.1/7',
|
||||||
|
'192.168.0.1/8',
|
||||||
|
'192.168.0.1/9',
|
||||||
|
];
|
||||||
|
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
|
||||||
|
});
|
||||||
|
test('ipv4 and cidr ipv4', () => {
|
||||||
|
const arr = [
|
||||||
|
'192.168.0.1/9',
|
||||||
|
'192.168.0.1',
|
||||||
|
'192.168.0.1/32',
|
||||||
|
'192.168.0.1/7',
|
||||||
|
'192.168.0.1/8',
|
||||||
|
];
|
||||||
|
const sortedArr = [
|
||||||
|
'192.168.0.1/7',
|
||||||
|
'192.168.0.1/8',
|
||||||
|
'192.168.0.1/9',
|
||||||
|
'192.168.0.1/32',
|
||||||
|
'192.168.0.1',
|
||||||
|
];
|
||||||
|
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
|
||||||
|
});
|
||||||
|
test('only ipv6 cidr', () => {
|
||||||
|
const arr = [
|
||||||
|
'2001:db8:11a3:9d7::1/32',
|
||||||
|
'2001:db8:11a3:9d7::1/64',
|
||||||
|
'2001:db8:11a3:9d7::1/128',
|
||||||
|
'2001:db8:11a3:9d7::1/24',
|
||||||
|
];
|
||||||
|
const sortedArr = [
|
||||||
|
'2001:db8:11a3:9d7::1/24',
|
||||||
|
'2001:db8:11a3:9d7::1/32',
|
||||||
|
'2001:db8:11a3:9d7::1/64',
|
||||||
|
'2001:db8:11a3:9d7::1/128',
|
||||||
|
];
|
||||||
|
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
|
||||||
|
});
|
||||||
|
test('ipv6 and cidr ipv6', () => {
|
||||||
|
const arr = [
|
||||||
|
'2001:db8:11a3:9d7::1/32',
|
||||||
|
'2001:db8:11a3:9d7::1',
|
||||||
|
'2001:db8:11a3:9d7::1/64',
|
||||||
|
'2001:db8:11a3:9d7::1/128',
|
||||||
|
'2001:db8:11a3:9d7::1/24',
|
||||||
|
];
|
||||||
|
const sortedArr = [
|
||||||
|
'2001:db8:11a3:9d7::1/24',
|
||||||
|
'2001:db8:11a3:9d7::1/32',
|
||||||
|
'2001:db8:11a3:9d7::1/64',
|
||||||
|
'2001:db8:11a3:9d7::1/128',
|
||||||
|
'2001:db8:11a3:9d7::1',
|
||||||
|
];
|
||||||
|
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('invalid input', () => {
|
||||||
|
const originalError = console.error;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
console.error = jest.fn();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
expect(console.error).toHaveBeenCalled();
|
||||||
|
console.error = originalError;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('invalid strings', () => {
|
||||||
|
const arr = ['invalid ip', 'invalid cidr'];
|
||||||
|
expect(arr.sort(sortIp)).toStrictEqual(arr);
|
||||||
|
});
|
||||||
|
test('invalid ip', () => {
|
||||||
|
const arr = ['127.0.0.2.', '.127.0.0.1.', '.2001:db8:11a3:9d7:0:0:0:0'];
|
||||||
|
expect(arr.sort(sortIp)).toStrictEqual(arr);
|
||||||
|
});
|
||||||
|
test('invalid cidr', () => {
|
||||||
|
const arr = ['127.0.0.2/33', '2001:db8:11a3:9d7:0:0:0:0/129'];
|
||||||
|
expect(arr.sort(sortIp)).toStrictEqual(arr);
|
||||||
|
});
|
||||||
|
test('valid and invalid ip', () => {
|
||||||
|
const arr = ['127.0.0.4.', '127.0.0.1', '.127.0.0.3', '127.0.0.2'];
|
||||||
|
expect(arr.sort(sortIp)).toStrictEqual(arr);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('mixed', () => {
|
||||||
|
test('ipv4, ipv6 in short and long forms and cidr', () => {
|
||||||
|
const arr = [
|
||||||
|
'2001:db8:11a3:9d7:0:0:0:1/32',
|
||||||
|
'192.168.1.2',
|
||||||
|
'127.0.0.2',
|
||||||
|
'2001:db8:11a3:9d7::1/128',
|
||||||
|
'2001:db8:11a3:9d7:0:0:0:1',
|
||||||
|
'127.0.0.1/12',
|
||||||
|
'192.168.1.1',
|
||||||
|
'2001:db8::/32',
|
||||||
|
'2001:db8:11a3:9d7::1/24',
|
||||||
|
'192.168.1.2/12',
|
||||||
|
'2001:db7::/32',
|
||||||
|
'127.0.0.1',
|
||||||
|
'2001:db8:11a3:9d7:0:0:0:2',
|
||||||
|
'192.168.1.1/24',
|
||||||
|
'2001:db7::/64',
|
||||||
|
'2001:db7::',
|
||||||
|
'2001:db8::',
|
||||||
|
'2001:db8:11a3:9d7:0:0:0:1/128',
|
||||||
|
'192.168.1.1/12',
|
||||||
|
'127.0.0.1/32',
|
||||||
|
'::1',
|
||||||
|
];
|
||||||
|
const sortedArr = [
|
||||||
|
'127.0.0.1/12',
|
||||||
|
'127.0.0.1/32',
|
||||||
|
'127.0.0.1',
|
||||||
|
'127.0.0.2',
|
||||||
|
'192.168.1.1/12',
|
||||||
|
'192.168.1.1/24',
|
||||||
|
'192.168.1.1',
|
||||||
|
'192.168.1.2/12',
|
||||||
|
'192.168.1.2',
|
||||||
|
'::1',
|
||||||
|
'2001:db7::/32',
|
||||||
|
'2001:db7::/64',
|
||||||
|
'2001:db7::',
|
||||||
|
'2001:db8::/32',
|
||||||
|
'2001:db8::',
|
||||||
|
'2001:db8:11a3:9d7::1/24',
|
||||||
|
'2001:db8:11a3:9d7:0:0:0:1/32',
|
||||||
|
'2001:db8:11a3:9d7::1/128',
|
||||||
|
'2001:db8:11a3:9d7:0:0:0:1/128',
|
||||||
|
'2001:db8:11a3:9d7:0:0:0:1',
|
||||||
|
'2001:db8:11a3:9d7:0:0:0:2',
|
||||||
|
];
|
||||||
|
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ import { createAction } from 'redux-actions';
|
|||||||
import i18next from 'i18next';
|
import i18next from 'i18next';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
import { isVersionGreater, splitByNewLine, sortClients } from '../helpers/helpers';
|
import { splitByNewLine, sortClients } from '../helpers/helpers';
|
||||||
import { CHECK_TIMEOUT, SETTINGS_NAMES } from '../helpers/constants';
|
import { CHECK_TIMEOUT, SETTINGS_NAMES } from '../helpers/constants';
|
||||||
|
import { areEqualVersions } from '../helpers/version';
|
||||||
import { getTlsStatus } from './encryption';
|
import { getTlsStatus } from './encryption';
|
||||||
import apiClient from '../api/Api';
|
import apiClient from '../api/Api';
|
||||||
import { addErrorToast, addNoticeToast, addSuccessToast } from './toasts';
|
import { addErrorToast, addNoticeToast, addSuccessToast } from './toasts';
|
||||||
@@ -121,7 +122,7 @@ export const getVersion = (recheck = false) => async (dispatch, getState) => {
|
|||||||
const { dnsVersion } = getState().dashboard;
|
const { dnsVersion } = getState().dashboard;
|
||||||
const currentVersion = dnsVersion === 'undefined' ? 0 : dnsVersion;
|
const currentVersion = dnsVersion === 'undefined' ? 0 : dnsVersion;
|
||||||
|
|
||||||
if (data && isVersionGreater(currentVersion, data.new_version)) {
|
if (data && !areEqualVersions(currentVersion, data.new_version)) {
|
||||||
dispatch(addSuccessToast('updates_checked'));
|
dispatch(addSuccessToast('updates_checked'));
|
||||||
} else {
|
} else {
|
||||||
dispatch(addSuccessToast('updates_version_equal'));
|
dispatch(addSuccessToast('updates_version_equal'));
|
||||||
@@ -147,7 +148,7 @@ const checkStatus = async (handleRequestSuccess, handleRequestError, attempts =
|
|||||||
const rmTimeout = (t) => t && clearTimeout(t);
|
const rmTimeout = (t) => t && clearTimeout(t);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await axios.get('control/status');
|
const response = await axios.get(`${apiClient.baseUrl}/status`);
|
||||||
rmTimeout(timeout);
|
rmTimeout(timeout);
|
||||||
if (response?.status === 200) {
|
if (response?.status === 200) {
|
||||||
handleRequestSuccess(response);
|
handleRequestSuccess(response);
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ import axios from 'axios';
|
|||||||
|
|
||||||
import { getPathWithQueryString } from '../helpers/helpers';
|
import { getPathWithQueryString } from '../helpers/helpers';
|
||||||
import { R_PATH_LAST_PART } from '../helpers/constants';
|
import { R_PATH_LAST_PART } from '../helpers/constants';
|
||||||
|
import { BASE_URL } from '../../constants';
|
||||||
|
|
||||||
class Api {
|
class Api {
|
||||||
baseUrl = 'control';
|
baseUrl = BASE_URL;
|
||||||
|
|
||||||
async makeRequest(path, method = 'POST', config) {
|
async makeRequest(path, method = 'POST', config) {
|
||||||
try {
|
try {
|
||||||
@@ -26,18 +27,30 @@ class Api {
|
|||||||
|
|
||||||
throw new Error(`${errorPath} | ${error.response.data} | ${error.response.status}`);
|
throw new Error(`${errorPath} | ${error.response.data} | ${error.response.status}`);
|
||||||
}
|
}
|
||||||
throw new Error(`${errorPath} | ${error.message ? error.message : error}`);
|
throw new Error(`${errorPath} | ${error.message || error}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Global methods
|
// Global methods
|
||||||
GLOBAL_STATUS = { path: 'status', method: 'GET' };
|
GLOBAL_STATUS = {
|
||||||
|
path: 'status',
|
||||||
|
method: 'GET',
|
||||||
|
};
|
||||||
|
|
||||||
GLOBAL_TEST_UPSTREAM_DNS = { path: 'test_upstream_dns', method: 'POST' };
|
GLOBAL_TEST_UPSTREAM_DNS = {
|
||||||
|
path: 'test_upstream_dns',
|
||||||
|
method: 'POST',
|
||||||
|
};
|
||||||
|
|
||||||
GLOBAL_VERSION = { path: 'version.json', method: 'POST' };
|
GLOBAL_VERSION = {
|
||||||
|
path: 'version.json',
|
||||||
|
method: 'POST',
|
||||||
|
};
|
||||||
|
|
||||||
GLOBAL_UPDATE = { path: 'update', method: 'POST' };
|
GLOBAL_UPDATE = {
|
||||||
|
path: 'update',
|
||||||
|
method: 'POST',
|
||||||
|
};
|
||||||
|
|
||||||
getGlobalStatus() {
|
getGlobalStatus() {
|
||||||
const { path, method } = this.GLOBAL_STATUS;
|
const { path, method } = this.GLOBAL_STATUS;
|
||||||
@@ -68,21 +81,45 @@ class Api {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Filtering
|
// Filtering
|
||||||
FILTERING_STATUS = { path: 'filtering/status', method: 'GET' };
|
FILTERING_STATUS = {
|
||||||
|
path: 'filtering/status',
|
||||||
|
method: 'GET',
|
||||||
|
};
|
||||||
|
|
||||||
FILTERING_ADD_FILTER = { path: 'filtering/add_url', method: 'POST' };
|
FILTERING_ADD_FILTER = {
|
||||||
|
path: 'filtering/add_url',
|
||||||
|
method: 'POST',
|
||||||
|
};
|
||||||
|
|
||||||
FILTERING_REMOVE_FILTER = { path: 'filtering/remove_url', method: 'POST' };
|
FILTERING_REMOVE_FILTER = {
|
||||||
|
path: 'filtering/remove_url',
|
||||||
|
method: 'POST',
|
||||||
|
};
|
||||||
|
|
||||||
FILTERING_SET_RULES = { path: 'filtering/set_rules', method: 'POST' };
|
FILTERING_SET_RULES = {
|
||||||
|
path: 'filtering/set_rules',
|
||||||
|
method: 'POST',
|
||||||
|
};
|
||||||
|
|
||||||
FILTERING_REFRESH = { path: 'filtering/refresh', method: 'POST' };
|
FILTERING_REFRESH = {
|
||||||
|
path: 'filtering/refresh',
|
||||||
|
method: 'POST',
|
||||||
|
};
|
||||||
|
|
||||||
FILTERING_SET_URL = { path: 'filtering/set_url', method: 'POST' };
|
FILTERING_SET_URL = {
|
||||||
|
path: 'filtering/set_url',
|
||||||
|
method: 'POST',
|
||||||
|
};
|
||||||
|
|
||||||
FILTERING_CONFIG = { path: 'filtering/config', method: 'POST' };
|
FILTERING_CONFIG = {
|
||||||
|
path: 'filtering/config',
|
||||||
|
method: 'POST',
|
||||||
|
};
|
||||||
|
|
||||||
FILTERING_CHECK_HOST = { path: 'filtering/check_host', method: 'GET' };
|
FILTERING_CHECK_HOST = {
|
||||||
|
path: 'filtering/check_host',
|
||||||
|
method: 'GET',
|
||||||
|
};
|
||||||
|
|
||||||
getFilteringStatus() {
|
getFilteringStatus() {
|
||||||
const { path, method } = this.FILTERING_STATUS;
|
const { path, method } = this.FILTERING_STATUS;
|
||||||
@@ -153,11 +190,20 @@ class Api {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parental
|
// Parental
|
||||||
PARENTAL_STATUS = { path: 'parental/status', method: 'GET' };
|
PARENTAL_STATUS = {
|
||||||
|
path: 'parental/status',
|
||||||
|
method: 'GET',
|
||||||
|
};
|
||||||
|
|
||||||
PARENTAL_ENABLE = { path: 'parental/enable', method: 'POST' };
|
PARENTAL_ENABLE = {
|
||||||
|
path: 'parental/enable',
|
||||||
|
method: 'POST',
|
||||||
|
};
|
||||||
|
|
||||||
PARENTAL_DISABLE = { path: 'parental/disable', method: 'POST' };
|
PARENTAL_DISABLE = {
|
||||||
|
path: 'parental/disable',
|
||||||
|
method: 'POST',
|
||||||
|
};
|
||||||
|
|
||||||
getParentalStatus() {
|
getParentalStatus() {
|
||||||
const { path, method } = this.PARENTAL_STATUS;
|
const { path, method } = this.PARENTAL_STATUS;
|
||||||
@@ -180,11 +226,20 @@ class Api {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Safebrowsing
|
// Safebrowsing
|
||||||
SAFEBROWSING_STATUS = { path: 'safebrowsing/status', method: 'GET' };
|
SAFEBROWSING_STATUS = {
|
||||||
|
path: 'safebrowsing/status',
|
||||||
|
method: 'GET',
|
||||||
|
};
|
||||||
|
|
||||||
SAFEBROWSING_ENABLE = { path: 'safebrowsing/enable', method: 'POST' };
|
SAFEBROWSING_ENABLE = {
|
||||||
|
path: 'safebrowsing/enable',
|
||||||
|
method: 'POST',
|
||||||
|
};
|
||||||
|
|
||||||
SAFEBROWSING_DISABLE = { path: 'safebrowsing/disable', method: 'POST' };
|
SAFEBROWSING_DISABLE = {
|
||||||
|
path: 'safebrowsing/disable',
|
||||||
|
method: 'POST',
|
||||||
|
};
|
||||||
|
|
||||||
getSafebrowsingStatus() {
|
getSafebrowsingStatus() {
|
||||||
const { path, method } = this.SAFEBROWSING_STATUS;
|
const { path, method } = this.SAFEBROWSING_STATUS;
|
||||||
@@ -202,11 +257,20 @@ class Api {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Safesearch
|
// Safesearch
|
||||||
SAFESEARCH_STATUS = { path: 'safesearch/status', method: 'GET' };
|
SAFESEARCH_STATUS = {
|
||||||
|
path: 'safesearch/status',
|
||||||
|
method: 'GET',
|
||||||
|
};
|
||||||
|
|
||||||
SAFESEARCH_ENABLE = { path: 'safesearch/enable', method: 'POST' };
|
SAFESEARCH_ENABLE = {
|
||||||
|
path: 'safesearch/enable',
|
||||||
|
method: 'POST',
|
||||||
|
};
|
||||||
|
|
||||||
SAFESEARCH_DISABLE = { path: 'safesearch/disable', method: 'POST' };
|
SAFESEARCH_DISABLE = {
|
||||||
|
path: 'safesearch/disable',
|
||||||
|
method: 'POST',
|
||||||
|
};
|
||||||
|
|
||||||
getSafesearchStatus() {
|
getSafesearchStatus() {
|
||||||
const { path, method } = this.SAFESEARCH_STATUS;
|
const { path, method } = this.SAFESEARCH_STATUS;
|
||||||
@@ -224,9 +288,15 @@ class Api {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Language
|
// Language
|
||||||
CURRENT_LANGUAGE = { path: 'i18n/current_language', method: 'GET' };
|
CURRENT_LANGUAGE = {
|
||||||
|
path: 'i18n/current_language',
|
||||||
|
method: 'GET',
|
||||||
|
};
|
||||||
|
|
||||||
CHANGE_LANGUAGE = { path: 'i18n/change_language', method: 'POST' };
|
CHANGE_LANGUAGE = {
|
||||||
|
path: 'i18n/change_language',
|
||||||
|
method: 'POST',
|
||||||
|
};
|
||||||
|
|
||||||
getCurrentLanguage() {
|
getCurrentLanguage() {
|
||||||
const { path, method } = this.CURRENT_LANGUAGE;
|
const { path, method } = this.CURRENT_LANGUAGE;
|
||||||
@@ -243,19 +313,40 @@ class Api {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DHCP
|
// DHCP
|
||||||
DHCP_STATUS = { path: 'dhcp/status', method: 'GET' };
|
DHCP_STATUS = {
|
||||||
|
path: 'dhcp/status',
|
||||||
|
method: 'GET',
|
||||||
|
};
|
||||||
|
|
||||||
DHCP_SET_CONFIG = { path: 'dhcp/set_config', method: 'POST' };
|
DHCP_SET_CONFIG = {
|
||||||
|
path: 'dhcp/set_config',
|
||||||
|
method: 'POST',
|
||||||
|
};
|
||||||
|
|
||||||
DHCP_FIND_ACTIVE = { path: 'dhcp/find_active_dhcp', method: 'POST' };
|
DHCP_FIND_ACTIVE = {
|
||||||
|
path: 'dhcp/find_active_dhcp',
|
||||||
|
method: 'POST',
|
||||||
|
};
|
||||||
|
|
||||||
DHCP_INTERFACES = { path: 'dhcp/interfaces', method: 'GET' };
|
DHCP_INTERFACES = {
|
||||||
|
path: 'dhcp/interfaces',
|
||||||
|
method: 'GET',
|
||||||
|
};
|
||||||
|
|
||||||
DHCP_ADD_STATIC_LEASE = { path: 'dhcp/add_static_lease', method: 'POST' };
|
DHCP_ADD_STATIC_LEASE = {
|
||||||
|
path: 'dhcp/add_static_lease',
|
||||||
|
method: 'POST',
|
||||||
|
};
|
||||||
|
|
||||||
DHCP_REMOVE_STATIC_LEASE = { path: 'dhcp/remove_static_lease', method: 'POST' };
|
DHCP_REMOVE_STATIC_LEASE = {
|
||||||
|
path: 'dhcp/remove_static_lease',
|
||||||
|
method: 'POST',
|
||||||
|
};
|
||||||
|
|
||||||
DHCP_RESET = { path: 'dhcp/reset', method: 'POST' };
|
DHCP_RESET = {
|
||||||
|
path: 'dhcp/reset',
|
||||||
|
method: 'POST',
|
||||||
|
};
|
||||||
|
|
||||||
getDhcpStatus() {
|
getDhcpStatus() {
|
||||||
const { path, method } = this.DHCP_STATUS;
|
const { path, method } = this.DHCP_STATUS;
|
||||||
@@ -309,11 +400,20 @@ class Api {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Installation
|
// Installation
|
||||||
INSTALL_GET_ADDRESSES = { path: 'install/get_addresses', method: 'GET' };
|
INSTALL_GET_ADDRESSES = {
|
||||||
|
path: 'install/get_addresses',
|
||||||
|
method: 'GET',
|
||||||
|
};
|
||||||
|
|
||||||
INSTALL_CONFIGURE = { path: 'install/configure', method: 'POST' };
|
INSTALL_CONFIGURE = {
|
||||||
|
path: 'install/configure',
|
||||||
|
method: 'POST',
|
||||||
|
};
|
||||||
|
|
||||||
INSTALL_CHECK_CONFIG = { path: 'install/check_config', method: 'POST' };
|
INSTALL_CHECK_CONFIG = {
|
||||||
|
path: 'install/check_config',
|
||||||
|
method: 'POST',
|
||||||
|
};
|
||||||
|
|
||||||
getDefaultAddresses() {
|
getDefaultAddresses() {
|
||||||
const { path, method } = this.INSTALL_GET_ADDRESSES;
|
const { path, method } = this.INSTALL_GET_ADDRESSES;
|
||||||
@@ -339,11 +439,20 @@ class Api {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DNS-over-HTTPS and DNS-over-TLS
|
// DNS-over-HTTPS and DNS-over-TLS
|
||||||
TLS_STATUS = { path: 'tls/status', method: 'GET' };
|
TLS_STATUS = {
|
||||||
|
path: 'tls/status',
|
||||||
|
method: 'GET',
|
||||||
|
};
|
||||||
|
|
||||||
TLS_CONFIG = { path: 'tls/configure', method: 'POST' };
|
TLS_CONFIG = {
|
||||||
|
path: 'tls/configure',
|
||||||
|
method: 'POST',
|
||||||
|
};
|
||||||
|
|
||||||
TLS_VALIDATE = { path: 'tls/validate', method: 'POST' };
|
TLS_VALIDATE = {
|
||||||
|
path: 'tls/validate',
|
||||||
|
method: 'POST',
|
||||||
|
};
|
||||||
|
|
||||||
getTlsStatus() {
|
getTlsStatus() {
|
||||||
const { path, method } = this.TLS_STATUS;
|
const { path, method } = this.TLS_STATUS;
|
||||||
@@ -369,15 +478,30 @@ class Api {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Per-client settings
|
// Per-client settings
|
||||||
GET_CLIENTS = { path: 'clients', method: 'GET' };
|
GET_CLIENTS = {
|
||||||
|
path: 'clients',
|
||||||
|
method: 'GET',
|
||||||
|
};
|
||||||
|
|
||||||
FIND_CLIENTS = { path: 'clients/find', method: 'GET' };
|
FIND_CLIENTS = {
|
||||||
|
path: 'clients/find',
|
||||||
|
method: 'GET',
|
||||||
|
};
|
||||||
|
|
||||||
ADD_CLIENT = { path: 'clients/add', method: 'POST' };
|
ADD_CLIENT = {
|
||||||
|
path: 'clients/add',
|
||||||
|
method: 'POST',
|
||||||
|
};
|
||||||
|
|
||||||
DELETE_CLIENT = { path: 'clients/delete', method: 'POST' };
|
DELETE_CLIENT = {
|
||||||
|
path: 'clients/delete',
|
||||||
|
method: 'POST',
|
||||||
|
};
|
||||||
|
|
||||||
UPDATE_CLIENT = { path: 'clients/update', method: 'POST' };
|
UPDATE_CLIENT = {
|
||||||
|
path: 'clients/update',
|
||||||
|
method: 'POST',
|
||||||
|
};
|
||||||
|
|
||||||
getClients() {
|
getClients() {
|
||||||
const { path, method } = this.GET_CLIENTS;
|
const { path, method } = this.GET_CLIENTS;
|
||||||
@@ -418,9 +542,15 @@ class Api {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DNS access settings
|
// DNS access settings
|
||||||
ACCESS_LIST = { path: 'access/list', method: 'GET' };
|
ACCESS_LIST = {
|
||||||
|
path: 'access/list',
|
||||||
|
method: 'GET',
|
||||||
|
};
|
||||||
|
|
||||||
ACCESS_SET = { path: 'access/set', method: 'POST' };
|
ACCESS_SET = {
|
||||||
|
path: 'access/set',
|
||||||
|
method: 'POST',
|
||||||
|
};
|
||||||
|
|
||||||
getAccessList() {
|
getAccessList() {
|
||||||
const { path, method } = this.ACCESS_LIST;
|
const { path, method } = this.ACCESS_LIST;
|
||||||
@@ -437,11 +567,20 @@ class Api {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DNS rewrites
|
// DNS rewrites
|
||||||
REWRITES_LIST = { path: 'rewrite/list', method: 'GET' };
|
REWRITES_LIST = {
|
||||||
|
path: 'rewrite/list',
|
||||||
|
method: 'GET',
|
||||||
|
};
|
||||||
|
|
||||||
REWRITE_ADD = { path: 'rewrite/add', method: 'POST' };
|
REWRITE_ADD = {
|
||||||
|
path: 'rewrite/add',
|
||||||
|
method: 'POST',
|
||||||
|
};
|
||||||
|
|
||||||
REWRITE_DELETE = { path: 'rewrite/delete', method: 'POST' };
|
REWRITE_DELETE = {
|
||||||
|
path: 'rewrite/delete',
|
||||||
|
method: 'POST',
|
||||||
|
};
|
||||||
|
|
||||||
getRewritesList() {
|
getRewritesList() {
|
||||||
const { path, method } = this.REWRITES_LIST;
|
const { path, method } = this.REWRITES_LIST;
|
||||||
@@ -467,9 +606,15 @@ class Api {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Blocked services
|
// Blocked services
|
||||||
BLOCKED_SERVICES_LIST = { path: 'blocked_services/list', method: 'GET' };
|
BLOCKED_SERVICES_LIST = {
|
||||||
|
path: 'blocked_services/list',
|
||||||
|
method: 'GET',
|
||||||
|
};
|
||||||
|
|
||||||
BLOCKED_SERVICES_SET = { path: 'blocked_services/set', method: 'POST' };
|
BLOCKED_SERVICES_SET = {
|
||||||
|
path: 'blocked_services/set',
|
||||||
|
method: 'POST',
|
||||||
|
};
|
||||||
|
|
||||||
getBlockedServices() {
|
getBlockedServices() {
|
||||||
const { path, method } = this.BLOCKED_SERVICES_LIST;
|
const { path, method } = this.BLOCKED_SERVICES_LIST;
|
||||||
@@ -486,13 +631,25 @@ class Api {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Settings for statistics
|
// Settings for statistics
|
||||||
GET_STATS = { path: 'stats', method: 'GET' };
|
GET_STATS = {
|
||||||
|
path: 'stats',
|
||||||
|
method: 'GET',
|
||||||
|
};
|
||||||
|
|
||||||
STATS_INFO = { path: 'stats_info', method: 'GET' };
|
STATS_INFO = {
|
||||||
|
path: 'stats_info',
|
||||||
|
method: 'GET',
|
||||||
|
};
|
||||||
|
|
||||||
STATS_CONFIG = { path: 'stats_config', method: 'POST' };
|
STATS_CONFIG = {
|
||||||
|
path: 'stats_config',
|
||||||
|
method: 'POST',
|
||||||
|
};
|
||||||
|
|
||||||
STATS_RESET = { path: 'stats_reset', method: 'POST' };
|
STATS_RESET = {
|
||||||
|
path: 'stats_reset',
|
||||||
|
method: 'POST',
|
||||||
|
};
|
||||||
|
|
||||||
getStats() {
|
getStats() {
|
||||||
const { path, method } = this.GET_STATS;
|
const { path, method } = this.GET_STATS;
|
||||||
@@ -519,13 +676,25 @@ class Api {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Query log
|
// Query log
|
||||||
GET_QUERY_LOG = { path: 'querylog', method: 'GET' };
|
GET_QUERY_LOG = {
|
||||||
|
path: 'querylog',
|
||||||
|
method: 'GET',
|
||||||
|
};
|
||||||
|
|
||||||
QUERY_LOG_CONFIG = { path: 'querylog_config', method: 'POST' };
|
QUERY_LOG_CONFIG = {
|
||||||
|
path: 'querylog_config',
|
||||||
|
method: 'POST',
|
||||||
|
};
|
||||||
|
|
||||||
QUERY_LOG_INFO = { path: 'querylog_info', method: 'GET' };
|
QUERY_LOG_INFO = {
|
||||||
|
path: 'querylog_info',
|
||||||
|
method: 'GET',
|
||||||
|
};
|
||||||
|
|
||||||
QUERY_LOG_CLEAR = { path: 'querylog_clear', method: 'POST' };
|
QUERY_LOG_CLEAR = {
|
||||||
|
path: 'querylog_clear',
|
||||||
|
method: 'POST',
|
||||||
|
};
|
||||||
|
|
||||||
getQueryLog(params) {
|
getQueryLog(params) {
|
||||||
const { path, method } = this.GET_QUERY_LOG;
|
const { path, method } = this.GET_QUERY_LOG;
|
||||||
@@ -553,7 +722,10 @@ class Api {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Login
|
// Login
|
||||||
LOGIN = { path: 'login', method: 'POST' };
|
LOGIN = {
|
||||||
|
path: 'login',
|
||||||
|
method: 'POST',
|
||||||
|
};
|
||||||
|
|
||||||
login(data) {
|
login(data) {
|
||||||
const { path, method } = this.LOGIN;
|
const { path, method } = this.LOGIN;
|
||||||
@@ -565,7 +737,10 @@ class Api {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Profile
|
// Profile
|
||||||
GET_PROFILE = { path: 'profile', method: 'GET' };
|
GET_PROFILE = {
|
||||||
|
path: 'profile',
|
||||||
|
method: 'GET',
|
||||||
|
};
|
||||||
|
|
||||||
getProfile() {
|
getProfile() {
|
||||||
const { path, method } = this.GET_PROFILE;
|
const { path, method } = this.GET_PROFILE;
|
||||||
@@ -573,9 +748,15 @@ class Api {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DNS config
|
// DNS config
|
||||||
GET_DNS_CONFIG = { path: 'dns_info', method: 'GET' };
|
GET_DNS_CONFIG = {
|
||||||
|
path: 'dns_info',
|
||||||
|
method: 'GET',
|
||||||
|
};
|
||||||
|
|
||||||
SET_DNS_CONFIG = { path: 'dns_config', method: 'POST' };
|
SET_DNS_CONFIG = {
|
||||||
|
path: 'dns_config',
|
||||||
|
method: 'POST',
|
||||||
|
};
|
||||||
|
|
||||||
getDnsConfig() {
|
getDnsConfig() {
|
||||||
const { path, method } = this.GET_DNS_CONFIG;
|
const { path, method } = this.GET_DNS_CONFIG;
|
||||||
|
|||||||
@@ -28,10 +28,6 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 992px) {
|
@media screen and (max-width: 992px) {
|
||||||
.container {
|
|
||||||
padding: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container--wrap {
|
.container--wrap {
|
||||||
min-height: calc(100vh);
|
min-height: calc(100vh);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +1,16 @@
|
|||||||
import React, { Component, Fragment } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import { HashRouter, Route } from 'react-router-dom';
|
import { HashRouter, Route } from 'react-router-dom';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { withTranslation } from 'react-i18next';
|
|
||||||
import LoadingBar from 'react-redux-loading-bar';
|
import LoadingBar from 'react-redux-loading-bar';
|
||||||
|
import { hot } from 'react-hot-loader/root';
|
||||||
|
|
||||||
import 'react-table/react-table.css';
|
import 'react-table/react-table.css';
|
||||||
import '../ui/Tabler.css';
|
import '../ui/Tabler.css';
|
||||||
import '../ui/ReactTable.css';
|
import '../ui/ReactTable.css';
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
import Header from '../../containers/Header';
|
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||||
import Dashboard from '../../containers/Dashboard';
|
|
||||||
import Settings from '../../containers/Settings';
|
|
||||||
|
|
||||||
import CustomRules from '../../containers/CustomRules';
|
import propTypes from 'prop-types';
|
||||||
import DnsBlocklist from '../../containers/DnsBlocklist';
|
|
||||||
import DnsAllowlist from '../../containers/DnsAllowlist';
|
|
||||||
import DnsRewrites from '../../containers/DnsRewrites';
|
|
||||||
|
|
||||||
import Dns from '../../containers/Dns';
|
|
||||||
import Encryption from '../../containers/Encryption';
|
|
||||||
import Dhcp from '../../containers/Dhcp';
|
|
||||||
import Clients from '../../containers/Clients';
|
|
||||||
|
|
||||||
import Logs from '../../containers/Logs';
|
|
||||||
import SetupGuide from '../../containers/SetupGuide';
|
|
||||||
import Toasts from '../Toasts';
|
import Toasts from '../Toasts';
|
||||||
import Footer from '../ui/Footer';
|
import Footer from '../ui/Footer';
|
||||||
import Status from '../ui/Status';
|
import Status from '../ui/Status';
|
||||||
@@ -35,31 +21,107 @@ import Icons from '../ui/Icons';
|
|||||||
import i18n from '../../i18n';
|
import i18n from '../../i18n';
|
||||||
import Loading from '../ui/Loading';
|
import Loading from '../ui/Loading';
|
||||||
import { FILTERS_URLS, MENU_URLS, SETTINGS_URLS } from '../../helpers/constants';
|
import { FILTERS_URLS, MENU_URLS, SETTINGS_URLS } from '../../helpers/constants';
|
||||||
import Services from '../Filters/Services';
|
|
||||||
import { getLogsUrlParams, setHtmlLangAttr } from '../../helpers/helpers';
|
import { getLogsUrlParams, setHtmlLangAttr } from '../../helpers/helpers';
|
||||||
|
import Header from '../Header';
|
||||||
|
import { changeLanguage, getDnsStatus } from '../../actions';
|
||||||
|
|
||||||
class App extends Component {
|
import Dashboard from '../../containers/Dashboard';
|
||||||
componentDidMount() {
|
import Logs from '../../containers/Logs';
|
||||||
this.props.getDnsStatus();
|
import SetupGuide from '../../containers/SetupGuide';
|
||||||
}
|
import Settings from '../../containers/Settings';
|
||||||
|
import Dns from '../../containers/Dns';
|
||||||
|
import Encryption from '../../containers/Encryption';
|
||||||
|
import Dhcp from '../../containers/Dhcp';
|
||||||
|
import Clients from '../../containers/Clients';
|
||||||
|
import DnsBlocklist from '../../containers/DnsBlocklist';
|
||||||
|
import DnsAllowlist from '../../containers/DnsAllowlist';
|
||||||
|
import DnsRewrites from '../../containers/DnsRewrites';
|
||||||
|
import CustomRules from '../../containers/CustomRules';
|
||||||
|
import Services from '../Filters/Services';
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
const ROUTES = [
|
||||||
if (this.props.dashboard.language !== prevProps.dashboard.language) {
|
{
|
||||||
this.setLanguage();
|
path: MENU_URLS.root,
|
||||||
}
|
component: Dashboard,
|
||||||
}
|
exact: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: [`${MENU_URLS.logs}${getLogsUrlParams(':search?', ':response_status?')}`, MENU_URLS.logs],
|
||||||
|
component: Logs,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: MENU_URLS.guide,
|
||||||
|
component: SetupGuide,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: SETTINGS_URLS.settings,
|
||||||
|
component: Settings,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: SETTINGS_URLS.dns,
|
||||||
|
component: Dns,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: SETTINGS_URLS.encryption,
|
||||||
|
component: Encryption,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: SETTINGS_URLS.dhcp,
|
||||||
|
component: Dhcp,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: SETTINGS_URLS.clients,
|
||||||
|
component: Clients,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: FILTERS_URLS.dns_blocklists,
|
||||||
|
component: DnsBlocklist,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: FILTERS_URLS.dns_allowlists,
|
||||||
|
component: DnsAllowlist,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: FILTERS_URLS.dns_rewrites,
|
||||||
|
component: DnsRewrites,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: FILTERS_URLS.custom_rules,
|
||||||
|
component: CustomRules,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: FILTERS_URLS.blocked_services,
|
||||||
|
component: Services,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
reloadPage = () => {
|
const renderRoute = ({ path, component, exact }, idx) => <Route
|
||||||
window.location.reload();
|
key={idx}
|
||||||
};
|
exact={exact}
|
||||||
|
path={path}
|
||||||
|
component={component}
|
||||||
|
/>;
|
||||||
|
|
||||||
handleUpdate = () => {
|
const App = () => {
|
||||||
this.props.getUpdate();
|
const dispatch = useDispatch();
|
||||||
};
|
const {
|
||||||
|
language,
|
||||||
|
isCoreRunning,
|
||||||
|
isUpdateAvailable,
|
||||||
|
processing,
|
||||||
|
} = useSelector((state) => state.dashboard, shallowEqual);
|
||||||
|
|
||||||
setLanguage = () => {
|
const { processing: processingEncryption } = useSelector((
|
||||||
const { processing, language } = this.props.dashboard;
|
state,
|
||||||
|
) => state.encryption, shallowEqual);
|
||||||
|
|
||||||
|
const updateAvailable = isCoreRunning && isUpdateAvailable;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
dispatch(getDnsStatus());
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const setLanguage = () => {
|
||||||
if (!processing) {
|
if (!processing) {
|
||||||
if (language) {
|
if (language) {
|
||||||
i18n.changeLanguage(language);
|
i18n.changeLanguage(language);
|
||||||
@@ -68,93 +130,52 @@ class App extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
i18n.on('languageChanged', (lang) => {
|
i18n.on('languageChanged', (lang) => {
|
||||||
this.props.changeLanguage(lang);
|
dispatch(changeLanguage(lang));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
useEffect(() => {
|
||||||
const { dashboard, encryption, getVersion } = this.props;
|
setLanguage();
|
||||||
const updateAvailable = dashboard.isCoreRunning && dashboard.isUpdateAvailable;
|
}, [language]);
|
||||||
|
|
||||||
return (
|
const reloadPage = () => {
|
||||||
<HashRouter hashType="noslash">
|
window.location.reload();
|
||||||
<Fragment>
|
};
|
||||||
{updateAvailable && (
|
|
||||||
<Fragment>
|
return (
|
||||||
<UpdateTopline
|
<HashRouter hashType="noslash">
|
||||||
url={dashboard.announcementUrl}
|
<>
|
||||||
version={dashboard.newVersion}
|
{updateAvailable && <>
|
||||||
canAutoUpdate={dashboard.canAutoUpdate}
|
<UpdateTopline />
|
||||||
getUpdate={this.handleUpdate}
|
<UpdateOverlay />
|
||||||
processingUpdate={dashboard.processingUpdate}
|
</>}
|
||||||
/>
|
{!processingEncryption && <EncryptionTopline />}
|
||||||
<UpdateOverlay processingUpdate={dashboard.processingUpdate} />
|
<LoadingBar className="loading-bar" updateTime={1000} />
|
||||||
</Fragment>
|
<Header />
|
||||||
)}
|
<div className="container container--wrap pb-5">
|
||||||
{!encryption.processing && (
|
{processing && <Loading />}
|
||||||
<EncryptionTopline notAfter={encryption.not_after} />
|
{!isCoreRunning && (
|
||||||
)}
|
<div className="row row-cards">
|
||||||
<LoadingBar className="loading-bar" updateTime={1000} />
|
<div className="col-lg-12">
|
||||||
<Route component={Header} />
|
<Status reloadPage={reloadPage} message="dns_start" />
|
||||||
<div className="container container--wrap pb-5">
|
<Loading />
|
||||||
{dashboard.processing && <Loading />}
|
|
||||||
{!dashboard.isCoreRunning && (
|
|
||||||
<div className="row row-cards">
|
|
||||||
<div className="col-lg-12">
|
|
||||||
<Status reloadPage={this.reloadPage}
|
|
||||||
message="dns_start"
|
|
||||||
/>
|
|
||||||
<Loading />
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
</div>
|
||||||
{!dashboard.processing && dashboard.isCoreRunning && (
|
)}
|
||||||
<>
|
{!processing && isCoreRunning && ROUTES.map(renderRoute)}
|
||||||
<Route path={MENU_URLS.root} exact component={Dashboard} />
|
</div>
|
||||||
<Route
|
<Footer />
|
||||||
path={[`${MENU_URLS.logs}${getLogsUrlParams(':search?', ':response_status?')}`, MENU_URLS.logs]}
|
<Toasts />
|
||||||
component={Logs} />
|
<Icons />
|
||||||
<Route path={MENU_URLS.guide} component={SetupGuide} />
|
</>
|
||||||
<Route path={SETTINGS_URLS.settings} component={Settings} />
|
</HashRouter>
|
||||||
<Route path={SETTINGS_URLS.dns} component={Dns} />
|
);
|
||||||
<Route path={SETTINGS_URLS.encryption} component={Encryption} />
|
|
||||||
<Route path={SETTINGS_URLS.dhcp} component={Dhcp} />
|
|
||||||
<Route path={SETTINGS_URLS.clients} component={Clients} />
|
|
||||||
<Route path={FILTERS_URLS.dns_blocklists}
|
|
||||||
component={DnsBlocklist} />
|
|
||||||
<Route path={FILTERS_URLS.dns_allowlists}
|
|
||||||
component={DnsAllowlist} />
|
|
||||||
<Route path={FILTERS_URLS.dns_rewrites} component={DnsRewrites} />
|
|
||||||
<Route path={FILTERS_URLS.custom_rules} component={CustomRules} />
|
|
||||||
<Route path={FILTERS_URLS.blocked_services} component={Services} />
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<Footer
|
|
||||||
dnsVersion={dashboard.dnsVersion}
|
|
||||||
dnsPort={dashboard.dnsPort}
|
|
||||||
processingVersion={dashboard.processingVersion}
|
|
||||||
getVersion={getVersion}
|
|
||||||
checkUpdateFlag={dashboard.checkUpdateFlag}
|
|
||||||
/>
|
|
||||||
<Toasts />
|
|
||||||
<Icons />
|
|
||||||
</Fragment>
|
|
||||||
</HashRouter>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
App.propTypes = {
|
|
||||||
getDnsStatus: PropTypes.func,
|
|
||||||
getUpdate: PropTypes.func,
|
|
||||||
enableDns: PropTypes.func,
|
|
||||||
dashboard: PropTypes.object,
|
|
||||||
isCoreRunning: PropTypes.bool,
|
|
||||||
error: PropTypes.string,
|
|
||||||
changeLanguage: PropTypes.func,
|
|
||||||
encryption: PropTypes.object,
|
|
||||||
getVersion: PropTypes.func,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withTranslation()(App);
|
renderRoute.propTypes = {
|
||||||
|
path: propTypes.oneOfType([propTypes.string, propTypes.arrayOf(propTypes.string)]).isRequired,
|
||||||
|
component: propTypes.element.isRequired,
|
||||||
|
exact: propTypes.bool,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default hot(App);
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { Trans, withTranslation } from 'react-i18next';
|
|||||||
import Card from '../ui/Card';
|
import Card from '../ui/Card';
|
||||||
import Cell from '../ui/Cell';
|
import Cell from '../ui/Cell';
|
||||||
|
|
||||||
import { getPercent, getIpMatchListStatus } from '../../helpers/helpers';
|
import { getPercent, getIpMatchListStatus, sortIp } from '../../helpers/helpers';
|
||||||
import { IP_MATCH_LIST_STATUS, STATUS_COLORS } from '../../helpers/constants';
|
import { IP_MATCH_LIST_STATUS, STATUS_COLORS } from '../../helpers/constants';
|
||||||
import { formatClientCell } from '../../helpers/formatClientCell';
|
import { formatClientCell } from '../../helpers/formatClientCell';
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ const clientCell = (t, toggleClientStatus, processing, disallowedClients) => fun
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="logs__row logs__row--overflow logs__row--column">
|
<div className="logs__row logs__row--overflow logs__row--column">
|
||||||
{formatClientCell(row, true)}
|
{formatClientCell(row, true, false)}
|
||||||
</div>
|
</div>
|
||||||
{ipMatchListStatus !== IP_MATCH_LIST_STATUS.CIDR
|
{ipMatchListStatus !== IP_MATCH_LIST_STATUS.CIDR
|
||||||
&& renderBlockingButton(ipMatchListStatus, value, toggleClientStatus, processing)}
|
&& renderBlockingButton(ipMatchListStatus, value, toggleClientStatus, processing)}
|
||||||
@@ -99,7 +99,7 @@ const Clients = ({
|
|||||||
{
|
{
|
||||||
Header: 'IP',
|
Header: 'IP',
|
||||||
accessor: 'ip',
|
accessor: 'ip',
|
||||||
sortMethod: (a, b) => parseInt(a.replace(/\./g, ''), 10) - parseInt(b.replace(/\./g, ''), 10),
|
sortMethod: sortIp,
|
||||||
Cell: clientCell(t, toggleClientStatus, processingAccessSet, disallowedClients),
|
Cell: clientCell(t, toggleClientStatus, processingAccessSet, disallowedClients),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ const Row = ({
|
|||||||
<Trans components={translationComponents}>{label}</Trans>
|
<Trans components={translationComponents}>{label}</Trans>
|
||||||
<Tooltip content={tooltipTitle} placement="top"
|
<Tooltip content={tooltipTitle} placement="top"
|
||||||
className="tooltip-container tooltip-custom--narrow text-center">
|
className="tooltip-container tooltip-custom--narrow text-center">
|
||||||
<svg className="icons icon--20 icon--lightgray ml-1">
|
<svg className="icons icon--20 icon--lightgray ml-2">
|
||||||
<use xlinkHref="#question" />
|
<use xlinkHref="#question" />
|
||||||
</svg>
|
</svg>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|||||||
@@ -62,23 +62,13 @@ class Table extends Component {
|
|||||||
showPagination
|
showPagination
|
||||||
defaultPageSize={10}
|
defaultPageSize={10}
|
||||||
minRows={5}
|
minRows={5}
|
||||||
previousText={
|
ofText="/"
|
||||||
<svg className="icons icon--24 icon--gray">
|
previousText={t('previous_btn')}
|
||||||
<use xlinkHref="#arrow-left" />
|
nextText={t('next_btn')}
|
||||||
</svg>}
|
pageText={t('page_table_footer_text')}
|
||||||
nextText={
|
|
||||||
<svg className="icons icon--24 icon--gray">
|
|
||||||
<use xlinkHref="#arrow-right" />
|
|
||||||
</svg>}
|
|
||||||
loadingText={t('loading_table_status')}
|
|
||||||
pageText=''
|
|
||||||
ofText=''
|
|
||||||
rowsText={t('rows_table_footer_text')}
|
rowsText={t('rows_table_footer_text')}
|
||||||
|
loadingText={t('loading_table_status')}
|
||||||
noDataText={t('rewrite_not_found')}
|
noDataText={t('rewrite_not_found')}
|
||||||
showPageSizeOptions={false}
|
|
||||||
showPageJump={false}
|
|
||||||
renderTotalPagesCount={() => false}
|
|
||||||
getPaginationProps={() => ({ className: 'custom-pagination' })}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,4 @@ const Services = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Services.propTypes = {};
|
|
||||||
|
|
||||||
export default Services;
|
export default Services;
|
||||||
|
|||||||
@@ -128,24 +128,15 @@ class Table extends Component {
|
|||||||
columns={this.columns}
|
columns={this.columns}
|
||||||
showPagination
|
showPagination
|
||||||
defaultPageSize={10}
|
defaultPageSize={10}
|
||||||
showPageSizeOptions={false}
|
|
||||||
showPageJump={false}
|
|
||||||
renderTotalPagesCount={() => false}
|
|
||||||
loading={loading}
|
loading={loading}
|
||||||
minRows={6}
|
minRows={6}
|
||||||
pageText=''
|
ofText="/"
|
||||||
ofText=''
|
previousText={t('previous_btn')}
|
||||||
|
nextText={t('next_btn')}
|
||||||
|
pageText={t('page_table_footer_text')}
|
||||||
|
rowsText={t('rows_table_footer_text')}
|
||||||
loadingText={t('loading_table_status')}
|
loadingText={t('loading_table_status')}
|
||||||
noDataText={whitelist ? t('no_whitelist_added') : t('no_blocklist_added')}
|
noDataText={whitelist ? t('no_whitelist_added') : t('no_blocklist_added')}
|
||||||
getPaginationProps={() => ({ className: 'custom-pagination' })}
|
|
||||||
previousText={
|
|
||||||
<svg className="icons icon--24 icon--gray w-100 h-100">
|
|
||||||
<use xlinkHref="#arrow-left" />
|
|
||||||
</svg>}
|
|
||||||
nextText={
|
|
||||||
<svg className="icons icon--24 icon--gray w-100 h-100">
|
|
||||||
<use xlinkHref="#arrow-right" />
|
|
||||||
</svg>}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,9 +90,8 @@ class Menu extends Component {
|
|||||||
};
|
};
|
||||||
|
|
||||||
getActiveClassForDropdown = (URLS) => {
|
getActiveClassForDropdown = (URLS) => {
|
||||||
const { pathname } = this.props.location;
|
|
||||||
const isActivePage = Object.values(URLS)
|
const isActivePage = Object.values(URLS)
|
||||||
.some((item) => item === pathname);
|
.some((item) => item === this.props.pathname);
|
||||||
|
|
||||||
return isActivePage ? 'active' : '';
|
return isActivePage ? 'active' : '';
|
||||||
};
|
};
|
||||||
@@ -180,9 +179,9 @@ class Menu extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Menu.propTypes = {
|
Menu.propTypes = {
|
||||||
isMenuOpen: PropTypes.bool,
|
isMenuOpen: PropTypes.bool.isRequired,
|
||||||
closeMenu: PropTypes.func,
|
closeMenu: PropTypes.func.isRequired,
|
||||||
location: PropTypes.object,
|
pathname: PropTypes.string.isRequired,
|
||||||
t: PropTypes.func,
|
t: PropTypes.func,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,83 +1,77 @@
|
|||||||
import React, { Component } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link, useLocation } from 'react-router-dom';
|
||||||
import PropTypes from 'prop-types';
|
import { shallowEqual, useSelector } from 'react-redux';
|
||||||
|
import { Trans } from 'react-i18next';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { Trans, withTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
import Menu from './Menu';
|
import Menu from './Menu';
|
||||||
import logo from '../ui/svg/logo.svg';
|
import logo from '../ui/svg/logo.svg';
|
||||||
import './Header.css';
|
import './Header.css';
|
||||||
|
|
||||||
class Header extends Component {
|
const Header = () => {
|
||||||
state = {
|
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||||
isMenuOpen: false,
|
|
||||||
|
const {
|
||||||
|
protectionEnabled,
|
||||||
|
processing,
|
||||||
|
isCoreRunning,
|
||||||
|
processingProfile,
|
||||||
|
name,
|
||||||
|
} = useSelector((state) => state.dashboard, shallowEqual);
|
||||||
|
|
||||||
|
const { pathname } = useLocation();
|
||||||
|
|
||||||
|
const toggleMenuOpen = () => {
|
||||||
|
setIsMenuOpen((isMenuOpen) => !isMenuOpen);
|
||||||
};
|
};
|
||||||
|
|
||||||
toggleMenuOpen = () => {
|
const closeMenu = () => {
|
||||||
this.setState((prevState) => ({ isMenuOpen: !prevState.isMenuOpen }));
|
setIsMenuOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
closeMenu = () => {
|
const badgeClass = classnames('badge dns-status', {
|
||||||
this.setState({ isMenuOpen: false });
|
'badge-success': protectionEnabled,
|
||||||
};
|
'badge-danger': !protectionEnabled,
|
||||||
|
});
|
||||||
|
|
||||||
render() {
|
return (
|
||||||
const { dashboard, location } = this.props;
|
<div className="header">
|
||||||
const { isMenuOpen } = this.state;
|
<div className="header__container">
|
||||||
const badgeClass = classnames({
|
<div className="header__row">
|
||||||
'badge dns-status': true,
|
<div
|
||||||
'badge-success': dashboard.protectionEnabled,
|
className="header-toggler d-lg-none ml-lg-0 collapsed"
|
||||||
'badge-danger': !dashboard.protectionEnabled,
|
onClick={toggleMenuOpen}
|
||||||
});
|
>
|
||||||
|
<span className="header-toggler-icon" />
|
||||||
return (
|
</div>
|
||||||
<div className="header">
|
<div className="header__column">
|
||||||
<div className="header__container">
|
<div className="d-flex align-items-center">
|
||||||
<div className="header__row">
|
<Link to="/" className="nav-link pl-0 pr-1">
|
||||||
<div
|
<img src={logo} alt="" className="header-brand-img" />
|
||||||
className="header-toggler d-lg-none ml-lg-0 collapsed"
|
</Link>
|
||||||
onClick={this.toggleMenuOpen}
|
{!processing && isCoreRunning && (
|
||||||
>
|
<span className={badgeClass}>
|
||||||
<span className="header-toggler-icon" />
|
<Trans>{protectionEnabled ? 'on' : 'off'}</Trans>
|
||||||
</div>
|
|
||||||
<div className="header__column">
|
|
||||||
<div className="d-flex align-items-center">
|
|
||||||
<Link to="/" className="nav-link pl-0 pr-1">
|
|
||||||
<img src={logo} alt="" className="header-brand-img" />
|
|
||||||
</Link>
|
|
||||||
{!dashboard.processing && dashboard.isCoreRunning && (
|
|
||||||
<span className={badgeClass}>
|
|
||||||
<Trans>{dashboard.protectionEnabled ? 'on' : 'off'}</Trans>
|
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<Menu
|
</div>
|
||||||
location={location}
|
<Menu
|
||||||
isMenuOpen={isMenuOpen}
|
pathname={pathname}
|
||||||
closeMenu={this.closeMenu}
|
isMenuOpen={isMenuOpen}
|
||||||
/>
|
closeMenu={closeMenu}
|
||||||
<div className="header__column">
|
/>
|
||||||
<div className="header__right">
|
<div className="header__column">
|
||||||
{!dashboard.processingProfile && dashboard.name
|
<div className="header__right">
|
||||||
&& <a href="control/logout" className="btn btn-sm btn-outline-secondary">
|
{!processingProfile && name
|
||||||
<Trans>sign_out</Trans>
|
&& <a href="control/logout" className="btn btn-sm btn-outline-secondary">
|
||||||
</a>
|
<Trans>sign_out</Trans>
|
||||||
}
|
</a>}
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
</div>
|
||||||
}
|
);
|
||||||
}
|
|
||||||
|
|
||||||
Header.propTypes = {
|
|
||||||
dashboard: PropTypes.object.isRequired,
|
|
||||||
location: PropTypes.object.isRequired,
|
|
||||||
getVersion: PropTypes.func.isRequired,
|
|
||||||
t: PropTypes.func.isRequired,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withTranslation()(Header);
|
export default Header;
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ const getClientCell = ({
|
|||||||
|
|
||||||
const autoClient = autoClients.find((autoClient) => autoClient.name === client);
|
const autoClient = autoClients.find((autoClient) => autoClient.name === client);
|
||||||
const source = autoClient?.source;
|
const source = autoClient?.source;
|
||||||
|
const whoisAvailable = whois_info && Object.keys(whois_info).length > 0;
|
||||||
|
|
||||||
const id = nanoid();
|
const id = nanoid();
|
||||||
|
|
||||||
@@ -33,7 +34,7 @@ const getClientCell = ({
|
|||||||
const isFiltered = checkFiltered(reason);
|
const isFiltered = checkFiltered(reason);
|
||||||
|
|
||||||
const nameClass = classNames('w-90 o-hidden d-flex flex-column', {
|
const nameClass = classNames('w-90 o-hidden d-flex flex-column', {
|
||||||
'mt-2': isDetailed && !name && !whois_info,
|
'mt-2': isDetailed && !name && !whoisAvailable,
|
||||||
'white-space--nowrap': isDetailed,
|
'white-space--nowrap': isDetailed,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -78,12 +79,19 @@ const getClientCell = ({
|
|||||||
content: processedData,
|
content: processedData,
|
||||||
placement: 'bottom',
|
placement: 'bottom',
|
||||||
})}
|
})}
|
||||||
<div
|
<div className={nameClass}>
|
||||||
className={nameClass}>
|
<div data-tip={true} data-for={id}>
|
||||||
<div data-tip={true} data-for={id}>{formatClientCell(row, isDetailed)}</div>
|
{formatClientCell(row, isDetailed)}
|
||||||
{isDetailed && name
|
</div>
|
||||||
&& !whois_info && <div className="detailed-info d-none d-sm-block logs__text"
|
|
||||||
title={name}>{name}</div>}
|
{isDetailed && name && !whoisAvailable && (
|
||||||
|
<div
|
||||||
|
className="detailed-info d-none d-sm-block logs__text"
|
||||||
|
title={name}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{renderBlockingButton(isFiltered, domain)}
|
{renderBlockingButton(isFiltered, domain)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -38,59 +38,20 @@ const getResponseCell = (row, filtering, t, isDetailed, getFilterName) => {
|
|||||||
})}</div>;
|
})}</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const FILTERED_STATUS_TO_FIELDS_MAP = {
|
const COMMON_CONTENT = {
|
||||||
[FILTERED_STATUS.NOT_FILTERED_NOT_FOUND]: {
|
encryption_status: boldStatusLabel,
|
||||||
encryption_status: boldStatusLabel,
|
install_settings_dns: upstream,
|
||||||
install_settings_dns: upstream,
|
elapsed: formattedElapsedMs,
|
||||||
elapsed: formattedElapsedMs,
|
response_code: status,
|
||||||
response_code: status,
|
filter,
|
||||||
response_table_header: renderResponses(response),
|
rule_label: rule,
|
||||||
},
|
response_table_header: renderResponses(response),
|
||||||
[FILTERED_STATUS.FILTERED_BLOCKED_SERVICE]: {
|
original_response: renderResponses(originalResponse),
|
||||||
encryption_status: boldStatusLabel,
|
|
||||||
install_settings_dns: upstream,
|
|
||||||
elapsed: formattedElapsedMs,
|
|
||||||
filter,
|
|
||||||
rule_label: rule,
|
|
||||||
response_code: status,
|
|
||||||
original_response: renderResponses(originalResponse),
|
|
||||||
},
|
|
||||||
[FILTERED_STATUS.NOT_FILTERED_WHITE_LIST]: {
|
|
||||||
encryption_status: boldStatusLabel,
|
|
||||||
install_settings_dns: upstream,
|
|
||||||
elapsed: formattedElapsedMs,
|
|
||||||
filter,
|
|
||||||
rule_label: rule,
|
|
||||||
response_code: status,
|
|
||||||
},
|
|
||||||
[FILTERED_STATUS.NOT_FILTERED_WHITE_LIST]: {
|
|
||||||
encryption_status: boldStatusLabel,
|
|
||||||
filter,
|
|
||||||
rule_label: rule,
|
|
||||||
response_code: status,
|
|
||||||
},
|
|
||||||
[FILTERED_STATUS.FILTERED_SAFE_SEARCH]: {
|
|
||||||
encryption_status: boldStatusLabel,
|
|
||||||
install_settings_dns: upstream,
|
|
||||||
elapsed: formattedElapsedMs,
|
|
||||||
response_code: status,
|
|
||||||
response_table_header: renderResponses(response),
|
|
||||||
},
|
|
||||||
[FILTERED_STATUS.FILTERED_BLACK_LIST]: {
|
|
||||||
encryption_status: boldStatusLabel,
|
|
||||||
filter,
|
|
||||||
rule_label: rule,
|
|
||||||
install_settings_dns: upstream,
|
|
||||||
elapsed: formattedElapsedMs,
|
|
||||||
response_code: status,
|
|
||||||
original_response: renderResponses(originalResponse),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const content = FILTERED_STATUS_TO_FIELDS_MAP[reason]
|
const content = rule
|
||||||
? Object.entries(FILTERED_STATUS_TO_FIELDS_MAP[reason])
|
? Object.entries(COMMON_CONTENT)
|
||||||
: Object.entries(FILTERED_STATUS_TO_FIELDS_MAP.NotFilteredNotFound);
|
: Object.entries({ ...COMMON_CONTENT, filter: '' });
|
||||||
|
|
||||||
const detailedInfo = isBlocked ? filter : formattedElapsedMs;
|
const detailedInfo = isBlocked ? filter : formattedElapsedMs;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -143,10 +143,11 @@ const Form = (props) => {
|
|||||||
const normalizeOnBlur = (data) => data.trim();
|
const normalizeOnBlur = (data) => data.trim();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form className="d-flex flex-wrap form-control--container"
|
<form
|
||||||
onSubmit={(e) => {
|
className="d-flex flex-wrap form-control--container"
|
||||||
e.preventDefault();
|
onSubmit={(e) => {
|
||||||
}}
|
e.preventDefault();
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="field__search">
|
<div className="field__search">
|
||||||
<Field
|
<Field
|
||||||
@@ -166,13 +167,21 @@ const Form = (props) => {
|
|||||||
<Field
|
<Field
|
||||||
name={FORM_NAMES.response_status}
|
name={FORM_NAMES.response_status}
|
||||||
component="select"
|
component="select"
|
||||||
className={classNames('form-control custom-select custom-select--logs custom-select__arrow--left ml-small form-control--transparent', responseStatusClass)}
|
className={classNames('form-control custom-select custom-select--logs custom-select__arrow--left form-control--transparent', responseStatusClass)}
|
||||||
>
|
>
|
||||||
{Object.values(RESPONSE_FILTER)
|
{Object.values(RESPONSE_FILTER)
|
||||||
.map(({
|
.map(({
|
||||||
query, label, disabled,
|
query, label, disabled,
|
||||||
}) => <option key={label} value={query}
|
}) => (
|
||||||
disabled={disabled}>{t(label)}</option>)}
|
<option
|
||||||
|
key={label}
|
||||||
|
value={query}
|
||||||
|
disabled={disabled}
|
||||||
|
>
|
||||||
|
{t(label)}
|
||||||
|
</option>
|
||||||
|
))
|
||||||
|
}
|
||||||
</Field>
|
</Field>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const Filters = ({ filter, refreshLogs, setIsLoading }) => (
|
|||||||
<Trans>query_log</Trans>
|
<Trans>query_log</Trans>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-icon--green ml-3 bg-transparent"
|
className="btn btn-icon--green logs__refresh"
|
||||||
onClick={refreshLogs}
|
onClick={refreshLogs}
|
||||||
>
|
>
|
||||||
<svg className="icons icon--24">
|
<svg className="icons icon--24">
|
||||||
|
|||||||
@@ -58,8 +58,7 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logs__text--wrap,
|
.logs__text--wrap {
|
||||||
.logs__text--whois {
|
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
}
|
}
|
||||||
@@ -71,6 +70,7 @@
|
|||||||
|
|
||||||
.logs__text--whois {
|
.logs__text--whois {
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
|
color: #9aa0ac;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logs__row .tooltip-custom {
|
.logs__row .tooltip-custom {
|
||||||
@@ -362,7 +362,7 @@
|
|||||||
display: flex !important;
|
display: flex !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.-pageInfo {
|
.logs__table .-pageInfo {
|
||||||
--side-size: 2rem;
|
--side-size: 2rem;
|
||||||
font-variant-numeric: tabular-nums !important;
|
font-variant-numeric: tabular-nums !important;
|
||||||
background-color: transparent !important;
|
background-color: transparent !important;
|
||||||
@@ -376,18 +376,18 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pagination-bottom {
|
.logs__table .pagination-bottom {
|
||||||
justify-content: center !important;
|
justify-content: center !important;
|
||||||
display: flex !important;
|
display: flex !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.-center:before {
|
.logs__table .-center:before {
|
||||||
content: '...';
|
content: '...';
|
||||||
transform: translateY(-0.25rem);
|
transform: translateY(-0.25rem);
|
||||||
margin: auto;
|
margin: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.-center:after {
|
.logs__table .-center:after {
|
||||||
content: '...';
|
content: '...';
|
||||||
transform: translateY(-0.25rem);
|
transform: translateY(-0.25rem);
|
||||||
margin: auto;
|
margin: auto;
|
||||||
@@ -437,12 +437,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.custom-select__arrow--left {
|
.custom-select__arrow--left {
|
||||||
background: #fff url('./chevron-down.svg') no-repeat left 0.2rem center;
|
background: var(--white) url('../ui/svg/chevron-down.svg') no-repeat;
|
||||||
background-size: 1.5rem;
|
background-position: 5px 9px;
|
||||||
|
background-size: 22px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-select--logs {
|
.custom-select--logs {
|
||||||
padding: 0.5rem 0.75rem 0.5rem 1.75rem !important;
|
padding: 0.5rem 0.75rem 0.5rem 2rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.bg--danger {
|
.bg--danger {
|
||||||
@@ -511,6 +512,8 @@
|
|||||||
|
|
||||||
.field__select {
|
.field__select {
|
||||||
margin-top: 1.5rem;
|
margin-top: 1.5rem;
|
||||||
|
padding-left: 24px;
|
||||||
|
padding-right: 24px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -536,3 +539,16 @@
|
|||||||
.loading__text {
|
.loading__text {
|
||||||
transform: translateY(3rem);
|
transform: translateY(3rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.logs__refresh {
|
||||||
|
position: relative;
|
||||||
|
top: 3px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
padding: 0;
|
||||||
|
margin-left: 15px;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|||||||
@@ -355,7 +355,7 @@ const Table = (props) => {
|
|||||||
response_details: 'title',
|
response_details: 'title',
|
||||||
install_settings_dns: upstream,
|
install_settings_dns: upstream,
|
||||||
elapsed: formattedElapsedMs,
|
elapsed: formattedElapsedMs,
|
||||||
filter: isBlocked ? filter : null,
|
filter: rule ? filter : null,
|
||||||
rule_label: rule,
|
rule_label: rule,
|
||||||
response_table_header: response?.join('\n'),
|
response_table_header: response?.join('\n'),
|
||||||
response_code: status,
|
response_code: status,
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#9aa0ac" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-chevron-down"><polyline points="6 9 12 15 18 9"></polyline></svg>
|
|
||||||
|
Before Width: | Height: | Size: 264 B |
@@ -10,7 +10,7 @@ import {
|
|||||||
BLOCK_ACTIONS,
|
BLOCK_ACTIONS,
|
||||||
TABLE_DEFAULT_PAGE_SIZE,
|
TABLE_DEFAULT_PAGE_SIZE,
|
||||||
TABLE_FIRST_PAGE,
|
TABLE_FIRST_PAGE,
|
||||||
smallScreenSize,
|
SMALL_SCREEN_SIZE,
|
||||||
} from '../../helpers/constants';
|
} from '../../helpers/constants';
|
||||||
import Loading from '../ui/Loading';
|
import Loading from '../ui/Loading';
|
||||||
import Filters from './Filters';
|
import Filters from './Filters';
|
||||||
@@ -76,7 +76,7 @@ const Logs = (props) => {
|
|||||||
const search = filter?.search || search_url_param;
|
const search = filter?.search || search_url_param;
|
||||||
const response_status = filter?.response_status || response_status_url_param;
|
const response_status = filter?.response_status || response_status_url_param;
|
||||||
|
|
||||||
const [isSmallScreen, setIsSmallScreen] = useState(window.innerWidth < smallScreenSize);
|
const [isSmallScreen, setIsSmallScreen] = useState(window.innerWidth < SMALL_SCREEN_SIZE);
|
||||||
const [detailedDataCurrent, setDetailedDataCurrent] = useState({});
|
const [detailedDataCurrent, setDetailedDataCurrent] = useState({});
|
||||||
const [buttonType, setButtonType] = useState(BLOCK_ACTIONS.BLOCK);
|
const [buttonType, setButtonType] = useState(BLOCK_ACTIONS.BLOCK);
|
||||||
const [isModalOpened, setModalOpened] = useState(false);
|
const [isModalOpened, setModalOpened] = useState(false);
|
||||||
@@ -114,7 +114,7 @@ const Logs = (props) => {
|
|||||||
},
|
},
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const mediaQuery = window.matchMedia(`(max-width: ${smallScreenSize}px)`);
|
const mediaQuery = window.matchMedia(`(max-width: ${SMALL_SCREEN_SIZE}px)`);
|
||||||
const mediaQueryHandler = (e) => {
|
const mediaQueryHandler = (e) => {
|
||||||
setIsSmallScreen(e.matches);
|
setIsSmallScreen(e.matches);
|
||||||
if (e.matches) {
|
if (e.matches) {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import CellWrap from '../../ui/CellWrap';
|
|||||||
|
|
||||||
import whoisCell from './whoisCell';
|
import whoisCell from './whoisCell';
|
||||||
import LogsSearchLink from '../../ui/LogsSearchLink';
|
import LogsSearchLink from '../../ui/LogsSearchLink';
|
||||||
|
import { sortIp } from '../../../helpers/helpers';
|
||||||
|
|
||||||
const COLUMN_MIN_WIDTH = 200;
|
const COLUMN_MIN_WIDTH = 200;
|
||||||
|
|
||||||
@@ -18,6 +19,7 @@ class AutoClients extends Component {
|
|||||||
accessor: 'ip',
|
accessor: 'ip',
|
||||||
minWidth: COLUMN_MIN_WIDTH,
|
minWidth: COLUMN_MIN_WIDTH,
|
||||||
Cell: CellWrap,
|
Cell: CellWrap,
|
||||||
|
sortMethod: sortIp,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Header: this.props.t('table_name'),
|
Header: this.props.t('table_name'),
|
||||||
@@ -85,23 +87,13 @@ class AutoClients extends Component {
|
|||||||
showPagination
|
showPagination
|
||||||
defaultPageSize={10}
|
defaultPageSize={10}
|
||||||
minRows={5}
|
minRows={5}
|
||||||
showPageSizeOptions={false}
|
ofText="/"
|
||||||
showPageJump={false}
|
previousText={t('previous_btn')}
|
||||||
renderTotalPagesCount={() => false}
|
nextText={t('next_btn')}
|
||||||
previousText={
|
pageText={t('page_table_footer_text')}
|
||||||
<svg className="icons icon--24 icon--gray w-100 h-100">
|
|
||||||
<use xlinkHref="#arrow-left" />
|
|
||||||
</svg>}
|
|
||||||
nextText={
|
|
||||||
<svg className="icons icon--24 icon--gray w-100 h-100">
|
|
||||||
<use xlinkHref="#arrow-right" />
|
|
||||||
</svg>}
|
|
||||||
loadingText={t('loading_table_status')}
|
|
||||||
pageText=''
|
|
||||||
ofText=''
|
|
||||||
rowsText={t('rows_table_footer_text')}
|
rowsText={t('rows_table_footer_text')}
|
||||||
|
loadingText={t('loading_table_status')}
|
||||||
noDataText={t('clients_not_found')}
|
noDataText={t('clients_not_found')}
|
||||||
getPaginationProps={() => ({ className: 'custom-pagination' })}
|
|
||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -297,23 +297,13 @@ class ClientsTable extends Component {
|
|||||||
showPagination
|
showPagination
|
||||||
defaultPageSize={10}
|
defaultPageSize={10}
|
||||||
minRows={5}
|
minRows={5}
|
||||||
showPageSizeOptions={false}
|
ofText="/"
|
||||||
showPageJump={false}
|
previousText={t('previous_btn')}
|
||||||
renderTotalPagesCount={() => false}
|
nextText={t('next_btn')}
|
||||||
previousText={
|
pageText={t('page_table_footer_text')}
|
||||||
<svg className="icons icon--24 icon--gray w-100 h-100">
|
|
||||||
<use xlinkHref="#arrow-left" />
|
|
||||||
</svg>}
|
|
||||||
nextText={
|
|
||||||
<svg className="icons icon--24 icon--gray w-100 h-100">
|
|
||||||
<use xlinkHref="#arrow-right" />
|
|
||||||
</svg>}
|
|
||||||
loadingText={t('loading_table_status')}
|
|
||||||
pageText=''
|
|
||||||
ofText=''
|
|
||||||
rowsText={t('rows_table_footer_text')}
|
rowsText={t('rows_table_footer_text')}
|
||||||
|
loadingText={t('loading_table_status')}
|
||||||
noDataText={t('clients_not_found')}
|
noDataText={t('clients_not_found')}
|
||||||
getPaginationProps={() => ({ className: 'custom-pagination' })}
|
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
|
|||||||
import ReactTable from 'react-table';
|
import ReactTable from 'react-table';
|
||||||
import { Trans, withTranslation } from 'react-i18next';
|
import { Trans, withTranslation } from 'react-i18next';
|
||||||
import { LEASES_TABLE_DEFAULT_PAGE_SIZE } from '../../../helpers/constants';
|
import { LEASES_TABLE_DEFAULT_PAGE_SIZE } from '../../../helpers/constants';
|
||||||
|
import { sortIp } from '../../../helpers/helpers';
|
||||||
|
|
||||||
class Leases extends Component {
|
class Leases extends Component {
|
||||||
cellWrap = ({ value }) => (
|
cellWrap = ({ value }) => (
|
||||||
@@ -27,6 +28,7 @@ class Leases extends Component {
|
|||||||
Header: 'IP',
|
Header: 'IP',
|
||||||
accessor: 'ip',
|
accessor: 'ip',
|
||||||
Cell: this.cellWrap,
|
Cell: this.cellWrap,
|
||||||
|
sortMethod: sortIp,
|
||||||
}, {
|
}, {
|
||||||
Header: <Trans>dhcp_table_hostname</Trans>,
|
Header: <Trans>dhcp_table_hostname</Trans>,
|
||||||
accessor: 'hostname',
|
accessor: 'hostname',
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
|
|||||||
import ReactTable from 'react-table';
|
import ReactTable from 'react-table';
|
||||||
import { Trans, withTranslation } from 'react-i18next';
|
import { Trans, withTranslation } from 'react-i18next';
|
||||||
import { LEASES_TABLE_DEFAULT_PAGE_SIZE } from '../../../../helpers/constants';
|
import { LEASES_TABLE_DEFAULT_PAGE_SIZE } from '../../../../helpers/constants';
|
||||||
|
import { sortIp } from '../../../../helpers/helpers';
|
||||||
import Modal from './Modal';
|
import Modal from './Modal';
|
||||||
|
|
||||||
class StaticLeases extends Component {
|
class StaticLeases extends Component {
|
||||||
@@ -49,6 +49,7 @@ class StaticLeases extends Component {
|
|||||||
{
|
{
|
||||||
Header: 'IP',
|
Header: 'IP',
|
||||||
accessor: 'ip',
|
accessor: 'ip',
|
||||||
|
sortMethod: sortIp,
|
||||||
Cell: this.cellWrap,
|
Cell: this.cellWrap,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { Component, Fragment } from 'react';
|
import React, { Component } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { Trans, withTranslation } from 'react-i18next';
|
import { Trans, withTranslation } from 'react-i18next';
|
||||||
@@ -34,13 +34,14 @@ class Dhcp extends Component {
|
|||||||
} = this.props.dhcp;
|
} = this.props.dhcp;
|
||||||
const otherDhcpFound = check?.otherServer
|
const otherDhcpFound = check?.otherServer
|
||||||
&& check.otherServer.found === DHCP_STATUS_RESPONSE.YES;
|
&& check.otherServer.found === DHCP_STATUS_RESPONSE.YES;
|
||||||
const filledConfig = Object.keys(config).every((key) => {
|
const filledConfig = Object.keys(config)
|
||||||
if (key === 'enabled' || key === 'icmp_timeout_msec') {
|
.every((key) => {
|
||||||
return true;
|
if (key === 'enabled' || key === 'icmp_timeout_msec') {
|
||||||
}
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return config[key];
|
return config[key];
|
||||||
});
|
});
|
||||||
|
|
||||||
if (config.enabled) {
|
if (config.enabled) {
|
||||||
return (
|
return (
|
||||||
@@ -114,40 +115,35 @@ class Dhcp extends Component {
|
|||||||
|
|
||||||
getStaticIpWarning = (t, check, interfaceName) => {
|
getStaticIpWarning = (t, check, interfaceName) => {
|
||||||
if (check.staticIP.static === DHCP_STATUS_RESPONSE.ERROR) {
|
if (check.staticIP.static === DHCP_STATUS_RESPONSE.ERROR) {
|
||||||
return (
|
return <>
|
||||||
<Fragment>
|
<div className="text-danger mb-2">
|
||||||
<div className="text-danger mb-2">
|
<Trans>dhcp_static_ip_error</Trans>
|
||||||
<Trans>dhcp_static_ip_error</Trans>
|
<div className="mt-2 mb-2">
|
||||||
<div className="mt-2 mb-2">
|
<Accordion label={t('error_details')}>
|
||||||
<Accordion label={t('error_details')}>
|
<span>{check.staticIP.error}</span>
|
||||||
<span>{check.staticIP.error}</span>
|
</Accordion>
|
||||||
</Accordion>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<hr className="mt-4 mb-4" />
|
</div>
|
||||||
</Fragment>
|
<hr className="mt-4 mb-4" />
|
||||||
);
|
</>;
|
||||||
} if (
|
}
|
||||||
check.staticIP.static === DHCP_STATUS_RESPONSE.NO
|
if (check.staticIP.static === DHCP_STATUS_RESPONSE.NO
|
||||||
&& check.staticIP.ip
|
&& check.staticIP.ip
|
||||||
&& interfaceName
|
&& interfaceName) {
|
||||||
) {
|
return <>
|
||||||
return (
|
<div className="text-secondary mb-2">
|
||||||
<Fragment>
|
<Trans
|
||||||
<div className="text-secondary mb-2">
|
components={[<strong key="0">example</strong>]}
|
||||||
<Trans
|
values={{
|
||||||
components={[<strong key="0">example</strong>]}
|
interfaceName,
|
||||||
values={{
|
ipAddress: check.staticIP.ip,
|
||||||
interfaceName,
|
}}
|
||||||
ipAddress: check.staticIP.ip,
|
>
|
||||||
}}
|
dhcp_dynamic_ip_found
|
||||||
>
|
</Trans>
|
||||||
dhcp_dynamic_ip_found
|
</div>
|
||||||
</Trans>
|
<hr className="mt-4 mb-4" />
|
||||||
</div>
|
</>;
|
||||||
<hr className="mt-4 mb-4" />
|
|
||||||
</Fragment>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return '';
|
return '';
|
||||||
@@ -163,104 +159,101 @@ class Dhcp extends Component {
|
|||||||
removeStaticLease,
|
removeStaticLease,
|
||||||
toggleLeaseModal,
|
toggleLeaseModal,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const statusButtonClass = classnames({
|
const statusButtonClass = classnames({
|
||||||
'btn btn-primary btn-standard': true,
|
'btn btn-primary btn-standard': true,
|
||||||
'btn btn-primary btn-standard btn-loading': dhcp.processingStatus,
|
'btn btn-primary btn-standard btn-loading': dhcp.processingStatus,
|
||||||
});
|
});
|
||||||
const { enabled, interface_name, ...values } = dhcp.config;
|
const { enabled, interface_name, ...values } = dhcp.config;
|
||||||
|
|
||||||
return (
|
return <>
|
||||||
<Fragment>
|
<PageTitle title={t('dhcp_settings')} />
|
||||||
<PageTitle title={t('dhcp_settings')} />
|
{(dhcp.processing || dhcp.processingInterfaces) && <Loading />}
|
||||||
{(dhcp.processing || dhcp.processingInterfaces) && <Loading />}
|
{!dhcp.processing && !dhcp.processingInterfaces && <>
|
||||||
{!dhcp.processing && !dhcp.processingInterfaces && (
|
<Card
|
||||||
<Fragment>
|
title={t('dhcp_title')}
|
||||||
<Card
|
subtitle={t('dhcp_description')}
|
||||||
title={t('dhcp_title')}
|
bodyType="card-body box-body--settings"
|
||||||
subtitle={t('dhcp_description')}
|
>
|
||||||
bodyType="card-body box-body--settings"
|
<div className="dhcp">
|
||||||
>
|
<>
|
||||||
<div className="dhcp">
|
<Form
|
||||||
<Fragment>
|
onSubmit={this.handleFormSubmit}
|
||||||
<Form
|
initialValues={{
|
||||||
onSubmit={this.handleFormSubmit}
|
interface_name,
|
||||||
initialValues={{
|
...values,
|
||||||
interface_name,
|
}}
|
||||||
...values,
|
interfaces={dhcp.interfaces}
|
||||||
}}
|
processingConfig={dhcp.processingConfig}
|
||||||
interfaces={dhcp.interfaces}
|
processingInterfaces={dhcp.processingInterfaces}
|
||||||
processingConfig={dhcp.processingConfig}
|
enabled={enabled}
|
||||||
processingInterfaces={dhcp.processingInterfaces}
|
resetDhcp={resetDhcp}
|
||||||
enabled={enabled}
|
/>
|
||||||
resetDhcp={resetDhcp}
|
<hr />
|
||||||
/>
|
<div className="card-actions mb-3">
|
||||||
<hr />
|
{this.getToggleDhcpButton()}
|
||||||
<div className="card-actions mb-3">
|
<button
|
||||||
{this.getToggleDhcpButton()}
|
type="button"
|
||||||
<button
|
className={statusButtonClass}
|
||||||
type="button"
|
onClick={() => findActiveDhcp(interface_name)}
|
||||||
className={statusButtonClass}
|
disabled={
|
||||||
onClick={() => findActiveDhcp(interface_name)}
|
enabled || !interface_name || dhcp.processingConfig
|
||||||
disabled={
|
}
|
||||||
enabled || !interface_name || dhcp.processingConfig
|
>
|
||||||
}
|
<Trans>check_dhcp_servers</Trans>
|
||||||
>
|
</button>
|
||||||
<Trans>check_dhcp_servers</Trans>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
{!enabled && dhcp.check && (
|
|
||||||
<Fragment>
|
|
||||||
{this.getStaticIpWarning(t, dhcp.check, interface_name)}
|
|
||||||
{this.getActiveDhcpMessage(t, dhcp.check)}
|
|
||||||
{this.getDhcpWarning(dhcp.check)}
|
|
||||||
</Fragment>
|
|
||||||
)}
|
|
||||||
</Fragment>
|
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
{!enabled && dhcp.check && (
|
||||||
{dhcp.config.enabled && (
|
<>
|
||||||
<Card
|
{this.getStaticIpWarning(t, dhcp.check, interface_name)}
|
||||||
title={t('dhcp_leases')}
|
{this.getActiveDhcpMessage(t, dhcp.check)}
|
||||||
bodyType="card-body box-body--settings"
|
{this.getDhcpWarning(dhcp.check)}
|
||||||
>
|
</>
|
||||||
<div className="row">
|
)}
|
||||||
<div className="col">
|
</>
|
||||||
<Leases leases={dhcp.leases} />
|
</div>
|
||||||
</div>
|
</Card>
|
||||||
</div>
|
{dhcp.config.enabled && (
|
||||||
</Card>
|
<Card
|
||||||
)}
|
title={t('dhcp_leases')}
|
||||||
<Card
|
bodyType="card-body box-body--settings"
|
||||||
title={t('dhcp_static_leases')}
|
>
|
||||||
bodyType="card-body box-body--settings"
|
<div className="row">
|
||||||
>
|
<div className="col">
|
||||||
<div className="row">
|
<Leases leases={dhcp.leases} />
|
||||||
<div className="col-12">
|
|
||||||
<StaticLeases
|
|
||||||
staticLeases={dhcp.staticLeases}
|
|
||||||
isModalOpen={dhcp.isModalOpen}
|
|
||||||
addStaticLease={addStaticLease}
|
|
||||||
removeStaticLease={removeStaticLease}
|
|
||||||
toggleLeaseModal={toggleLeaseModal}
|
|
||||||
processingAdding={dhcp.processingAdding}
|
|
||||||
processingDeleting={dhcp.processingDeleting}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="col-12">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="btn btn-success btn-standard mt-3"
|
|
||||||
onClick={() => toggleLeaseModal()}
|
|
||||||
>
|
|
||||||
<Trans>dhcp_add_static_lease</Trans>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
</Fragment>
|
</Card>
|
||||||
)}
|
)}
|
||||||
</Fragment>
|
<Card
|
||||||
);
|
title={t('dhcp_static_leases')}
|
||||||
|
bodyType="card-body box-body--settings"
|
||||||
|
>
|
||||||
|
<div className="row">
|
||||||
|
<div className="col-12">
|
||||||
|
<StaticLeases
|
||||||
|
staticLeases={dhcp.staticLeases}
|
||||||
|
isModalOpen={dhcp.isModalOpen}
|
||||||
|
addStaticLease={addStaticLease}
|
||||||
|
removeStaticLease={removeStaticLease}
|
||||||
|
toggleLeaseModal={toggleLeaseModal}
|
||||||
|
processingAdding={dhcp.processingAdding}
|
||||||
|
processingDeleting={dhcp.processingDeleting}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="col-12">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="btn btn-success btn-standard mt-3"
|
||||||
|
onClick={() => toggleLeaseModal()}
|
||||||
|
>
|
||||||
|
<Trans>dhcp_add_static_lease</Trans>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</>}
|
||||||
|
</>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,40 +1,36 @@
|
|||||||
import React, { Component } from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { withTranslation } from 'react-i18next';
|
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||||
|
|
||||||
import Form from './Form';
|
import Form from './Form';
|
||||||
import Card from '../../../ui/Card';
|
import Card from '../../../ui/Card';
|
||||||
|
import { setAccessList } from '../../../../actions/access';
|
||||||
|
|
||||||
class Access extends Component {
|
const Access = () => {
|
||||||
handleFormSubmit = (values) => {
|
const { t } = useTranslation();
|
||||||
this.props.setAccessList(values);
|
const dispatch = useDispatch();
|
||||||
|
const {
|
||||||
|
processing,
|
||||||
|
processingSet,
|
||||||
|
...values
|
||||||
|
} = useSelector((state) => state.access, shallowEqual);
|
||||||
|
|
||||||
|
const handleFormSubmit = (values) => {
|
||||||
|
dispatch(setAccessList(values));
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
return (
|
||||||
const { t, access } = this.props;
|
<Card
|
||||||
|
title={t('access_title')}
|
||||||
const { processing, processingSet, ...values } = access;
|
subtitle={t('access_desc')}
|
||||||
|
bodyType="card-body box-body--settings"
|
||||||
return (
|
>
|
||||||
<Card
|
<Form
|
||||||
title={t('access_title')}
|
initialValues={values}
|
||||||
subtitle={t('access_desc')}
|
onSubmit={handleFormSubmit}
|
||||||
bodyType="card-body box-body--settings"
|
processingSet={processingSet}
|
||||||
>
|
/>
|
||||||
<Form
|
</Card>
|
||||||
initialValues={values}
|
);
|
||||||
onSubmit={this.handleFormSubmit}
|
|
||||||
processingSet={processingSet}
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Access.propTypes = {
|
|
||||||
access: PropTypes.object.isRequired,
|
|
||||||
setAccessList: PropTypes.func.isRequired,
|
|
||||||
t: PropTypes.func.isRequired,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withTranslation()(Access);
|
export default Access;
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ const Form = ({
|
|||||||
{INPUTS_FIELDS.map(({
|
{INPUTS_FIELDS.map(({
|
||||||
name, title, description, placeholder, validate, max,
|
name, title, description, placeholder, validate, max,
|
||||||
}) => <div className="col-12" key={name}>
|
}) => <div className="col-12" key={name}>
|
||||||
<div className="col-7 p-0">
|
<div className="col-12 col-md-7 p-0">
|
||||||
<div className="form__group form__group--settings">
|
<div className="form__group form__group--settings">
|
||||||
<label htmlFor={name}
|
<label htmlFor={name}
|
||||||
className="form__label form__label--with-desc">{t(title)}</label>
|
className="form__label form__label--with-desc">{t(title)}</label>
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
import React, { Fragment } from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { connect } from 'react-redux';
|
import { shallowEqual, useSelector } from 'react-redux';
|
||||||
import { Field, reduxForm, formValueSelector } from 'redux-form';
|
import { Field, reduxForm } from 'redux-form';
|
||||||
import { Trans, withTranslation } from 'react-i18next';
|
import { Trans, useTranslation } from 'react-i18next';
|
||||||
import flow from 'lodash/flow';
|
|
||||||
import {
|
import {
|
||||||
renderInputField,
|
renderInputField,
|
||||||
renderRadioField,
|
renderRadioField,
|
||||||
@@ -18,32 +17,36 @@ import {
|
|||||||
} from '../../../../helpers/validators';
|
} from '../../../../helpers/validators';
|
||||||
import { BLOCKING_MODES, FORM_NAME } from '../../../../helpers/constants';
|
import { BLOCKING_MODES, FORM_NAME } from '../../../../helpers/constants';
|
||||||
|
|
||||||
const checkboxes = [{
|
const checkboxes = [
|
||||||
name: 'edns_cs_enabled',
|
{
|
||||||
placeholder: 'edns_enable',
|
name: 'edns_cs_enabled',
|
||||||
subtitle: 'edns_cs_desc',
|
placeholder: 'edns_enable',
|
||||||
},
|
subtitle: 'edns_cs_desc',
|
||||||
{
|
},
|
||||||
name: 'dnssec_enabled',
|
{
|
||||||
placeholder: 'dnssec_enable',
|
name: 'dnssec_enabled',
|
||||||
subtitle: 'dnssec_enable_desc',
|
placeholder: 'dnssec_enable',
|
||||||
},
|
subtitle: 'dnssec_enable_desc',
|
||||||
{
|
},
|
||||||
name: 'disable_ipv6',
|
{
|
||||||
placeholder: 'disable_ipv6',
|
name: 'disable_ipv6',
|
||||||
subtitle: 'disable_ipv6_desc',
|
placeholder: 'disable_ipv6',
|
||||||
}];
|
subtitle: 'disable_ipv6_desc',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const customIps = [{
|
const customIps = [
|
||||||
description: 'blocking_ipv4_desc',
|
{
|
||||||
name: 'blocking_ipv4',
|
description: 'blocking_ipv4_desc',
|
||||||
validateIp: validateIpv4,
|
name: 'blocking_ipv4',
|
||||||
},
|
validateIp: validateIpv4,
|
||||||
{
|
},
|
||||||
description: 'blocking_ipv6_desc',
|
{
|
||||||
name: 'blocking_ipv6',
|
description: 'blocking_ipv6_desc',
|
||||||
validateIp: validateIpv6,
|
name: 'blocking_ipv6',
|
||||||
}];
|
validateIp: validateIpv6,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const getFields = (processing, t) => Object.values(BLOCKING_MODES)
|
const getFields = (processing, t) => Object.values(BLOCKING_MODES)
|
||||||
.map((mode) => (
|
.map((mode) => (
|
||||||
@@ -58,114 +61,107 @@ const getFields = (processing, t) => Object.values(BLOCKING_MODES)
|
|||||||
/>
|
/>
|
||||||
));
|
));
|
||||||
|
|
||||||
let Form = ({
|
const Form = ({
|
||||||
handleSubmit, submitting, invalid, processing, blockingMode, t,
|
handleSubmit, submitting, invalid, processing,
|
||||||
}) => <form onSubmit={handleSubmit}>
|
}) => {
|
||||||
<div className="row">
|
const { t } = useTranslation();
|
||||||
<div className="col-12 col-sm-6">
|
const {
|
||||||
<div className="form__group form__group--settings">
|
blocking_mode,
|
||||||
<label htmlFor="ratelimit"
|
} = useSelector((state) => state.form[FORM_NAME.BLOCKING_MODE].values ?? {}, shallowEqual);
|
||||||
className="form__label form__label--with-desc">
|
|
||||||
<Trans>rate_limit</Trans>
|
return <form onSubmit={handleSubmit}>
|
||||||
</label>
|
<div className="row">
|
||||||
<div className="form__desc form__desc--top">
|
<div className="col-12 col-sm-6">
|
||||||
<Trans>rate_limit_desc</Trans>
|
<div className="form__group form__group--settings">
|
||||||
</div>
|
<label htmlFor="ratelimit"
|
||||||
<Field
|
className="form__label form__label--with-desc">
|
||||||
name="ratelimit"
|
<Trans>rate_limit</Trans>
|
||||||
type="number"
|
</label>
|
||||||
component={renderInputField}
|
<div className="form__desc form__desc--top">
|
||||||
className="form-control"
|
<Trans>rate_limit_desc</Trans>
|
||||||
placeholder={t('form_enter_rate_limit')}
|
|
||||||
normalize={toNumber}
|
|
||||||
validate={[validateRequiredValue, validateBiggerOrEqualZeroValue]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{checkboxes.map(({ name, placeholder, subtitle }) => <div className="col-12" key={name}>
|
|
||||||
<div className="form__group form__group--settings">
|
|
||||||
<Field
|
|
||||||
name={name}
|
|
||||||
type="checkbox"
|
|
||||||
component={renderSelectField}
|
|
||||||
placeholder={t(placeholder)}
|
|
||||||
disabled={processing}
|
|
||||||
subtitle={t(subtitle)}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>)}
|
|
||||||
<div className="col-12">
|
|
||||||
<div className="form__group form__group--settings mb-4">
|
|
||||||
<label className="form__label form__label--with-desc">
|
|
||||||
<Trans>blocking_mode</Trans>
|
|
||||||
</label>
|
|
||||||
<div className="form__desc form__desc--top">
|
|
||||||
{Object.values(BLOCKING_MODES)
|
|
||||||
.map((mode) => (
|
|
||||||
<li key={mode}>
|
|
||||||
<Trans>{`blocking_mode_${mode}`}</Trans>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<div className="custom-controls-stacked">
|
|
||||||
{getFields(processing, t)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{blockingMode === BLOCKING_MODES.custom_ip && (
|
|
||||||
<Fragment>
|
|
||||||
{customIps.map(({
|
|
||||||
description,
|
|
||||||
name,
|
|
||||||
validateIp,
|
|
||||||
}) => <div className="col-12 col-sm-6" key={name}>
|
|
||||||
<div className="form__group form__group--settings">
|
|
||||||
<label className="form__label form__label--with-desc"
|
|
||||||
htmlFor={name}><Trans>{name}</Trans>
|
|
||||||
</label>
|
|
||||||
<div className="form__desc form__desc--top">
|
|
||||||
<Trans>{description}</Trans>
|
|
||||||
</div>
|
|
||||||
<Field
|
|
||||||
name={name}
|
|
||||||
component={renderInputField}
|
|
||||||
className="form-control"
|
|
||||||
placeholder={t('form_enter_ip')}
|
|
||||||
validate={[validateIp, validateRequiredValue]}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>)}
|
<Field
|
||||||
</Fragment>
|
name="ratelimit"
|
||||||
)}
|
type="number"
|
||||||
</div>
|
component={renderInputField}
|
||||||
<button
|
className="form-control"
|
||||||
type="submit"
|
placeholder={t('form_enter_rate_limit')}
|
||||||
className="btn btn-success btn-standard btn-large"
|
normalize={toNumber}
|
||||||
disabled={submitting || invalid || processing}
|
validate={[validateRequiredValue, validateBiggerOrEqualZeroValue]}
|
||||||
>
|
/>
|
||||||
<Trans>save_btn</Trans>
|
</div>
|
||||||
</button>
|
</div>
|
||||||
</form>;
|
{checkboxes.map(({ name, placeholder, subtitle }) => <div className="col-12" key={name}>
|
||||||
|
<div className="form__group form__group--settings">
|
||||||
|
<Field
|
||||||
|
name={name}
|
||||||
|
type="checkbox"
|
||||||
|
component={renderSelectField}
|
||||||
|
placeholder={t(placeholder)}
|
||||||
|
disabled={processing}
|
||||||
|
subtitle={t(subtitle)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>)}
|
||||||
|
<div className="col-12">
|
||||||
|
<div className="form__group form__group--settings mb-4">
|
||||||
|
<label className="form__label form__label--with-desc">
|
||||||
|
<Trans>blocking_mode</Trans>
|
||||||
|
</label>
|
||||||
|
<div className="form__desc form__desc--top">
|
||||||
|
{Object.values(BLOCKING_MODES)
|
||||||
|
.map((mode) => (
|
||||||
|
<li key={mode}>
|
||||||
|
<Trans>{`blocking_mode_${mode}`}</Trans>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="custom-controls-stacked">
|
||||||
|
{getFields(processing, t)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{blocking_mode === BLOCKING_MODES.custom_ip && (
|
||||||
|
<>
|
||||||
|
{customIps.map(({
|
||||||
|
description,
|
||||||
|
name,
|
||||||
|
validateIp,
|
||||||
|
}) => <div className="col-12 col-sm-6" key={name}>
|
||||||
|
<div className="form__group form__group--settings">
|
||||||
|
<label className="form__label form__label--with-desc"
|
||||||
|
htmlFor={name}><Trans>{name}</Trans>
|
||||||
|
</label>
|
||||||
|
<div className="form__desc form__desc--top">
|
||||||
|
<Trans>{description}</Trans>
|
||||||
|
</div>
|
||||||
|
<Field
|
||||||
|
name={name}
|
||||||
|
component={renderInputField}
|
||||||
|
className="form-control"
|
||||||
|
placeholder={t('form_enter_ip')}
|
||||||
|
validate={[validateIp, validateRequiredValue]}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="btn btn-success btn-standard btn-large"
|
||||||
|
disabled={submitting || invalid || processing}
|
||||||
|
>
|
||||||
|
<Trans>save_btn</Trans>
|
||||||
|
</button>
|
||||||
|
</form>;
|
||||||
|
};
|
||||||
|
|
||||||
Form.propTypes = {
|
Form.propTypes = {
|
||||||
blockingMode: PropTypes.string.isRequired,
|
|
||||||
handleSubmit: PropTypes.func.isRequired,
|
handleSubmit: PropTypes.func.isRequired,
|
||||||
submitting: PropTypes.bool.isRequired,
|
submitting: PropTypes.bool.isRequired,
|
||||||
invalid: PropTypes.bool.isRequired,
|
invalid: PropTypes.bool.isRequired,
|
||||||
processing: PropTypes.bool.isRequired,
|
processing: PropTypes.bool.isRequired,
|
||||||
t: PropTypes.func.isRequired,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const selector = formValueSelector(FORM_NAME.BLOCKING_MODE);
|
export default reduxForm({ form: FORM_NAME.BLOCKING_MODE })(Form);
|
||||||
|
|
||||||
Form = connect((state) => {
|
|
||||||
const blockingMode = selector(state, 'blocking_mode');
|
|
||||||
return {
|
|
||||||
blockingMode,
|
|
||||||
};
|
|
||||||
})(Form);
|
|
||||||
|
|
||||||
export default flow([
|
|
||||||
withTranslation(),
|
|
||||||
reduxForm({ form: FORM_NAME.BLOCKING_MODE }),
|
|
||||||
])(Form);
|
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { withTranslation } from 'react-i18next';
|
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||||
|
|
||||||
import Card from '../../../ui/Card';
|
import Card from '../../../ui/Card';
|
||||||
import Form from './Form';
|
import Form from './Form';
|
||||||
|
import { setDnsConfig } from '../../../../actions/dnsConfig';
|
||||||
|
|
||||||
const Config = ({ t, dnsConfig, setDnsConfig }) => {
|
const Config = () => {
|
||||||
const handleFormSubmit = (values) => {
|
const { t } = useTranslation();
|
||||||
setDnsConfig(values);
|
const dispatch = useDispatch();
|
||||||
};
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
blocking_mode,
|
blocking_mode,
|
||||||
ratelimit,
|
ratelimit,
|
||||||
@@ -19,7 +17,11 @@ const Config = ({ t, dnsConfig, setDnsConfig }) => {
|
|||||||
dnssec_enabled,
|
dnssec_enabled,
|
||||||
disable_ipv6,
|
disable_ipv6,
|
||||||
processingSetConfig,
|
processingSetConfig,
|
||||||
} = dnsConfig;
|
} = useSelector((state) => state.dnsConfig, shallowEqual);
|
||||||
|
|
||||||
|
const handleFormSubmit = (values) => {
|
||||||
|
dispatch(setDnsConfig(values));
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
@@ -46,10 +48,4 @@ const Config = ({ t, dnsConfig, setDnsConfig }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Config.propTypes = {
|
export default Config;
|
||||||
dnsConfig: PropTypes.object.isRequired,
|
|
||||||
setDnsConfig: PropTypes.func.isRequired,
|
|
||||||
t: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default withTranslation()(Config);
|
|
||||||
|
|||||||
@@ -52,10 +52,11 @@ const Form = ({
|
|||||||
submitting, invalid, processingSetConfig, processingTestUpstream, handleSubmit,
|
submitting, invalid, processingSetConfig, processingTestUpstream, handleSubmit,
|
||||||
}) => {
|
}) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const [t] = useTranslation();
|
const { t } = useTranslation();
|
||||||
const upstream_dns = useSelector((store) => store.form[FORM_NAME.UPSTREAM].values.upstream_dns);
|
const upstream_dns = useSelector((store) => store.form[FORM_NAME.UPSTREAM].values.upstream_dns);
|
||||||
const bootstrap_dns = useSelector((store) => store.form[FORM_NAME.UPSTREAM]
|
const bootstrap_dns = useSelector(
|
||||||
.values.bootstrap_dns);
|
(store) => store.form[FORM_NAME.UPSTREAM].values.bootstrap_dns,
|
||||||
|
);
|
||||||
|
|
||||||
const handleUpstreamTest = () => dispatch(testUpstream({
|
const handleUpstreamTest = () => dispatch(testUpstream({
|
||||||
upstream_dns,
|
upstream_dns,
|
||||||
|
|||||||
@@ -1,56 +1,46 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useDispatch } from 'react-redux';
|
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||||
import Form from './Form';
|
import Form from './Form';
|
||||||
import Card from '../../../ui/Card';
|
import Card from '../../../ui/Card';
|
||||||
import { setDnsConfig } from '../../../../actions/dnsConfig';
|
import { setDnsConfig } from '../../../../actions/dnsConfig';
|
||||||
|
|
||||||
const Upstream = (props) => {
|
const Upstream = () => {
|
||||||
const [t] = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
const {
|
||||||
|
upstream_dns,
|
||||||
|
bootstrap_dns,
|
||||||
|
upstream_mode,
|
||||||
|
processingSetConfig,
|
||||||
|
} = useSelector((state) => state.dnsConfig, shallowEqual);
|
||||||
|
|
||||||
|
const { processingTestUpstream } = useSelector((state) => state.settings, shallowEqual);
|
||||||
|
|
||||||
const handleSubmit = (values) => {
|
const handleSubmit = (values) => {
|
||||||
dispatch(setDnsConfig(values));
|
dispatch(setDnsConfig(values));
|
||||||
};
|
};
|
||||||
|
|
||||||
const {
|
return <Card
|
||||||
processingTestUpstream,
|
title={t('upstream_dns')}
|
||||||
dnsConfig: {
|
subtitle={t('upstream_dns_hint')}
|
||||||
upstream_dns,
|
bodyType="card-body box-body--settings"
|
||||||
bootstrap_dns,
|
>
|
||||||
processingSetConfig,
|
<div className="row">
|
||||||
upstream_mode,
|
<div className="col">
|
||||||
},
|
<Form
|
||||||
} = props;
|
initialValues={{
|
||||||
|
upstream_dns,
|
||||||
return (
|
bootstrap_dns,
|
||||||
<Card
|
upstream_mode,
|
||||||
title={t('upstream_dns')}
|
}}
|
||||||
subtitle={t('upstream_dns_hint')}
|
onSubmit={handleSubmit}
|
||||||
bodyType="card-body box-body--settings"
|
processingTestUpstream={processingTestUpstream}
|
||||||
>
|
processingSetConfig={processingSetConfig}
|
||||||
<div className="row">
|
/>
|
||||||
<div className="col">
|
|
||||||
<Form
|
|
||||||
initialValues={{
|
|
||||||
upstream_dns,
|
|
||||||
bootstrap_dns,
|
|
||||||
upstream_mode,
|
|
||||||
}}
|
|
||||||
onSubmit={handleSubmit}
|
|
||||||
processingTestUpstream={processingTestUpstream}
|
|
||||||
processingSetConfig={processingSetConfig}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
);
|
</Card>;
|
||||||
};
|
|
||||||
|
|
||||||
Upstream.propTypes = {
|
|
||||||
processingTestUpstream: PropTypes.bool.isRequired,
|
|
||||||
dnsConfig: PropTypes.object.isRequired,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Upstream;
|
export default Upstream;
|
||||||
|
|||||||
@@ -1,67 +1,40 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
import Upstream from './Upstream';
|
import Upstream from './Upstream';
|
||||||
import Access from './Access';
|
import Access from './Access';
|
||||||
import Config from './Config';
|
import Config from './Config';
|
||||||
import PageTitle from '../../ui/PageTitle';
|
import PageTitle from '../../ui/PageTitle';
|
||||||
import Loading from '../../ui/Loading';
|
import Loading from '../../ui/Loading';
|
||||||
import CacheConfig from './Cache';
|
import CacheConfig from './Cache';
|
||||||
|
import { getDnsConfig } from '../../../actions/dnsConfig';
|
||||||
|
import { getAccessList } from '../../../actions/access';
|
||||||
|
|
||||||
const Dns = (props) => {
|
const Dns = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const processing = useSelector((state) => state.access.processing);
|
||||||
|
const processingGetConfig = useSelector((state) => state.dnsConfig.processingGetConfig);
|
||||||
|
|
||||||
|
const isDataLoading = processing || processingGetConfig;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
props.getAccessList();
|
dispatch(getAccessList());
|
||||||
props.getDnsConfig();
|
dispatch(getDnsConfig());
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const {
|
return <>
|
||||||
settings,
|
<PageTitle title={t('dns_settings')} />
|
||||||
access,
|
{isDataLoading
|
||||||
setAccessList,
|
? <Loading />
|
||||||
dnsConfig,
|
: <>
|
||||||
setDnsConfig,
|
<Upstream />
|
||||||
} = props;
|
<Config />
|
||||||
|
<CacheConfig />
|
||||||
const isDataLoading = access.processing || dnsConfig.processingGetConfig;
|
<Access />
|
||||||
|
</>}
|
||||||
return (
|
</>;
|
||||||
<>
|
|
||||||
<PageTitle title={t('dns_settings')} />
|
|
||||||
{isDataLoading
|
|
||||||
? <Loading />
|
|
||||||
: <>
|
|
||||||
<Upstream
|
|
||||||
processingTestUpstream={settings.processingTestUpstream}
|
|
||||||
dnsConfig={dnsConfig}
|
|
||||||
/>
|
|
||||||
<Config
|
|
||||||
dnsConfig={dnsConfig}
|
|
||||||
setDnsConfig={setDnsConfig}
|
|
||||||
/>
|
|
||||||
<CacheConfig
|
|
||||||
dnsConfig={dnsConfig}
|
|
||||||
setDnsConfig={setDnsConfig}
|
|
||||||
/>
|
|
||||||
<Access
|
|
||||||
access={access}
|
|
||||||
setAccessList={setAccessList}
|
|
||||||
/>
|
|
||||||
</>}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
Dns.propTypes = {
|
|
||||||
settings: PropTypes.object.isRequired,
|
|
||||||
getAccessList: PropTypes.func.isRequired,
|
|
||||||
setAccessList: PropTypes.func.isRequired,
|
|
||||||
access: PropTypes.object.isRequired,
|
|
||||||
dnsConfig: PropTypes.object.isRequired,
|
|
||||||
setDnsConfig: PropTypes.func.isRequired,
|
|
||||||
getDnsConfig: PropTypes.func.isRequired,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Dns;
|
export default Dns;
|
||||||
|
|||||||
@@ -10,53 +10,50 @@ import Checkbox from '../ui/Checkbox';
|
|||||||
import Loading from '../ui/Loading';
|
import Loading from '../ui/Loading';
|
||||||
import PageTitle from '../ui/PageTitle';
|
import PageTitle from '../ui/PageTitle';
|
||||||
import Card from '../ui/Card';
|
import Card from '../ui/Card';
|
||||||
|
import { getObjectKeysSorted } from '../../helpers/helpers';
|
||||||
import './Settings.css';
|
import './Settings.css';
|
||||||
|
|
||||||
class Settings extends Component {
|
const ORDER_KEY = 'order';
|
||||||
settings = {
|
|
||||||
safebrowsing: {
|
|
||||||
enabled: false,
|
|
||||||
title: 'use_adguard_browsing_sec',
|
|
||||||
subtitle: 'use_adguard_browsing_sec_hint',
|
|
||||||
},
|
|
||||||
parental: {
|
|
||||||
enabled: false,
|
|
||||||
title: 'use_adguard_parental',
|
|
||||||
subtitle: 'use_adguard_parental_hint',
|
|
||||||
},
|
|
||||||
safesearch: {
|
|
||||||
enabled: false,
|
|
||||||
title: 'enforce_safe_search',
|
|
||||||
subtitle: 'enforce_save_search_hint',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
|
const SETTINGS = {
|
||||||
|
safebrowsing: {
|
||||||
|
enabled: false,
|
||||||
|
title: 'use_adguard_browsing_sec',
|
||||||
|
subtitle: 'use_adguard_browsing_sec_hint',
|
||||||
|
[ORDER_KEY]: 0,
|
||||||
|
},
|
||||||
|
parental: {
|
||||||
|
enabled: false,
|
||||||
|
title: 'use_adguard_parental',
|
||||||
|
subtitle: 'use_adguard_parental_hint',
|
||||||
|
[ORDER_KEY]: 1,
|
||||||
|
},
|
||||||
|
safesearch: {
|
||||||
|
enabled: false,
|
||||||
|
title: 'enforce_safe_search',
|
||||||
|
subtitle: 'enforce_save_search_hint',
|
||||||
|
[ORDER_KEY]: 2,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
class Settings extends Component {
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this.props.initSettings(this.settings);
|
this.props.initSettings(SETTINGS);
|
||||||
this.props.getStatsConfig();
|
this.props.getStatsConfig();
|
||||||
this.props.getLogsConfig();
|
this.props.getLogsConfig();
|
||||||
this.props.getFilteringStatus();
|
this.props.getFilteringStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
renderSettings = (settings) => {
|
renderSettings = (settings) => getObjectKeysSorted(settings, ORDER_KEY)
|
||||||
const settingsKeys = Object.keys(settings);
|
.map((key) => {
|
||||||
|
const setting = settings[key];
|
||||||
if (settingsKeys.length > 0) {
|
const { enabled } = setting;
|
||||||
return settingsKeys.map((key) => {
|
return <Checkbox
|
||||||
const setting = settings[key];
|
{...setting}
|
||||||
const { enabled } = setting;
|
key={key}
|
||||||
return (
|
handleChange={() => this.props.toggleSetting(key, enabled)}
|
||||||
<Checkbox
|
/>;
|
||||||
{...settings[key]}
|
});
|
||||||
key={key}
|
|
||||||
handleChange={() => this.props.toggleSetting(key, enabled)}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return '';
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
|
|||||||
@@ -13,3 +13,17 @@
|
|||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.guide__address {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 7px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 768px) {
|
||||||
|
.guide__address {
|
||||||
|
display: list-item;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -24,8 +24,8 @@ const SetupGuide = ({
|
|||||||
<div className="mt-1">
|
<div className="mt-1">
|
||||||
<Trans>install_devices_address</Trans>:
|
<Trans>install_devices_address</Trans>:
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 font-weight-bold">
|
<div className="mt-3">
|
||||||
{dnsAddresses.map((ip) => <li key={ip}>{ip}</li>)}
|
{dnsAddresses.map((ip) => <li key={ip} className="guide__address">{ip}</li>)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Guide dnsAddresses={dnsAddresses} />
|
<Guide dnsAddresses={dnsAddresses} />
|
||||||
|
|||||||
@@ -1,19 +1,20 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import { Trans } from 'react-i18next';
|
||||||
import { Trans, withTranslation } from 'react-i18next';
|
|
||||||
import isAfter from 'date-fns/is_after';
|
import isAfter from 'date-fns/is_after';
|
||||||
import addDays from 'date-fns/add_days';
|
import addDays from 'date-fns/add_days';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
import Topline from './Topline';
|
import Topline from './Topline';
|
||||||
import { EMPTY_DATE } from '../../helpers/constants';
|
import { EMPTY_DATE } from '../../helpers/constants';
|
||||||
|
|
||||||
const EncryptionTopline = (props) => {
|
const EncryptionTopline = () => {
|
||||||
if (props.notAfter === EMPTY_DATE) {
|
const not_after = useSelector((state) => state.encryption.not_after);
|
||||||
return false;
|
|
||||||
|
if (not_after === EMPTY_DATE) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isAboutExpire = isAfter(addDays(Date.now(), 30), props.notAfter);
|
const isAboutExpire = isAfter(addDays(Date.now(), 30), not_after);
|
||||||
const isExpired = isAfter(Date.now(), props.notAfter);
|
const isExpired = isAfter(Date.now(), not_after);
|
||||||
|
|
||||||
if (isExpired) {
|
if (isExpired) {
|
||||||
return (
|
return (
|
||||||
@@ -23,7 +24,9 @@ const EncryptionTopline = (props) => {
|
|||||||
</Trans>
|
</Trans>
|
||||||
</Topline>
|
</Topline>
|
||||||
);
|
);
|
||||||
} if (isAboutExpire) {
|
}
|
||||||
|
|
||||||
|
if (isAboutExpire) {
|
||||||
return (
|
return (
|
||||||
<Topline type="warning">
|
<Topline type="warning">
|
||||||
<Trans components={[<a href="#encryption" key="0">link</a>]}>
|
<Trans components={[<a href="#encryption" key="0">link</a>]}>
|
||||||
@@ -36,8 +39,4 @@ const EncryptionTopline = (props) => {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
EncryptionTopline.propTypes = {
|
export default EncryptionTopline;
|
||||||
notAfter: PropTypes.string.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default withTranslation()(EncryptionTopline);
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
@@ -28,7 +27,7 @@ const linksData = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const Footer = (props) => {
|
const Footer = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const getYear = () => {
|
const getYear = () => {
|
||||||
@@ -59,11 +58,6 @@ const Footer = (props) => {
|
|||||||
{t(name)}
|
{t(name)}
|
||||||
</a>);
|
</a>);
|
||||||
|
|
||||||
|
|
||||||
const {
|
|
||||||
dnsVersion, processingVersion, getVersion, checkUpdateFlag,
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<footer className="footer">
|
<footer className="footer">
|
||||||
@@ -94,12 +88,7 @@ const Footer = (props) => {
|
|||||||
<div className="footer__row">
|
<div className="footer__row">
|
||||||
{renderCopyright()}
|
{renderCopyright()}
|
||||||
<div className="footer__column footer__column--language">
|
<div className="footer__column footer__column--language">
|
||||||
<Version
|
<Version />
|
||||||
dnsVersion={dnsVersion}
|
|
||||||
processingVersion={processingVersion}
|
|
||||||
getVersion={getVersion}
|
|
||||||
checkUpdateFlag={checkUpdateFlag}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -108,11 +97,4 @@ const Footer = (props) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Footer.propTypes = {
|
|
||||||
dnsVersion: PropTypes.string,
|
|
||||||
processingVersion: PropTypes.bool,
|
|
||||||
getVersion: PropTypes.func,
|
|
||||||
checkUpdateFlag: PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Footer;
|
export default Footer;
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import classNames from 'classnames';
|
||||||
import './Loading.css';
|
import './Loading.css';
|
||||||
|
|
||||||
const Loading = () => (
|
const Loading = ({ className }) => (
|
||||||
<div className="loading" />
|
<div className={classNames('loading', className)} />
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Loading.propTypes = {
|
||||||
|
className: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
export default Loading;
|
export default Loading;
|
||||||
|
|||||||
@@ -6,18 +6,24 @@
|
|||||||
.page-header--logs {
|
.page-header--logs {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
margin: 2rem 0 3rem;
|
margin: 2rem 0 2.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header--logs .page-title {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 991px) {
|
@media (max-width: 991px) {
|
||||||
.page-header--logs {
|
.page-header--logs {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 1.5rem;
|
margin: 1.1rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.page-header--logs .page-title {
|
.page-header--logs .page-title {
|
||||||
padding-bottom: 2.5rem;;
|
margin-bottom: 1.1rem;
|
||||||
|
font-size: 1.8rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,16 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
padding: 15px 0;
|
padding: 10px 0;
|
||||||
border-bottom: 1px solid #e8e8e8;
|
border-bottom: 1px solid #e8e8e8;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 768px) {
|
||||||
|
.tabs__controls {
|
||||||
|
padding: 15px 0;
|
||||||
|
overflow: initial;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabs__controls--form {
|
.tabs__controls--form {
|
||||||
@@ -26,11 +34,18 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
min-width: 70px;
|
min-width: 70px;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
|
white-space: nowrap;
|
||||||
color: #555555;
|
color: #555555;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
opacity: 0.6;
|
opacity: 0.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 768px) {
|
||||||
|
.tab__control {
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.tab__control:hover,
|
.tab__control:hover,
|
||||||
.tab__control:focus {
|
.tab__control:focus {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
|
.tooltip-container {
|
||||||
|
border: 0;
|
||||||
|
padding: 0.7rem;
|
||||||
|
box-shadow: 1px 1px 6px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
.tooltip-custom--narrow {
|
.tooltip-custom--narrow {
|
||||||
max-width: 13.75rem;
|
max-width: 14rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tooltip-custom--wide {
|
.tooltip-custom--wide {
|
||||||
|
|||||||
@@ -2,7 +2,12 @@ import React from 'react';
|
|||||||
import TooltipTrigger from 'react-popper-tooltip';
|
import TooltipTrigger from 'react-popper-tooltip';
|
||||||
import propTypes from 'prop-types';
|
import propTypes from 'prop-types';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { HIDE_TOOLTIP_DELAY } from '../../helpers/constants';
|
|
||||||
|
import {
|
||||||
|
HIDE_TOOLTIP_DELAY,
|
||||||
|
MEDIUM_SCREEN_SIZE,
|
||||||
|
SHOW_TOOLTIP_DELAY,
|
||||||
|
} from '../../helpers/constants';
|
||||||
import 'react-popper-tooltip/dist/styles.css';
|
import 'react-popper-tooltip/dist/styles.css';
|
||||||
import './Tooltip.css';
|
import './Tooltip.css';
|
||||||
|
|
||||||
@@ -13,30 +18,51 @@ const Tooltip = ({
|
|||||||
className = 'tooltip-container',
|
className = 'tooltip-container',
|
||||||
placement = 'bottom',
|
placement = 'bottom',
|
||||||
trigger = 'hover',
|
trigger = 'hover',
|
||||||
|
delayShow = SHOW_TOOLTIP_DELAY,
|
||||||
delayHide = HIDE_TOOLTIP_DELAY,
|
delayHide = HIDE_TOOLTIP_DELAY,
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const touchEventsAvailable = 'ontouchstart' in window;
|
||||||
|
|
||||||
return <TooltipTrigger
|
let triggerValue = trigger;
|
||||||
placement={placement}
|
let delayHideValue = delayHide;
|
||||||
trigger={trigger}
|
let delayShowValue = delayShow;
|
||||||
delayHide={delayHide}
|
|
||||||
tooltip={({
|
if (window.matchMedia(`(max-width: ${MEDIUM_SCREEN_SIZE}px)`).matches || touchEventsAvailable) {
|
||||||
tooltipRef,
|
triggerValue = 'click';
|
||||||
getTooltipProps,
|
delayHideValue = 0;
|
||||||
}) => <div {...getTooltipProps({
|
delayShowValue = 0;
|
||||||
ref: tooltipRef,
|
}
|
||||||
className,
|
|
||||||
})}>
|
return (
|
||||||
{typeof content === 'string' ? t(content) : content}
|
<TooltipTrigger
|
||||||
</div>
|
placement={placement}
|
||||||
}>{({ getTriggerProps, triggerRef }) => <span
|
trigger={triggerValue}
|
||||||
{...getTriggerProps({
|
delayHide={delayHideValue}
|
||||||
ref: triggerRef,
|
delayShow={delayShowValue}
|
||||||
className: triggerClass,
|
tooltip={({ tooltipRef, getTooltipProps }) => (
|
||||||
})}
|
<div
|
||||||
>{children}</span>}
|
{...getTooltipProps({
|
||||||
</TooltipTrigger>;
|
ref: tooltipRef,
|
||||||
|
className,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{typeof content === 'string' ? t(content) : content}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{({ getTriggerProps, triggerRef }) => (
|
||||||
|
<span
|
||||||
|
{...getTriggerProps({
|
||||||
|
ref: triggerRef,
|
||||||
|
className: triggerClass,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</TooltipTrigger>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Tooltip.propTypes = {
|
Tooltip.propTypes = {
|
||||||
@@ -51,6 +77,7 @@ Tooltip.propTypes = {
|
|||||||
placement: propTypes.string,
|
placement: propTypes.string,
|
||||||
trigger: propTypes.string,
|
trigger: propTypes.string,
|
||||||
delayHide: propTypes.string,
|
delayHide: propTypes.string,
|
||||||
|
delayShow: propTypes.string,
|
||||||
className: propTypes.string,
|
className: propTypes.string,
|
||||||
triggerClass: propTypes.string,
|
triggerClass: propTypes.string,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import { Trans } from 'react-i18next';
|
||||||
import { Trans, withTranslation } from 'react-i18next';
|
|
||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
import './Overlay.css';
|
import './Overlay.css';
|
||||||
|
|
||||||
const UpdateOverlay = (props) => {
|
const UpdateOverlay = () => {
|
||||||
const overlayClass = classnames({
|
const processingUpdate = useSelector((state) => state.dashboard.processingUpdate);
|
||||||
overlay: true,
|
const overlayClass = classnames('overlay', {
|
||||||
'overlay--visible': props.processingUpdate,
|
'overlay--visible': processingUpdate,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -19,8 +18,4 @@ const UpdateOverlay = (props) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
UpdateOverlay.propTypes = {
|
export default UpdateOverlay;
|
||||||
processingUpdate: PropTypes.bool,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default withTranslation()(UpdateOverlay);
|
|
||||||
|
|||||||
@@ -1,42 +1,46 @@
|
|||||||
import React, { Fragment } from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import { Trans } from 'react-i18next';
|
||||||
import { Trans, withTranslation } from 'react-i18next';
|
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||||
|
|
||||||
import Topline from './Topline';
|
import Topline from './Topline';
|
||||||
|
import { getUpdate } from '../../actions';
|
||||||
|
|
||||||
const UpdateTopline = (props) => (
|
const UpdateTopline = () => {
|
||||||
<Topline type="info">
|
const {
|
||||||
<Fragment>
|
announcementUrl,
|
||||||
|
newVersion,
|
||||||
|
canAutoUpdate,
|
||||||
|
processingUpdate,
|
||||||
|
} = useSelector((state) => state.dashboard, shallowEqual);
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const handleUpdate = () => {
|
||||||
|
dispatch(getUpdate());
|
||||||
|
};
|
||||||
|
|
||||||
|
return <Topline type="info">
|
||||||
|
<>
|
||||||
<Trans
|
<Trans
|
||||||
values={{ version: props.version }}
|
values={{ version: newVersion }}
|
||||||
components={[
|
components={[
|
||||||
<a href={props.url} target="_blank" rel="noopener noreferrer" key="0">
|
<a href={announcementUrl} target="_blank" rel="noopener noreferrer" key="0">
|
||||||
Click here
|
Click here
|
||||||
</a>,
|
</a>,
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
update_announcement
|
update_announcement
|
||||||
</Trans>
|
</Trans>
|
||||||
{props.canAutoUpdate
|
{canAutoUpdate
|
||||||
&& <button
|
&& <button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-sm btn-primary ml-3"
|
className="btn btn-sm btn-primary ml-3"
|
||||||
onClick={props.getUpdate}
|
onClick={handleUpdate}
|
||||||
disabled={props.processingUpdate}
|
disabled={processingUpdate}
|
||||||
>
|
>
|
||||||
<Trans>update_now</Trans>
|
<Trans>update_now</Trans>
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
</Fragment>
|
</>
|
||||||
</Topline>
|
</Topline>;
|
||||||
);
|
|
||||||
|
|
||||||
UpdateTopline.propTypes = {
|
|
||||||
version: PropTypes.string,
|
|
||||||
url: PropTypes.string.isRequired,
|
|
||||||
canAutoUpdate: PropTypes.bool,
|
|
||||||
getUpdate: PropTypes.func,
|
|
||||||
processingUpdate: PropTypes.bool,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withTranslation()(UpdateTopline);
|
export default UpdateTopline;
|
||||||
|
|||||||
@@ -1,13 +1,21 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import { Trans, useTranslation } from 'react-i18next';
|
||||||
import { Trans, withTranslation } from 'react-i18next';
|
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { getVersion } from '../../actions';
|
||||||
import './Version.css';
|
import './Version.css';
|
||||||
|
|
||||||
const Version = (props) => {
|
const Version = () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const { t } = useTranslation();
|
||||||
const {
|
const {
|
||||||
dnsVersion, processingVersion, t, checkUpdateFlag,
|
dnsVersion,
|
||||||
} = props;
|
processingVersion,
|
||||||
|
checkUpdateFlag,
|
||||||
|
} = useSelector((state) => state?.dashboard ?? {}, shallowEqual);
|
||||||
|
|
||||||
|
const onClick = () => {
|
||||||
|
dispatch(getVersion(true));
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="version">
|
<div className="version">
|
||||||
@@ -20,7 +28,7 @@ const Version = (props) => {
|
|||||||
{checkUpdateFlag && <button
|
{checkUpdateFlag && <button
|
||||||
type="button"
|
type="button"
|
||||||
className="btn btn-icon btn-icon-sm btn-outline-primary btn-sm ml-2"
|
className="btn btn-icon btn-icon-sm btn-outline-primary btn-sm ml-2"
|
||||||
onClick={() => props.getVersion(true)}
|
onClick={onClick}
|
||||||
disabled={processingVersion}
|
disabled={processingVersion}
|
||||||
title={t('check_updates_now')}
|
title={t('check_updates_now')}
|
||||||
>
|
>
|
||||||
@@ -33,12 +41,4 @@ const Version = (props) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
Version.propTypes = {
|
export default Version;
|
||||||
dnsVersion: PropTypes.string,
|
|
||||||
getVersion: PropTypes.func,
|
|
||||||
processingVersion: PropTypes.bool,
|
|
||||||
checkUpdateFlag: PropTypes.bool,
|
|
||||||
t: PropTypes.func.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default withTranslation()(Version);
|
|
||||||
|
|||||||
@@ -1 +1,4 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#9aa0ac" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-chevron-down"><polyline points="6 9 12 15 18 9"></polyline></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#9aa0ac"
|
||||||
|
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-chevron-down">
|
||||||
|
<polyline points="6 9 12 15 18 9"></polyline>
|
||||||
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 264 B After Width: | Height: | Size: 276 B |
@@ -1 +1,5 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#9aa0ac" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-globe"><circle cx="12" cy="12" r="10"/><path d="M2 12h20M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#9aa0ac"
|
||||||
|
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-globe">
|
||||||
|
<circle cx="12" cy="12" r="10"/>
|
||||||
|
<path d="M2 12h20M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/>
|
||||||
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 354 B After Width: | Height: | Size: 371 B |
@@ -1 +1,6 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#9aa0ac" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-help-circle"><circle cx="12" cy="12" r="10"></circle><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path><line x1="12" y1="17" x2="12" y2="17"></line></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#9aa0ac"
|
||||||
|
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-help-circle">
|
||||||
|
<circle cx="12" cy="12" r="10"></circle>
|
||||||
|
<path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path>
|
||||||
|
<line x1="12" y1="17" x2="12" y2="17"></line>
|
||||||
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 357 B After Width: | Height: | Size: 379 B |
@@ -1 +1,8 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="164" height="41" viewBox="0 0 164 41"><g fill-rule="evenodd"><path d="M129.984 22l-1.162-2.945h-5.792L121.931 22H118l6.277-15h3.509L134 22h-4.016zm-4.016-10.996l-1.902 5.149h3.762l-1.86-5.149zM117 16.1c0 .88-.153 1.682-.46 2.404a5.223 5.223 0 0 1-1.318 1.857c-.57.516-1.26.918-2.066 1.207-.807.289-1.703.433-2.688.433-1 0-1.9-.144-2.699-.433-.8-.29-1.477-.691-2.034-1.207a5.232 5.232 0 0 1-1.285-1.857c-.3-.722-.45-1.524-.45-2.404V7h3.64v8.81c0 .4.054.777.161 1.135.108.358.272.677.493.96.221.281.514.505.878.67.364.165.803.248 1.317.248.514 0 .953-.083 1.317-.248.365-.165.66-.389.89-.67.228-.283.392-.602.492-.96.1-.358.15-.736.15-1.135V7H117v9.099zm-16 4.673c-.733.362-1.59.658-2.57.886-.98.228-2.047.342-3.203.342-1.199 0-2.302-.181-3.31-.544-1.008-.362-1.875-.872-2.601-1.53a6.977 6.977 0 0 1-1.703-2.366c-.409-.92-.613-1.943-.613-3.07 0-1.141.208-2.175.624-3.1a6.903 6.903 0 0 1 1.723-2.367 7.71 7.71 0 0 1 2.58-1.5C92.914 7.174 93.98 7 95.121 7c1.184 0 2.284.171 3.299.513 1.015.343 1.84.802 2.474 1.38l-2.284 2.476c-.352-.39-.817-.708-1.395-.956-.579-.249-1.234-.373-1.967-.373-.635 0-1.22.111-1.756.332a4.23 4.23 0 0 0-1.395.927 4.178 4.178 0 0 0-.92 1.41 4.734 4.734 0 0 0-.328 1.78c0 .659.099 1.263.296 1.813.197.55.49 1.024.878 1.42.387.395.867.704 1.438.926.57.221 1.223.332 1.956.332.423 0 .825-.03 1.205-.09.381-.061.733-.158 1.058-.293V16h-2.855v-2.779H101v7.55zm63-6.314c0 1.313-.244 2.447-.73 3.4a6.855 6.855 0 0 1-1.928 2.352 8.035 8.035 0 0 1-2.7 1.356 10.94 10.94 0 0 1-3.05.434H150V7h5.422c1.06 0 2.104.124 3.135.37a7.866 7.866 0 0 1 2.753 1.23c.805.572 1.454 1.338 1.949 2.298.494.96.741 2.147.741 3.56zm-3.77 0c0-.848-.138-1.55-.413-2.108a3.549 3.549 0 0 0-1.101-1.335 4.405 4.405 0 0 0-1.568-.71 7.7 7.7 0 0 0-1.81-.212h-1.8v8.771h1.715c.65 0 1.274-.074 1.874-.222a4.43 4.43 0 0 0 1.589-.731 3.62 3.62 0 0 0 1.1-1.356c.276-.565.414-1.264.414-2.097zm-75.23 0c0 1.313-.244 2.447-.73 3.4a6.855 6.855 0 0 1-1.928 2.352 8.035 8.035 0 0 1-2.7 1.356 10.94 10.94 0 0 1-3.05.434H71V7h5.422c1.06 0 2.104.124 3.135.37A7.866 7.866 0 0 1 82.31 8.6c.805.572 1.454 1.338 1.949 2.298.494.96.741 2.147.741 3.56zm-3.77 0c0-.848-.138-1.55-.413-2.108a3.549 3.549 0 0 0-1.101-1.335 4.405 4.405 0 0 0-1.568-.71 7.7 7.7 0 0 0-1.81-.212h-1.8v8.771h1.715c.65 0 1.274-.074 1.874-.222a4.43 4.43 0 0 0 1.589-.731 3.62 3.62 0 0 0 1.1-1.356c.276-.565.414-1.264.414-2.097zM65.984 22l-1.162-2.945H59.03L57.931 22H54l6.277-15h3.509L70 22h-4.016zm-4.016-10.996l-1.902 5.149h3.762l-1.86-5.149zM143.855 22l-3.171-5.953h-1.202V22H136V7h5.596c.705 0 1.392.074 2.062.222.67.149 1.271.4 1.803.753a3.9 3.9 0 0 1 1.275 1.398c.318.579.476 1.3.476 2.16 0 1.018-.269 1.872-.808 2.564-.539.693-1.285 1.187-2.238 1.484L148 22h-4.145zm-.145-10.403c0-.353-.073-.639-.218-.858a1.502 1.502 0 0 0-.56-.508 2.393 2.393 0 0 0-.766-.244 5.535 5.535 0 0 0-.819-.063h-1.886v3.495h1.679c.29 0 .587-.024.891-.074.304-.05.58-.137.83-.264.248-.128.452-.311.61-.551.16-.24.239-.551.239-.933zM55 37.851v-8.702h.951v3.866h4.866V29.15h.952v8.702h-.952v-3.916h-4.866v3.916H55zM68.068 38c-2.565 0-4.288-2.076-4.288-4.5 0-2.4 1.747-4.5 4.312-4.5 2.565 0 4.288 2.076 4.288 4.5 0 2.4-1.747 4.5-4.312 4.5zm.024-.907c1.927 0 3.3-1.592 3.3-3.593 0-1.977-1.397-3.593-3.324-3.593-1.927 0-3.3 1.592-3.3 3.593 0 1.977 1.397 3.593 3.324 3.593zm6.3.758v-8.702h.963l3.07 4.749 3.072-4.749h.964v8.702h-.952v-7.049l-3.071 4.662h-.048l-3.071-4.65v7.037h-.928zm10.453 0v-8.702h6.095v.895h-5.143v2.971h4.6v.895h-4.6v3.046H91v.895h-6.155z"/><path fill-rule="nonzero" d="M2.831 14.045c.775 4.287 2.266 8.333 4.685 12.143 2.958 4.659 7.21 8.797 12.984 12.319 5.774-3.522 10.026-7.66 12.984-12.319 2.42-3.81 3.91-7.856 4.685-12.143.489-2.706.644-4.844.672-8.003C33.368 3.522 26.636 2.14 20.5 2.14c-6.137 0-12.869 1.381-18.341 3.9.028 3.16.183 5.298.672 8.004zM20.5 0C26.908 0 34.637 1.47 41 4.706c0 6.988.087 24.398-20.5 36.294C-.088 29.104 0 11.694 0 4.706 6.363 1.47 14.092 0 20.5 0z"/><path d="M20.234 27L33 11.344c-.935-.682-1.756-.2-2.208.172l-.016.001-10.644 10.076-4.01-4.392c-1.913-2.011-4.514-.477-5.122-.072L20.234 27"/></g></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="164" height="41" viewBox="0 0 164 41">
|
||||||
|
<g fill-rule="evenodd">
|
||||||
|
<path d="M129.984 22l-1.162-2.945h-5.792L121.931 22H118l6.277-15h3.509L134 22h-4.016zm-4.016-10.996l-1.902 5.149h3.762l-1.86-5.149zM117 16.1c0 .88-.153 1.682-.46 2.404a5.223 5.223 0 0 1-1.318 1.857c-.57.516-1.26.918-2.066 1.207-.807.289-1.703.433-2.688.433-1 0-1.9-.144-2.699-.433-.8-.29-1.477-.691-2.034-1.207a5.232 5.232 0 0 1-1.285-1.857c-.3-.722-.45-1.524-.45-2.404V7h3.64v8.81c0 .4.054.777.161 1.135.108.358.272.677.493.96.221.281.514.505.878.67.364.165.803.248 1.317.248.514 0 .953-.083 1.317-.248.365-.165.66-.389.89-.67.228-.283.392-.602.492-.96.1-.358.15-.736.15-1.135V7H117v9.099zm-16 4.673c-.733.362-1.59.658-2.57.886-.98.228-2.047.342-3.203.342-1.199 0-2.302-.181-3.31-.544-1.008-.362-1.875-.872-2.601-1.53a6.977 6.977 0 0 1-1.703-2.366c-.409-.92-.613-1.943-.613-3.07 0-1.141.208-2.175.624-3.1a6.903 6.903 0 0 1 1.723-2.367 7.71 7.71 0 0 1 2.58-1.5C92.914 7.174 93.98 7 95.121 7c1.184 0 2.284.171 3.299.513 1.015.343 1.84.802 2.474 1.38l-2.284 2.476c-.352-.39-.817-.708-1.395-.956-.579-.249-1.234-.373-1.967-.373-.635 0-1.22.111-1.756.332a4.23 4.23 0 0 0-1.395.927 4.178 4.178 0 0 0-.92 1.41 4.734 4.734 0 0 0-.328 1.78c0 .659.099 1.263.296 1.813.197.55.49 1.024.878 1.42.387.395.867.704 1.438.926.57.221 1.223.332 1.956.332.423 0 .825-.03 1.205-.09.381-.061.733-.158 1.058-.293V16h-2.855v-2.779H101v7.55zm63-6.314c0 1.313-.244 2.447-.73 3.4a6.855 6.855 0 0 1-1.928 2.352 8.035 8.035 0 0 1-2.7 1.356 10.94 10.94 0 0 1-3.05.434H150V7h5.422c1.06 0 2.104.124 3.135.37a7.866 7.866 0 0 1 2.753 1.23c.805.572 1.454 1.338 1.949 2.298.494.96.741 2.147.741 3.56zm-3.77 0c0-.848-.138-1.55-.413-2.108a3.549 3.549 0 0 0-1.101-1.335 4.405 4.405 0 0 0-1.568-.71 7.7 7.7 0 0 0-1.81-.212h-1.8v8.771h1.715c.65 0 1.274-.074 1.874-.222a4.43 4.43 0 0 0 1.589-.731 3.62 3.62 0 0 0 1.1-1.356c.276-.565.414-1.264.414-2.097zm-75.23 0c0 1.313-.244 2.447-.73 3.4a6.855 6.855 0 0 1-1.928 2.352 8.035 8.035 0 0 1-2.7 1.356 10.94 10.94 0 0 1-3.05.434H71V7h5.422c1.06 0 2.104.124 3.135.37A7.866 7.866 0 0 1 82.31 8.6c.805.572 1.454 1.338 1.949 2.298.494.96.741 2.147.741 3.56zm-3.77 0c0-.848-.138-1.55-.413-2.108a3.549 3.549 0 0 0-1.101-1.335 4.405 4.405 0 0 0-1.568-.71 7.7 7.7 0 0 0-1.81-.212h-1.8v8.771h1.715c.65 0 1.274-.074 1.874-.222a4.43 4.43 0 0 0 1.589-.731 3.62 3.62 0 0 0 1.1-1.356c.276-.565.414-1.264.414-2.097zM65.984 22l-1.162-2.945H59.03L57.931 22H54l6.277-15h3.509L70 22h-4.016zm-4.016-10.996l-1.902 5.149h3.762l-1.86-5.149zM143.855 22l-3.171-5.953h-1.202V22H136V7h5.596c.705 0 1.392.074 2.062.222.67.149 1.271.4 1.803.753a3.9 3.9 0 0 1 1.275 1.398c.318.579.476 1.3.476 2.16 0 1.018-.269 1.872-.808 2.564-.539.693-1.285 1.187-2.238 1.484L148 22h-4.145zm-.145-10.403c0-.353-.073-.639-.218-.858a1.502 1.502 0 0 0-.56-.508 2.393 2.393 0 0 0-.766-.244 5.535 5.535 0 0 0-.819-.063h-1.886v3.495h1.679c.29 0 .587-.024.891-.074.304-.05.58-.137.83-.264.248-.128.452-.311.61-.551.16-.24.239-.551.239-.933zM55 37.851v-8.702h.951v3.866h4.866V29.15h.952v8.702h-.952v-3.916h-4.866v3.916H55zM68.068 38c-2.565 0-4.288-2.076-4.288-4.5 0-2.4 1.747-4.5 4.312-4.5 2.565 0 4.288 2.076 4.288 4.5 0 2.4-1.747 4.5-4.312 4.5zm.024-.907c1.927 0 3.3-1.592 3.3-3.593 0-1.977-1.397-3.593-3.324-3.593-1.927 0-3.3 1.592-3.3 3.593 0 1.977 1.397 3.593 3.324 3.593zm6.3.758v-8.702h.963l3.07 4.749 3.072-4.749h.964v8.702h-.952v-7.049l-3.071 4.662h-.048l-3.071-4.65v7.037h-.928zm10.453 0v-8.702h6.095v.895h-5.143v2.971h4.6v.895h-4.6v3.046H91v.895h-6.155z"/>
|
||||||
|
<path fill-rule="nonzero"
|
||||||
|
d="M2.831 14.045c.775 4.287 2.266 8.333 4.685 12.143 2.958 4.659 7.21 8.797 12.984 12.319 5.774-3.522 10.026-7.66 12.984-12.319 2.42-3.81 3.91-7.856 4.685-12.143.489-2.706.644-4.844.672-8.003C33.368 3.522 26.636 2.14 20.5 2.14c-6.137 0-12.869 1.381-18.341 3.9.028 3.16.183 5.298.672 8.004zM20.5 0C26.908 0 34.637 1.47 41 4.706c0 6.988.087 24.398-20.5 36.294C-.088 29.104 0 11.694 0 4.706 6.363 1.47 14.092 0 20.5 0z"/>
|
||||||
|
<path d="M20.234 27L33 11.344c-.935-.682-1.756-.2-2.208.172l-.016.001-10.644 10.076-4.01-4.392c-1.913-2.011-4.514-.477-5.122-.072L20.234 27"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.1 KiB |
@@ -1 +1,7 @@
|
|||||||
<svg stroke="#84868c" fill="none" height="24" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m3 6h2 16"/><path d="m19 6v14a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2-2v-14m3 0v-2a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><path d="m10 11v6"/><path d="m14 11v6"/></svg>
|
<svg stroke="#84868c" fill="none" height="24" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="m3 6h2 16"/>
|
||||||
|
<path d="m19 6v14a2 2 0 0 1 -2 2h-10a2 2 0 0 1 -2-2v-14m3 0v-2a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
|
||||||
|
<path d="m10 11v6"/>
|
||||||
|
<path d="m14 11v6"/>
|
||||||
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 340 B After Width: | Height: | Size: 367 B |
@@ -1 +1,5 @@
|
|||||||
<svg stroke="#84868c" fill="none" height="24" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><path d="m18 6-12 12"/><path d="m6 6 12 12"/></svg>
|
<svg stroke="#84868c" fill="none" height="24" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||||
|
viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="m18 6-12 12"/>
|
||||||
|
<path d="m6 6 12 12"/>
|
||||||
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 227 B After Width: | Height: | Size: 244 B |
@@ -1,14 +0,0 @@
|
|||||||
import { connect } from 'react-redux';
|
|
||||||
import * as actionCreators from '../actions';
|
|
||||||
import App from '../components/App';
|
|
||||||
|
|
||||||
const mapStateToProps = (state) => {
|
|
||||||
const { dashboard, encryption } = state;
|
|
||||||
const props = { dashboard, encryption };
|
|
||||||
return props;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
mapStateToProps,
|
|
||||||
actionCreators,
|
|
||||||
)(App);
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import { connect } from 'react-redux';
|
|
||||||
import { getVersion } from '../actions';
|
|
||||||
import Header from '../components/Header';
|
|
||||||
|
|
||||||
const mapStateToProps = (state) => {
|
|
||||||
const { dashboard } = state;
|
|
||||||
const props = { dashboard };
|
|
||||||
return props;
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
|
||||||
getVersion,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
mapStateToProps,
|
|
||||||
mapDispatchToProps,
|
|
||||||
)(Header);
|
|
||||||
@@ -1,13 +1,23 @@
|
|||||||
export const R_URL_REQUIRES_PROTOCOL = /^https?:\/\/[^/\s]+(\/.*)?$/;
|
export const R_URL_REQUIRES_PROTOCOL = /^https?:\/\/[^/\s]+(\/.*)?$/;
|
||||||
export const R_HOST = /^(\*\.)?([\w-]+\.)+[\w-]+$/;
|
|
||||||
|
// matches hostname or *.wildcard
|
||||||
|
export const R_HOST = /^(\*\.)?[\w.-]+$/;
|
||||||
|
|
||||||
export const R_IPV4 = /^(?:(?:^|\.)(?:2(?:5[0-5]|[0-4]\d)|1?\d?\d)){4}$/;
|
export const R_IPV4 = /^(?:(?:^|\.)(?:2(?:5[0-5]|[0-4]\d)|1?\d?\d)){4}$/;
|
||||||
|
|
||||||
export const R_IPV6 = /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/;
|
export const R_IPV6 = /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/;
|
||||||
|
|
||||||
export const R_CIDR = /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$/;
|
export const R_CIDR = /^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/([0-9]|[1-2][0-9]|3[0-2]))$/;
|
||||||
|
|
||||||
export const R_MAC = /^((([a-fA-F0-9][a-fA-F0-9]+[-]){5}|([a-fA-F0-9][a-fA-F0-9]+[:]){5})([a-fA-F0-9][a-fA-F0-9])$)|(^([a-fA-F0-9][a-fA-F0-9][a-fA-F0-9][a-fA-F0-9]+[.]){2}([a-fA-F0-9][a-fA-F0-9][a-fA-F0-9][a-fA-F0-9]))$/;
|
export const R_MAC = /^((([a-fA-F0-9][a-fA-F0-9]+[-]){5}|([a-fA-F0-9][a-fA-F0-9]+[:]){5})([a-fA-F0-9][a-fA-F0-9])$)|(^([a-fA-F0-9][a-fA-F0-9][a-fA-F0-9][a-fA-F0-9]+[.]){2}([a-fA-F0-9][a-fA-F0-9][a-fA-F0-9][a-fA-F0-9]))$/;
|
||||||
|
|
||||||
export const R_CIDR_IPV6 = /^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(\/(12[0-8]|1[0-1][0-9]|[1-9][0-9]|[0-9]))$/;
|
export const R_CIDR_IPV6 = /^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(\/(12[0-8]|1[0-1][0-9]|[1-9][0-9]|[0-9]))$/;
|
||||||
|
|
||||||
export const R_PATH_LAST_PART = /\/[^/]*$/;
|
export const R_PATH_LAST_PART = /\/[^/]*$/;
|
||||||
|
|
||||||
// eslint-disable-next-line no-control-regex
|
// eslint-disable-next-line no-control-regex
|
||||||
export const R_UNIX_ABSOLUTE_PATH = /^(\/[^/\x00]+)+$/;
|
export const R_UNIX_ABSOLUTE_PATH = /^(\/[^/\x00]+)+$/;
|
||||||
|
|
||||||
// eslint-disable-next-line no-control-regex
|
// eslint-disable-next-line no-control-regex
|
||||||
export const R_WIN_ABSOLUTE_PATH = /^([a-zA-Z]:)?(\\|\/)(?:[^\\/:*?"<>|\x00]+\\)*[^\\/:*?"<>|\x00]*$/;
|
export const R_WIN_ABSOLUTE_PATH = /^([a-zA-Z]:)?(\\|\/)(?:[^\\/:*?"<>|\x00]+\\)*[^\\/:*?"<>|\x00]*$/;
|
||||||
|
|
||||||
@@ -62,6 +72,7 @@ export const CHECK_TIMEOUT = 1000;
|
|||||||
export const SUCCESS_TOAST_TIMEOUT = 5000;
|
export const SUCCESS_TOAST_TIMEOUT = 5000;
|
||||||
export const FAILURE_TOAST_TIMEOUT = 30000;
|
export const FAILURE_TOAST_TIMEOUT = 30000;
|
||||||
export const HIDE_TOOLTIP_DELAY = 300;
|
export const HIDE_TOOLTIP_DELAY = 300;
|
||||||
|
export const SHOW_TOOLTIP_DELAY = 200;
|
||||||
export const MODAL_OPEN_TIMEOUT = 150;
|
export const MODAL_OPEN_TIMEOUT = 150;
|
||||||
|
|
||||||
export const UNSAFE_PORTS = [
|
export const UNSAFE_PORTS = [
|
||||||
@@ -288,50 +299,6 @@ export const WHOIS_ICONS = {
|
|||||||
descr: '',
|
descr: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const DNS_RECORD_TYPES = [
|
|
||||||
'A',
|
|
||||||
'AAAA',
|
|
||||||
'AFSDB',
|
|
||||||
'APL',
|
|
||||||
'CAA',
|
|
||||||
'CDNSKEY',
|
|
||||||
'CDS',
|
|
||||||
'CERT',
|
|
||||||
'CNAME',
|
|
||||||
'CSYNC',
|
|
||||||
'DHCID',
|
|
||||||
'DLV',
|
|
||||||
'DNAME',
|
|
||||||
'DNSKEY',
|
|
||||||
'DS',
|
|
||||||
'HIP',
|
|
||||||
'IPSECKEY',
|
|
||||||
'KEY',
|
|
||||||
'KX',
|
|
||||||
'LOC',
|
|
||||||
'MX',
|
|
||||||
'NAPTR',
|
|
||||||
'NS',
|
|
||||||
'NSEC',
|
|
||||||
'NSEC3',
|
|
||||||
'NSEC3PARAM',
|
|
||||||
'OPENPGPKEY',
|
|
||||||
'PTR',
|
|
||||||
'RRSIG',
|
|
||||||
'RP',
|
|
||||||
'SIG',
|
|
||||||
'SMIMEA',
|
|
||||||
'SOA',
|
|
||||||
'SRV',
|
|
||||||
'SSHFP',
|
|
||||||
'TA',
|
|
||||||
'TKEY',
|
|
||||||
'TLSA',
|
|
||||||
'TSIG',
|
|
||||||
'TXT',
|
|
||||||
'URI',
|
|
||||||
];
|
|
||||||
|
|
||||||
export const DEFAULT_LOGS_FILTER = {
|
export const DEFAULT_LOGS_FILTER = {
|
||||||
search: '',
|
search: '',
|
||||||
response_status: '',
|
response_status: '',
|
||||||
@@ -339,7 +306,7 @@ export const DEFAULT_LOGS_FILTER = {
|
|||||||
|
|
||||||
export const DEFAULT_LANGUAGE = 'en';
|
export const DEFAULT_LANGUAGE = 'en';
|
||||||
|
|
||||||
export const TABLE_DEFAULT_PAGE_SIZE = 50;
|
export const TABLE_DEFAULT_PAGE_SIZE = 25;
|
||||||
|
|
||||||
export const TABLE_FIRST_PAGE = 0;
|
export const TABLE_FIRST_PAGE = 0;
|
||||||
|
|
||||||
@@ -370,11 +337,6 @@ export const RESPONSE_FILTER = {
|
|||||||
query: 'processed',
|
query: 'processed',
|
||||||
label: 'show_processed_responses',
|
label: 'show_processed_responses',
|
||||||
},
|
},
|
||||||
SPACE: {
|
|
||||||
query: 'all',
|
|
||||||
label: '',
|
|
||||||
disabled: true,
|
|
||||||
},
|
|
||||||
BLOCKED: {
|
BLOCKED: {
|
||||||
query: 'blocked',
|
query: 'blocked',
|
||||||
label: 'show_blocked_responses',
|
label: 'show_blocked_responses',
|
||||||
@@ -401,10 +363,11 @@ export const RESPONSE_FILTER = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const RESPONSE_FILTER_QUERIES = Object.values(RESPONSE_FILTER).reduce((acc, { query }) => {
|
export const RESPONSE_FILTER_QUERIES = Object.values(RESPONSE_FILTER)
|
||||||
acc[query] = query;
|
.reduce((acc, { query }) => {
|
||||||
return acc;
|
acc[query] = query;
|
||||||
}, {});
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
export const FILTERED_STATUS_TO_META_MAP = {
|
export const FILTERED_STATUS_TO_META_MAP = {
|
||||||
[FILTERED_STATUS.NOT_FILTERED_WHITE_LIST]: {
|
[FILTERED_STATUS.NOT_FILTERED_WHITE_LIST]: {
|
||||||
@@ -516,6 +479,7 @@ export const FORM_NAME = {
|
|||||||
CACHE: 'cache',
|
CACHE: 'cache',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const smallScreenSize = 767;
|
export const SMALL_SCREEN_SIZE = 767;
|
||||||
|
export const MEDIUM_SCREEN_SIZE = 1023;
|
||||||
|
|
||||||
export const SECONDS_IN_HOUR = 60 * 60;
|
export const SECONDS_IN_HOUR = 60 * 60;
|
||||||
|
|||||||
@@ -75,7 +75,7 @@
|
|||||||
"the-big-list-of-hacked-malware-web-sites": {
|
"the-big-list-of-hacked-malware-web-sites": {
|
||||||
"name": "The Big List of Hacked Malware Web Sites",
|
"name": "The Big List of Hacked Malware Web Sites",
|
||||||
"categoryId": "security",
|
"categoryId": "security",
|
||||||
"homepage": "https://github.com/hoshsadiq/adblock-nocoin-list/",
|
"homepage": "https://github.com/mitchellkrogza/The-Big-List-of-Hacked-Malware-Web-Sites",
|
||||||
"source": "https://raw.githubusercontent.com/mitchellkrogza/The-Big-List-of-Hacked-Malware-Web-Sites/master/hacked-domains.list"
|
"source": "https://raw.githubusercontent.com/mitchellkrogza/The-Big-List-of-Hacked-Malware-Web-Sites/master/hacked-domains.list"
|
||||||
},
|
},
|
||||||
"scam-blocklist-by-durable-napkin": {
|
"scam-blocklist-by-durable-napkin": {
|
||||||
@@ -147,8 +147,8 @@
|
|||||||
"CHN-anti-ad" : {
|
"CHN-anti-ad" : {
|
||||||
"name": "CHN: anti-AD",
|
"name": "CHN: anti-AD",
|
||||||
"categoryId": "regional",
|
"categoryId": "regional",
|
||||||
"homepage": "https://github.com/privacy-protection-tools/anti-AD",
|
"homepage": "https://anti-ad.net/",
|
||||||
"source": "https://gitee.com/privacy-protection-tools/anti-ad/raw/master/easylist.txt"
|
"source": "https://anti-ad.net/easylist.txt"
|
||||||
},
|
},
|
||||||
"BarbBlock": {
|
"BarbBlock": {
|
||||||
"name": "BarbBlock",
|
"name": "BarbBlock",
|
||||||
|
|||||||
@@ -24,25 +24,38 @@ const getFormattedWhois = (whois) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const formatClientCell = (row, isDetailed = false) => {
|
export const formatClientCell = (row, isDetailed = false, isLogs = true) => {
|
||||||
const { value, original: { info } } = row;
|
const { value, original: { info } } = row;
|
||||||
let whoisContainer = '';
|
let whoisContainer = '';
|
||||||
let nameContainer = value;
|
let nameContainer = value;
|
||||||
|
|
||||||
if (info) {
|
if (info) {
|
||||||
const { name, whois_info } = info;
|
const { name, whois_info } = info;
|
||||||
|
const whoisAvailable = whois_info && Object.keys(whois_info).length > 0;
|
||||||
|
|
||||||
if (name) {
|
if (name) {
|
||||||
nameContainer = !whois_info && isDetailed
|
if (isLogs) {
|
||||||
? <small title={value}>{value}</small>
|
nameContainer = !whoisAvailable && isDetailed
|
||||||
: <div className="logs__text logs__text--nowrap" title={`${name} (${value})`}>
|
? (
|
||||||
{name}
|
<small title={value}>{value}</small>
|
||||||
{' '}
|
) : (
|
||||||
<small>{`(${value})`}</small>
|
<div className="logs__text logs__text--nowrap" title={`${name} (${value})`}>
|
||||||
</div>;
|
{name} <small>{`(${value})`}</small>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
nameContainer = (
|
||||||
|
<div
|
||||||
|
className="logs__text logs__text--nowrap"
|
||||||
|
title={`${name} (${value})`}
|
||||||
|
>
|
||||||
|
{name} <small>{`(${value})`}</small>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (whois_info && isDetailed) {
|
if (whoisAvailable && isDetailed) {
|
||||||
whoisContainer = (
|
whoisContainer = (
|
||||||
<div className="logs__text logs__text--wrap logs__text--whois">
|
<div className="logs__text logs__text--wrap logs__text--whois">
|
||||||
{getFormattedWhois(whois_info)}
|
{getFormattedWhois(whois_info)}
|
||||||
|
|||||||
@@ -5,14 +5,12 @@ import subHours from 'date-fns/sub_hours';
|
|||||||
import addHours from 'date-fns/add_hours';
|
import addHours from 'date-fns/add_hours';
|
||||||
import addDays from 'date-fns/add_days';
|
import addDays from 'date-fns/add_days';
|
||||||
import subDays from 'date-fns/sub_days';
|
import subDays from 'date-fns/sub_days';
|
||||||
import isSameDay from 'date-fns/is_same_day';
|
|
||||||
import round from 'lodash/round';
|
import round from 'lodash/round';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import i18n from 'i18next';
|
import i18n from 'i18next';
|
||||||
import uniqBy from 'lodash/uniqBy';
|
import uniqBy from 'lodash/uniqBy';
|
||||||
import ipaddr from 'ipaddr.js';
|
import ipaddr from 'ipaddr.js';
|
||||||
import queryString from 'query-string';
|
import queryString from 'query-string';
|
||||||
import versionCompare from './versionCompare';
|
|
||||||
import { getTrackerData } from './trackers/trackers';
|
import { getTrackerData } from './trackers/trackers';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -21,7 +19,6 @@ import {
|
|||||||
DEFAULT_LANGUAGE,
|
DEFAULT_LANGUAGE,
|
||||||
DEFAULT_TIME_FORMAT,
|
DEFAULT_TIME_FORMAT,
|
||||||
DETAILED_DATE_FORMAT_OPTIONS,
|
DETAILED_DATE_FORMAT_OPTIONS,
|
||||||
DNS_RECORD_TYPES,
|
|
||||||
FILTERED,
|
FILTERED,
|
||||||
FILTERED_STATUS,
|
FILTERED_STATUS,
|
||||||
IP_MATCH_LIST_STATUS,
|
IP_MATCH_LIST_STATUS,
|
||||||
@@ -32,6 +29,7 @@ import {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param time {string} The time to format
|
* @param time {string} The time to format
|
||||||
|
* @param options {string}
|
||||||
* @returns {string} Returns the time in the format HH:mm:ss
|
* @returns {string} Returns the time in the format HH:mm:ss
|
||||||
*/
|
*/
|
||||||
export const formatTime = (time, options = DEFAULT_TIME_FORMAT) => {
|
export const formatTime = (time, options = DEFAULT_TIME_FORMAT) => {
|
||||||
@@ -61,12 +59,6 @@ export const formatDetailedDateTime = (dateTime) => formatDateTime(
|
|||||||
dateTime, DETAILED_DATE_FORMAT_OPTIONS,
|
dateTime, DETAILED_DATE_FORMAT_OPTIONS,
|
||||||
);
|
);
|
||||||
|
|
||||||
/**
|
|
||||||
* @param date {string}
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
export const isToday = (date) => isSameDay(new Date(date), new Date());
|
|
||||||
|
|
||||||
export const normalizeLogs = (logs) => logs.map((log) => {
|
export const normalizeLogs = (logs) => logs.map((log) => {
|
||||||
const {
|
const {
|
||||||
answer,
|
answer,
|
||||||
@@ -352,39 +344,6 @@ export const normalizeTopClients = (topClients) => topClients.reduce(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
export const getClientInfo = (clients, ip) => {
|
|
||||||
const client = clients
|
|
||||||
.find((item) => item.ip_addrs?.find((clientIp) => clientIp === ip));
|
|
||||||
|
|
||||||
if (!client) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
const { name, whois_info } = client;
|
|
||||||
const whois = Object.keys(whois_info).length > 0 ? whois_info : '';
|
|
||||||
|
|
||||||
return {
|
|
||||||
name,
|
|
||||||
whois,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getAutoClientInfo = (clients, ip) => {
|
|
||||||
const client = clients.find((item) => ip === item.ip);
|
|
||||||
|
|
||||||
if (!client) {
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
|
|
||||||
const { name, whois_info } = client;
|
|
||||||
const whois = Object.keys(whois_info).length > 0 ? whois_info : '';
|
|
||||||
|
|
||||||
return {
|
|
||||||
name,
|
|
||||||
whois,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const sortClients = (clients) => {
|
export const sortClients = (clients) => {
|
||||||
const compare = (a, b) => {
|
const compare = (a, b) => {
|
||||||
const nameA = a.name.toUpperCase();
|
const nameA = a.name.toUpperCase();
|
||||||
@@ -418,10 +377,6 @@ export const secondsToMilliseconds = (seconds) => {
|
|||||||
export const normalizeRulesTextarea = (text) => text?.replace(/^\n/g, '')
|
export const normalizeRulesTextarea = (text) => text?.replace(/^\n/g, '')
|
||||||
.replace(/\n\s*\n/g, '\n');
|
.replace(/\n\s*\n/g, '\n');
|
||||||
|
|
||||||
export const isVersionGreater = (currentVersion, previousVersion) => (
|
|
||||||
versionCompare(currentVersion, previousVersion) === -1
|
|
||||||
);
|
|
||||||
|
|
||||||
export const normalizeWhois = (whois) => {
|
export const normalizeWhois = (whois) => {
|
||||||
if (Object.keys(whois).length > 0) {
|
if (Object.keys(whois).length > 0) {
|
||||||
const {
|
const {
|
||||||
@@ -448,8 +403,6 @@ export const normalizeWhois = (whois) => {
|
|||||||
return whois;
|
return whois;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isValidQuestionType = (type) => type && DNS_RECORD_TYPES.includes(type.toUpperCase());
|
|
||||||
|
|
||||||
export const getPathWithQueryString = (path, params) => {
|
export const getPathWithQueryString = (path, params) => {
|
||||||
const searchParams = new URLSearchParams(params);
|
const searchParams = new URLSearchParams(params);
|
||||||
|
|
||||||
@@ -547,10 +500,10 @@ export const getMap = (arr, key, value) => arr.reduce((acc, curr) => {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param parsedIp {object} ipaddr.js IPv4 or IPv6 object
|
* @param parsedIp {object} ipaddr.js IPv4 or IPv6 object
|
||||||
* @param cidr {array} ipaddr.js CIDR array
|
* @param parsedCidr {array} ipaddr.js CIDR array
|
||||||
* @returns {boolean}
|
* @returns {boolean}
|
||||||
*/
|
*/
|
||||||
export const isIpMatchCidr = (parsedIp, parsedCidr) => {
|
const isIpMatchCidr = (parsedIp, parsedCidr) => {
|
||||||
try {
|
try {
|
||||||
const cidrIpVersion = parsedCidr[0].kind();
|
const cidrIpVersion = parsedCidr[0].kind();
|
||||||
const ipVersion = parsedIp.kind();
|
const ipVersion = parsedIp.kind();
|
||||||
@@ -561,6 +514,75 @@ export const isIpMatchCidr = (parsedIp, parsedCidr) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The purpose of this method is to quickly check
|
||||||
|
* if this IP can possibly be in the specified CIDR range.
|
||||||
|
*
|
||||||
|
* @param ip {string}
|
||||||
|
* @param listItem {string}
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
const isIpQuickMatchCIDR = (ip, listItem) => {
|
||||||
|
const ipv6 = ip.indexOf(':') !== -1;
|
||||||
|
const cidrIpv6 = listItem.indexOf(':') !== -1;
|
||||||
|
if (ipv6 !== cidrIpv6) {
|
||||||
|
// CIDR is for a different IP type
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cidrIpv6) {
|
||||||
|
// We don't do quick check for IPv6 addresses
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const idx = listItem.indexOf('/');
|
||||||
|
if (idx === -1) {
|
||||||
|
// Not a CIDR, return false immediately
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cidrIp = listItem.substring(0, idx);
|
||||||
|
const cidrRange = parseInt(listItem.substring(idx + 1), 10);
|
||||||
|
if (Number.isNaN(cidrRange)) {
|
||||||
|
// Not a valid CIDR
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parts = cidrIp.split('.');
|
||||||
|
if (parts.length !== 4) {
|
||||||
|
// Invalid IP, return immediately
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now depending on the range we check if the IP can possibly be in that range
|
||||||
|
if (cidrRange < 8) {
|
||||||
|
// Use the slow approach
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cidrRange < 16) {
|
||||||
|
// Check the first part
|
||||||
|
// Example: 0.0.0.0/8 matches 0.*.*.*
|
||||||
|
return ip.indexOf(`${parts[0]}.`) === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cidrRange < 24) {
|
||||||
|
// Check the first two parts
|
||||||
|
// Example: 0.0.0.0/16 matches 0.0.*.*
|
||||||
|
return ip.indexOf(`${parts[0]}.${parts[1]}.`) === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cidrRange <= 32) {
|
||||||
|
// Check the first two parts
|
||||||
|
// Example: 0.0.0.0/16 matches 0.0.*.*
|
||||||
|
return ip.indexOf(`${parts[0]}.${parts[1]}.${parts[2]}.`) === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// range for IPv4 CIDR cannot be more than 32
|
||||||
|
// no need to check further, this CIDR is invalid
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param ip {string}
|
* @param ip {string}
|
||||||
* @param list {string}
|
* @param list {string}
|
||||||
@@ -578,20 +600,29 @@ export const getIpMatchListStatus = (ip, list) => {
|
|||||||
for (let i = 0; i < listArr.length; i += 1) {
|
for (let i = 0; i < listArr.length; i += 1) {
|
||||||
const listItem = listArr[i];
|
const listItem = listArr[i];
|
||||||
|
|
||||||
const parsedIp = ipaddr.parse(ip);
|
if (ip === listItem.trim()) {
|
||||||
const isItemAnIp = ipaddr.isValid(listItem);
|
|
||||||
const parsedItem = isItemAnIp ? ipaddr.parse(listItem) : ipaddr.parseCIDR(listItem);
|
|
||||||
|
|
||||||
if (isItemAnIp && parsedIp.toString() === parsedItem.toString()) {
|
|
||||||
return IP_MATCH_LIST_STATUS.EXACT;
|
return IP_MATCH_LIST_STATUS.EXACT;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isItemAnIp && isIpMatchCidr(parsedIp, parsedItem)) {
|
// Using ipaddr.js is quite slow so we first do a quick check
|
||||||
return IP_MATCH_LIST_STATUS.CIDR;
|
// to see if it's possible that this IP may be in the specified CIDR range
|
||||||
|
if (isIpQuickMatchCIDR(ip, listItem)) {
|
||||||
|
const parsedIp = ipaddr.parse(ip);
|
||||||
|
const isItemAnIp = ipaddr.isValid(listItem);
|
||||||
|
const parsedItem = isItemAnIp ? ipaddr.parse(listItem) : ipaddr.parseCIDR(listItem);
|
||||||
|
|
||||||
|
if (isItemAnIp && parsedIp.toString() === parsedItem.toString()) {
|
||||||
|
return IP_MATCH_LIST_STATUS.EXACT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isItemAnIp && isIpMatchCidr(parsedIp, parsedItem)) {
|
||||||
|
return IP_MATCH_LIST_STATUS.CIDR;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return IP_MATCH_LIST_STATUS.NOT_FOUND;
|
return IP_MATCH_LIST_STATUS.NOT_FOUND;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
return IP_MATCH_LIST_STATUS.NOT_FOUND;
|
return IP_MATCH_LIST_STATUS.NOT_FOUND;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -638,5 +669,80 @@ export const getLogsUrlParams = (search, response_status) => `?${queryString.str
|
|||||||
})}`;
|
})}`;
|
||||||
|
|
||||||
export const processContent = (content) => (Array.isArray(content)
|
export const processContent = (content) => (Array.isArray(content)
|
||||||
? content.filter(([, value]) => value)
|
? content.filter(([, value]) => value).reduce((acc, val) => acc.concat(val), [])
|
||||||
.flat() : content);
|
: content
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param object {object}
|
||||||
|
* @param sortKey {string}
|
||||||
|
* @returns {string[]}
|
||||||
|
*/
|
||||||
|
export const getObjectKeysSorted = (object, sortKey) => Object.entries(object)
|
||||||
|
.sort(([, { [sortKey]: order1 }], [, { [sortKey]: order2 }]) => order1 - order2)
|
||||||
|
.map(([key]) => key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param ip
|
||||||
|
* @returns {[IPv4|IPv6, 33|129]}
|
||||||
|
*/
|
||||||
|
const getParsedIpWithPrefixLength = (ip) => {
|
||||||
|
const MAX_PREFIX_LENGTH_V4 = 32;
|
||||||
|
const MAX_PREFIX_LENGTH_V6 = 128;
|
||||||
|
|
||||||
|
const parsedIp = ipaddr.parse(ip);
|
||||||
|
const prefixLength = parsedIp.kind() === 'ipv4' ? MAX_PREFIX_LENGTH_V4 : MAX_PREFIX_LENGTH_V6;
|
||||||
|
|
||||||
|
// Increment prefix length to always put IP after CIDR, e.g. 127.0.0.1/32, 127.0.0.1
|
||||||
|
return [parsedIp, prefixLength + 1];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function for IP and CIDR comparison (supports both v4 and v6)
|
||||||
|
* @param item - ip or cidr
|
||||||
|
* @returns {number[]}
|
||||||
|
*/
|
||||||
|
const getAddressesComparisonBytes = (item) => {
|
||||||
|
// Sort ipv4 before ipv6
|
||||||
|
const IP_V4_COMPARISON_CODE = 0;
|
||||||
|
const IP_V6_COMPARISON_CODE = 1;
|
||||||
|
|
||||||
|
const [parsedIp, cidr] = ipaddr.isValid(item)
|
||||||
|
? getParsedIpWithPrefixLength(item)
|
||||||
|
: ipaddr.parseCIDR(item);
|
||||||
|
|
||||||
|
const [normalizedBytes, ipVersionComparisonCode] = parsedIp.kind() === 'ipv4'
|
||||||
|
? [parsedIp.toIPv4MappedAddress().parts, IP_V4_COMPARISON_CODE]
|
||||||
|
: [parsedIp.parts, IP_V6_COMPARISON_CODE];
|
||||||
|
|
||||||
|
return [ipVersionComparisonCode, ...normalizedBytes, cidr];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare function for IP and CIDR sort in ascending order (supports both v4 and v6)
|
||||||
|
* @param a
|
||||||
|
* @param b
|
||||||
|
* @returns {number} -1 | 0 | 1
|
||||||
|
*/
|
||||||
|
export const sortIp = (a, b) => {
|
||||||
|
try {
|
||||||
|
const comparisonBytesA = getAddressesComparisonBytes(a);
|
||||||
|
const comparisonBytesB = getAddressesComparisonBytes(b);
|
||||||
|
|
||||||
|
for (let i = 0; i < comparisonBytesA.length; i += 1) {
|
||||||
|
const byteA = comparisonBytesA[i];
|
||||||
|
const byteB = comparisonBytesB[i];
|
||||||
|
|
||||||
|
if (byteA === byteB) {
|
||||||
|
// eslint-disable-next-line no-continue
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return byteA > byteB ? 1 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|||||||
17
client/src/helpers/version.js
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
/**
|
||||||
|
* Checks if versions are equal.
|
||||||
|
* Please note, that this method strips the "v" prefix.
|
||||||
|
*
|
||||||
|
* @param left {string} - left version
|
||||||
|
* @param right {string} - right version
|
||||||
|
* @return {boolean} true if versions are equal
|
||||||
|
*/
|
||||||
|
export const areEqualVersions = (left, right) => {
|
||||||
|
if (!left || !right) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const leftVersion = left.replace(/^v/, '');
|
||||||
|
const rightVersion = right.replace(/^v/, '');
|
||||||
|
return leftVersion === rightVersion;
|
||||||
|
};
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
/*
|
|
||||||
* Project: tiny-version-compare https://github.com/bfred-it/tiny-version-compare
|
|
||||||
* License (MIT) https://github.com/bfred-it/tiny-version-compare/blob/master/LICENSE
|
|
||||||
*/
|
|
||||||
const split = (v) => String(v).replace(/^[vr]/, '') // Drop initial 'v' or 'r'
|
|
||||||
.replace(/([a-z]+)/gi, '.$1.') // Sort each word separately
|
|
||||||
.replace(/[-.]+/g, '.') // Consider dashes as separators (+ trim multiple separators)
|
|
||||||
.split('.');
|
|
||||||
|
|
||||||
// Development versions are considered "negative",
|
|
||||||
// but localeCompare doesn't handle negative numbers.
|
|
||||||
// This offset is applied to reset the lowest development version to 0
|
|
||||||
const offset = (part) => {
|
|
||||||
// Not numeric, return as is
|
|
||||||
if (Number.isNaN(part)) {
|
|
||||||
return part;
|
|
||||||
}
|
|
||||||
return 5 + Number(part);
|
|
||||||
};
|
|
||||||
|
|
||||||
const parsePart = (part) => {
|
|
||||||
// Missing, consider it zero
|
|
||||||
if (typeof part === 'undefined') {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
// Sort development versions
|
|
||||||
switch (part.toLowerCase()) {
|
|
||||||
case 'dev':
|
|
||||||
return -5;
|
|
||||||
case 'alpha':
|
|
||||||
return -4;
|
|
||||||
case 'beta':
|
|
||||||
return -3;
|
|
||||||
case 'rc':
|
|
||||||
return -2;
|
|
||||||
case 'pre':
|
|
||||||
return -1;
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
// Return as is, it’s either a plain number or text that will be sorted alphabetically
|
|
||||||
return part;
|
|
||||||
};
|
|
||||||
|
|
||||||
const versionCompare = (prev, next) => {
|
|
||||||
const a = split(prev);
|
|
||||||
const b = split(next);
|
|
||||||
for (let i = 0; i < a.length || i < b.length; i += 1) {
|
|
||||||
const ai = offset(parsePart(a[i]));
|
|
||||||
const bi = offset(parsePart(b[i]));
|
|
||||||
const sort = String(ai).localeCompare(bi, 'en', {
|
|
||||||
numeric: true,
|
|
||||||
});
|
|
||||||
// Once the difference is found,
|
|
||||||
// stop comparing the rest of the parts
|
|
||||||
if (sort !== 0) {
|
|
||||||
return sort;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// No difference found
|
|
||||||
return 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default versionCompare;
|
|
||||||
@@ -1,13 +1,14 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
import './components/App/index.css';
|
|
||||||
import App from './containers/App';
|
|
||||||
import configureStore from './configureStore';
|
import configureStore from './configureStore';
|
||||||
import reducers from './reducers';
|
import reducers from './reducers';
|
||||||
|
import App from './components/App';
|
||||||
|
import './components/App/index.css';
|
||||||
import './i18n';
|
import './i18n';
|
||||||
|
|
||||||
const store = configureStore(reducers, {}); // set initial state
|
const store = configureStore(reducers, {}); // set initial state
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<App />
|
<App />
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { combineReducers } from 'redux';
|
|||||||
import { handleActions } from 'redux-actions';
|
import { handleActions } from 'redux-actions';
|
||||||
import { loadingBarReducer } from 'react-redux-loading-bar';
|
import { loadingBarReducer } from 'react-redux-loading-bar';
|
||||||
import { reducer as formReducer } from 'redux-form';
|
import { reducer as formReducer } from 'redux-form';
|
||||||
import { isVersionGreater } from '../helpers/helpers';
|
|
||||||
|
|
||||||
import * as actions from '../actions';
|
import * as actions from '../actions';
|
||||||
import toasts from './toasts';
|
import toasts from './toasts';
|
||||||
@@ -15,6 +14,7 @@ import stats from './stats';
|
|||||||
import queryLogs from './queryLogs';
|
import queryLogs from './queryLogs';
|
||||||
import dnsConfig from './dnsConfig';
|
import dnsConfig from './dnsConfig';
|
||||||
import filtering from './filtering';
|
import filtering from './filtering';
|
||||||
|
import { areEqualVersions } from '../helpers/version';
|
||||||
|
|
||||||
const settings = handleActions(
|
const settings = handleActions(
|
||||||
{
|
{
|
||||||
@@ -82,7 +82,7 @@ const dashboard = handleActions(
|
|||||||
[actions.getVersionSuccess]: (state, { payload }) => {
|
[actions.getVersionSuccess]: (state, { payload }) => {
|
||||||
const currentVersion = state.dnsVersion === 'undefined' ? 0 : state.dnsVersion;
|
const currentVersion = state.dnsVersion === 'undefined' ? 0 : state.dnsVersion;
|
||||||
|
|
||||||
if (!payload.disabled && isVersionGreater(currentVersion, payload.new_version)) {
|
if (!payload.disabled && !areEqualVersions(currentVersion, payload.new_version)) {
|
||||||
const {
|
const {
|
||||||
announcement_url: announcementUrl,
|
announcement_url: announcementUrl,
|
||||||
new_version: newVersion,
|
new_version: newVersion,
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ const queryLogs = handleActions(
|
|||||||
const rowsStart = pageSize * page;
|
const rowsStart = pageSize * page;
|
||||||
const rowsEnd = rowsStart + pageSize;
|
const rowsEnd = rowsStart + pageSize;
|
||||||
const logsSlice = logs.slice(rowsStart, rowsEnd);
|
const logsSlice = logs.slice(rowsStart, rowsEnd);
|
||||||
const isFiltered = Object.keys(filter).some((key) => filter[key]);
|
const isFiltered = filter && Object.keys(filter).some((key) => filter[key]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
|||||||
28
client/webpack.common.js
vendored
@@ -5,6 +5,7 @@ const flexBugsFixes = require('postcss-flexbugs-fixes');
|
|||||||
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
|
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
|
||||||
const CopyPlugin = require('copy-webpack-plugin');
|
const CopyPlugin = require('copy-webpack-plugin');
|
||||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||||
|
const { BUILD_ENVS } = require('./constants');
|
||||||
|
|
||||||
const RESOURCES_PATH = path.resolve(__dirname);
|
const RESOURCES_PATH = path.resolve(__dirname);
|
||||||
const ENTRY_REACT = path.resolve(RESOURCES_PATH, 'src/index.js');
|
const ENTRY_REACT = path.resolve(RESOURCES_PATH, 'src/index.js');
|
||||||
@@ -18,13 +19,10 @@ const ASSETS_PATH = path.resolve(RESOURCES_PATH, 'public/assets');
|
|||||||
const PUBLIC_PATH = path.resolve(__dirname, '../build/static');
|
const PUBLIC_PATH = path.resolve(__dirname, '../build/static');
|
||||||
const PUBLIC_ASSETS_PATH = path.resolve(PUBLIC_PATH, 'assets');
|
const PUBLIC_ASSETS_PATH = path.resolve(PUBLIC_PATH, 'assets');
|
||||||
|
|
||||||
const BUILD_ENVS = {
|
|
||||||
dev: 'development',
|
|
||||||
prod: 'production',
|
|
||||||
};
|
|
||||||
|
|
||||||
const BUILD_ENV = BUILD_ENVS[process.env.BUILD_ENV];
|
const BUILD_ENV = BUILD_ENVS[process.env.BUILD_ENV];
|
||||||
|
|
||||||
|
const isDev = BUILD_ENV === BUILD_ENVS.dev;
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
mode: BUILD_ENV,
|
mode: BUILD_ENV,
|
||||||
target: 'web',
|
target: 'web',
|
||||||
@@ -36,22 +34,35 @@ const config = {
|
|||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
path: PUBLIC_PATH,
|
path: PUBLIC_PATH,
|
||||||
filename: '[name].[chunkhash].js',
|
filename: '[name].[hash].js',
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
modules: ['node_modules'],
|
modules: ['node_modules'],
|
||||||
alias: {
|
alias: {
|
||||||
MainRoot: path.resolve(__dirname, '../'),
|
MainRoot: path.resolve(__dirname, '../'),
|
||||||
ClientRoot: path.resolve(__dirname, './src'),
|
ClientRoot: path.resolve(__dirname, './src'),
|
||||||
|
// TODO: change to '@hot-loader/react-dom' when v16.13.1 is released
|
||||||
|
// https://stackoverflow.com/a/62671689/12942752
|
||||||
|
'react-dom': 'react-dom',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.ya?ml$/,
|
||||||
|
type: 'json',
|
||||||
|
use: 'yaml-loader',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
test: /\.css$/i,
|
test: /\.css$/i,
|
||||||
use: [
|
use: [
|
||||||
'style-loader',
|
'style-loader',
|
||||||
MiniCssExtractPlugin.loader,
|
{
|
||||||
|
loader: MiniCssExtractPlugin.loader,
|
||||||
|
options: {
|
||||||
|
hmr: isDev,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
loader: 'css-loader',
|
loader: 'css-loader',
|
||||||
options: {
|
options: {
|
||||||
@@ -122,7 +133,8 @@ const config = {
|
|||||||
template: HTML_LOGIN_PATH,
|
template: HTML_LOGIN_PATH,
|
||||||
}),
|
}),
|
||||||
new MiniCssExtractPlugin({
|
new MiniCssExtractPlugin({
|
||||||
filename: '[name].[contenthash].css',
|
filename: isDev ? '[name].css' : '[name].[hash].css',
|
||||||
|
chunkFilename: isDev ? '[id].css' : '[id].[hash].css',
|
||||||
}),
|
}),
|
||||||
new CopyPlugin({
|
new CopyPlugin({
|
||||||
patterns: [
|
patterns: [
|
||||||
|
|||||||
46
client/webpack.dev.js
vendored
@@ -1,5 +1,50 @@
|
|||||||
const merge = require('webpack-merge');
|
const merge = require('webpack-merge');
|
||||||
|
const yaml = require('js-yaml');
|
||||||
|
const fs = require('fs');
|
||||||
const common = require('./webpack.common.js');
|
const common = require('./webpack.common.js');
|
||||||
|
const { BASE_URL } = require('./constants');
|
||||||
|
|
||||||
|
const ZERO_HOST = '0.0.0.0';
|
||||||
|
const LOCALHOST = '127.0.0.1';
|
||||||
|
const DEFAULT_PORT = 80;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get document, or throw exception on error
|
||||||
|
* @returns {{bind_host: string, bind_port: number}}
|
||||||
|
*/
|
||||||
|
const importConfig = () => {
|
||||||
|
try {
|
||||||
|
const doc = yaml.safeLoad(fs.readFileSync('../AdguardHome.yaml', 'utf8'));
|
||||||
|
const { bind_host, bind_port } = doc;
|
||||||
|
return {
|
||||||
|
bind_host,
|
||||||
|
bind_port,
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
return {
|
||||||
|
bind_host: ZERO_HOST,
|
||||||
|
bind_port: DEFAULT_PORT,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getDevServerConfig = (proxyUrl = BASE_URL) => {
|
||||||
|
const { bind_host: host, bind_port: port } = importConfig();
|
||||||
|
const { DEV_SERVER_PORT } = process.env;
|
||||||
|
|
||||||
|
const devServerHost = host === ZERO_HOST ? LOCALHOST : host;
|
||||||
|
const devServerPort = DEV_SERVER_PORT || port + 8000;
|
||||||
|
|
||||||
|
return {
|
||||||
|
hot: true,
|
||||||
|
host: devServerHost,
|
||||||
|
port: devServerPort,
|
||||||
|
proxy: {
|
||||||
|
[proxyUrl]: `http://${devServerHost}:${port}`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = merge(common, {
|
module.exports = merge(common, {
|
||||||
devtool: 'eval-source-map',
|
devtool: 'eval-source-map',
|
||||||
@@ -16,4 +61,5 @@ module.exports = merge(common, {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
devServer: process.env.WEBPACK_DEV_SERVER ? getDevServerConfig(BASE_URL) : undefined,
|
||||||
});
|
});
|
||||||
|
|||||||
19
client/webpack.prod.js
vendored
@@ -2,18 +2,19 @@ const StyleLintPlugin = require('stylelint-webpack-plugin');
|
|||||||
const merge = require('webpack-merge');
|
const merge = require('webpack-merge');
|
||||||
const common = require('./webpack.common.js');
|
const common = require('./webpack.common.js');
|
||||||
|
|
||||||
|
|
||||||
module.exports = merge(common, {
|
module.exports = merge(common, {
|
||||||
module: {
|
module: {
|
||||||
rules: [{
|
rules: [
|
||||||
test: /\.js$/,
|
{
|
||||||
exclude: /node_modules/,
|
test: /\.js$/,
|
||||||
loader: 'eslint-loader',
|
exclude: /node_modules/,
|
||||||
options: {
|
loader: 'eslint-loader',
|
||||||
failOnError: true,
|
options: {
|
||||||
configFile: 'prod.eslintrc',
|
failOnError: true,
|
||||||
|
configFile: 'prod.eslintrc',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}],
|
],
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new StyleLintPlugin({
|
new StyleLintPlugin({
|
||||||
|
|||||||
@@ -47,9 +47,9 @@ func NewForTest(c *Config, filters []Filter) *Dnsfilter {
|
|||||||
setts = RequestFilteringSettings{}
|
setts = RequestFilteringSettings{}
|
||||||
setts.FilteringEnabled = true
|
setts.FilteringEnabled = true
|
||||||
if c != nil {
|
if c != nil {
|
||||||
c.SafeBrowsingCacheSize = 1000
|
c.SafeBrowsingCacheSize = 10000
|
||||||
|
c.ParentalCacheSize = 10000
|
||||||
c.SafeSearchCacheSize = 1000
|
c.SafeSearchCacheSize = 1000
|
||||||
c.ParentalCacheSize = 1000
|
|
||||||
c.CacheTime = 30
|
c.CacheTime = 30
|
||||||
setts.SafeSearchEnabled = c.SafeSearchEnabled
|
setts.SafeSearchEnabled = c.SafeSearchEnabled
|
||||||
setts.SafeBrowsingEnabled = c.SafeBrowsingEnabled
|
setts.SafeBrowsingEnabled = c.SafeBrowsingEnabled
|
||||||
@@ -146,12 +146,6 @@ func TestEtcHostsMatching(t *testing.T) {
|
|||||||
|
|
||||||
// SAFE BROWSING
|
// SAFE BROWSING
|
||||||
|
|
||||||
func TestSafeBrowsingHash(t *testing.T) {
|
|
||||||
q, hashes := hostnameToHashParam("1.2.3.4.5.6")
|
|
||||||
assert.Equal(t, "0132d0fa.b5413b4e.5fa067c1.e7f6c011.", q)
|
|
||||||
assert.Equal(t, 4, len(hashes))
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestSafeBrowsing(t *testing.T) {
|
func TestSafeBrowsing(t *testing.T) {
|
||||||
d := NewForTest(&Config{SafeBrowsingEnabled: true}, nil)
|
d := NewForTest(&Config{SafeBrowsingEnabled: true}, nil)
|
||||||
defer d.Close()
|
defer d.Close()
|
||||||
|
|||||||
149
dnsfilter/safe_search.go
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
package dnsfilter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/gob"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/golibs/cache"
|
||||||
|
"github.com/AdguardTeam/golibs/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
expire byte[4]
|
||||||
|
res Result
|
||||||
|
*/
|
||||||
|
func (d *Dnsfilter) setCacheResult(cache cache.Cache, host string, res Result) int {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
expire := uint(time.Now().Unix()) + d.Config.CacheTime*60
|
||||||
|
var exp []byte
|
||||||
|
exp = make([]byte, 4)
|
||||||
|
binary.BigEndian.PutUint32(exp, uint32(expire))
|
||||||
|
_, _ = buf.Write(exp)
|
||||||
|
|
||||||
|
enc := gob.NewEncoder(&buf)
|
||||||
|
err := enc.Encode(res)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("gob.Encode(): %s", err)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
val := buf.Bytes()
|
||||||
|
_ = cache.Set([]byte(host), val)
|
||||||
|
return len(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCachedResult(cache cache.Cache, host string) (Result, bool) {
|
||||||
|
data := cache.Get([]byte(host))
|
||||||
|
if data == nil {
|
||||||
|
return Result{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
exp := int(binary.BigEndian.Uint32(data[:4]))
|
||||||
|
if exp <= int(time.Now().Unix()) {
|
||||||
|
cache.Del([]byte(host))
|
||||||
|
return Result{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
buf.Write(data[4:])
|
||||||
|
dec := gob.NewDecoder(&buf)
|
||||||
|
r := Result{}
|
||||||
|
err := dec.Decode(&r)
|
||||||
|
if err != nil {
|
||||||
|
log.Debug("gob.Decode(): %s", err)
|
||||||
|
return Result{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
return r, true
|
||||||
|
}
|
||||||
|
|
||||||
|
// SafeSearchDomain returns replacement address for search engine
|
||||||
|
func (d *Dnsfilter) SafeSearchDomain(host string) (string, bool) {
|
||||||
|
val, ok := safeSearchDomains[host]
|
||||||
|
return val, ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Dnsfilter) checkSafeSearch(host string) (Result, error) {
|
||||||
|
if log.GetLevel() >= log.DEBUG {
|
||||||
|
timer := log.StartTimer()
|
||||||
|
defer timer.LogElapsed("SafeSearch: lookup for %s", host)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check cache. Return cached result if it was found
|
||||||
|
cachedValue, isFound := getCachedResult(gctx.safeSearchCache, host)
|
||||||
|
if isFound {
|
||||||
|
// atomic.AddUint64(&gctx.stats.Safesearch.CacheHits, 1)
|
||||||
|
log.Tracef("SafeSearch: found in cache: %s", host)
|
||||||
|
return cachedValue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
safeHost, ok := d.SafeSearchDomain(host)
|
||||||
|
if !ok {
|
||||||
|
return Result{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
res := Result{IsFiltered: true, Reason: FilteredSafeSearch}
|
||||||
|
if ip := net.ParseIP(safeHost); ip != nil {
|
||||||
|
res.IP = ip
|
||||||
|
valLen := d.setCacheResult(gctx.safeSearchCache, host, res)
|
||||||
|
log.Debug("SafeSearch: stored in cache: %s (%d bytes)", host, valLen)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO this address should be resolved with upstream that was configured in dnsforward
|
||||||
|
addrs, err := net.LookupIP(safeHost)
|
||||||
|
if err != nil {
|
||||||
|
log.Tracef("SafeSearchDomain for %s was found but failed to lookup for %s cause %s", host, safeHost, err)
|
||||||
|
return Result{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, i := range addrs {
|
||||||
|
if ipv4 := i.To4(); ipv4 != nil {
|
||||||
|
res.IP = ipv4
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(res.IP) == 0 {
|
||||||
|
return Result{}, fmt.Errorf("no ipv4 addresses in safe search response for %s", safeHost)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache result
|
||||||
|
valLen := d.setCacheResult(gctx.safeSearchCache, host, res)
|
||||||
|
log.Debug("SafeSearch: stored in cache: %s (%d bytes)", host, valLen)
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Dnsfilter) handleSafeSearchEnable(w http.ResponseWriter, r *http.Request) {
|
||||||
|
d.Config.SafeSearchEnabled = true
|
||||||
|
d.Config.ConfigModified()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Dnsfilter) handleSafeSearchDisable(w http.ResponseWriter, r *http.Request) {
|
||||||
|
d.Config.SafeSearchEnabled = false
|
||||||
|
d.Config.ConfigModified()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Dnsfilter) handleSafeSearchStatus(w http.ResponseWriter, r *http.Request) {
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"enabled": d.Config.SafeSearchEnabled,
|
||||||
|
}
|
||||||
|
jsonVal, err := json.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
httpError(r, w, http.StatusInternalServerError, "Unable to marshal status json: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
_, err = w.Write(jsonVal)
|
||||||
|
if err != nil {
|
||||||
|
httpError(r, w, http.StatusInternalServerError, "Unable to write response json: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// Parental Control, Safe Browsing, Safe Search
|
// Safe Browsing, Parental Control
|
||||||
|
|
||||||
package dnsfilter
|
package dnsfilter
|
||||||
|
|
||||||
@@ -6,12 +6,12 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/gob"
|
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -22,9 +22,6 @@ import (
|
|||||||
"golang.org/x/net/publicsuffix"
|
"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 dnsTimeout = 3 * time.Second
|
||||||
const defaultSafebrowsingServer = "https://dns-family.adguard.com/dns-query"
|
const defaultSafebrowsingServer = "https://dns-family.adguard.com/dns-query"
|
||||||
const defaultParentalServer = "https://dns-family.adguard.com/dns-query"
|
const defaultParentalServer = "https://dns-family.adguard.com/dns-query"
|
||||||
@@ -35,7 +32,15 @@ func (d *Dnsfilter) initSecurityServices() error {
|
|||||||
var err error
|
var err error
|
||||||
d.safeBrowsingServer = defaultSafebrowsingServer
|
d.safeBrowsingServer = defaultSafebrowsingServer
|
||||||
d.parentalServer = defaultParentalServer
|
d.parentalServer = defaultParentalServer
|
||||||
opts := upstream.Options{Timeout: dnsTimeout, Bootstrap: bootstrapServers}
|
opts := upstream.Options{
|
||||||
|
Timeout: dnsTimeout,
|
||||||
|
ServerIPAddrs: []net.IP{
|
||||||
|
net.ParseIP("176.103.130.132"),
|
||||||
|
net.ParseIP("176.103.130.134"),
|
||||||
|
net.ParseIP("2a00:5a60::bad1:ff"),
|
||||||
|
net.ParseIP("2a00:5a60::bad2:ff"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
d.parentalUpstream, err = upstream.AddressToUpstream(d.parentalServer, opts)
|
d.parentalUpstream, err = upstream.AddressToUpstream(d.parentalServer, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -52,115 +57,65 @@ func (d *Dnsfilter) initSecurityServices() error {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
expire byte[4]
|
expire byte[4]
|
||||||
res Result
|
hash byte[32]
|
||||||
|
...
|
||||||
*/
|
*/
|
||||||
func (d *Dnsfilter) setCacheResult(cache cache.Cache, host string, res Result) int {
|
func (c *sbCtx) setCache(prefix []byte, hashes []byte) {
|
||||||
var buf bytes.Buffer
|
d := make([]byte, 4+len(hashes))
|
||||||
|
expire := uint(time.Now().Unix()) + c.cacheTime*60
|
||||||
expire := uint(time.Now().Unix()) + d.Config.CacheTime*60
|
binary.BigEndian.PutUint32(d[:4], uint32(expire))
|
||||||
var exp []byte
|
copy(d[4:], hashes)
|
||||||
exp = make([]byte, 4)
|
c.cache.Set(prefix, d)
|
||||||
binary.BigEndian.PutUint32(exp, uint32(expire))
|
log.Debug("%s: stored in cache: %v", c.svc, prefix)
|
||||||
_, _ = 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) {
|
func (c *sbCtx) getCached() int {
|
||||||
data := cache.Get([]byte(host))
|
now := time.Now().Unix()
|
||||||
if data == nil {
|
hashesToRequest := map[[32]byte]string{}
|
||||||
return Result{}, false
|
for k, v := range c.hashToHost {
|
||||||
}
|
key := k[0:2]
|
||||||
|
val := c.cache.Get(key)
|
||||||
exp := int(binary.BigEndian.Uint32(data[:4]))
|
if val != nil {
|
||||||
if exp <= int(time.Now().Unix()) {
|
expire := binary.BigEndian.Uint32(val)
|
||||||
cache.Del([]byte(host))
|
if now >= int64(expire) {
|
||||||
return Result{}, false
|
val = nil
|
||||||
}
|
} else {
|
||||||
|
for i := 4; i < len(val); i += 32 {
|
||||||
var buf bytes.Buffer
|
hash := val[i : i+32]
|
||||||
buf.Write(data[4:])
|
var hash32 [32]byte
|
||||||
dec := gob.NewDecoder(&buf)
|
copy(hash32[:], hash[0:32])
|
||||||
r := Result{}
|
_, found := c.hashToHost[hash32]
|
||||||
err := dec.Decode(&r)
|
if found {
|
||||||
if err != nil {
|
log.Debug("%s: found in cache: %s: blocked by %v", c.svc, c.host, hash32)
|
||||||
log.Debug("gob.Decode(): %s", err)
|
return 1
|
||||||
return Result{}, false
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return r, true
|
}
|
||||||
}
|
if val == nil {
|
||||||
|
hashesToRequest[k] = v
|
||||||
// SafeSearchDomain returns replacement address for search engine
|
|
||||||
func (d *Dnsfilter) SafeSearchDomain(host string) (string, bool) {
|
|
||||||
val, ok := safeSearchDomains[host]
|
|
||||||
return val, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Dnsfilter) checkSafeSearch(host string) (Result, error) {
|
|
||||||
if log.GetLevel() >= log.DEBUG {
|
|
||||||
timer := log.StartTimer()
|
|
||||||
defer timer.LogElapsed("SafeSearch: lookup for %s", host)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check cache. Return cached result if it was found
|
|
||||||
cachedValue, isFound := getCachedResult(gctx.safeSearchCache, host)
|
|
||||||
if isFound {
|
|
||||||
// atomic.AddUint64(&gctx.stats.Safesearch.CacheHits, 1)
|
|
||||||
log.Tracef("SafeSearch: found in cache: %s", host)
|
|
||||||
return cachedValue, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
safeHost, ok := d.SafeSearchDomain(host)
|
|
||||||
if !ok {
|
|
||||||
return Result{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
res := Result{IsFiltered: true, Reason: FilteredSafeSearch}
|
|
||||||
if ip := net.ParseIP(safeHost); ip != nil {
|
|
||||||
res.IP = ip
|
|
||||||
valLen := d.setCacheResult(gctx.safeSearchCache, host, res)
|
|
||||||
log.Debug("SafeSearch: stored in cache: %s (%d bytes)", host, valLen)
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO this address should be resolved with upstream that was configured in dnsforward
|
|
||||||
addrs, err := net.LookupIP(safeHost)
|
|
||||||
if err != nil {
|
|
||||||
log.Tracef("SafeSearchDomain for %s was found but failed to lookup for %s cause %s", host, safeHost, err)
|
|
||||||
return Result{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, i := range addrs {
|
|
||||||
if ipv4 := i.To4(); ipv4 != nil {
|
|
||||||
res.IP = ipv4
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(res.IP) == 0 {
|
if len(hashesToRequest) == 0 {
|
||||||
return Result{}, fmt.Errorf("no ipv4 addresses in safe search response for %s", safeHost)
|
log.Debug("%s: found in cache: %s: not blocked", c.svc, c.host)
|
||||||
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache result
|
c.hashToHost = hashesToRequest
|
||||||
valLen := d.setCacheResult(gctx.safeSearchCache, host, res)
|
return 0
|
||||||
log.Debug("SafeSearch: stored in cache: %s (%d bytes)", host, valLen)
|
|
||||||
return res, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// for each dot, hash it and add it to string
|
type sbCtx struct {
|
||||||
// The maximum is 4 components: "a.b.c.d"
|
host string
|
||||||
func hostnameToHashParam(host string) (string, map[string]bool) {
|
svc string
|
||||||
var hashparam bytes.Buffer
|
hashToHost map[[32]byte]string
|
||||||
hashes := map[string]bool{}
|
cache cache.Cache
|
||||||
|
cacheTime uint
|
||||||
|
}
|
||||||
|
|
||||||
|
func hostnameToHashes(host string) map[[32]byte]string {
|
||||||
|
hashes := map[[32]byte]string{}
|
||||||
tld, icann := publicsuffix.PublicSuffix(host)
|
tld, icann := publicsuffix.PublicSuffix(host)
|
||||||
if !icann {
|
if !icann {
|
||||||
// private suffixes like cloudfront.net
|
// private suffixes like cloudfront.net
|
||||||
@@ -190,8 +145,7 @@ func hostnameToHashParam(host string) (string, map[string]bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
sum := sha256.Sum256([]byte(curhost))
|
sum := sha256.Sum256([]byte(curhost))
|
||||||
hashes[hex.EncodeToString(sum[:])] = true
|
hashes[sum] = curhost
|
||||||
hashparam.WriteString(fmt.Sprintf("%s.", hex.EncodeToString(sum[0:4])))
|
|
||||||
|
|
||||||
pos := strings.IndexByte(curhost, byte('.'))
|
pos := strings.IndexByte(curhost, byte('.'))
|
||||||
if pos < 0 {
|
if pos < 0 {
|
||||||
@@ -199,26 +153,91 @@ func hostnameToHashParam(host string) (string, map[string]bool) {
|
|||||||
}
|
}
|
||||||
curhost = curhost[pos+1:]
|
curhost = curhost[pos+1:]
|
||||||
}
|
}
|
||||||
return hashparam.String(), hashes
|
return hashes
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert hash array to string
|
||||||
|
func (c *sbCtx) getQuestion() string {
|
||||||
|
q := ""
|
||||||
|
for hash := range c.hashToHost {
|
||||||
|
q += fmt.Sprintf("%s.", hex.EncodeToString(hash[0:2]))
|
||||||
|
}
|
||||||
|
if c.svc == "SafeBrowsing" {
|
||||||
|
q += sbTXTSuffix
|
||||||
|
} else {
|
||||||
|
q += pcTXTSuffix
|
||||||
|
}
|
||||||
|
return q
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the target hash in TXT response
|
// Find the target hash in TXT response
|
||||||
func (d *Dnsfilter) processTXT(svc, host string, resp *dns.Msg, hashes map[string]bool) bool {
|
func (c *sbCtx) processTXT(resp *dns.Msg) (bool, [][]byte) {
|
||||||
|
matched := false
|
||||||
|
hashes := [][]byte{}
|
||||||
for _, a := range resp.Answer {
|
for _, a := range resp.Answer {
|
||||||
txt, ok := a.(*dns.TXT)
|
txt, ok := a.(*dns.TXT)
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
log.Tracef("%s: hashes for %s: %v", svc, host, txt.Txt)
|
log.Debug("%s: received hashes for %s: %v", c.svc, c.host, txt.Txt)
|
||||||
|
|
||||||
for _, t := range txt.Txt {
|
for _, t := range txt.Txt {
|
||||||
_, ok := hashes[t]
|
|
||||||
if ok {
|
if len(t) != 32*2 {
|
||||||
log.Tracef("%s: matched %s by %s", svc, host, t)
|
continue
|
||||||
return true
|
}
|
||||||
|
hash, err := hex.DecodeString(t)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
hashes = append(hashes, hash)
|
||||||
|
|
||||||
|
if !matched {
|
||||||
|
var hash32 [32]byte
|
||||||
|
copy(hash32[:], hash)
|
||||||
|
hashHost, ok := c.hashToHost[hash32]
|
||||||
|
if ok {
|
||||||
|
log.Debug("%s: matched %s by %s/%s", c.svc, c.host, hashHost, t)
|
||||||
|
matched = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
|
return matched, hashes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *sbCtx) storeCache(hashes [][]byte) {
|
||||||
|
sort.Slice(hashes, func(a, b int) bool {
|
||||||
|
return bytes.Compare(hashes[a], hashes[b]) < 0
|
||||||
|
})
|
||||||
|
|
||||||
|
var curData []byte
|
||||||
|
var prevPrefix []byte
|
||||||
|
for i, hash := range hashes {
|
||||||
|
prefix := hash[0:2]
|
||||||
|
if !bytes.Equal(prefix, prevPrefix) {
|
||||||
|
if i != 0 {
|
||||||
|
c.setCache(prevPrefix, curData)
|
||||||
|
curData = nil
|
||||||
|
}
|
||||||
|
prevPrefix = hashes[i][0:2]
|
||||||
|
}
|
||||||
|
curData = append(curData, hash...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(prevPrefix) != 0 {
|
||||||
|
c.setCache(prevPrefix, curData)
|
||||||
|
}
|
||||||
|
|
||||||
|
for hash := range c.hashToHost {
|
||||||
|
prefix := hash[0:2]
|
||||||
|
val := c.cache.Get(prefix)
|
||||||
|
if val == nil {
|
||||||
|
c.setCache(prefix, nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disabling "dupl": the algorithm of SB/PC is similar, but it uses different data
|
// Disabling "dupl": the algorithm of SB/PC is similar, but it uses different data
|
||||||
@@ -229,18 +248,29 @@ func (d *Dnsfilter) checkSafeBrowsing(host string) (Result, error) {
|
|||||||
defer timer.LogElapsed("SafeBrowsing lookup for %s", host)
|
defer timer.LogElapsed("SafeBrowsing lookup for %s", host)
|
||||||
}
|
}
|
||||||
|
|
||||||
// check cache
|
result := Result{}
|
||||||
cachedValue, isFound := getCachedResult(gctx.safebrowsingCache, host)
|
hashes := hostnameToHashes(host)
|
||||||
if isFound {
|
|
||||||
// atomic.AddUint64(&gctx.stats.Safebrowsing.CacheHits, 1)
|
c := &sbCtx{
|
||||||
log.Tracef("SafeBrowsing: found in cache: %s", host)
|
host: host,
|
||||||
return cachedValue, nil
|
svc: "SafeBrowsing",
|
||||||
|
hashToHost: hashes,
|
||||||
|
cache: gctx.safebrowsingCache,
|
||||||
|
cacheTime: d.Config.CacheTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
result := Result{}
|
// check cache
|
||||||
question, hashes := hostnameToHashParam(host)
|
match := c.getCached()
|
||||||
question = question + sbTXTSuffix
|
if match < 0 {
|
||||||
|
return result, nil
|
||||||
|
} else if match > 0 {
|
||||||
|
result.IsFiltered = true
|
||||||
|
result.Reason = FilteredSafeBrowsing
|
||||||
|
result.Rule = "adguard-malware-shavar"
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
question := c.getQuestion()
|
||||||
log.Tracef("SafeBrowsing: checking %s: %s", host, question)
|
log.Tracef("SafeBrowsing: checking %s: %s", host, question)
|
||||||
|
|
||||||
req := dns.Msg{}
|
req := dns.Msg{}
|
||||||
@@ -250,14 +280,14 @@ func (d *Dnsfilter) checkSafeBrowsing(host string) (Result, error) {
|
|||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.processTXT("SafeBrowsing", host, resp, hashes) {
|
matched, receivedHashes := c.processTXT(resp)
|
||||||
|
if matched {
|
||||||
result.IsFiltered = true
|
result.IsFiltered = true
|
||||||
result.Reason = FilteredSafeBrowsing
|
result.Reason = FilteredSafeBrowsing
|
||||||
result.Rule = "adguard-malware-shavar"
|
result.Rule = "adguard-malware-shavar"
|
||||||
}
|
}
|
||||||
|
c.storeCache(receivedHashes)
|
||||||
|
|
||||||
valLen := d.setCacheResult(gctx.safebrowsingCache, host, result)
|
|
||||||
log.Debug("SafeBrowsing: stored in cache: %s (%d bytes)", host, valLen)
|
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,18 +299,29 @@ func (d *Dnsfilter) checkParental(host string) (Result, error) {
|
|||||||
defer timer.LogElapsed("Parental lookup for %s", host)
|
defer timer.LogElapsed("Parental lookup for %s", host)
|
||||||
}
|
}
|
||||||
|
|
||||||
// check cache
|
result := Result{}
|
||||||
cachedValue, isFound := getCachedResult(gctx.parentalCache, host)
|
hashes := hostnameToHashes(host)
|
||||||
if isFound {
|
|
||||||
// atomic.AddUint64(&gctx.stats.Parental.CacheHits, 1)
|
c := &sbCtx{
|
||||||
log.Tracef("Parental: found in cache: %s", host)
|
host: host,
|
||||||
return cachedValue, nil
|
svc: "Parental",
|
||||||
|
hashToHost: hashes,
|
||||||
|
cache: gctx.parentalCache,
|
||||||
|
cacheTime: d.Config.CacheTime,
|
||||||
}
|
}
|
||||||
|
|
||||||
result := Result{}
|
// check cache
|
||||||
question, hashes := hostnameToHashParam(host)
|
match := c.getCached()
|
||||||
question = question + pcTXTSuffix
|
if match < 0 {
|
||||||
|
return result, nil
|
||||||
|
} else if match > 0 {
|
||||||
|
result.IsFiltered = true
|
||||||
|
result.Reason = FilteredParental
|
||||||
|
result.Rule = "parental CATEGORY_BLACKLISTED"
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
question := c.getQuestion()
|
||||||
log.Tracef("Parental: checking %s: %s", host, question)
|
log.Tracef("Parental: checking %s: %s", host, question)
|
||||||
|
|
||||||
req := dns.Msg{}
|
req := dns.Msg{}
|
||||||
@@ -290,14 +331,14 @@ func (d *Dnsfilter) checkParental(host string) (Result, error) {
|
|||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.processTXT("Parental", host, resp, hashes) {
|
matched, receivedHashes := c.processTXT(resp)
|
||||||
|
if matched {
|
||||||
result.IsFiltered = true
|
result.IsFiltered = true
|
||||||
result.Reason = FilteredParental
|
result.Reason = FilteredParental
|
||||||
result.Rule = "parental CATEGORY_BLACKLISTED"
|
result.Rule = "parental CATEGORY_BLACKLISTED"
|
||||||
}
|
}
|
||||||
|
c.storeCache(receivedHashes)
|
||||||
|
|
||||||
valLen := d.setCacheResult(gctx.parentalCache, host, result)
|
|
||||||
log.Debug("Parental: stored in cache: %s (%d bytes)", host, valLen)
|
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,34 +403,6 @@ func (d *Dnsfilter) handleParentalStatus(w http.ResponseWriter, r *http.Request)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Dnsfilter) handleSafeSearchEnable(w http.ResponseWriter, r *http.Request) {
|
|
||||||
d.Config.SafeSearchEnabled = true
|
|
||||||
d.Config.ConfigModified()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Dnsfilter) handleSafeSearchDisable(w http.ResponseWriter, r *http.Request) {
|
|
||||||
d.Config.SafeSearchEnabled = false
|
|
||||||
d.Config.ConfigModified()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Dnsfilter) handleSafeSearchStatus(w http.ResponseWriter, r *http.Request) {
|
|
||||||
data := map[string]interface{}{
|
|
||||||
"enabled": d.Config.SafeSearchEnabled,
|
|
||||||
}
|
|
||||||
jsonVal, err := json.Marshal(data)
|
|
||||||
if err != nil {
|
|
||||||
httpError(r, w, http.StatusInternalServerError, "Unable to marshal status json: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
_, err = w.Write(jsonVal)
|
|
||||||
if err != nil {
|
|
||||||
httpError(r, w, http.StatusInternalServerError, "Unable to write response json: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *Dnsfilter) registerSecurityHandlers() {
|
func (d *Dnsfilter) registerSecurityHandlers() {
|
||||||
d.Config.HTTPRegister("POST", "/control/safebrowsing/enable", d.handleSafeBrowsingEnable)
|
d.Config.HTTPRegister("POST", "/control/safebrowsing/enable", d.handleSafeBrowsingEnable)
|
||||||
d.Config.HTTPRegister("POST", "/control/safebrowsing/disable", d.handleSafeBrowsingDisable)
|
d.Config.HTTPRegister("POST", "/control/safebrowsing/disable", d.handleSafeBrowsingDisable)
|
||||||
91
dnsfilter/sb_pc_test.go
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
package dnsfilter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/AdguardTeam/golibs/cache"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSafeBrowsingHash(t *testing.T) {
|
||||||
|
// test hostnameToHashes()
|
||||||
|
hashes := hostnameToHashes("1.2.3.sub.host.com")
|
||||||
|
assert.Equal(t, 3, len(hashes))
|
||||||
|
_, ok := hashes[sha256.Sum256([]byte("3.sub.host.com"))]
|
||||||
|
assert.True(t, ok)
|
||||||
|
_, ok = hashes[sha256.Sum256([]byte("sub.host.com"))]
|
||||||
|
assert.True(t, ok)
|
||||||
|
_, ok = hashes[sha256.Sum256([]byte("host.com"))]
|
||||||
|
assert.True(t, ok)
|
||||||
|
_, ok = hashes[sha256.Sum256([]byte("com"))]
|
||||||
|
assert.False(t, ok)
|
||||||
|
|
||||||
|
c := &sbCtx{
|
||||||
|
svc: "SafeBrowsing",
|
||||||
|
}
|
||||||
|
|
||||||
|
// test getQuestion()
|
||||||
|
c.hashToHost = hashes
|
||||||
|
q := c.getQuestion()
|
||||||
|
assert.True(t, strings.Index(q, "7a1b.") >= 0)
|
||||||
|
assert.True(t, strings.Index(q, "af5a.") >= 0)
|
||||||
|
assert.True(t, strings.Index(q, "eb11.") >= 0)
|
||||||
|
assert.True(t, strings.Index(q, "sb.dns.adguard.com.") > 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSafeBrowsingCache(t *testing.T) {
|
||||||
|
c := &sbCtx{
|
||||||
|
svc: "SafeBrowsing",
|
||||||
|
cacheTime: 100,
|
||||||
|
}
|
||||||
|
conf := cache.Config{}
|
||||||
|
c.cache = cache.New(conf)
|
||||||
|
|
||||||
|
// store in cache hashes for "3.sub.host.com" and "host.com"
|
||||||
|
// and empty data for hash-prefix for "sub.host.com"
|
||||||
|
hash := sha256.Sum256([]byte("sub.host.com"))
|
||||||
|
c.hashToHost = make(map[[32]byte]string)
|
||||||
|
c.hashToHost[hash] = "sub.host.com"
|
||||||
|
var hashesArray [][]byte
|
||||||
|
hash4 := sha256.Sum256([]byte("3.sub.host.com"))
|
||||||
|
hashesArray = append(hashesArray, hash4[:])
|
||||||
|
hash2 := sha256.Sum256([]byte("host.com"))
|
||||||
|
hashesArray = append(hashesArray, hash2[:])
|
||||||
|
c.storeCache(hashesArray)
|
||||||
|
|
||||||
|
// match "3.sub.host.com" or "host.com" from cache
|
||||||
|
c.hashToHost = make(map[[32]byte]string)
|
||||||
|
hash = sha256.Sum256([]byte("3.sub.host.com"))
|
||||||
|
c.hashToHost[hash] = "3.sub.host.com"
|
||||||
|
hash = sha256.Sum256([]byte("sub.host.com"))
|
||||||
|
c.hashToHost[hash] = "sub.host.com"
|
||||||
|
hash = sha256.Sum256([]byte("host.com"))
|
||||||
|
c.hashToHost[hash] = "host.com"
|
||||||
|
assert.Equal(t, 1, c.getCached())
|
||||||
|
|
||||||
|
// match "sub.host.com" from cache
|
||||||
|
c.hashToHost = make(map[[32]byte]string)
|
||||||
|
hash = sha256.Sum256([]byte("sub.host.com"))
|
||||||
|
c.hashToHost[hash] = "sub.host.com"
|
||||||
|
assert.Equal(t, -1, c.getCached())
|
||||||
|
|
||||||
|
// match "sub.host.com" from cache,
|
||||||
|
// but another hash for "nonexisting.com" is not in cache
|
||||||
|
// which means that we must get data from server for it
|
||||||
|
c.hashToHost = make(map[[32]byte]string)
|
||||||
|
hash = sha256.Sum256([]byte("sub.host.com"))
|
||||||
|
c.hashToHost[hash] = "sub.host.com"
|
||||||
|
hash = sha256.Sum256([]byte("nonexisting.com"))
|
||||||
|
c.hashToHost[hash] = "nonexisting.com"
|
||||||
|
assert.Equal(t, 0, c.getCached())
|
||||||
|
|
||||||
|
hash = sha256.Sum256([]byte("sub.host.com"))
|
||||||
|
_, ok := c.hashToHost[hash]
|
||||||
|
assert.False(t, ok)
|
||||||
|
|
||||||
|
hash = sha256.Sum256([]byte("nonexisting.com"))
|
||||||
|
_, ok = c.hashToHost[hash]
|
||||||
|
assert.True(t, ok)
|
||||||
|
}
|
||||||
@@ -81,6 +81,7 @@ type FilteringConfig struct {
|
|||||||
AAAADisabled bool `yaml:"aaaa_disabled"` // Respond with an empty answer to all AAAA requests
|
AAAADisabled bool `yaml:"aaaa_disabled"` // Respond with an empty answer to all AAAA requests
|
||||||
EnableDNSSEC bool `yaml:"enable_dnssec"` // Set DNSSEC flag in outcoming DNS request
|
EnableDNSSEC bool `yaml:"enable_dnssec"` // Set DNSSEC flag in outcoming DNS request
|
||||||
EnableEDNSClientSubnet bool `yaml:"edns_client_subnet"` // Enable EDNS Client Subnet option
|
EnableEDNSClientSubnet bool `yaml:"edns_client_subnet"` // Enable EDNS Client Subnet option
|
||||||
|
MaxGoroutines uint32 `yaml:"max_goroutines"` // Max. number of parallel goroutines for processing incoming requests
|
||||||
}
|
}
|
||||||
|
|
||||||
// TLSConfig is the TLS configuration for HTTPS, DNS-over-HTTPS, and DNS-over-TLS
|
// TLSConfig is the TLS configuration for HTTPS, DNS-over-HTTPS, and DNS-over-TLS
|
||||||
@@ -133,8 +134,8 @@ var defaultValues = ServerConfig{
|
|||||||
// createProxyConfig creates and validates configuration for the main proxy
|
// createProxyConfig creates and validates configuration for the main proxy
|
||||||
func (s *Server) createProxyConfig() (proxy.Config, error) {
|
func (s *Server) createProxyConfig() (proxy.Config, error) {
|
||||||
proxyConfig := proxy.Config{
|
proxyConfig := proxy.Config{
|
||||||
UDPListenAddr: s.conf.UDPListenAddr,
|
UDPListenAddr: []*net.UDPAddr{s.conf.UDPListenAddr},
|
||||||
TCPListenAddr: s.conf.TCPListenAddr,
|
TCPListenAddr: []*net.TCPAddr{s.conf.TCPListenAddr},
|
||||||
Ratelimit: int(s.conf.Ratelimit),
|
Ratelimit: int(s.conf.Ratelimit),
|
||||||
RatelimitWhitelist: s.conf.RatelimitWhitelist,
|
RatelimitWhitelist: s.conf.RatelimitWhitelist,
|
||||||
RefuseAny: s.conf.RefuseAny,
|
RefuseAny: s.conf.RefuseAny,
|
||||||
@@ -144,6 +145,7 @@ func (s *Server) createProxyConfig() (proxy.Config, error) {
|
|||||||
BeforeRequestHandler: s.beforeRequestHandler,
|
BeforeRequestHandler: s.beforeRequestHandler,
|
||||||
RequestHandler: s.handleDNSRequest,
|
RequestHandler: s.handleDNSRequest,
|
||||||
EnableEDNSClientSubnet: s.conf.EnableEDNSClientSubnet,
|
EnableEDNSClientSubnet: s.conf.EnableEDNSClientSubnet,
|
||||||
|
MaxGoroutines: int(s.conf.MaxGoroutines),
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.conf.CacheSize != 0 {
|
if s.conf.CacheSize != 0 {
|
||||||
@@ -229,7 +231,7 @@ func (s *Server) prepareIntlProxy() {
|
|||||||
// prepareTLS - prepares TLS configuration for the DNS proxy
|
// prepareTLS - prepares TLS configuration for the DNS proxy
|
||||||
func (s *Server) prepareTLS(proxyConfig *proxy.Config) error {
|
func (s *Server) prepareTLS(proxyConfig *proxy.Config) error {
|
||||||
if s.conf.TLSListenAddr != nil && len(s.conf.CertificateChainData) != 0 && len(s.conf.PrivateKeyData) != 0 {
|
if s.conf.TLSListenAddr != nil && len(s.conf.CertificateChainData) != 0 && len(s.conf.PrivateKeyData) != 0 {
|
||||||
proxyConfig.TLSListenAddr = s.conf.TLSListenAddr
|
proxyConfig.TLSListenAddr = []*net.TCPAddr{s.conf.TLSListenAddr}
|
||||||
var err error
|
var err error
|
||||||
s.conf.cert, err = tls.X509KeyPair(s.conf.CertificateChainData, s.conf.PrivateKeyData)
|
s.conf.cert, err = tls.X509KeyPair(s.conf.CertificateChainData, s.conf.PrivateKeyData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -51,6 +51,9 @@ type Server struct {
|
|||||||
stats stats.Stats
|
stats stats.Stats
|
||||||
access *accessCtx
|
access *accessCtx
|
||||||
|
|
||||||
|
tableHostToIP map[string]net.IP // "hostname -> IP" table for internal addresses (DHCP)
|
||||||
|
tableHostToIPLock sync.Mutex
|
||||||
|
|
||||||
tablePTR map[string]string // "IP -> hostname" table for reverse lookup
|
tablePTR map[string]string // "IP -> hostname" table for reverse lookup
|
||||||
tablePTRLock sync.Mutex
|
tablePTRLock sync.Mutex
|
||||||
|
|
||||||
@@ -176,6 +179,9 @@ func (s *Server) Prepare(config *ServerConfig) error {
|
|||||||
return fmt.Errorf("DNS: invalid custom blocking IP address specified")
|
return fmt.Errorf("DNS: invalid custom blocking IP address specified")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if s.conf.MaxGoroutines == 0 {
|
||||||
|
s.conf.MaxGoroutines = 50
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Set default values in the case if nothing is configured
|
// 2. Set default values in the case if nothing is configured
|
||||||
|
|||||||