Compare commits
135 Commits
v0.103.0-b
...
fix-contex
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cc1060d428 | ||
|
|
06594bde8f | ||
|
|
84938c5603 | ||
|
|
15a82233f3 | ||
|
|
8dc0108868 | ||
|
|
fc43e2ac6f | ||
|
|
1a6bd29462 | ||
|
|
340052090c | ||
|
|
b54ce85d3d | ||
|
|
9e33bd5259 | ||
|
|
050e996a35 | ||
|
|
07db05dd80 | ||
|
|
4efc464e98 | ||
|
|
e56c746b60 | ||
|
|
7b9cef3a08 | ||
|
|
f363c95ef5 | ||
|
|
729f4b1766 | ||
|
|
67bf027616 | ||
|
|
7931e50673 | ||
|
|
6b61429572 | ||
|
|
0a4781be97 | ||
|
|
268d90b5bc | ||
|
|
7d3a72e626 | ||
|
|
c822297065 | ||
|
|
f04acaf92e | ||
|
|
facf72f774 | ||
|
|
c2ba8e4c09 | ||
|
|
1531175da2 | ||
|
|
fa252ac2ec | ||
|
|
98b6eb320f | ||
|
|
d23acd2016 | ||
|
|
8cdef18358 | ||
|
|
9634ef9c03 | ||
|
|
704291e88d | ||
|
|
9c999f98fb | ||
|
|
06af130bb7 | ||
|
|
719ef16b93 | ||
|
|
cb6ca3b0c4 | ||
|
|
eb3999a261 | ||
|
|
546a02b49e | ||
|
|
f8924f0785 | ||
|
|
888c9d5714 | ||
|
|
1806816d9c | ||
|
|
fcc34ca80b | ||
|
|
1d35d73fc5 | ||
|
|
c9f58ce4a7 | ||
|
|
dd46cd5f36 | ||
|
|
e7bef3a448 | ||
|
|
23752377b7 | ||
|
|
6090400ea0 | ||
|
|
ec24d18f83 | ||
|
|
89c3926ba5 | ||
|
|
a3317c08c4 | ||
|
|
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
2
.github/workflows/lint.yml
vendored
@@ -44,4 +44,4 @@ jobs:
|
||||
fields: repo,message,commit,author
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"hr": "Hrvatski",
|
||||
"id": "Indonesian",
|
||||
"it": "Italiano",
|
||||
"hu": "Magyar",
|
||||
"no": "Norsk",
|
||||
"pl": "Polski",
|
||||
"pt-br": "Português (BR)",
|
||||
@@ -32,7 +33,8 @@
|
||||
"zh-tw": "正體中文",
|
||||
"zh-cn": "简体中文",
|
||||
"ko": "한국어",
|
||||
"th": "ภาษาไทย"
|
||||
"th": "ภาษาไทย",
|
||||
"si-lk": "සිංහල"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
219
AGHTechDoc.md
219
AGHTechDoc.md
@@ -12,6 +12,7 @@ Contents:
|
||||
* Updating
|
||||
* Get version command
|
||||
* Update command
|
||||
* API: Get global status
|
||||
* TLS
|
||||
* API: Get TLS configuration
|
||||
* API: Set TLS configuration
|
||||
@@ -22,12 +23,15 @@ Contents:
|
||||
* Update client
|
||||
* Delete client
|
||||
* API: Find clients by IP
|
||||
* Enable DHCP server
|
||||
* "Show DHCP status" command
|
||||
* "Check DHCP" command
|
||||
* "Enable DHCP" command
|
||||
* DHCP server
|
||||
* DHCP server in DNS
|
||||
* DHCP Custom Options
|
||||
* API: Show DHCP interfaces
|
||||
* API: Show DHCP status
|
||||
* API: Check DHCP
|
||||
* API: Enable DHCP
|
||||
* Static IP check/set
|
||||
* Add a static lease
|
||||
* API: Add a static lease
|
||||
* API: Reset DHCP configuration
|
||||
* DNS general settings
|
||||
* API: Get DNS general settings
|
||||
@@ -64,6 +68,8 @@ Contents:
|
||||
* API: Log in
|
||||
* API: Log out
|
||||
* API: Get current user info
|
||||
* Safe services
|
||||
* ipset
|
||||
|
||||
|
||||
## Relations between subsystems
|
||||
@@ -374,9 +380,31 @@ Error response:
|
||||
UI shows error message "Auto-update has failed"
|
||||
|
||||
|
||||
## Enable DHCP server
|
||||
## API: Get global status
|
||||
|
||||
Algorithm:
|
||||
Request:
|
||||
|
||||
GET /control/status
|
||||
|
||||
Response:
|
||||
|
||||
200 OK
|
||||
|
||||
{
|
||||
"dns_addresses":["..."],
|
||||
"dns_port":53,
|
||||
"http_port":3000,
|
||||
"language":"en",
|
||||
"protection_enabled":true,
|
||||
"running":true,
|
||||
"dhcp_available":true,
|
||||
"version":"undefined"
|
||||
}
|
||||
|
||||
|
||||
## DHCP server
|
||||
|
||||
Enable DHCP server algorithm:
|
||||
|
||||
* UI shows DHCP configuration screen with "Enabled DHCP" button disabled, and "Check DHCP" button enabled
|
||||
* User clicks on "Check DHCP"; UI sends request to server
|
||||
@@ -388,7 +416,58 @@ Algorithm:
|
||||
* UI shows the status
|
||||
|
||||
|
||||
### "Show DHCP status" command
|
||||
### 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.
|
||||
|
||||
|
||||
### DHCP Custom Options
|
||||
|
||||
Option with arbitrary hexadecimal data:
|
||||
|
||||
DEC_CODE hex HEX_DATA
|
||||
|
||||
where DEC_CODE is a decimal DHCPv4 option code in range [1..255]
|
||||
|
||||
Option with IP data (only 1 IP is supported):
|
||||
|
||||
DEC_CODE ip IP_ADDR
|
||||
|
||||
|
||||
### API: Show DHCP interfaces
|
||||
|
||||
Request:
|
||||
|
||||
GET /control/dhcp/interfaces
|
||||
|
||||
Response:
|
||||
|
||||
200 OK
|
||||
|
||||
{
|
||||
"iface_name":{
|
||||
"name":"iface_name",
|
||||
"hardware_address":"...",
|
||||
"ipv4_addresses":["ipv4 addr", ...],
|
||||
"ipv6_addresses":["ipv6 addr", ...],
|
||||
"gateway_ip":"...",
|
||||
"flags":"up|broadcast|multicast"
|
||||
}
|
||||
...
|
||||
}
|
||||
|
||||
|
||||
### API: Show DHCP status
|
||||
|
||||
Request:
|
||||
|
||||
@@ -399,16 +478,19 @@ Response:
|
||||
200 OK
|
||||
|
||||
{
|
||||
"config":{
|
||||
"enabled":false,
|
||||
"interface_name":"...",
|
||||
"enabled":false,
|
||||
"interface_name":"...",
|
||||
"v4":{
|
||||
"gateway_ip":"...",
|
||||
"subnet_mask":"...",
|
||||
"range_start":"...",
|
||||
"range_start":"...", // if empty: DHCPv4 won't be enabled
|
||||
"range_end":"...",
|
||||
"lease_duration":60,
|
||||
"icmp_timeout_msec":0
|
||||
},
|
||||
"v6":{
|
||||
"range_start":"...", // if empty: DHCPv6 won't be enabled
|
||||
"lease_duration":60,
|
||||
}
|
||||
"leases":[
|
||||
{"ip":"...","mac":"...","hostname":"...","expires":"..."}
|
||||
...
|
||||
@@ -420,7 +502,7 @@ Response:
|
||||
}
|
||||
|
||||
|
||||
### "Check DHCP" command
|
||||
### API: Check DHCP
|
||||
|
||||
Request:
|
||||
|
||||
@@ -433,13 +515,21 @@ Response:
|
||||
200 OK
|
||||
|
||||
{
|
||||
"other_server": {
|
||||
"found": "yes|no|error",
|
||||
"error": "Error message", // set if found=error
|
||||
},
|
||||
"static_ip": {
|
||||
"static": "yes|no|error",
|
||||
"ip": "<Current dynamic IP address>", // set if static=no
|
||||
v4: {
|
||||
"other_server": {
|
||||
"found": "yes|no|error",
|
||||
"error": "Error message", // set if found=error
|
||||
},
|
||||
"static_ip": {
|
||||
"static": "yes|no|error",
|
||||
"ip": "<Current dynamic IP address>", // set if static=no
|
||||
}
|
||||
}
|
||||
v6: {
|
||||
"other_server": {
|
||||
"found": "yes|no|error",
|
||||
"error": "Error message", // set if found=error
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -460,21 +550,26 @@ If `static_ip.static` is:
|
||||
In order to use DHCP server a static IP address must be set. We failed to determine if this network interface is configured using static IP address. Please set a static IP address manually.
|
||||
|
||||
|
||||
### "Enable DHCP" command
|
||||
### API: Enable DHCP
|
||||
|
||||
Request:
|
||||
|
||||
POST /control/dhcp/set_config
|
||||
|
||||
{
|
||||
"enabled":true,
|
||||
"interface_name":"vboxnet0",
|
||||
"enabled":true,
|
||||
"interface_name":"vboxnet0",
|
||||
"v4":{
|
||||
"gateway_ip":"192.169.56.1",
|
||||
"subnet_mask":"255.255.255.0",
|
||||
"range_start":"192.169.56.3",
|
||||
"range_end":"192.169.56.3",
|
||||
"range_start":"192.169.56.100",
|
||||
"range_end":"192.169.56.200", // Note: first 3 octects must match "range_start"
|
||||
"lease_duration":60,
|
||||
"icmp_timeout_msec":0
|
||||
},
|
||||
"v6":{
|
||||
"range_start":"...",
|
||||
"lease_duration":60,
|
||||
}
|
||||
}
|
||||
|
||||
Response:
|
||||
@@ -483,6 +578,10 @@ Response:
|
||||
|
||||
OK
|
||||
|
||||
For v4, if range_start = "1.2.3.4", the range_end must be "1.2.3.X" where X > 4.
|
||||
|
||||
For v6, if range_start = "2001::1", the last IP is "2001:ff".
|
||||
|
||||
|
||||
### Static IP check/set
|
||||
|
||||
@@ -578,7 +677,7 @@ or:
|
||||
systemctl restart system-networkd
|
||||
|
||||
|
||||
### Add a static lease
|
||||
### API: Add a static lease
|
||||
|
||||
Request:
|
||||
|
||||
@@ -1340,6 +1439,11 @@ When UI asks for data from query log (see "API: Get query log"), server reads th
|
||||
|
||||
We store data for a limited amount of time - the log file is automatically rotated.
|
||||
|
||||
* On AGH startup read the first line from query logs and store its time value
|
||||
* If there's no log file yet, set the time value of the first log event when the file is created
|
||||
* If this time value is older than our time limit, perform file rotate procedure
|
||||
* While AGH is running, check the previous condition every 24 hours
|
||||
|
||||
|
||||
### API: Get query log
|
||||
|
||||
@@ -1747,3 +1851,62 @@ 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],...
|
||||
...
|
||||
|
||||
|
||||
## ipset
|
||||
|
||||
AGH can add IP addresses of the specified in configuration domain names to an ipset list.
|
||||
|
||||
Prepare: user creates an ipset list and configures AGH for using it.
|
||||
|
||||
1. User --( ipset create my_ipset hash:ip ) -> OS
|
||||
2. User --( ipset: host.com,host2.com/my_ipset )-> AGH
|
||||
|
||||
Syntax:
|
||||
|
||||
ipset: "DOMAIN[,DOMAIN].../IPSET1_NAME[,IPSET2_NAME]..."
|
||||
|
||||
IPv4 addresses are added to an ipset list with `ipv4` family, IPv6 addresses - to `ipv6` ipset list.
|
||||
|
||||
Run-time: AGH adds IP addresses of a domain name to a corresponding ipset list.
|
||||
|
||||
1. AGH --( resolve host.com )-> upstream
|
||||
2. AGH <-( host.com:[1.1.1.1,2.2.2.2] )-- upstream
|
||||
3. AGH --( ipset.add(my_ipset, [1.1.1.1,2.2.2.2] ))-> OS
|
||||
|
||||
130
Makefile
130
Makefile
@@ -14,6 +14,13 @@
|
||||
# Building releases:
|
||||
#
|
||||
# * release -- builds AdGuard Home distros. CHANNEL must be specified (edge, release or beta).
|
||||
# * release_and_sign -- builds AdGuard Home distros and signs the binary files.
|
||||
# CHANNEL must be specified (edge, release or beta).
|
||||
# * sign -- Repacks all release archive files and signs the binary files inside them.
|
||||
# For signing to work, the public+private key pair for $(GPG_KEY) must be imported:
|
||||
# gpg --import public.txt
|
||||
# gpg --import private.txt
|
||||
# GPG_KEY_PASSPHRASE must contain the GPG key passphrase
|
||||
# * docker-multi-arch -- builds a multi-arch image. If you want it to be pushed to docker hub,
|
||||
# you must specify:
|
||||
# * DOCKER_IMAGE_NAME - adguard/adguard-home
|
||||
@@ -23,6 +30,9 @@ GOPATH := $(shell go env GOPATH)
|
||||
PWD := $(shell pwd)
|
||||
TARGET=AdGuardHome
|
||||
BASE_URL="https://static.adguard.com/adguardhome/$(CHANNEL)"
|
||||
GPG_KEY := devteam@adguard.com
|
||||
GPG_KEY_PASSPHRASE :=
|
||||
GPG_CMD := gpg --detach-sig --default-key $(GPG_KEY) --pinentry-mode loopback --passphrase $(GPG_KEY_PASSPHRASE)
|
||||
|
||||
# See release target
|
||||
DIST_DIR=dist
|
||||
@@ -39,6 +49,12 @@ endif
|
||||
endif
|
||||
endif
|
||||
|
||||
# Version history URL (see
|
||||
VERSION_HISTORY_URL="https://github.com/AdguardTeam/AdGuardHome/releases"
|
||||
ifeq ($(CHANNEL),edge)
|
||||
VERSION_HISTORY_URL="https://github.com/AdguardTeam/AdGuardHome/commits/master"
|
||||
endif
|
||||
|
||||
# goreleaser command depends on the $CHANNEL
|
||||
GORELEASER_COMMAND=goreleaser release --rm-dist --skip-publish --snapshot
|
||||
ifneq ($(CHANNEL),edge)
|
||||
@@ -55,7 +71,11 @@ SNAPSHOT_VERSION=$(RELEASE_VERSION)-SNAPSHOT-$(COMMIT)
|
||||
# Set proper version
|
||||
VERSION=
|
||||
ifeq ($(TAG_NAME),$(shell git describe --abbrev=4))
|
||||
VERSION=$(RELEASE_VERSION)
|
||||
ifeq ($(CHANNEL),edge)
|
||||
VERSION=$(SNAPSHOT_VERSION)
|
||||
else
|
||||
VERSION=$(RELEASE_VERSION)
|
||||
endif
|
||||
else
|
||||
VERSION=$(SNAPSHOT_VERSION)
|
||||
endif
|
||||
@@ -88,13 +108,20 @@ ifndef DOCKER_IMAGE_NAME
|
||||
$(error DOCKER_IMAGE_NAME value is not set)
|
||||
endif
|
||||
|
||||
# OS-specific flags
|
||||
TEST_FLAGS := -race
|
||||
ifeq ($(OS),Windows_NT)
|
||||
TEST_FLAGS :=
|
||||
endif
|
||||
|
||||
.PHONY: all build client client-watch docker lint lint-js lint-go test dependencies clean release docker-multi-arch
|
||||
all: build
|
||||
|
||||
init:
|
||||
git config core.hooksPath .githooks
|
||||
|
||||
build: dependencies client
|
||||
build: client_with_deps
|
||||
go mod download
|
||||
PATH=$(GOPATH)/bin:$(PATH) go generate ./...
|
||||
CGO_ENABLED=0 go build -ldflags="-s -w -X main.version=$(VERSION) -X main.channel=$(CHANNEL) -X main.goarm=$(GOARM)"
|
||||
PATH=$(GOPATH)/bin:$(PATH) packr clean
|
||||
@@ -102,6 +129,10 @@ build: dependencies client
|
||||
client:
|
||||
npm --prefix client run build-prod
|
||||
|
||||
client_with_deps:
|
||||
npm --prefix client ci
|
||||
npm --prefix client run build-prod
|
||||
|
||||
client-watch:
|
||||
npm --prefix client run watch
|
||||
|
||||
@@ -121,7 +152,7 @@ docker:
|
||||
|
||||
lint: lint-js lint-go
|
||||
|
||||
lint-js:
|
||||
lint-js: dependencies
|
||||
@echo Running js linter
|
||||
npm --prefix client run lint
|
||||
|
||||
@@ -130,10 +161,14 @@ lint-go:
|
||||
golangci-lint run
|
||||
|
||||
test:
|
||||
@echo Running unit-tests
|
||||
go test -race -v -bench=. -coverprofile=coverage.txt -covermode=atomic ./...
|
||||
@echo Running JS unit-tests
|
||||
npm run test --prefix client
|
||||
@echo Running Go unit-tests
|
||||
go test $(TEST_FLAGS) -v -coverprofile=coverage.txt -covermode=atomic ./...
|
||||
|
||||
ci: dependencies client test
|
||||
ci: client_with_deps
|
||||
go mod download
|
||||
$(MAKE) test
|
||||
|
||||
dependencies:
|
||||
npm --prefix client ci
|
||||
@@ -170,12 +205,20 @@ docker-multi-arch:
|
||||
@echo If the image was pushed to the registry, you can now run it:
|
||||
@echo docker run --name "adguard-home" -p 53:53/tcp -p 53:53/udp -p 80:80/tcp -p 443:443/tcp -p 853:853/tcp -p 3000:3000/tcp $(DOCKER_IMAGE_NAME)
|
||||
|
||||
release: dependencies client
|
||||
release: client_with_deps
|
||||
go mod download
|
||||
@echo Starting release build: version $(VERSION), channel $(CHANNEL)
|
||||
CHANNEL=$(CHANNEL) $(GORELEASER_COMMAND)
|
||||
$(call write_version_file,$(VERSION))
|
||||
PATH=$(GOPATH)/bin:$(PATH) packr clean
|
||||
|
||||
release_and_sign: client_with_deps
|
||||
$(MAKE) release
|
||||
$(call repack_dist)
|
||||
|
||||
sign:
|
||||
$(call repack_dist)
|
||||
|
||||
define write_version_file
|
||||
$(eval version := $(1))
|
||||
|
||||
@@ -190,8 +233,8 @@ define write_version_file
|
||||
echo "{" >> $(DIST_DIR)/version.json
|
||||
echo " \"version\": \"$(version)\"," >> $(DIST_DIR)/version.json
|
||||
echo " \"announcement\": \"AdGuard Home $(version) is now available!\"," >> $(DIST_DIR)/version.json
|
||||
echo " \"announcement_url\": \"https://github.com/AdguardTeam/AdGuardHome/releases\"," >> $(DIST_DIR)/version.json
|
||||
echo " \"selfupdate_min_version\": \"v0.0\"," >> $(DIST_DIR)/version.json
|
||||
echo " \"announcement_url\": \"$(VERSION_HISTORY_URL)\"," >> $(DIST_DIR)/version.json
|
||||
echo " \"selfupdate_min_version\": \"0.0\"," >> $(DIST_DIR)/version.json
|
||||
|
||||
# Windows builds
|
||||
echo " \"download_windows_amd64\": \"$(BASE_URL)/AdGuardHome_windows_amd64.zip\"," >> $(DIST_DIR)/version.json
|
||||
@@ -232,3 +275,72 @@ define write_version_file
|
||||
# Finish
|
||||
echo "}" >> $(DIST_DIR)/version.json
|
||||
endef
|
||||
|
||||
define repack_dist
|
||||
# Repack archive files
|
||||
# A temporary solution for our auto-update code to be able to unpack these archive files
|
||||
# The problem is that goreleaser doesn't add directory AdGuardHome/ to the archive file
|
||||
# and we can't create it
|
||||
rm -rf $(DIST_DIR)/AdGuardHome
|
||||
|
||||
# Windows builds
|
||||
$(call zip_repack_windows,AdGuardHome_windows_amd64.zip)
|
||||
$(call zip_repack_windows,AdGuardHome_windows_386.zip)
|
||||
|
||||
# MacOS builds
|
||||
$(call zip_repack,AdGuardHome_darwin_amd64.zip)
|
||||
$(call zip_repack,AdGuardHome_darwin_386.zip)
|
||||
|
||||
# Linux
|
||||
$(call tar_repack,AdGuardHome_linux_amd64.tar.gz)
|
||||
$(call tar_repack,AdGuardHome_linux_386.tar.gz)
|
||||
|
||||
# Linux, all kinds of ARM
|
||||
$(call tar_repack,AdGuardHome_linux_armv5.tar.gz)
|
||||
$(call tar_repack,AdGuardHome_linux_armv6.tar.gz)
|
||||
$(call tar_repack,AdGuardHome_linux_armv7.tar.gz)
|
||||
$(call tar_repack,AdGuardHome_linux_arm64.tar.gz)
|
||||
|
||||
# Linux, MIPS
|
||||
$(call tar_repack,AdGuardHome_linux_mips_softfloat.tar.gz)
|
||||
$(call tar_repack,AdGuardHome_linux_mipsle_softfloat.tar.gz)
|
||||
$(call tar_repack,AdGuardHome_linux_mips64_softfloat.tar.gz)
|
||||
$(call tar_repack,AdGuardHome_linux_mips64le_softfloat.tar.gz)
|
||||
|
||||
# FreeBSD
|
||||
$(call tar_repack,AdGuardHome_freebsd_386.tar.gz)
|
||||
$(call tar_repack,AdGuardHome_freebsd_amd64.tar.gz)
|
||||
|
||||
# FreeBSD, all kinds of ARM
|
||||
$(call tar_repack,AdGuardHome_freebsd_armv5.tar.gz)
|
||||
$(call tar_repack,AdGuardHome_freebsd_armv6.tar.gz)
|
||||
$(call tar_repack,AdGuardHome_freebsd_armv7.tar.gz)
|
||||
$(call tar_repack,AdGuardHome_freebsd_arm64.tar.gz)
|
||||
endef
|
||||
|
||||
define zip_repack_windows
|
||||
$(eval ARC := $(1))
|
||||
cd $(DIST_DIR) && \
|
||||
unzip $(ARC) && \
|
||||
$(GPG_CMD) AdGuardHome/AdGuardHome.exe && \
|
||||
zip -r $(ARC) AdGuardHome/ && \
|
||||
rm -rf AdGuardHome
|
||||
endef
|
||||
|
||||
define zip_repack
|
||||
$(eval ARC := $(1))
|
||||
cd $(DIST_DIR) && \
|
||||
unzip $(ARC) && \
|
||||
$(GPG_CMD) AdGuardHome/AdGuardHome && \
|
||||
zip -r $(ARC) AdGuardHome/ && \
|
||||
rm -rf AdGuardHome
|
||||
endef
|
||||
|
||||
define tar_repack
|
||||
$(eval ARC := $(1))
|
||||
cd $(DIST_DIR) && \
|
||||
tar xzf $(ARC) && \
|
||||
$(GPG_CMD) AdGuardHome/AdGuardHome && \
|
||||
tar czf $(ARC) AdGuardHome/ && \
|
||||
rm -rf AdGuardHome
|
||||
endef
|
||||
|
||||
22
README.md
22
README.md
@@ -20,8 +20,11 @@
|
||||
<a href="https://goreportcard.com/report/AdguardTeam/AdGuardHome">
|
||||
<img src="https://goreportcard.com/badge/github.com/AdguardTeam/AdGuardHome" alt="Go Report Card" />
|
||||
</a>
|
||||
<a href="https://golangci.com/r/github.com/AdguardTeam/AdGuardHome">
|
||||
<img src="https://golangci.com/badges/github.com/AdguardTeam/AdGuardHome.svg" alt="GolangCI" />
|
||||
<a href="https://hub.docker.com/r/adguard/adguardhome">
|
||||
<img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/adguard/adguardhome.svg?maxAge=604800" />
|
||||
</a>
|
||||
<a href="https://hub.docker.com/r/adguard/adguardhome">
|
||||
<img alt="Docker Stars" src="https://img.shields.io/docker/stars/adguard/adguardhome.svg?maxAge=604800" />
|
||||
</a>
|
||||
<br />
|
||||
<a href="https://github.com/AdguardTeam/AdGuardHome/releases">
|
||||
@@ -30,12 +33,6 @@
|
||||
<a href="https://snapcraft.io/adguard-home">
|
||||
<img alt="adguard-home" src="https://snapcraft.io/adguard-home/badge.svg" />
|
||||
</a>
|
||||
<a href="https://hub.docker.com/r/adguard/adguardhome">
|
||||
<img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/adguard/adguardhome.svg?maxAge=604800" />
|
||||
</a>
|
||||
<a href="https://hub.docker.com/r/adguard/adguardhome">
|
||||
<img alt="Docker Stars" src="https://img.shields.io/docker/stars/adguard/adguardhome.svg?maxAge=604800" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<br />
|
||||
@@ -155,7 +152,10 @@ Run `make init` to prepare the development environment.
|
||||
You will need this to build AdGuard Home:
|
||||
|
||||
* [go](https://golang.org/dl/) v1.14 or later.
|
||||
* [node.js](https://nodejs.org/en/download/) v10 or later.
|
||||
* [node.js](https://nodejs.org/en/download/) v10.16.2 or later.
|
||||
* [npm](https://www.npmjs.com/) v6.14 or later.
|
||||
|
||||
Optionally, for Go devs:
|
||||
* [golangci-lint](https://github.com/golangci/golangci-lint)
|
||||
|
||||
### Building
|
||||
@@ -214,6 +214,8 @@ You may need to prepare before using these builds:
|
||||
|
||||
You are welcome to fork this repository, make your changes and submit a pull request — https://github.com/AdguardTeam/AdGuardHome/pulls
|
||||
|
||||
Please note that we don't expect people to contribute to both UI and golang parts of the program simultaneously. Ideally, the golang part is implemented first, i.e. configuration, API, and the functionality itself. The UI part can be implemented later in a different pull request by a different person.
|
||||
|
||||
<a id="test-unstable-versions"></a>
|
||||
### Test unstable versions
|
||||
|
||||
@@ -230,7 +232,7 @@ There are three options how you can install an unstable version:
|
||||
|
||||
There are three options how you can install an unstable version.
|
||||
|
||||
1. You can either install a beta version of AdGuard Home which we update periodically.
|
||||
1. You can either install AdGuard Home from "beta" or "edge" distribution channel which we update periodically. If you're already using stable version of AdGuard Home, just replace the executable file with a new one.
|
||||
2. You can use the Docker image from the `edge` tag, which is synced with the repo master branch.
|
||||
3. You can install AdGuard Home from `beta` or `edge` channels on the Snap Store.
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
module.exports = {
|
||||
"disableEmoji": true,
|
||||
"list": [
|
||||
"+",
|
||||
"*",
|
||||
"-",
|
||||
"+ ",
|
||||
"* ",
|
||||
"- ",
|
||||
],
|
||||
"maxMessageLength": 64,
|
||||
"minMessageLength": 3,
|
||||
@@ -12,7 +12,7 @@ module.exports = {
|
||||
"scope",
|
||||
"subject",
|
||||
"body",
|
||||
"issues"
|
||||
"issues",
|
||||
],
|
||||
"scopes": [
|
||||
"",
|
||||
@@ -26,20 +26,20 @@ module.exports = {
|
||||
"documentation",
|
||||
],
|
||||
"types": {
|
||||
"+": {
|
||||
"+ ": {
|
||||
"description": "A new feature",
|
||||
"emoji": "",
|
||||
"value": "+"
|
||||
"value": "+ "
|
||||
},
|
||||
"*": {
|
||||
"* ": {
|
||||
"description": "A code change that neither fixes a bug or adds a feature",
|
||||
"emoji": "",
|
||||
"value": "*"
|
||||
"value": "* "
|
||||
},
|
||||
"-": {
|
||||
"- ": {
|
||||
"description": "A bug fix",
|
||||
"emoji": "",
|
||||
"value": "-"
|
||||
"value": "- "
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
83
client/.eslintrc.json
vendored
83
client/.eslintrc.json
vendored
@@ -1,18 +1,15 @@
|
||||
{
|
||||
"parser": "babel-eslint",
|
||||
|
||||
"extends": [
|
||||
"plugin:react/recommended",
|
||||
"airbnb-base"
|
||||
],
|
||||
|
||||
"env": {
|
||||
"jest": true,
|
||||
"node": true,
|
||||
"browser": true,
|
||||
"commonjs": true
|
||||
},
|
||||
|
||||
"settings": {
|
||||
"react": {
|
||||
"pragma": "React",
|
||||
@@ -24,35 +21,65 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"rules": {
|
||||
"indent": ["error", 4, {
|
||||
"SwitchCase": 1,
|
||||
"VariableDeclarator": 1,
|
||||
"outerIIFEBody": 1,
|
||||
"FunctionDeclaration": {
|
||||
"parameters": 1,
|
||||
"body": 1
|
||||
},
|
||||
"FunctionExpression": {
|
||||
"parameters": 1,
|
||||
"body": 1
|
||||
},
|
||||
"CallExpression": {
|
||||
"arguments": 1
|
||||
},
|
||||
"ArrayExpression": 1,
|
||||
"ObjectExpression": 1,
|
||||
"ImportDeclaration": 1,
|
||||
"flatTernaryExpressions": false,
|
||||
"ignoredNodes": ["JSXElement", "JSXElement > *", "JSXAttribute", "JSXIdentifier", "JSXNamespacedName", "JSXMemberExpression", "JSXSpreadAttribute", "JSXExpressionContainer", "JSXOpeningElement", "JSXClosingElement", "JSXText", "JSXEmptyExpression", "JSXSpreadChild"],
|
||||
"ignoreComments": false
|
||||
}],
|
||||
"indent": [
|
||||
"error",
|
||||
4,
|
||||
{
|
||||
"SwitchCase": 1,
|
||||
"VariableDeclarator": 1,
|
||||
"outerIIFEBody": 1,
|
||||
"FunctionDeclaration": {
|
||||
"parameters": 1,
|
||||
"body": 1
|
||||
},
|
||||
"FunctionExpression": {
|
||||
"parameters": 1,
|
||||
"body": 1
|
||||
},
|
||||
"CallExpression": {
|
||||
"arguments": 1
|
||||
},
|
||||
"ArrayExpression": 1,
|
||||
"ObjectExpression": 1,
|
||||
"ImportDeclaration": 1,
|
||||
"flatTernaryExpressions": false,
|
||||
"ignoredNodes": [
|
||||
"JSXElement",
|
||||
"JSXElement > *",
|
||||
"JSXAttribute",
|
||||
"JSXIdentifier",
|
||||
"JSXNamespacedName",
|
||||
"JSXMemberExpression",
|
||||
"JSXSpreadAttribute",
|
||||
"JSXExpressionContainer",
|
||||
"JSXOpeningElement",
|
||||
"JSXClosingElement",
|
||||
"JSXText",
|
||||
"JSXEmptyExpression",
|
||||
"JSXSpreadChild"
|
||||
],
|
||||
"ignoreComments": false
|
||||
}
|
||||
],
|
||||
"class-methods-use-this": "off",
|
||||
"no-shadow": "off",
|
||||
"camelcase": "off",
|
||||
"no-console": ["warn", { "allow": ["warn", "error"] }],
|
||||
"import/no-extraneous-dependencies": ["error", { "devDependencies": true }],
|
||||
"no-console": [
|
||||
"warn",
|
||||
{
|
||||
"allow": [
|
||||
"warn",
|
||||
"error"
|
||||
]
|
||||
}
|
||||
],
|
||||
"import/no-extraneous-dependencies": [
|
||||
"error",
|
||||
{
|
||||
"devDependencies": true
|
||||
}
|
||||
],
|
||||
"import/prefer-default-export": "off",
|
||||
"no-alert": "off"
|
||||
}
|
||||
|
||||
1
client/babel.config.js
vendored
1
client/babel.config.js
vendored
@@ -11,6 +11,7 @@ module.exports = (api) => {
|
||||
'@babel/plugin-proposal-object-rest-spread',
|
||||
'@babel/plugin-proposal-nullish-coalescing-operator',
|
||||
'@babel/plugin-proposal-optional-chaining',
|
||||
'react-hot-loader/babel',
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
11
client/constants.js
vendored
Normal file
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,
|
||||
};
|
||||
264
client/package-lock.json
generated
vendored
264
client/package-lock.json
generated
vendored
@@ -2257,6 +2257,12 @@
|
||||
"@types/istanbul-lib-report": "*"
|
||||
}
|
||||
},
|
||||
"@types/json-schema": {
|
||||
"version": "7.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.5.tgz",
|
||||
"integrity": "sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/minimatch": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
|
||||
@@ -2728,7 +2734,6 @@
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
||||
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"sprintf-js": "~1.0.2"
|
||||
}
|
||||
@@ -5108,6 +5113,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"dom-walk": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz",
|
||||
"integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==",
|
||||
"dev": true
|
||||
},
|
||||
"domain-browser": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz",
|
||||
@@ -5201,9 +5212,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"elliptic": {
|
||||
"version": "6.5.2",
|
||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz",
|
||||
"integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==",
|
||||
"version": "6.5.3",
|
||||
"resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz",
|
||||
"integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"bn.js": "^4.4.0",
|
||||
@@ -5216,9 +5227,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"bn.js": {
|
||||
"version": "4.11.8",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
|
||||
"integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==",
|
||||
"version": "4.11.9",
|
||||
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz",
|
||||
"integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
@@ -5887,8 +5898,7 @@
|
||||
"esprima": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
||||
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
|
||||
"dev": true
|
||||
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
|
||||
},
|
||||
"esquery": {
|
||||
"version": "1.3.1",
|
||||
@@ -6844,6 +6854,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"global": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz",
|
||||
"integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"min-document": "^2.19.0",
|
||||
"process": "^0.11.10"
|
||||
}
|
||||
},
|
||||
"global-modules": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz",
|
||||
@@ -7357,18 +7377,6 @@
|
||||
"requires-port": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"http-proxy-middleware": {
|
||||
"version": "0.19.1",
|
||||
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz",
|
||||
"integrity": "sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"http-proxy": "^1.17.0",
|
||||
"is-glob": "^4.0.0",
|
||||
"lodash": "^4.17.11",
|
||||
"micromatch": "^3.1.10"
|
||||
}
|
||||
},
|
||||
"http-signature": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
|
||||
@@ -7393,11 +7401,21 @@
|
||||
"dev": true
|
||||
},
|
||||
"i18next": {
|
||||
"version": "19.4.4",
|
||||
"resolved": "https://registry.npmjs.org/i18next/-/i18next-19.4.4.tgz",
|
||||
"integrity": "sha512-ofaHtdsDdX3A5nYur1HWblB7J4hIcjr2ACdnwTAJgc8hTfPbyzZfGX0hVkKpI3vzDIgO6Uzc4v1ffW2W6gG6zw==",
|
||||
"version": "19.6.2",
|
||||
"resolved": "https://registry.npmjs.org/i18next/-/i18next-19.6.2.tgz",
|
||||
"integrity": "sha512-Zyd/Z32FY+sD+Eg6sLj5DeDSlrIN3WZ4onuOBRGcjDx/rvodsyUZ9TJ2Y+3aD9Vu8MPbiMU2WesIER/rs1ioyw==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.3.1"
|
||||
"@babel/runtime": "^7.10.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": {
|
||||
"version": "7.10.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.5.tgz",
|
||||
"integrity": "sha512-otddXKhdNn7d0ptoFRHtMLa8LqDxLYwTjB4nYgM1yy5N6gU/MUf8zqyyLltCH3yAVitBzmwK4us+DD0l/MauAg==",
|
||||
"requires": {
|
||||
"regenerator-runtime": "^0.13.4"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"i18next-browser-languagedetector": {
|
||||
@@ -9898,10 +9916,9 @@
|
||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
|
||||
},
|
||||
"js-yaml": {
|
||||
"version": "3.13.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
|
||||
"integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
|
||||
"dev": true,
|
||||
"version": "3.14.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz",
|
||||
"integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==",
|
||||
"requires": {
|
||||
"argparse": "^1.0.7",
|
||||
"esprima": "^4.0.0"
|
||||
@@ -10168,9 +10185,9 @@
|
||||
}
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.15",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
|
||||
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
|
||||
"version": "4.17.19",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
|
||||
"integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ=="
|
||||
},
|
||||
"lodash-es": {
|
||||
"version": "4.17.15",
|
||||
@@ -10628,6 +10645,15 @@
|
||||
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
|
||||
"dev": true
|
||||
},
|
||||
"min-document": {
|
||||
"version": "2.19.0",
|
||||
"resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz",
|
||||
"integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"dom-walk": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"min-indent": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.0.tgz",
|
||||
@@ -10747,6 +10773,24 @@
|
||||
"yallist": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"mississippi": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz",
|
||||
"integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"concat-stream": "^1.5.0",
|
||||
"duplexify": "^3.4.2",
|
||||
"end-of-stream": "^1.1.0",
|
||||
"flush-write-stream": "^1.0.0",
|
||||
"from2": "^2.1.0",
|
||||
"parallel-transform": "^1.1.0",
|
||||
"pump": "^3.0.0",
|
||||
"pumpify": "^1.3.3",
|
||||
"stream-each": "^1.1.0",
|
||||
"through2": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"mixin-deep": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz",
|
||||
@@ -12132,9 +12176,9 @@
|
||||
}
|
||||
},
|
||||
"pump": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz",
|
||||
"integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==",
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
|
||||
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"end-of-stream": "^1.1.0",
|
||||
@@ -12150,6 +12194,18 @@
|
||||
"duplexify": "^3.6.0",
|
||||
"inherits": "^2.0.3",
|
||||
"pump": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"pump": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz",
|
||||
"integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"end-of-stream": "^1.1.0",
|
||||
"once": "^1.3.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"punycode": {
|
||||
@@ -12287,10 +12343,43 @@
|
||||
"scheduler": "^0.19.1"
|
||||
}
|
||||
},
|
||||
"react-hot-loader": {
|
||||
"version": "4.12.21",
|
||||
"resolved": "https://registry.npmjs.org/react-hot-loader/-/react-hot-loader-4.12.21.tgz",
|
||||
"integrity": "sha512-Ynxa6ROfWUeKWsTHxsrL2KMzujxJVPjs385lmB2t5cHUxdoRPGind9F00tOkdc1l5WBleOF4XEAMILY1KPIIDA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fast-levenshtein": "^2.0.6",
|
||||
"global": "^4.3.0",
|
||||
"hoist-non-react-statics": "^3.3.0",
|
||||
"loader-utils": "^1.1.0",
|
||||
"prop-types": "^15.6.1",
|
||||
"react-lifecycles-compat": "^3.0.4",
|
||||
"shallowequal": "^1.1.0",
|
||||
"source-map": "^0.7.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"hoist-non-react-statics": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
||||
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"react-is": "^16.7.0"
|
||||
}
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.7.3",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz",
|
||||
"integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"react-i18next": {
|
||||
"version": "11.4.0",
|
||||
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-11.4.0.tgz",
|
||||
"integrity": "sha512-lyOZSSQkif4H9HnHN3iEKVkryLI+WkdZSEw3VAZzinZLopfYRMHVY5YxCopdkXPLEHs6S5GjKYPh3+j0j336Fg==",
|
||||
"version": "11.7.2",
|
||||
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-11.7.2.tgz",
|
||||
"integrity": "sha512-Djj3K3hh5Tecla2CI9rLO3TZBYGMFrGilm0JY4cLofAQONCi5TK6nVmUPKoB59n1ZffgjfgJt6zlbE9aGF6Q0Q==",
|
||||
"requires": {
|
||||
"@babel/runtime": "^7.3.1",
|
||||
"html-parse-stringify2": "2.0.1"
|
||||
@@ -13211,10 +13300,13 @@
|
||||
}
|
||||
},
|
||||
"serialize-javascript": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-3.0.0.tgz",
|
||||
"integrity": "sha512-skZcHYw2vEX4bw90nAr2iTTsz6x2SrHEnfxgKYmZlvJYBEZrvbKtobJWlQ20zczKb3bsHHXXTYt48zBA7ni9cw==",
|
||||
"dev": true
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-3.1.0.tgz",
|
||||
"integrity": "sha512-JIJT1DGiWmIKhzRsG91aS6Ze4sFUrYbltlkg2onR5OrnNM02Kl/hnY/T4FN2omvyeBbQmMJv+K4cPOpGzOTFBg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"randombytes": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"serve-index": {
|
||||
"version": "1.9.1",
|
||||
@@ -13328,6 +13420,12 @@
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"shallowequal": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
|
||||
"integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==",
|
||||
"dev": true
|
||||
},
|
||||
"shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
@@ -13752,8 +13850,7 @@
|
||||
"sprintf-js": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
|
||||
"dev": true
|
||||
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
|
||||
},
|
||||
"sshpk": {
|
||||
"version": "1.16.1",
|
||||
@@ -14080,12 +14177,13 @@
|
||||
}
|
||||
},
|
||||
"schema-utils": {
|
||||
"version": "2.6.6",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.6.tgz",
|
||||
"integrity": "sha512-wHutF/WPSbIi9x6ctjGGk2Hvl0VOz5l3EKEuKbjPlB30mKZUzb9A5k9yEXRX3pwyqVLPvpfZZEllaFq/M718hA==",
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz",
|
||||
"integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ajv": "^6.12.0",
|
||||
"@types/json-schema": "^7.0.4",
|
||||
"ajv": "^6.12.2",
|
||||
"ajv-keywords": "^3.4.1"
|
||||
}
|
||||
}
|
||||
@@ -14543,16 +14641,16 @@
|
||||
}
|
||||
},
|
||||
"terser-webpack-plugin": {
|
||||
"version": "1.4.3",
|
||||
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.3.tgz",
|
||||
"integrity": "sha512-QMxecFz/gHQwteWwSo5nTc6UaICqN1bMedC5sMtUc7y3Ha3Q8y6ZO0iCR8pq4RJC8Hjf0FEPEHZqcMB/+DFCrA==",
|
||||
"version": "1.4.5",
|
||||
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz",
|
||||
"integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"cacache": "^12.0.2",
|
||||
"find-cache-dir": "^2.1.0",
|
||||
"is-wsl": "^1.1.0",
|
||||
"schema-utils": "^1.0.0",
|
||||
"serialize-javascript": "^2.1.2",
|
||||
"serialize-javascript": "^4.0.0",
|
||||
"source-map": "^0.6.1",
|
||||
"terser": "^4.1.2",
|
||||
"webpack-sources": "^1.4.0",
|
||||
@@ -14612,15 +14710,6 @@
|
||||
"path-exists": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"lru-cache": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
|
||||
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"yallist": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"make-dir": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
|
||||
@@ -14631,24 +14720,6 @@
|
||||
"semver": "^5.6.0"
|
||||
}
|
||||
},
|
||||
"mississippi": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz",
|
||||
"integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"concat-stream": "^1.5.0",
|
||||
"duplexify": "^3.4.2",
|
||||
"end-of-stream": "^1.1.0",
|
||||
"flush-write-stream": "^1.0.0",
|
||||
"from2": "^2.1.0",
|
||||
"parallel-transform": "^1.1.0",
|
||||
"pump": "^3.0.0",
|
||||
"pumpify": "^1.3.3",
|
||||
"stream-each": "^1.1.0",
|
||||
"through2": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"p-limit": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
|
||||
@@ -14688,22 +14759,15 @@
|
||||
"find-up": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"pump": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
|
||||
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
|
||||
"serialize-javascript": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz",
|
||||
"integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"end-of-stream": "^1.1.0",
|
||||
"once": "^1.3.1"
|
||||
"randombytes": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"serialize-javascript": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-2.1.2.tgz",
|
||||
"integrity": "sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ==",
|
||||
"dev": true
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
@@ -14718,12 +14782,6 @@
|
||||
"requires": {
|
||||
"figgy-pudding": "^3.5.1"
|
||||
}
|
||||
},
|
||||
"yallist": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
||||
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -15931,6 +15989,18 @@
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"http-proxy-middleware": {
|
||||
"version": "0.19.1",
|
||||
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz",
|
||||
"integrity": "sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"http-proxy": "^1.17.0",
|
||||
"is-glob": "^4.0.0",
|
||||
"lodash": "^4.17.11",
|
||||
"micromatch": "^3.1.10"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
|
||||
11
client/package.json
vendored
11
client/package.json
vendored
@@ -4,8 +4,9 @@
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build-dev": "cross-env BUILD_ENV=dev webpack --config webpack.dev.js",
|
||||
"watch": "cross-env BUILD_ENV=dev webpack --config webpack.dev.js --watch",
|
||||
"build-prod": "cross-env BUILD_ENV=prod webpack --config webpack.prod.js",
|
||||
"watch": "cross-env BUILD_ENV=dev webpack --config webpack.dev.js --watch",
|
||||
"watch:hot": "cross-env BUILD_ENV=dev webpack-dev-server --config webpack.dev.js",
|
||||
"lint": "eslint src",
|
||||
"lint:fix": "eslint src --fix",
|
||||
"test": "jest",
|
||||
@@ -16,17 +17,18 @@
|
||||
"axios": "^0.19.2",
|
||||
"classnames": "^2.2.6",
|
||||
"date-fns": "^1.29.0",
|
||||
"i18next": "^19.4.4",
|
||||
"i18next": "^19.6.2",
|
||||
"i18next-browser-languagedetector": "^4.2.0",
|
||||
"ipaddr.js": "^1.9.1",
|
||||
"lodash": "^4.17.15",
|
||||
"js-yaml": "^3.14.0",
|
||||
"lodash": "^4.17.19",
|
||||
"nanoid": "^3.1.9",
|
||||
"prop-types": "^15.7.2",
|
||||
"query-string": "^6.13.1",
|
||||
"react": "^16.13.1",
|
||||
"react-click-outside": "^3.0.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-i18next": "^11.4.0",
|
||||
"react-i18next": "^11.7.2",
|
||||
"react-modal": "^3.11.2",
|
||||
"react-popper-tooltip": "^2.11.1",
|
||||
"react-redux": "^7.2.0",
|
||||
@@ -73,6 +75,7 @@
|
||||
"path": "^0.12.7",
|
||||
"postcss-flexbugs-fixes": "4.2.1",
|
||||
"postcss-loader": "^3.0.0",
|
||||
"react-hot-loader": "^4.12.21",
|
||||
"style-loader": "^1.2.1",
|
||||
"stylelint": "^13.5.0",
|
||||
"stylelint-webpack-plugin": "2.0.0",
|
||||
|
||||
@@ -139,8 +139,8 @@
|
||||
"page_table_footer_text": "Страница",
|
||||
"rows_table_footer_text": "редове",
|
||||
"updated_custom_filtering_toast": "Обновени местни правила за филтриране",
|
||||
"rule_removed_from_custom_filtering_toast": "Премахнато от местни правила за филтриране",
|
||||
"rule_added_to_custom_filtering_toast": "Добавено до местни правила за филтриране",
|
||||
"rule_removed_from_custom_filtering_toast": "Премахнато от местни правила за филтриране: {{rule}}",
|
||||
"rule_added_to_custom_filtering_toast": "Добавено до местни правила за филтриране: {{rule}}",
|
||||
"plain_dns": "Обикновен DNS",
|
||||
"source_label": "Източник",
|
||||
"found_in_known_domain_db": "Намерен в списъците с домейни.",
|
||||
@@ -236,5 +236,6 @@
|
||||
"reset_settings": "Изтрий всички настройки",
|
||||
"update_announcement": "Има нова AdGuard Home {{version}}! <0>Цъкни тук</0> за повече информация.",
|
||||
"disable_ipv6": "Изключете IPv6 протокола",
|
||||
"show_blocked_responses": "Блокирано"
|
||||
"show_blocked_responses": "Блокирано",
|
||||
"port_53_faq_link": "Порт 53 често е зает от \"DNSStubListener\" или \"systemd-resolved\" услуги. Моля, прочетете <0>тази инструкция</0> как да решите това."
|
||||
}
|
||||
@@ -208,8 +208,8 @@
|
||||
"page_table_footer_text": "Stránka",
|
||||
"rows_table_footer_text": "řádky",
|
||||
"updated_custom_filtering_toast": "Aktualizovaná vlastní pravidla filtrování",
|
||||
"rule_removed_from_custom_filtering_toast": "Pravidlo odstraněno z vlastních pravidel filtrování",
|
||||
"rule_added_to_custom_filtering_toast": "Pravidlo přidáno do vlastních pravidel filtrování",
|
||||
"rule_removed_from_custom_filtering_toast": "Pravidlo odstraněno z vlastních pravidel filtrování: {{rule}}",
|
||||
"rule_added_to_custom_filtering_toast": "Pravidlo přidáno do vlastních pravidel filtrování: {{rule}}",
|
||||
"query_log_response_status": "Status: {{value}}",
|
||||
"query_log_filtered": "Filtrováno pomocí {{filter}}",
|
||||
"query_log_confirm_clear": "Opravdu chcete vymazat celý protokol dotazů?",
|
||||
@@ -562,5 +562,6 @@
|
||||
"filter_category_regional_desc": "Seznamy, které jsou zaměřené na regionální reklamy a sledovací servery",
|
||||
"filter_category_other_desc": "Další seznamy zakázaných",
|
||||
"original_response": "Původní odezva",
|
||||
"click_to_view_queries": "Klikněte pro zobrazení dotazů"
|
||||
}
|
||||
"click_to_view_queries": "Klikněte pro zobrazení dotazů",
|
||||
"port_53_faq_link": "Port 53 je často obsazen službami \"DNSStubListener\" nebo \"systemd-resolved\". Přečtěte si <0>tento návod</0> o tom, jak to vyřešit."
|
||||
}
|
||||
|
||||
@@ -208,8 +208,8 @@
|
||||
"page_table_footer_text": "Side",
|
||||
"rows_table_footer_text": "rækker",
|
||||
"updated_custom_filtering_toast": "De brugerdefinerede filtreringsregler er blevet opdateret",
|
||||
"rule_removed_from_custom_filtering_toast": "Regel fjernet fra de brugerdefinerede filtreringsregler",
|
||||
"rule_added_to_custom_filtering_toast": "Regel tilføjet til de brugerdefinerede filtreringsregler",
|
||||
"rule_removed_from_custom_filtering_toast": "Regel fjernet fra de brugerdefinerede filtreringsregler: {{rule}}",
|
||||
"rule_added_to_custom_filtering_toast": "Regel tilføjet til de brugerdefinerede filtreringsregler: {{rule}}",
|
||||
"query_log_response_status": "Status: {{value}}",
|
||||
"query_log_filtered": "Filtreret af {{filter}}",
|
||||
"query_log_confirm_clear": "Er du sikker på, at du vil rydde hele forespørgselsloggen?",
|
||||
@@ -562,5 +562,6 @@
|
||||
"filter_category_regional_desc": "Lister, der fokuserer på regionale annoncer og tracking-servere",
|
||||
"filter_category_other_desc": "Andre blokeringslister",
|
||||
"original_response": "Oprindeligt svar",
|
||||
"click_to_view_queries": "Klik for at se forespørgsler"
|
||||
}
|
||||
"click_to_view_queries": "Klik for at se forespørgsler",
|
||||
"port_53_faq_link": "Port 53 optages ofte af \"DNSStubListener\" eller \"systemd-resolved\" tjenester. Læs <0>denne instruktion</0> om, hvordan du løser dette."
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
"dhcp_warning": "Wenn Sie den DHCP-Server trotzdem aktivieren möchten, stellen Sie sicher, dass sich in Ihrem Netzwerk kein anderer aktiver DHCP-Server befindet. Andernfalls kann es bei angeschlossenen Geräten zu einem Ausfall des Internets kommen!",
|
||||
"dhcp_error": "Es konnte nicht ermittelt werden, ob es einen anderen DHCP-Server im Netzwerk gibt.",
|
||||
"dhcp_static_ip_error": "Um den DHCP-Server nutzen zu können, muss eine statische IP-Adresse festgelegt werden. Es konnte nicht ermittelt werden, ob diese Netzwerkschnittstelle mit statischer IP-Adresse konfiguriert ist. Bitte legen Sie eine statische IP-Adresse manuell fest.",
|
||||
"dhcp_dynamic_ip_found": "Ihr System verwendet die dynamische Konfiguration der IP-Adresse für die Schnittstelle <0>{{interfaceName}}</0>. Um den DHCP-Server nutzen zu können, muss eine statische IP-Adresse festgelegt werden. Ihre aktuelle IP-Adresse ist <0>{{ipAddress}}}</0>. Diese IP-Adresse wird automatisch als statisch festgelegt, sobald Sie auf die Schaltfläche „DHCP aktivieren” klicken.",
|
||||
"dhcp_dynamic_ip_found": "Ihr System verwendet die dynamische Konfiguration der IP-Adresse für die Schnittstelle <0>{{interfaceName}}</0>. Um den DHCP-Server nutzen zu können, muss eine statische IP-Adresse festgelegt werden. Ihre aktuelle IP-Adresse ist <0>{{ipAddress}}</0>. Diese IP-Adresse wird automatisch als statisch festgelegt, sobald Sie auf die Schaltfläche „DHCP aktivieren” klicken.",
|
||||
"dhcp_lease_added": "Statischer Lease „{{key}}” erfolgreich hinzugefügt",
|
||||
"dhcp_lease_deleted": "Statischer Lease „{{key}}” erfolgreich entfernt",
|
||||
"dhcp_new_static_lease": "Neuer statischer Lease",
|
||||
@@ -99,7 +99,7 @@
|
||||
"no_clients_found": "Keine Clients gefunden",
|
||||
"general_statistics": "Allgemeine Statistiken",
|
||||
"number_of_dns_query_days": "Anzahl der in den letzten {{count}} Tagen verarbeiteten DNS-Anfragen",
|
||||
"number_of_dns_query_days_plural": "Anzahl der DNS-Abfragen, die in den letzten {{count}}} Tagen verarbeitet wurden",
|
||||
"number_of_dns_query_days_plural": "Anzahl der DNS-Abfragen, die in den letzten {{count}} Tagen verarbeitet wurden",
|
||||
"number_of_dns_query_24_hours": "Anzahl der in den letzten 24 Stunden durchgeführten DNS-Anfragen",
|
||||
"number_of_dns_query_blocked_24_hours": "Anzahl der durch Werbefilter und Host-Blocklisten geblockten DNS-Anfragen",
|
||||
"number_of_dns_query_blocked_24_hours_by_sec": "Anzahl der durch das AdGuard-Modul für Internet-Sicherheit blockierten DNS-Anfragen",
|
||||
@@ -208,8 +208,8 @@
|
||||
"page_table_footer_text": "Seite",
|
||||
"rows_table_footer_text": "Reihen",
|
||||
"updated_custom_filtering_toast": "Die benutzerdefinierten Filterregeln wurden aktualisiert",
|
||||
"rule_removed_from_custom_filtering_toast": "Regel wurde aus den benutzerdefinierten Filterregeln entfernt",
|
||||
"rule_added_to_custom_filtering_toast": "Regel wurde zu den benutzerdefinierten Filterregeln hinzugefügt",
|
||||
"rule_removed_from_custom_filtering_toast": "Regel wurde aus den benutzerdefinierten Filterregeln entfernt: {{rule}}",
|
||||
"rule_added_to_custom_filtering_toast": "Regel wurde zu den benutzerdefinierten Filterregeln hinzugefügt: {{rule}}",
|
||||
"query_log_response_status": "Status: {{value}}",
|
||||
"query_log_filtered": "Gefiltert nach {{filter}}",
|
||||
"query_log_confirm_clear": "Möchten Sie wirklich das Abfrageprotokoll vollständig löschen?",
|
||||
@@ -562,5 +562,6 @@
|
||||
"filter_category_regional_desc": "Listen, die sich auf regionale Werbeanzeigen und Tracking-Server konzentrieren",
|
||||
"filter_category_other_desc": "Weitere Sperrlisten",
|
||||
"original_response": "Ursprüngliche Antwort",
|
||||
"click_to_view_queries": "Anklicken, um Abfragen anzuzeigen"
|
||||
}
|
||||
"click_to_view_queries": "Anklicken, um Abfragen anzuzeigen",
|
||||
"port_53_faq_link": "Port 53 wird oft von Diensten wie „DNSStubListener” oder „systemresolved” belegt. Bitte lesen Sie <0>diese Anweisung</0>, wie dies behoben werden kann."
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@
|
||||
"save_config": "Save config",
|
||||
"enabled_dhcp": "DHCP server enabled",
|
||||
"disabled_dhcp": "DHCP server disabled",
|
||||
"unavailable_dhcp": "DHCP is unavailable",
|
||||
"unavailable_dhcp_desc": "AdGuard Home cannot run a DHCP server on your OS",
|
||||
"dhcp_title": "DHCP server (experimental!)",
|
||||
"dhcp_description": "If your router does not provide DHCP settings, you can use AdGuard's own built-in DHCP server.",
|
||||
"dhcp_enable": "Enable DHCP server",
|
||||
@@ -21,6 +23,8 @@
|
||||
"dhcp_static_leases": "DHCP static leases",
|
||||
"dhcp_leases_not_found": "No DHCP leases found",
|
||||
"dhcp_config_saved": "DHCP config successfully saved",
|
||||
"dhcp_ipv4_settings": "DHCP IPv4 Settings",
|
||||
"dhcp_ipv6_settings": "DHCP IPv6 Settings",
|
||||
"form_error_required": "Required field",
|
||||
"form_error_ip4_format": "Invalid IPv4 format",
|
||||
"form_error_ip6_format": "Invalid IPv6 format",
|
||||
@@ -29,6 +33,7 @@
|
||||
"form_error_client_id_format": "Invalid client ID format",
|
||||
"form_error_positive": "Must be greater than 0",
|
||||
"form_error_negative": "Must be equal to 0 or greater",
|
||||
"range_end_error": "Must be greater than range start",
|
||||
"dhcp_form_gateway_input": "Gateway IP",
|
||||
"dhcp_form_subnet_input": "Subnet mask",
|
||||
"dhcp_form_range_title": "Range of IP addresses",
|
||||
@@ -189,6 +194,10 @@
|
||||
"dns_test_not_ok_toast": "Server \"{{key}}\": could not be used, please check that you've written it correctly",
|
||||
"unblock": "Unblock",
|
||||
"block": "Block",
|
||||
"disallow_this_client": "Disallow this client",
|
||||
"allow_this_client": "Allow this client",
|
||||
"block_for_this_client_only": "Block for this client only",
|
||||
"unblock_for_this_client_only": "Unblock for this client only",
|
||||
"time_table_header": "Time",
|
||||
"date": "Date",
|
||||
"domain_name_table_header": "Domain name",
|
||||
@@ -208,8 +217,8 @@
|
||||
"page_table_footer_text": "Page",
|
||||
"rows_table_footer_text": "rows",
|
||||
"updated_custom_filtering_toast": "Updated the custom filtering rules",
|
||||
"rule_removed_from_custom_filtering_toast": "Rule removed from the custom filtering rules",
|
||||
"rule_added_to_custom_filtering_toast": "Rule added to the custom filtering rules",
|
||||
"rule_removed_from_custom_filtering_toast": "Rule removed from the custom filtering rules: {{rule}}",
|
||||
"rule_added_to_custom_filtering_toast": "Rule added to the custom filtering rules: {{rule}}",
|
||||
"query_log_response_status": "Status: {{value}}",
|
||||
"query_log_filtered": "Filtered by {{filter}}",
|
||||
"query_log_confirm_clear": "Are you sure you want to clear the entire query log?",
|
||||
@@ -354,7 +363,7 @@
|
||||
"fix": "Fix",
|
||||
"dns_providers": "Here is a <0>list of known DNS providers</0> to choose from.",
|
||||
"update_now": "Update now",
|
||||
"update_failed": "Auto-update failed. Please <a href='https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started#update'>follow the steps</a> to update manually.",
|
||||
"update_failed": "Auto-update failed. Please <a>follow these steps</a> to update manually.",
|
||||
"processing_update": "Please wait, AdGuard Home is being updated",
|
||||
"clients_title": "Clients",
|
||||
"clients_desc": "Configure devices connected to AdGuard Home",
|
||||
@@ -542,7 +551,6 @@
|
||||
"safe_search": "Safe search",
|
||||
"blocklist": "Blocklist",
|
||||
"milliseconds_abbreviation": "ms",
|
||||
"dnssec_enable_desc": "Set DNSSEC flag in the outcoming DNS queries and check the result (DNSSEC-enabled resolver is required)",
|
||||
"cache_size": "Cache size",
|
||||
"cache_size_desc": "DNS cache size (in bytes)",
|
||||
"cache_ttl_min_override": "Override minimum TTL",
|
||||
@@ -562,7 +570,9 @@
|
||||
"filter_category_security_desc": "Lists that specialize on blocking malware, phishing or scam domains",
|
||||
"filter_category_regional_desc": "Lists that focus on regional ads and tracking servers",
|
||||
"filter_category_other_desc": "Other blocklists",
|
||||
"setup_config_to_enable_dhcp_server": "Setup config to enable DHCP server",
|
||||
"original_response": "Original response",
|
||||
"click_to_view_queries": "Click to view queries",
|
||||
"port_53_faq_link": "Port 53 is often occupied by \"DNSStubListener\" or \"systemd-resolved\" services. Please read <0>this instruction</0> on how to resolve this."
|
||||
"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.",
|
||||
"adg_will_drop_dns_queries": "AdGuard Home will be dropping all DNS queries from this client."
|
||||
}
|
||||
|
||||
@@ -208,8 +208,8 @@
|
||||
"page_table_footer_text": "Página",
|
||||
"rows_table_footer_text": "filas",
|
||||
"updated_custom_filtering_toast": "Reglas de filtrado personalizado actualizadas",
|
||||
"rule_removed_from_custom_filtering_toast": "Regla eliminada de las reglas de filtrado personalizado",
|
||||
"rule_added_to_custom_filtering_toast": "Regla añadida a las reglas de filtrado personalizado",
|
||||
"rule_removed_from_custom_filtering_toast": "Regla eliminada de las reglas de filtrado personalizado: {{rule}}",
|
||||
"rule_added_to_custom_filtering_toast": "Regla añadida a las reglas de filtrado personalizado: {{rule}}",
|
||||
"query_log_response_status": "Estado: {{value}}",
|
||||
"query_log_filtered": "Filtrado por {{filter}}",
|
||||
"query_log_confirm_clear": "¿Está seguro de que desea borrar todo el registro de consultas?",
|
||||
@@ -354,8 +354,8 @@
|
||||
"fix": "Corregir",
|
||||
"dns_providers": "Aquí hay una <0>lista de proveedores DNS</0> conocidos para elegir.",
|
||||
"update_now": "Actualizar ahora",
|
||||
"update_failed": "Error en la actualización automática. Por favor <a href='https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started#update'>siga los pasos</a> para actualizar manualmente.",
|
||||
"processing_update": "Por favor espere, AdGuard Home se está actualizando",
|
||||
"update_failed": "Error en la actualización automática. Por favor <a href='https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started#update'>sigue los pasos</a> para actualizar manualmente.",
|
||||
"processing_update": "Por favor espera, AdGuard Home se está actualizando",
|
||||
"clients_title": "Clientes",
|
||||
"clients_desc": "Configurar dispositivos conectados con AdGuard Home",
|
||||
"settings_global": "Global",
|
||||
@@ -562,5 +562,6 @@
|
||||
"filter_category_regional_desc": "Listas que se centran en anuncios regionales y servidores de rastreo",
|
||||
"filter_category_other_desc": "Otras listas de bloqueo",
|
||||
"original_response": "Respuesta original",
|
||||
"click_to_view_queries": "Clic para ver las consultas"
|
||||
}
|
||||
"click_to_view_queries": "Clic para ver las consultas",
|
||||
"port_53_faq_link": "El puerto 53 suele estar ocupado por los servicios \"DNSStubListener\" o \"systemd-resolved\". Por favor lee <0>esta instrucción</0> sobre cómo resolver esto."
|
||||
}
|
||||
|
||||
@@ -202,8 +202,8 @@
|
||||
"page_table_footer_text": "صفحه",
|
||||
"rows_table_footer_text": "سطر",
|
||||
"updated_custom_filtering_toast": "دستورات فیلترینگ دستی بروز رسانی شده است",
|
||||
"rule_removed_from_custom_filtering_toast": "دستور از دستورات فیلترینگ دستی حذف شد",
|
||||
"rule_added_to_custom_filtering_toast": "دستور به دستورات فیلترینگ دستی اضافه شد",
|
||||
"rule_removed_from_custom_filtering_toast": "دستور از دستورات فیلترینگ دستی حذف شد {{rule}}",
|
||||
"rule_added_to_custom_filtering_toast": "دستور به دستورات فیلترینگ دستی اضافه شد {{rule}}",
|
||||
"query_log_response_status": "وضعیت: {{value}}",
|
||||
"query_log_filtered": "فیلتر شده با {{filter}}",
|
||||
"query_log_confirm_clear": "آیا واقعا میخواهید کل وقایع جستار را پاک کنید؟",
|
||||
|
||||
@@ -208,8 +208,8 @@
|
||||
"page_table_footer_text": "Page",
|
||||
"rows_table_footer_text": "lignes",
|
||||
"updated_custom_filtering_toast": "Règles de filtrage d'utilisateur mises à jour",
|
||||
"rule_removed_from_custom_filtering_toast": "Règle retirée des règles d'utilisateur",
|
||||
"rule_added_to_custom_filtering_toast": "Règle ajoutée aux règles d'utilisateur",
|
||||
"rule_removed_from_custom_filtering_toast": "Règle retirée des règles d'utilisateur: {{rule}}",
|
||||
"rule_added_to_custom_filtering_toast": "Règle ajoutée aux règles d'utilisateur: {{rule}}",
|
||||
"query_log_response_status": "Statut : {{value}}",
|
||||
"query_log_filtered": "Filtré par {{filter}}",
|
||||
"query_log_confirm_clear": "Êtes-vous sûr de vouloir effacer tout le journal des requêtes ?",
|
||||
@@ -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_regional_desc": "Listes axées sur les annonces régionales et les serveurs de pistage",
|
||||
"filter_category_other_desc": "Autres listes noires",
|
||||
"click_to_view_queries": "Cliquez pour voir les requêtes"
|
||||
}
|
||||
"click_to_view_queries": "Cliquez pour voir les requêtes",
|
||||
"port_53_faq_link": "Le port 53 est souvent occupé par les services « DNSStubListener » ou « systemd-resolved ». Veuillez lire <0>cette instruction</0> pour savoir comment résoudre ce problème."
|
||||
}
|
||||
|
||||
@@ -84,7 +84,7 @@
|
||||
"disable_protection": "Onemogući zaštitu",
|
||||
"disabled_protection": "Onemogućena zaštita",
|
||||
"refresh_statics": "Osvježi statistiku",
|
||||
"dns_query": "DNS Upiti",
|
||||
"dns_query": "DNS upiti",
|
||||
"blocked_by": "<0>Blokirano filtrima</0>",
|
||||
"stats_malware_phishing": "Blokiran zločudni program/krađe identiteta",
|
||||
"stats_adult": "Blokirane web stranice za odrasle",
|
||||
@@ -111,7 +111,7 @@
|
||||
"block_domain_use_filters_and_hosts": "Blokiraj domene koristeći filtre ili hosts datoteke",
|
||||
"filters_block_toggle_hint": "Pravila blokiranja možete postaviti u postavkama <a href='#filters'>filtara</a>.",
|
||||
"use_adguard_browsing_sec": "Koristi AdGuard uslugu zaštite pregledavanja",
|
||||
"use_adguard_browsing_sec_hint": "AdGuard Home će provjeriti nalazi li se domena na popisu neželjenih domena od usluge zaštite pregledavanja. Za provjeru će se koristiti API za provjeru koji poštuje vašu privatnost. Samo mali dio SHA256 hash-a odnaziva domene se šalje poslužitelju.",
|
||||
"use_adguard_browsing_sec_hint": "AdGuard Home će provjeriti nalazi li se domena na popisu neželjenih domena od usluge zaštite pregledavanja. Za provjeru će se koristiti API za provjeru koji poštuje vašu privatnost. Samo mali dio SHA256 hash-a od naziva domene se šalje poslužitelju.",
|
||||
"use_adguard_parental": "Koristi web uslugu AdGuard roditeljske zaštite",
|
||||
"use_adguard_parental_hint": "AdGuard Home provjeriti će sadrži li domena sadržaj za odrasle. Koristi isti API za zaštitu privatnosti kao i naša usluga zaštite pregledavanja.",
|
||||
"enforce_safe_search": "Omogući sigurno pretraživanje",
|
||||
@@ -208,8 +208,8 @@
|
||||
"page_table_footer_text": "Stranica",
|
||||
"rows_table_footer_text": "redova",
|
||||
"updated_custom_filtering_toast": "Ažurirana su prilagođena pravila filtriranja",
|
||||
"rule_removed_from_custom_filtering_toast": "Pravilo je uklonjeno iz prilagođenih pravila filtriranja",
|
||||
"rule_added_to_custom_filtering_toast": "Pravilo je dodano u prilagođena pravila filtriranja",
|
||||
"rule_removed_from_custom_filtering_toast": "Pravilo je uklonjeno iz prilagođenih pravila filtriranja: {{rule}}",
|
||||
"rule_added_to_custom_filtering_toast": "Pravilo je dodano u prilagođena pravila filtriranja: {{rule}}",
|
||||
"query_log_response_status": "Status: {{value}}",
|
||||
"query_log_filtered": "Filtrirao {{filter}}",
|
||||
"query_log_confirm_clear": "Jeste li sigurni da želite ukloniti zapise upita?",
|
||||
@@ -562,5 +562,6 @@
|
||||
"filter_category_regional_desc": "Popisi koji se fokusiraju na regionalne oglase i poslužitelje za praćenje",
|
||||
"filter_category_other_desc": "Ostali popisi blokiranih",
|
||||
"original_response": "Originalni odgovor",
|
||||
"click_to_view_queries": "Kliknite za pregled upita"
|
||||
}
|
||||
"click_to_view_queries": "Kliknite za pregled upita",
|
||||
"port_53_faq_link": "Port 53 često zauzimaju usluge \"DNSStubListener\" ili \"systemd-resolved\". Molimo pročitajte <0>ove upute</0> o tome kako to riješiti."
|
||||
}
|
||||
|
||||
565
client/src/__locales/hu.json
Normal file
565
client/src/__locales/hu.json
Normal file
@@ -0,0 +1,565 @@
|
||||
{
|
||||
"client_settings": "Kliens beállítások",
|
||||
"example_upstream_reserved": "megadhat egy DNS-t <0> felfelé egy adott tartományhoz </0>",
|
||||
"upstream_parallel": "A párhuzamos lekérdezések segítségével felgyorsíthatja a megoldást az összes upstream kiszolgáló egyidejű lekérdezésével",
|
||||
"parallel_requests": "Párhuzamos kérelmek",
|
||||
"load_balancing": "Terheléselosztás",
|
||||
"load_balancing_desc": "Egyszerre csak egy szerverről történjen lekérdezés. Az AdGuard Home egy súlyozott, véletlenszerű algoritmust fog használni a megfelelő szerver kiválasztására, így a leggyorsabb szervert gyakrabban fogja használni.",
|
||||
"bootstrap_dns": "Bootstrap DNS kiszolgálók",
|
||||
"bootstrap_dns_desc": "A Bootstrap DNS-kiszolgálók a DoH / DoT-megoldók IP-címeinek feloldására szolgálnak",
|
||||
"check_dhcp_servers": "Ellenőrizze a DHCP-kiszolgálókat",
|
||||
"save_config": "Konfiguráció mentése",
|
||||
"enabled_dhcp": "A DHCP-kiszolgáló engedélyezve van",
|
||||
"disabled_dhcp": "A DHCP-kiszolgáló le van tiltva",
|
||||
"dhcp_title": "DHCP-kiszolgáló (kísérleti!)",
|
||||
"dhcp_description": "Ha az útválasztó nem nyújt DHCP-beállításokat, akkor az AdGuard saját beépített DHCP-kiszolgálóját használhatod",
|
||||
"dhcp_enable": "A DHCP-kiszolgáló engedélyezése",
|
||||
"dhcp_disable": "A DHCP-kiszolgáló letiltása",
|
||||
"dhcp_not_found": "Biztonságos a beépített DHCP-kiszolgáló engedélyezése - nem találtunk aktív DHCP-kiszolgálókat a hálózaton. Javasoljuk azonban, hogy kézzel ellenőrizze, hogy az automatikus tesztünk jelenleg nem ad 100% -os garanciát.",
|
||||
"dhcp_found": "Aktív DHCP-kiszolgáló található a hálózaton. Nem biztonságos a beépített DHCP-kiszolgáló engedélyezése.",
|
||||
"dhcp_leases": "DHCP bérlés",
|
||||
"dhcp_static_leases": "Statikus DHCP",
|
||||
"dhcp_leases_not_found": "Nem találhatóak DHCP kliensek",
|
||||
"dhcp_config_saved": "DHCP beállítások elmentve",
|
||||
"form_error_required": "Kötelező mező",
|
||||
"form_error_ip4_format": "Helytelen IPv4 formátum",
|
||||
"form_error_ip6_format": "Érvénytelen IPv6 formátum",
|
||||
"form_error_ip_format": "Érvénytelen IPv4 formátum",
|
||||
"form_error_mac_format": "Érvénytelen MAC formátum",
|
||||
"form_error_client_id_format": "Érvénytelen kliens ID formátum",
|
||||
"form_error_positive": "Legfeljebb nulla legyen",
|
||||
"form_error_negative": "Legalább 0-nak kell lennie",
|
||||
"dhcp_form_gateway_input": "Átjáró IP",
|
||||
"dhcp_form_subnet_input": "Alhálózati maszk",
|
||||
"dhcp_form_range_title": "IP címtartomány",
|
||||
"dhcp_form_range_start": "Tartomány kezdete",
|
||||
"dhcp_form_range_end": "Tartomány vége",
|
||||
"dhcp_form_lease_title": "DHCP bérlési ideje (másodpercben)",
|
||||
"dhcp_form_lease_input": "Bérlési idő",
|
||||
"dhcp_interface_select": "Válaszd ki a DHCP interface-t",
|
||||
"dhcp_hardware_address": "Hardvercím",
|
||||
"dhcp_ip_addresses": "Ip címek",
|
||||
"ip": "IP",
|
||||
"dhcp_table_hostname": "Host név",
|
||||
"dhcp_table_expires": "Lejár",
|
||||
"dhcp_warning": "Ha egyébként engedélyezni szeretné a DHCP-kiszolgálót, győződjön meg arról, hogy nincs-e más aktív DHCP-kiszolgáló a hálózaton. Ellenkező esetben a csatlakoztatott eszközöket megszakíthatja az interneten!",
|
||||
"dhcp_error": "Nem tudtuk meghatározni, hogy van-e másik DHCP-kiszolgáló a hálózaton.",
|
||||
"dhcp_static_ip_error": "A DHCP-kiszolgáló használatához statikus IP-címet kell beállítani. Nem sikerült meghatározni, hogy ez a hálózati interfész statikus IP-cím használatával van-e beállítva. Állítsa be kézzel egy statikus IP-címet.",
|
||||
"dhcp_dynamic_ip_found": "A rendszer dinamikus IP-címkonfigurációt használ az <0> {{interfaceName}} </0> interfészhez. A DHCP-kiszolgáló használatához statikus IP-címet kell beállítani. Jelenlegi IP-címe <0> {{ipAddress}} </0>. Ha automatikusan megnyomja az Enable DHCP gombot, automatikusan beállítjuk ezt az IP-címet statikusnak.",
|
||||
"dhcp_lease_added": "Statikus bérlet \"{{key}}\" sikeresen hozzáadva",
|
||||
"dhcp_lease_deleted": "Statikus bérlet \"{{key}}\" sikeresen törölve",
|
||||
"dhcp_new_static_lease": "Új statikus bérlet",
|
||||
"dhcp_static_leases_not_found": "Nincs DHCP statikus bérlet",
|
||||
"dhcp_add_static_lease": "Statikus bérlet hozzáadása",
|
||||
"dhcp_reset": "Biztosan visszaállítod a DHCP beállításokat?",
|
||||
"country": "Ország",
|
||||
"city": "Város",
|
||||
"delete_confirm": "Biztosan törli a \"{{key}}\" -t?",
|
||||
"form_enter_hostname": "Adja meg a hosztnevet",
|
||||
"error_details": "Hiba részletei",
|
||||
"response_details": "Válasz adatai",
|
||||
"request_details": "Kérés adatai",
|
||||
"client_details": "Kliens részletei",
|
||||
"details": "Részletek",
|
||||
"back": "Vissza",
|
||||
"dashboard": "Irányítópult",
|
||||
"settings": "Beállítások",
|
||||
"filters": "Szűrők",
|
||||
"filter": "Szűrő",
|
||||
"query_log": "Lekérdezési napló",
|
||||
"compact": "Kompakt",
|
||||
"nothing_found": "Nincs találat",
|
||||
"faq": "GYIK",
|
||||
"version": "verzió",
|
||||
"address": "Cím",
|
||||
"protocol": "Protokoll",
|
||||
"on": "Be",
|
||||
"off": "Ki",
|
||||
"copyright": "Szerzői jog",
|
||||
"homepage": "Honlap",
|
||||
"report_an_issue": "Probléma bejelentése",
|
||||
"privacy_policy": "Adatvédelmi irányelvek",
|
||||
"enable_protection": "Védelem engedélyezése",
|
||||
"enabled_protection": "Engedélyezett védelem",
|
||||
"disable_protection": "Védelem letiltása",
|
||||
"disabled_protection": "Letiltott védelem",
|
||||
"refresh_statics": "Statisztikák frissítése",
|
||||
"dns_query": "DNS lekérdezések",
|
||||
"blocked_by": "<0>Szűrők által blokkolt</0>",
|
||||
"stats_malware_phishing": "Blokkolt vírusok/adathalászat",
|
||||
"stats_adult": "Blokkolt felnőtt tartalmak",
|
||||
"stats_query_domain": "A legjobban lekérdezett területek",
|
||||
"for_last_24_hours": "Utolsó 24 órában",
|
||||
"for_last_days": "a legutolsó {{count}} napra",
|
||||
"for_last_days_plural": "a legutolsó {{count}} napra",
|
||||
"no_domains_found": "Nem található domain",
|
||||
"requests_count": "Kérések száma",
|
||||
"top_blocked_domains": "A legjobban blokkolt tartományok",
|
||||
"top_clients": "Legaktívabb kliensek",
|
||||
"no_clients_found": "Nem található kliens",
|
||||
"general_statistics": "Általános statisztikák",
|
||||
"number_of_dns_query_days": "Lekérdezések száma az utolsó {{count}} napban",
|
||||
"number_of_dns_query_days_plural": "Feldolgozott lekérdezések száma az utolsó {{count}} napban",
|
||||
"number_of_dns_query_24_hours": "Számos DNS lekérdezések feldolgozása az elmúlt 24 órában",
|
||||
"number_of_dns_query_blocked_24_hours": "Számos DNS-kérés blokkolva van az adblock-szűrők által, és blokklistákat tartalmaz",
|
||||
"number_of_dns_query_blocked_24_hours_by_sec": "Számos DNS-kérés blokkolva van az AdGuard böngésző biztonsági moduljában",
|
||||
"number_of_dns_query_blocked_24_hours_adult": "Blokkolt felnőtt tartalmak száma",
|
||||
"enforced_save_search": "Erősített biztonságos keresés",
|
||||
"number_of_dns_query_to_safe_search": "Számos DNS-kérelem olyan keresőmotorokhoz, amelyekre a Biztonságos keresés érvényesült",
|
||||
"average_processing_time": "Átlagos feldolgozási idő",
|
||||
"average_processing_time_hint": "Átlagos idő milliszekundumban a DNS-kérelem feldolgozásakor",
|
||||
"block_domain_use_filters_and_hosts": "A tartományok blokkolása szűrőkkel és házigazdákkal",
|
||||
"filters_block_toggle_hint": "A <a href='#filters'> Szűrők </a> beállításaiban beállíthatja a blokkolási szabályokat.",
|
||||
"use_adguard_browsing_sec": "Használja az AdGuard böngészés biztonsági webszolgáltatását",
|
||||
"use_adguard_browsing_sec_hint": "Az AdGuard Home ellenőrzi, hogy a böngésző biztonsági webszolgáltatás a tartományt feketelistára tette-e. Az ellenőrzés elvégzéséhez adatvédelmi keresési API-t fog használni: az SHA256-os hash-név csak egy rövid előtagot küld a kiszolgálónak.",
|
||||
"use_adguard_parental": "Használja az AdGuard szülői felügyelet webszolgáltatását",
|
||||
"use_adguard_parental_hint": "Az AdGuard Home ellenőrzi, hogy a domain tartalmaz-e felnőtt anyagokat. Ugyanazokat az adatvédelmi API-kat használja, mint a böngésző biztonsági webszolgáltatás.",
|
||||
"enforce_safe_search": "A biztonságos keresés végrehajtása",
|
||||
"enforce_save_search_hint": "Az AdGuard Home a következő keresőmotorokban biztosíthatja a biztonságos keresést: Google, Youtube, Bing, DuckDuckGo és Yandex.",
|
||||
"no_servers_specified": "Nincsenek megadott kiszolgálók",
|
||||
"general_settings": "Általános beállítások",
|
||||
"dns_settings": "DNS beállítások",
|
||||
"dns_blocklists": "DNS blokkolási listák",
|
||||
"dns_allowlists": "DNS engedélyezési listák",
|
||||
"dns_blocklists_desc": "Az AdGuard Home blokkolni fogja azokat a domaineket, amik szerepelnek a blokkolási listán.",
|
||||
"dns_allowlists_desc": "A DNS engedélyezési listán szereplő domainek engedélyezve lesznek, akkor is, ha szerepelnek bármelyik blokkolási listán.",
|
||||
"custom_filtering_rules": "Egyéni szűrési szabályok",
|
||||
"encryption_settings": "Titkosítási beállítások",
|
||||
"dhcp_settings": "DHCP beállítások",
|
||||
"upstream_dns": "Upstream DNS-kiszolgálók",
|
||||
"upstream_dns_hint": "Ha üresen hagyod ezt a mezőt, az AdGuard a(z) <a href='https://www.quad9.net/' target='_blank'>Quad9</a>-t fogja használni.",
|
||||
"test_upstream_btn": "Upstreamek tesztelése",
|
||||
"upstreams": "Feltöltés",
|
||||
"apply_btn": "Alkalmaz",
|
||||
"disabled_filtering_toast": "Letiltott szűrés",
|
||||
"enabled_filtering_toast": "Engedélyezett szűrés",
|
||||
"disabled_safe_browsing_toast": "Letiltott biztonságos böngészés",
|
||||
"enabled_safe_browsing_toast": "Engedélyezett biztonságos böngészés",
|
||||
"disabled_parental_toast": "Letiltott szülői felügyelet",
|
||||
"enabled_parental_toast": "Engedélyezett szülői felügyelet",
|
||||
"disabled_safe_search_toast": "Letiltott biztonságos keresés",
|
||||
"enabled_save_search_toast": "Engedélyezett biztonságos keresés",
|
||||
"enabled_table_header": "Engedélyezve",
|
||||
"name_table_header": "Név",
|
||||
"list_url_table_header": "Lista URL-je",
|
||||
"rules_count_table_header": "Szabályok száma",
|
||||
"last_time_updated_table_header": "Utoljára frissítve",
|
||||
"actions_table_header": "Akciók",
|
||||
"request_table_header": "Kérelem",
|
||||
"edit_table_action": "Szerkesztés",
|
||||
"delete_table_action": "Törlés",
|
||||
"elapsed": "Eltelt időtartam",
|
||||
"filters_and_hosts_hint": "Az AdGuard Home megérti az alapvető adblock szabályokat és a fájlokat tartalmazó szintaxist.",
|
||||
"no_blocklist_added": "Nincsnek blokkolási listák hozzáadva",
|
||||
"no_whitelist_added": "Nincsenek engedélyezési listák hozzáadva",
|
||||
"add_blocklist": "Blokkolási lista hozzáadása",
|
||||
"add_allowlist": "Engedélyezési lista hozzáadása",
|
||||
"cancel_btn": "Megszünteti",
|
||||
"enter_name_hint": "Adja meg a nevet",
|
||||
"enter_url_or_path_hint": "Írjon be egy URL-t vagy egy útvonalat a listához",
|
||||
"check_updates_btn": "Frissítések keresése",
|
||||
"new_blocklist": "Új blokkolási lista",
|
||||
"new_allowlist": "Új engedélyezési lista",
|
||||
"edit_blocklist": "Blokkolási lista módosítása",
|
||||
"edit_allowlist": "Engedélyezési lista módosítása",
|
||||
"choose_blocklist": "Blokkolási lista választás",
|
||||
"choose_allowlist": "Engedélyezési lista választás",
|
||||
"enter_valid_blocklist": "Adjon meg egy érvényes URL-t a blokkolási listához.",
|
||||
"enter_valid_allowlist": "Adjon meg egy érvényes URL-t az engedélyezési listához.",
|
||||
"form_error_url_format": "Egyedi URL formátum",
|
||||
"form_error_url_or_path_format": "Helytelen URL vagy elérési út a listához",
|
||||
"custom_filter_rules": "Egyéni szűrési szabályok",
|
||||
"custom_filter_rules_hint": "Adjon meg egy szabályt egy sorban. Használhatja az adblock szabályokat vagy a fájlokat tartalmazó szintaxist.",
|
||||
"examples_title": "Példák",
|
||||
"example_meaning_filter_block": "blokkolja a example.org domain és az összes aldomain hozzáférését",
|
||||
"example_meaning_filter_whitelist": "example.org tartomány és az összes aldomain hozzáférésének feloldása",
|
||||
"example_meaning_host_block": "Az AdGuard Home most visszatér a 127.0.0.1 címre a example.org domainhez (de nem az aldomainjeihez).",
|
||||
"example_comment": "! Ide írhat egy megjegyzést",
|
||||
"example_comment_meaning": "Csak egy megjegyzés",
|
||||
"example_comment_hash": "# Megjegyzés is",
|
||||
"example_regex_meaning": "megakadályozza a hozzáférést a reguláris kifejezéssel egyező domainek-nél",
|
||||
"example_upstream_regular": "rendszeres DNS (UDP felett)",
|
||||
"example_upstream_dot": "titkosított <0>DNS-over-TLS</0>",
|
||||
"example_upstream_doh": "titkosított <0>DNS-over-HTTPS</0>",
|
||||
"example_upstream_sdns": "a <0> DNS bélyegek </0> használatával <1> DNSCrypt </1> vagy <2> DNS-over-HTTPS </2>",
|
||||
"example_upstream_tcp": "hagyományos DNS (TCP felett)",
|
||||
"all_lists_up_to_date_toast": "Már minden lista naprakész",
|
||||
"updated_upstream_dns_toast": "Frissítette az upstream DNS-kiszolgálókat",
|
||||
"dns_test_ok_toast": "A megadott DNS-kiszolgálók megfelelően működnek",
|
||||
"dns_test_not_ok_toast": "Szerver \"{{key}}\": nem használható, ellenőrizze, hogy helyesen írta-e be",
|
||||
"unblock": "Blokkolás feloldása",
|
||||
"block": "Blokkolás",
|
||||
"time_table_header": "Idő",
|
||||
"date": "Dátum",
|
||||
"domain_name_table_header": "Domain név",
|
||||
"domain_or_client": "Webcím vagy kliens",
|
||||
"type_table_header": "Típus",
|
||||
"response_table_header": "Válasz",
|
||||
"response_code": "Válaszkód",
|
||||
"client_table_header": "Kliens",
|
||||
"empty_response_status": "Üres",
|
||||
"show_all_filter_type": "Mutasd az összeset",
|
||||
"show_filtered_type": "Szűrés megjelenítése",
|
||||
"no_logs_found": "Nem található napló",
|
||||
"refresh_btn": "Frissítés",
|
||||
"previous_btn": "Előző",
|
||||
"next_btn": "Következő",
|
||||
"loading_table_status": "Töltés...",
|
||||
"page_table_footer_text": "Oldal",
|
||||
"rows_table_footer_text": "sor",
|
||||
"updated_custom_filtering_toast": "Egyéni szűrési módok frissítése",
|
||||
"rule_removed_from_custom_filtering_toast": "Szabály eltávolítva az egyedi szűrési módok közül",
|
||||
"rule_added_to_custom_filtering_toast": "Szabály hozzáadva az egyedi szűrési módokhoz",
|
||||
"query_log_response_status": "Státusz: {{value}}",
|
||||
"query_log_filtered": "{{filter}} által szűrve",
|
||||
"query_log_confirm_clear": "Biztosan törlöd a lekérdezési naplót?",
|
||||
"query_log_cleared": "A lekérdezési napló sikeresen törölve",
|
||||
"query_log_updated": "A lekérdezési napló sikeresen frissítve lett",
|
||||
"query_log_clear": "Lekérdezési napló törlése",
|
||||
"query_log_retention": "Lekérdezési naplók megtartása",
|
||||
"query_log_enable": "Naplózás engedélyezése",
|
||||
"query_log_configuration": "Naplózás beállítása",
|
||||
"query_log_disabled": "Lekérdezési napló kikapcsolva. Bekapcsolható a <0>Beállítások</0>ban",
|
||||
"query_log_strict_search": "Használj \"dupla idézőjelet\" a pontos kereséshez",
|
||||
"query_log_retention_confirm": "Biztos benne, hogy megváltoztatja a kérések naplójának megőrzési idejét? Ha csökkentette az értéket, a megadottnál korábbi adatok elvesznek",
|
||||
"anonymize_client_ip": "Kliens IP-címének anonimizálása",
|
||||
"anonymize_client_ip_desc": "Ne mentse el a kliens teljes IP-címét a naplókban és a statisztikákban",
|
||||
"dns_config": "DNS szerver beállításai",
|
||||
"dns_cache_config": "DNS gyorsítótár beállításai",
|
||||
"dns_cache_config_desc": "Itt tudja konfigurálni a DNS gyorsítótárat",
|
||||
"blocking_mode": "Blokkolás módja",
|
||||
"default": "Alapértelmezett",
|
||||
"nxdomain": "NXDOMAIN",
|
||||
"null_ip": "Null IP-cím",
|
||||
"custom_ip": "Egyedi IP",
|
||||
"blocking_ipv4": "IPv4 blokkolása",
|
||||
"blocking_ipv6": "IPv6 blokkolása",
|
||||
"dns_over_https": "DNS-over-HTTPS",
|
||||
"dns_over_tls": "DNS-over-TLS",
|
||||
"plain_dns": "Egyszerű DNS",
|
||||
"form_enter_rate_limit": "Adja meg a kérések maximális számát",
|
||||
"rate_limit": "Kérések korlátozása",
|
||||
"edns_enable": "EDNS kliens alhálózat engedélyezése",
|
||||
"edns_cs_desc": "Ha engedélyezve van, az AdGuard Home a kliensek alhálózatait küldi el a DNS-kiszolgálóknak.",
|
||||
"rate_limit_desc": "Maximálisan hány kérést küldhet egy kliens másodpercenként (0: korlátlan)",
|
||||
"blocking_ipv4_desc": "A blokkolt A kéréshez visszaadandó IP-cím",
|
||||
"blocking_ipv6_desc": "A blokkolt AAAA kéréshez visszaadandó IP-cím",
|
||||
"blocking_mode_default": "Alapértelmezés: Adblock-stílusú szabály esetén NXDOMAIN válasz küldése, /etc/hosts-stílusú szabály esetén pedig a szabályban meghatározott IP-címmel való válasz küldése",
|
||||
"blocking_mode_nxdomain": "NXDOMAIN: Az NXDOMAIN kóddal fog válaszolni",
|
||||
"blocking_mode_null_ip": "Null IP: Nullákból álló IP-címmel válaszol (0.0.0.0 for A; :: for AAAA)",
|
||||
"blocking_mode_custom_ip": "Egyedi IP: Válasz egy kézzel beállított IP címmel",
|
||||
"upstream_dns_client_desc": "Ha üresen hagyja ezt a mezőt, az AdGuard Home azokat a szervereket fogja használni, amik a <0>DNS beállításokban</0> vannak beállítva.",
|
||||
"tracker_source": "Követő forrása",
|
||||
"source_label": "Forrás",
|
||||
"found_in_known_domain_db": "Benne van az ismert domainek listájában.",
|
||||
"category_label": "Kategória",
|
||||
"rule_label": "Szabály",
|
||||
"list_label": "Lista",
|
||||
"unknown_filter": "Ismeretlen szűrő: {{filterId}}",
|
||||
"known_tracker": "Ismert követő",
|
||||
"install_welcome_title": "Üdvözli az AdGuard Home!",
|
||||
"install_welcome_desc": "Az AdGuard Home egy, a teljes hálózatot lefedő hirdetés és követő blokkoló DNS szerver. Az a célja, hogy lehetővé tegye a teljes hálózat és az összes eszköz vezérlését, és nem igényel kliensoldali programot.",
|
||||
"install_settings_title": "Webes admin felület",
|
||||
"install_settings_listen": "Figyelő felület",
|
||||
"install_settings_port": "Port",
|
||||
"install_settings_interface_link": "Az AdGuard Home webes admin felülete elérhető a következő címe(ke)n:",
|
||||
"form_error_port": "Adjon meg egy érvényes portot",
|
||||
"install_settings_dns": "DNS szerver",
|
||||
"install_settings_dns_desc": "Be kell állítania az eszközeit vagy a routerét, hogy használni tudja a DNS szervert a következő címeken:",
|
||||
"install_settings_all_interfaces": "Minden felület",
|
||||
"install_auth_title": "Hitelesítés",
|
||||
"install_auth_desc": "Erősen ajánlott a jelszavas hitelesítés beállítása az AdGuard Home webes admin felületéhez. Még akkor is, ha csak a helyi hálózaton érhető el, óvja meg az illetéktelen hozzáférésektől.",
|
||||
"install_auth_username": "Felhasználónév",
|
||||
"install_auth_password": "Jelszó",
|
||||
"install_auth_confirm": "Jelszó megerősítése",
|
||||
"install_auth_username_enter": "Felhasználónév megadása",
|
||||
"install_auth_password_enter": "Jelszó megadása",
|
||||
"install_step": "Lépés",
|
||||
"install_devices_title": "Állítsa be az eszközeit",
|
||||
"install_devices_desc": "Az AdGuard Home használatának megkezdéséhez be kell állítania az eszközeit, hogy azok használni tudják.",
|
||||
"install_submit_title": "Gratulálunk!",
|
||||
"install_submit_desc": "A telepítési folyamat befejeződött, minden készen áll az AdGuard Home használatára.",
|
||||
"install_devices_router": "Router",
|
||||
"install_devices_router_desc": "Ez a beállítás lefed minden eszközt, amik az Ön routeréhez csatlakoznak, így azokat nem kell külön, kézzel beállítania.",
|
||||
"install_devices_address": "Az AdGuard DNS szerver a következő címeket figyeli",
|
||||
"install_devices_router_list_1": "Nyissa meg a router beállításait. Ez általában a böngészőn keresztül történik egy URL megadásával (pl. http://192.168.0.1/ vagy http://192.168.1.1/). Ez az oldal valószínűleg felhasználónevet és jelszót fog kérni. Ha nem tudja a belépési adatokat, nézze meg a router dobozát, a router alján levő fehér címkét vagy a technikai dokumentációt az interneten, végső esetben pedig visszaállíthatja a routert. Néhány router speciális alkalmazást igényel, amik lehetséges, hogy már telepítve vannak a számítógépére vagy a mobil eszközére.",
|
||||
"install_devices_router_list_2": "Keresse meg a DHCP/DNS beállításokat. Keresse a DNS szót egy olyan mező mellett, amely egy 4 csoportból álló, 1-3 számjegyű számsort vár.",
|
||||
"install_devices_router_list_3": "Adja meg az AdGuard Home szerver címét itt.",
|
||||
"install_devices_router_list_4": "Bizonyos típusú routereknél nem állíthat be egyéni DNS-kiszolgálót. Ebben az esetben segíthet, ha az AdGuard Home-t DHCP-szerverként állítja be. Ellenkező esetben keresse meg az adott router kézikönyvében a DNS-kiszolgálók testreszabását.",
|
||||
"install_devices_windows_list_1": "Nyissa meg a Vezérlőpultot a Start menün vagy a Windows keresőn keresztül.",
|
||||
"install_devices_windows_list_2": "Válassza a Hálózat és internet kategóriát, majd pedig a Hálózati és megosztási központot.",
|
||||
"install_devices_windows_list_3": "A képernyő bal oldalán keresse meg az Adapterbeállítások módosítása lehetőséget és kattintson rá.",
|
||||
"install_devices_windows_list_4": "Válassza ki a jelenleg is használt kapcsolatot, majd jobb egérgombbal kattintson rá és a megjelenő menüből válassza a Tulajdonságok elemet.",
|
||||
"install_devices_windows_list_5": "Keresse meg az Internet Protocol Version 4 (TCP/IP) elemet a listában, válassza ki, majd ismét kattintson a Tulajdonságokra.",
|
||||
"install_devices_windows_list_6": "Válassza a Következő DNS címek használata lehetőséget és adja meg az AdGuard Home szerver címeit.",
|
||||
"install_devices_macos_list_1": "Kattintson az Apple ikonra és válassza a Rendszerbeállításokat.",
|
||||
"install_devices_macos_list_2": "Kattintson a Hálózat lehetőségre.",
|
||||
"install_devices_macos_list_3": "Válassza ki az első kapcsolatot a listából és kattintson a Haladó beállításokra.",
|
||||
"install_devices_macos_list_4": "Válassza ki a DNS fület és adja meg az AdGuard Home szerver címeit.",
|
||||
"install_devices_android_list_1": "Az Android kezdőképernyőjén érintse meg a Beállítások gombot.",
|
||||
"install_devices_android_list_2": "Érintse meg a Wi-Fi gombot a menüben. Ekkor a képernyőre kerül az összes elérhető hálózat (mobilinternethez nem lehet egyedi DNS-t megadni).",
|
||||
"install_devices_android_list_3": "Nyomjon hosszan arra a hálózatra a listából, amelyikre éppen csatlakozva van, majd válassza a Hálózat módosítása lehetőséget.",
|
||||
"install_devices_android_list_4": "Egyes eszközökön előfordulhat, hogy a további beállítások megtekintéséhez a Speciális/haladó beállítások részt kell megnyitni. Az Android DNS-beállításainak módosításához ekkor az IP-beállításokat DHCP-ről statikusra kell váltania.",
|
||||
"install_devices_android_list_5": "Változtassa meg a DNS 1 és a DNS 2 értékét az AdGuard Home szerver címeire.",
|
||||
"install_devices_ios_list_1": "A kezdőképernyőn érintse meg a Beállítások gombot.",
|
||||
"install_devices_ios_list_2": "Válassza ki a Wi-Fi-t a bal oldali menüből (mobilinternetnél nem lehetséges a DNS beállítása).",
|
||||
"install_devices_ios_list_3": "Érintse meg a jelenleg használt hálózat nevét.",
|
||||
"install_devices_ios_list_4": "A DNS mezőbe adja meg az AdGuard Home szerver címeit.",
|
||||
"get_started": "Kezdés",
|
||||
"next": "Következő",
|
||||
"open_dashboard": "Irányítópult megnyitása",
|
||||
"install_saved": "Sikeres mentés",
|
||||
"encryption_title": "Titkosítás",
|
||||
"encryption_desc": "Titkosítás (HTTPS/TLS) támogatása mind a DNS, mind pedig a webes admin felület számára",
|
||||
"encryption_config_saved": "Titkosítási beállítások mentve",
|
||||
"encryption_server": "Szerver neve",
|
||||
"encryption_server_enter": "Adja meg az Ön domain címét",
|
||||
"encryption_server_desc": "A HTTPS használatához be kell írnia egy, az SSL-tanúsítvánnyal megegyező kiszolgálónevet.",
|
||||
"encryption_redirect": "Automatikus átirányítás HTTPS kapcsolatra",
|
||||
"encryption_redirect_desc": "Ha be van jelölve, az AdGuard Home automatikusan átirányítja a HTTP kapcsolatokat a biztonságos HTTPS protokollra.",
|
||||
"encryption_https": "HTTPS port",
|
||||
"encryption_https_desc": "Ha a HTTPS port konfigurálva van, akkor az AdGuard Home admin felülete elérhető lesz a HTTPS-en keresztül, és ezenkívül DNS-over-HTTPS-t is biztosít a '/dns-query' helyen.",
|
||||
"encryption_dot": "DNS-over-TLS port",
|
||||
"encryption_dot_desc": "Ha ez a port be van állítva, az AdGuard Home DNS-over-TLS szerverként tud futni ezen a porton.",
|
||||
"encryption_certificates": "Tanúsítványok",
|
||||
"encryption_certificates_desc": "A titkosítás használatához érvényes SSL tanúsítványláncot kell megadnia a domainjéhez. Ingyenes tanúsítványt kaphat a <0>{{link}}</0> webhelyen, vagy megvásárolhatja az egyik megbízható tanúsítványkibocsátó hatóságtól.",
|
||||
"encryption_certificates_input": "Másolja be ide a PEM-kódolt tanúsítványt.",
|
||||
"encryption_status": "Állapot",
|
||||
"encryption_expire": "Lejár",
|
||||
"encryption_key": "Privát kulcs",
|
||||
"encryption_key_input": "Másolja ki és illessze be ide a tanúsítványa PEM-kódolt privát kulcsát.",
|
||||
"encryption_enable": "Titkosítás engedélyezése (HTTPS, DNS-over-HTTPS, és DNS-over-TLS)",
|
||||
"encryption_enable_desc": "Ha a titkosítás engedélyezve van, az AdGuard Home admin felülete működik HTTPS-en keresztül, és a DNS szerver is várja a kéréseket DNS-over-HTTPS-en, valamint DNS-over-TLS-en keresztül.",
|
||||
"encryption_chain_valid": "A tanúsítványlánc érvényes",
|
||||
"encryption_chain_invalid": "A tanúsítványlánc érvénytelen",
|
||||
"encryption_key_valid": "Ez egy érvényes {{type}} privát kulcs",
|
||||
"encryption_key_invalid": "Ez egy érvénytelen {{type}} privát kulcs",
|
||||
"encryption_subject": "Tárgy",
|
||||
"encryption_issuer": "Kibocsátó",
|
||||
"encryption_hostnames": "Hosztnevek",
|
||||
"encryption_reset": "Biztosan visszaállítja a titkosítási beállításokat?",
|
||||
"topline_expiring_certificate": "Az SSL-tanúsítványa hamarosan lejár. Frissítse a <0>Titkosítási beállításokat</0>.",
|
||||
"topline_expired_certificate": "Az SSL-tanúsítványa lejárt. Frissítse a <0>Titkosítási beállításokat</0>.",
|
||||
"form_error_port_range": "A port értékét a 80-65535 tartományban adja meg",
|
||||
"form_error_port_unsafe": "Ez a port nem biztonságos",
|
||||
"form_error_equal": "Nem egyezhetnek",
|
||||
"form_error_password": "A jelszavak nem egyeznek",
|
||||
"reset_settings": "Beállítások visszaállítása",
|
||||
"update_announcement": "Az AdGuard Home {{version}} verziója elérhető! <0>Kattintson ide</0> további információkért.",
|
||||
"setup_guide": "Beállítási útmutató",
|
||||
"dns_addresses": "DNS címek",
|
||||
"dns_start": "A DNS szerver indul",
|
||||
"dns_status_error": "Hiba történt a DNS szerver állapotának ellenőrzésekor",
|
||||
"down": "Nem elérhető",
|
||||
"fix": "Állandó",
|
||||
"dns_providers": "Itt van az <0>ismert DNS szolgáltatók listája</0>, amelyekből választhat.",
|
||||
"update_now": "Frissítés most",
|
||||
"update_failed": "Az automatikus frissítés nem sikerült. Kérjük, hogy <a href='https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started#update'>kövesse ezeket a lépéseket</a> a manuális frissítéshez.",
|
||||
"processing_update": "Kérjük várjon, az AdGuard Home frissítése folyamatban van",
|
||||
"clients_title": "Kliensek",
|
||||
"clients_desc": "Az AdGuard Home-hoz csatlakozó eszközök kezelése",
|
||||
"settings_global": "Globális",
|
||||
"settings_custom": "Egyéni",
|
||||
"table_client": "Kliens",
|
||||
"table_name": "Név",
|
||||
"save_btn": "Mentés",
|
||||
"client_add": "Kliens hozzáadása",
|
||||
"client_new": "Új kliens",
|
||||
"client_edit": "Kliens módosítása",
|
||||
"client_identifier": "Azonosító",
|
||||
"ip_address": "IP cím",
|
||||
"client_identifier_desc": "A klienseket be lehet azonosítani IP-cím, CIDR, valamint MAC-cím alapján. Kérjük, vegye figyelembe, hogy a MAC-cím alapján történő azonosítás csak akkor működik, ha az AdGuard Home egyben <0>DHCP szerverként</0> is funkcionál",
|
||||
"form_enter_ip": "IP-cím megadása",
|
||||
"form_enter_mac": "MAC-cím megadása",
|
||||
"form_enter_id": "Azonosító megadása",
|
||||
"form_add_id": "Azonosító hozzáadása",
|
||||
"form_client_name": "Adja meg a kliens nevét",
|
||||
"name": "Név",
|
||||
"client_global_settings": "Globális beállítások használata",
|
||||
"client_deleted": "A(z) \"{{key}}\" kliens sikeresen el lett távolítva",
|
||||
"client_added": "A(z) \"{{key}}\" kliens sikeresen hozzá lett adva",
|
||||
"client_updated": "A(z) \"{{key}}\" kliens sikeresen frissítve lett",
|
||||
"clients_not_found": "Nem található kliens",
|
||||
"client_confirm_delete": "Biztosan törölni szeretné az \"{{key}}\" klienst?",
|
||||
"list_confirm_delete": "Biztosan törölni kívánja ezt a listát?",
|
||||
"auto_clients_title": "Kliensek (futási idő)",
|
||||
"auto_clients_desc": "Az AdGuard Home-ot használó, de a konfigurációban nem tárolt ügyfelek adatai",
|
||||
"access_title": "Hozzáférési beállítások",
|
||||
"access_desc": "Itt konfigurálhatja az AdGuard Home DNS-kiszolgáló hozzáférési szabályait.",
|
||||
"access_allowed_title": "Engedélyezett kliensek",
|
||||
"access_allowed_desc": "A CIDR vagy IP címek listája. Ha konfigurálva van, az AdGuard Home csak az alábbi IP-címekről fogadja el a kéréseket.",
|
||||
"access_disallowed_title": "Nem engedélyezett kliensek",
|
||||
"access_disallowed_desc": "A CIDR vagy IP címek listája. Ha konfigurálva van, az AdGuard Home ebből az IP-címből lekéri a kéréseket.",
|
||||
"access_blocked_title": "Blokkolt tartományok",
|
||||
"access_blocked_desc": "Ne keverje össze ezt a szűrőkkel. Az AdGuard Home a DNS-lekérdezéseket a lekérdezés kérdésében el fogja hagyni ezeken a tartományokon",
|
||||
"access_settings_saved": "A hozzáférési beállítások sikeresen mentésre kerültek",
|
||||
"updates_checked": "A frissítések sikeresen ellenőrizve lettek",
|
||||
"updates_version_equal": "Az AdGuard Home naprakész",
|
||||
"check_updates_now": "Frissítések ellenőrzése most",
|
||||
"dns_privacy": "DNS Adatvédelem",
|
||||
"setup_dns_privacy_1": "<0>DNS-over-TLS:</0> Használja a(z) <1>{{address}}</1> szöveget.",
|
||||
"setup_dns_privacy_2": "<0>DNS-over-HTTPS:</0> Használja a(z) <1>{{address}}</1> szöveget.",
|
||||
"setup_dns_privacy_3": "<0>Kérjük, vegye figyelembe, hogy a titkosított DNS protokollok csak Android 9-től vannak támogatva. Tehát további szoftvert kell telepítenie más operációs rendszerekhez.</0><0>Itt megtalálja azon szoftverek listáját, amelyeket használhat.</0>",
|
||||
"setup_dns_privacy_android_1": "Az Android 9 natív módon támogatja a DNS-over-TLS-t. A beállításához menjen a Beállítások → Hálózat & internet → Speciális → Privát DNS menübe, és adja meg itt a domaint.",
|
||||
"setup_dns_privacy_android_2": "Az <0>AdGuard for Android</0> támogatja a <1>DNS-over-HTTPS</1>-t és a <1>DNS-over-TLS</1>-t.",
|
||||
"setup_dns_privacy_android_3": "Az <0>Intra</0> hozzáadja a <1>DNS-over-HTTPS</1> támogatást az Androidhoz.",
|
||||
"setup_dns_privacy_ios_1": "A <0>DNSCloak</0> támogatja a <1>DNS-over-HTTPS</1>-t, de ahhoz, hogy a saját szerverhez konfigurálhassa, létre kell hoznia egy <2>DNS bélyeget</2> hozzá.",
|
||||
"setup_dns_privacy_ios_2": "Az <0>AdGuard for iOS</0> támogatja a <1>DNS-over-HTTPS</1> és a <1>DNS-over-TLS</1> beállításokat.",
|
||||
"setup_dns_privacy_other_title": "Egyéb megvalósítások",
|
||||
"setup_dns_privacy_other_1": "Maga az AdGuard Home bármilyen platformon biztonságos DNS-kliens lehet.",
|
||||
"setup_dns_privacy_other_2": "A <0>dnsproxy</0> támogatja az összes ismert biztonságos DNS protokollt.",
|
||||
"setup_dns_privacy_other_3": "A <0>dnscrypt-proxy</0> támogatja a <1>DNS-over-HTTPS</1>-t.",
|
||||
"setup_dns_privacy_other_4": "A <0>Mozilla Firefox</0> támogatja a <1>DNS-over-HTTPS</1>-t.",
|
||||
"setup_dns_privacy_other_5": "További megvalósításokat találhat <0>ide</0> és <1>ide</1> kattintva.",
|
||||
"setup_dns_notice": "Ahhoz, hogy a <1>DNS-over-HTTPS</1> vagy a <1>DNS-over-TLS</1> valamelyikét használja, muszáj <0>beállítania a titkosítást</0> az AdGuard Home beállításaiban.",
|
||||
"rewrite_added": "DNS átírás a(z) \"{{key}}\" kulcshoz sikeresen hozzáadva",
|
||||
"rewrite_deleted": "DNS átírás a(z) \"{{key}}\" kulcshoz sikeresen törölve",
|
||||
"rewrite_add": "DNS átírás hozzáadása",
|
||||
"rewrite_not_found": "Nem találhatók DNS átírások",
|
||||
"rewrite_confirm_delete": "Biztosan törölni szeretné a DNS átírást ehhez: \"{{key}}\"?",
|
||||
"rewrite_desc": "Lehetővé teszi, hogy egyszerűen beállítson egyéni DNS választ egy adott domain névhez.",
|
||||
"rewrite_applied": "Alkalmazott átírási szabály",
|
||||
"rewrite_hosts_applied": "Átírt a gazda fájl szabálya által",
|
||||
"dns_rewrites": "DNS átírások",
|
||||
"form_domain": "Adja meg a domain nevet vagy a helyettesítő karaktert",
|
||||
"form_answer": "Adjon meg egy IP-címet vagy egy domain nevet",
|
||||
"form_error_domain_format": "Érvénytelen domain formátum",
|
||||
"form_error_answer_format": "Érvénytelen válasz formátum",
|
||||
"configure": "Beállítás",
|
||||
"main_settings": "Fő beállítások",
|
||||
"block_services": "Speciális szolgáltatások blokkolása",
|
||||
"blocked_services": "Blokkolt szolgáltatások",
|
||||
"blocked_services_desc": "Lehetővé teszi a népszerű oldalak és szolgáltatások blokkolását.",
|
||||
"blocked_services_saved": "Blokkolt szolgáltatások sikeresen mentve",
|
||||
"blocked_services_global": "A globálisan tiltott szolgáltatások használata",
|
||||
"blocked_service": "Blokkolt szolgáltatás",
|
||||
"block_all": "Összes blokkolása",
|
||||
"unblock_all": "Összes feloldása",
|
||||
"encryption_certificate_path": "Tanúsítvány útvonala",
|
||||
"encryption_private_key_path": "Privát kulcs útvonala",
|
||||
"encryption_certificates_source_path": "Tanúsítványfájl útvonalának megadása",
|
||||
"encryption_certificates_source_content": "Tanúsítvány tartalmának megadása",
|
||||
"encryption_key_source_path": "Privát kulcsfájl beállítása",
|
||||
"encryption_key_source_content": "Privát kulcs tartalmának megadása",
|
||||
"stats_params": "Statisztikai beállítások",
|
||||
"config_successfully_saved": "A beállítások sikeresen el lettek mentve",
|
||||
"interval_24_hour": "24 óra",
|
||||
"interval_days": "{{count}} nap",
|
||||
"interval_days_plural": "{{count}} nap",
|
||||
"domain": "Domain",
|
||||
"answer": "Válasz",
|
||||
"filter_added_successfully": "A lista sikeresen hozzá lett adva",
|
||||
"filter_removed_successfully": "A lista sikeresen el lett távolítva",
|
||||
"filter_updated": "A lista sikeresen frissítve lett",
|
||||
"statistics_configuration": "Statisztikai beállítások",
|
||||
"statistics_retention": "Statisztika megőrzése",
|
||||
"statistics_retention_desc": "Ha csökkenti az intervallum értékét, az előtte levő adatok elvesznek",
|
||||
"statistics_clear": " Statisztikák visszaállítása",
|
||||
"statistics_clear_confirm": "Biztosan vissza akarja állítani a statisztikákat?",
|
||||
"statistics_retention_confirm": "Biztos benne, hogy megváltoztatja a statisztika megőrzési idejét? Ha csökkentette az értéket, a megadottnál korábbi adatok elvesznek",
|
||||
"statistics_cleared": "A statisztikák sikeresen vissza lettek állítva",
|
||||
"interval_hours": "{{count}} óra",
|
||||
"interval_hours_plural": "{{count}} óra",
|
||||
"filters_configuration": "Szűrők beállításai",
|
||||
"filters_enable": "Szűrők engedélyezése",
|
||||
"filters_interval": "Szűrőfrissítési gyakoriság",
|
||||
"disabled": "Kikapcsolva",
|
||||
"username_label": "Felhasználónév",
|
||||
"username_placeholder": "Felhasználónév megadása",
|
||||
"password_label": "Jelszó",
|
||||
"password_placeholder": "Jelszó megadása",
|
||||
"sign_in": "Bejelentkezés",
|
||||
"sign_out": "Kijelentkezés",
|
||||
"forgot_password": "Elfelejtette a jelszót?",
|
||||
"forgot_password_desc": "Kérjük, hogy kövesse <0>ezeket a lépéseket</0> a jelszó visszaállításához.",
|
||||
"location": "Helyzet",
|
||||
"orgname": "Szervezet neve",
|
||||
"netname": "Hálózat neve",
|
||||
"network": "Hálózat",
|
||||
"descr": "Leírás",
|
||||
"whois": "Whois",
|
||||
"filtering_rules_learn_more": "<0>Tudjon meg többet</0> a saját hosztlisták létrehozásáról.",
|
||||
"blocked_by_response": "Blokkolva a CNAME vagy a válasz IP-címe alapján",
|
||||
"blocked_by_cname_or_ip": "CNAME vagy IP által blokkolva",
|
||||
"try_again": "Próbálja újra",
|
||||
"domain_desc": "Adja meg a domain nevet vagy a helyettesítő karaktert ahhoz a címhez, amit át kíván íratni.",
|
||||
"example_rewrite_domain": "csak ehhez a domainhez írja át a válaszokat.",
|
||||
"example_rewrite_wildcard": "az <0>example.org</0> összes aldomainjéhez átírja a válaszokat.",
|
||||
"rewrite_ip_address": "IP-cím: használja ezt az IP-t A vagy AAAA válaszban",
|
||||
"rewrite_domain_name": "Domain név: CNAME rekord hozzáadása",
|
||||
"rewrite_A": "<0>A</0>: speciális érték, megtartja az upstream felől érkező <0>A</0> rekordokat",
|
||||
"rewrite_AAAA": "<0>AAAA</0>: speciális érték, megtartja az upstream felől érkező <0>AAAA</0> rekordokat",
|
||||
"disable_ipv6": "IPv6 letiltása",
|
||||
"disable_ipv6_desc": "Ha ez a szolgáltatás engedélyezve van, akkor az összes IPv6-cím (AAAA típus) DNS-lekérdezése elveszik.",
|
||||
"fastest_addr": "Leggyorsabb IP-cím",
|
||||
"fastest_addr_desc": "Kérdezze le az összes DNS szervert és küldje vissza a leggyorsabb IP-címet az összes válasz alapján",
|
||||
"autofix_warning_text": "Ha a \"Javítás\" lehetőségre kattint, az AdGuard Home megpróbálja beállítani a rendszerét, hogy használja az AdGuard Home DNS szervert.",
|
||||
"autofix_warning_list": "A következő feladatokat hajtja végre: <0>A DNSStubListener rendszer kikapcsolása</0><0>Beállítja a DNS-kiszolgáló címét 127.0.0.1-re.</0><0>Lecseréli az /etc/resolv.conf szimbolikus útvonalat erre: /run/systemd/resolve/resolv.conf</0><0>A DNSStubListener leállítása (a rendszer által feloldott szolgáltatás újratöltése)</0>",
|
||||
"autofix_warning_result": "Mindennek eredményeként az Ön rendszeréből származó összes DNS-kérést alapértelmezés szerint az AdGuard Home dolgozza fel.",
|
||||
"tags_title": "Címkék",
|
||||
"tags_desc": "Kiválaszthatja a klienseknek megfelelő címkéket. A címkék beilleszthetők a szűrési szabályokba, és lehetővé teszik azok pontosabb alkalmazását. <0>További információ</0>",
|
||||
"form_select_tags": "Válasszon kliens címkéket",
|
||||
"check_title": "Szűrés ellenőrzése",
|
||||
"check_desc": "Ellenőrzi, hogy a hosztnév szűrve van-e",
|
||||
"check": "Ellenőrzés",
|
||||
"form_enter_host": "Adja meg a hosztnevet",
|
||||
"filtered_custom_rules": "Szűrve van az egyéni szűrési szabályok alapján",
|
||||
"choose_from_list": "Választás a listából",
|
||||
"add_custom_list": "Egyedi lista hozzáadása",
|
||||
"host_whitelisted": "Ez a hoszt a kivételek között szerepel",
|
||||
"check_ip": "IP-címek: {{ip}}",
|
||||
"check_cname": "CNAME: {{cname}}",
|
||||
"check_reason": "Indok: {{reason}}",
|
||||
"check_rule": "Szabály: {{rule}}",
|
||||
"check_service": "Szolgáltatás neve: {{service}}",
|
||||
"check_not_found": "Nem található az Ön szűrőlistái között",
|
||||
"client_confirm_block": "Biztosan blokkolni szeretné a(z) \"{{ip}}\" klienst?",
|
||||
"client_confirm_unblock": "Biztosan fel szeretné oldani a(z) \"{{ip}}\" kliens blokkolását?",
|
||||
"client_blocked": "A(z) \"{{ip}}\" kliens sikeresen blokkolva",
|
||||
"client_unblocked": "A(z) \"{{ip}}\" kliens blokkolása sikeresen feloldva",
|
||||
"static_ip": "Statikus IP-cím",
|
||||
"static_ip_desc": "Az AdGuard Home egy szerver, tehát statikus IP-címre van szüksége a megfelelő működéshez. Ellenkező esetben a router valamikor más IP-címet rendelhet ehhez az eszközhöz.",
|
||||
"set_static_ip": "Statikus IP-cím beállítása",
|
||||
"install_static_ok": "Jó hír! A statikus IP-cím már be van állítva",
|
||||
"install_static_error": "Az AdGuard Home nem tudja automatikusan konfigurálni ezt a hálózati felületet. Kérjük, nézzen utána, hogyan kell ezt manuálisan elvégezni.",
|
||||
"install_static_configure": "Úgy észleltük, hogy dinamikus IP-cím van használatban — <0>{{ip}}</0>. Szeretné ezt statikus IP-címként használni?",
|
||||
"confirm_static_ip": "Az AdGuard Home beállítja az {{ip}} IP-címet az Ön statikus IP-címének. Biztosan folytatni kívánja?",
|
||||
"list_updated": "{{count}} lista frissítve lett",
|
||||
"list_updated_plural": "{{count}} lista frissítve lett",
|
||||
"dnssec_enable": "DNSSEC engedélyezése",
|
||||
"dnssec_enable_desc": "Állítsa be a DNSSEC jelzőt a kimenő DNS-lekérdezésekbe, és ellenőrizze az eredményt (szükséges a DNSSEC-kompatibilis feloldás)",
|
||||
"validated_with_dnssec": "DNSSEC-kel ellenőrizve",
|
||||
"all_queries": "Minden kérés",
|
||||
"show_blocked_responses": "Tiltva",
|
||||
"show_whitelisted_responses": "Kivételezett",
|
||||
"show_processed_responses": "Feldolgozott",
|
||||
"blocked_safebrowsing": "Blokkolva a biztonságos böngészés által",
|
||||
"blocked_adult_websites": "Blokkolva a felnőtt tartalmak által",
|
||||
"blocked_threats": "Blokkolt fenyegetések",
|
||||
"allowed": "Engedélyezve",
|
||||
"filtered": "Megszűrt",
|
||||
"rewritten": "Átírt",
|
||||
"safe_search": "Biztonságos keresés",
|
||||
"blocklist": "Tiltási lista",
|
||||
"milliseconds_abbreviation": "ms",
|
||||
"cache_size": "Gyorsítótár mérete",
|
||||
"cache_size_desc": "DNS gyorsítótár mérete (bájtokban)",
|
||||
"enter_cache_size": "Adja meg a gyorsítótár méretét",
|
||||
"enter_cache_ttl_min_override": "Adja meg a minimális TTL-t",
|
||||
"enter_cache_ttl_max_override": "Adja meg a maximális TTL-t",
|
||||
"cache_ttl_min_override_desc": "Felülbírálja az upstream kiszolgálótól kapott (minimális) TTL értéket. Ez az érték nem lehet nagyobb, mint 3600 másodperc (vagyis 1 óra)",
|
||||
"cache_ttl_max_override_desc": "Felülbírálja az upstream kiszolgálótól kapott (maximális) TTL értéket",
|
||||
"min_exceeds_max_value": "A minimális érték meghaladja a maximális értéket",
|
||||
"value_not_larger_than": "Az érték nem lehet nagyobb, mint {{maximum}}",
|
||||
"filter_category_general": "Általános",
|
||||
"filter_category_security": "Biztonság",
|
||||
"filter_category_regional": "Regionális",
|
||||
"filter_category_other": "Egyéb",
|
||||
"filter_category_general_desc": "Olyan listák, amelyek blokkolják a nyomkövetést és a hirdetéseket a legtöbb eszközön",
|
||||
"filter_category_security_desc": "Olyan listák, amelyek a kártékony, adathalász vagy átverős oldalak tiltására vannak kifejlesztve",
|
||||
"filter_category_regional_desc": "Olyan listák, amelyek a regionális hirdetések, valamint a nyomkövető szerverek ellen vannak kifejlesztve",
|
||||
"filter_category_other_desc": "További tiltólisták",
|
||||
"original_response": "Eredeti válasz",
|
||||
"click_to_view_queries": "Kattintson a lekérésekért",
|
||||
"port_53_faq_link": "Az 53-as portot gyakran a \"DNSStubListener\" vagy a \"systemd-resolved\" (rendszer által feloldott) szolgáltatások használják. Kérjük, olvassa el <0>ezt az útmutatót</0> a probléma megoldásához."
|
||||
}
|
||||
@@ -3,6 +3,8 @@
|
||||
"example_upstream_reserved": "Anda dapat menetapkan DNS upstream <0>untuk domain spesifik</0>",
|
||||
"upstream_parallel": "Gunakan kueri paralel untuk mempercepat resoluasi dengan menanyakan semua server upstream secara bersamaan",
|
||||
"parallel_requests": "Permintaan paralel",
|
||||
"load_balancing": "Penyeimbang beban",
|
||||
"load_balancing_desc": "Permintaan satu server pada satu waktu. AdGuard Home akan menggunakan algoritma acak tertimbang untuk memilih server sehingga server tercepat akan lebih sering digunakan.",
|
||||
"bootstrap_dns": "Server DNS bootstrap",
|
||||
"bootstrap_dns_desc": "Server Bootstrap DNS dapat digunakan untuk meresolve alamat IP pada DoH/DoT resolvers yang Anda tentukan sebagai upstreams.",
|
||||
"check_dhcp_servers": "Cek untuk server DHCP",
|
||||
@@ -51,9 +53,11 @@
|
||||
"dhcp_add_static_lease": "Tambah static lease",
|
||||
"dhcp_reset": "Apakah anda yakin ingin mengatur ulang konfigurasi DHCP anda?",
|
||||
"country": "Negara",
|
||||
"city": "Kota",
|
||||
"delete_confirm": "Apakah anda yakin ingin menghapus \"{{key}}\"?",
|
||||
"form_enter_hostname": "Masukkan hostname",
|
||||
"error_details": "Detail kesalahan",
|
||||
"response_details": "Detail respon",
|
||||
"request_details": "Detai permintaan",
|
||||
"client_details": "Detail klien",
|
||||
"details": "Detail",
|
||||
@@ -63,6 +67,8 @@
|
||||
"filters": "Penyaring",
|
||||
"filter": "Filter",
|
||||
"query_log": "Catatan Kueri",
|
||||
"compact": "Rapat",
|
||||
"nothing_found": "Tidak ditemukan",
|
||||
"faq": "Tanya Jawab",
|
||||
"version": "versi",
|
||||
"address": "Alamat",
|
||||
@@ -114,12 +120,16 @@
|
||||
"general_settings": "Pengaturan umum",
|
||||
"dns_settings": "Pengaturan DNS",
|
||||
"dns_blocklists": "Daftar blokir DNS",
|
||||
"dns_allowlists": "Daftar putih DNS",
|
||||
"dns_blocklists_desc": "AdGuard Home akan memblokir domain yang cocok dengan daftar hitam.",
|
||||
"dns_allowlists_desc": "Domain dari daftar putih DNS akan diizinkan bahkan jika mereka ada juga di daftar hitam.",
|
||||
"custom_filtering_rules": "Aturan penyaringan khusus",
|
||||
"encryption_settings": "Pengaturan enkripsi",
|
||||
"dhcp_settings": "Pengaturan DHCP",
|
||||
"upstream_dns": "Server DNS hulu",
|
||||
"upstream_dns_hint": "Jika Anda mengosongkan kolom ini, AdGuard Home akan menggunakan <a href='https://1.1.1.1/' target='_blank'>Cloudflare DNS</a> sebagai hulu. Gunakan tls:// untuk server DNS over TLS.",
|
||||
"test_upstream_btn": "Uji hulu",
|
||||
"upstreams": "Upstream",
|
||||
"apply_btn": "Terapkan",
|
||||
"disabled_filtering_toast": "Penyaringan nonaktif",
|
||||
"enabled_filtering_toast": "Penyaringan aktif",
|
||||
@@ -131,6 +141,7 @@
|
||||
"enabled_save_search_toast": "Pencarian aman diaktifkan",
|
||||
"enabled_table_header": "Diaktifkan",
|
||||
"name_table_header": "Nama",
|
||||
"list_url_table_header": "Daftar URL",
|
||||
"rules_count_table_header": "Jumlah Aturan",
|
||||
"last_time_updated_table_header": "Terakhir diperbaharui",
|
||||
"actions_table_header": "Aksi",
|
||||
@@ -139,10 +150,23 @@
|
||||
"delete_table_action": "Hapus",
|
||||
"elapsed": "Berlalu",
|
||||
"filters_and_hosts_hint": "AdGuard Home memahami aturan dasar adblock dan sintak file hosts.",
|
||||
"no_blocklist_added": "Tiada daftar hitam ditambahkan",
|
||||
"no_whitelist_added": "Tiada daftar putih ditambahkan",
|
||||
"add_blocklist": "Tambah daftar hitam",
|
||||
"add_allowlist": "Tambah daftar putih",
|
||||
"cancel_btn": "Batal",
|
||||
"enter_name_hint": "Masukkan nama",
|
||||
"enter_url_or_path_hint": "Masukan sebuah URL atau jalur absolut dari daftar",
|
||||
"check_updates_btn": "Cek pembaruan",
|
||||
"new_blocklist": "Daftar hitam baru",
|
||||
"new_allowlist": "Daftar putih baru",
|
||||
"edit_blocklist": "Edit daftar hitam",
|
||||
"edit_allowlist": "Edit daftar putih",
|
||||
"choose_blocklist": "Pilih daftar hitam",
|
||||
"choose_allowlist": "Pilih daftar putih",
|
||||
"enter_valid_blocklist": "Masukkan valid URL ke daftar hitam.",
|
||||
"enter_valid_allowlist": "Masukkan valid URL ke daftar putih.",
|
||||
"form_error_url_format": "Format URL tidak valid",
|
||||
"form_error_url_or_path_format": "URL atau jalur absolut dari daftar tidak valid",
|
||||
"custom_filter_rules": "Aturan penyaringan khusus",
|
||||
"custom_filter_rules_hint": "Masukkan satu aturan dalam sebuah baris. Anda dapat menggunakan baik aturan adblock maupun sintaks file hosts.",
|
||||
@@ -159,6 +183,7 @@
|
||||
"example_upstream_doh": "terenkripsi <0>DNS-over-HTTPS</0>",
|
||||
"example_upstream_sdns": "anda bisa menggunakan <0>Stempel DNS</0> untuk <1>DNSCrypt</1> atau pengarah <2>DNS-over-HTTPS</2>",
|
||||
"example_upstream_tcp": "DNS reguler (melalui TCP)",
|
||||
"all_lists_up_to_date_toast": "Semua daftar sudah diperbarui",
|
||||
"updated_upstream_dns_toast": "Server DNS hulu terbarui",
|
||||
"dns_test_ok_toast": "Server DNS yang ditentukan bekerja dengan benar",
|
||||
"dns_test_not_ok_toast": "Server \"{{key}}\": tidak dapat digunakan, mohon cek bahwa Anda telah menulisnya dengan benar",
|
||||
@@ -170,6 +195,7 @@
|
||||
"domain_or_client": "Domain atau klien",
|
||||
"type_table_header": "Tipe",
|
||||
"response_table_header": "Respon",
|
||||
"response_code": "Kode respon",
|
||||
"client_table_header": "Klien",
|
||||
"empty_response_status": "Kosong",
|
||||
"show_all_filter_type": "Tampilkan semua",
|
||||
@@ -182,12 +208,13 @@
|
||||
"page_table_footer_text": "Halaman",
|
||||
"rows_table_footer_text": "baris",
|
||||
"updated_custom_filtering_toast": "Perbarui aturan penyaringan khusus",
|
||||
"rule_removed_from_custom_filtering_toast": "Aturan dihapus dari aturan penyaringan khusus",
|
||||
"rule_added_to_custom_filtering_toast": "Aturan ditambah ke aturan penyaringan khusus",
|
||||
"rule_removed_from_custom_filtering_toast": "Aturan dihapus dari aturan penyaringan khusus: {{rule}}",
|
||||
"rule_added_to_custom_filtering_toast": "Aturan ditambah ke aturan penyaringan khusus: {{rule}}",
|
||||
"query_log_response_status": "Status: {{value}}",
|
||||
"query_log_filtered": "Difilter oleh {{filter}}",
|
||||
"query_log_confirm_clear": "Apakah Anda yakin ingin menghapus seluruh kueri log?",
|
||||
"query_log_cleared": "Kueri log telah berhasil dihapus",
|
||||
"query_log_updated": "Log permintaan telah berhasil diperbarui",
|
||||
"query_log_clear": "Hapus kueri log",
|
||||
"query_log_retention": "Retensi kueri log",
|
||||
"query_log_enable": "Aktifkan log",
|
||||
@@ -195,19 +222,39 @@
|
||||
"query_log_disabled": "Kueri log dinonaktifkan dan dapat dikonfigurasi di <0>pengaturan</0>",
|
||||
"query_log_strict_search": "Gunakan tanda kutip ganda untuk pencarian ketat",
|
||||
"query_log_retention_confirm": "Apakah Anda yakin ingin mengubah retensi kueri log? Jika Anda menurunkan nilai interval, beberapa data akan hilang",
|
||||
"anonymize_client_ip": "Anonim IP klien",
|
||||
"anonymize_client_ip_desc": "Jangan simpan alamat lengkap IP klien dalam log dan statistik",
|
||||
"dns_config": "Konfigurasi server DNS",
|
||||
"dns_cache_config": "Konfigurasi cache DNS",
|
||||
"dns_cache_config_desc": "Disini Anda bisa mengonfigurasi cache DNS",
|
||||
"blocking_mode": "Mode blokir",
|
||||
"default": "Standar",
|
||||
"nxdomain": "NXDOMAIN",
|
||||
"null_ip": "Null IP",
|
||||
"custom_ip": "Custom IP",
|
||||
"blocking_ipv4": "Blokiran IPv4",
|
||||
"blocking_ipv6": "Blokiran IPv6",
|
||||
"dns_over_https": "DNS-over-HTTPS",
|
||||
"dns_over_tls": "DNS-over-TLS",
|
||||
"plain_dns": "Plain DNS",
|
||||
"form_enter_rate_limit": "Masukkan batas nilai",
|
||||
"rate_limit": "Batas nilai",
|
||||
"edns_enable": "Aktifkan EDNS Klien Subnet",
|
||||
"edns_cs_desc": "Apabila dinyalakan, AdGuard Home akan mengirim subnet klien ke server-server DNS.",
|
||||
"rate_limit_desc": "Jumlah permintaan per detik yang diperbolehkan untuk satu klien (0: tidak terbatas)",
|
||||
"blocking_ipv4_desc": "Alamat IP akan dikembalikan untuk permintaan A yang diblokir",
|
||||
"blocking_ipv6_desc": "Alamat IP akan dipulihkan untuk permintaan AAAA yang diblokir",
|
||||
"blocking_mode_default": "Standar: Respon pakai NXDOMAIN saat diblokir oleh aturan gaya Adblock; membalas dengan alamat IP yang ditentukan dalam aturan ketika diblokir oleh /et /aturan hosts-style",
|
||||
"blocking_mode_nxdomain": "NXDOMAIN: Respon pakai kode NXDOMAIN",
|
||||
"blocking_mode_null_ip": "Null IP: Respon pakai alamat IP kosong (0.0.0.0 untuk A; :: untuk AAAA)",
|
||||
"blocking_mode_custom_ip": "IP kustom: respon dengan alamat IP yang diset secara manual",
|
||||
"upstream_dns_client_desc": "Jika Anda biarkan bidang ini kosong, AdGuard Home akan memakai server yang dikonfigurasi di<0>Pengaturan DNS</0>.",
|
||||
"tracker_source": "Sumber pelacak",
|
||||
"source_label": "Sumber",
|
||||
"found_in_known_domain_db": "Ditemukan di database domain dikenal",
|
||||
"category_label": "Kategori",
|
||||
"rule_label": "Aturan",
|
||||
"list_label": "Daftar",
|
||||
"unknown_filter": "Penyaringan {{filterId}} tidak dikenal",
|
||||
"known_tracker": "Pelacak yang dikenal",
|
||||
"install_welcome_title": "Selamat datang di AdGuard Home!",
|
||||
@@ -324,6 +371,8 @@
|
||||
"client_identifier_desc": "Klien dapat diidentifikasi dengan alamat IP atau alamat MAC. Harap dicatat bahwa menggunakan MAC sebagai pengidentifikasi hanya dimungkinkan jika AdGuard Home juga merupakan <0>server DHCP</0>",
|
||||
"form_enter_ip": "Masukkan IP",
|
||||
"form_enter_mac": "Masukkan MAC",
|
||||
"form_enter_id": "Masukkan pengenal",
|
||||
"form_add_id": "Tambah pengenal",
|
||||
"form_client_name": "Masukkan nama klien",
|
||||
"name": "Nama",
|
||||
"client_global_settings": "Gunakan pengaturan global",
|
||||
@@ -332,6 +381,7 @@
|
||||
"client_updated": "Klien \"{{key}}\" berhasil diperbarui",
|
||||
"clients_not_found": "Tidak ada klien ditemukan",
|
||||
"client_confirm_delete": "Apakah anda yakin ingin menghapus klien \"{{key}}\"?",
|
||||
"list_confirm_delete": "Anda yakin ingin menghapus daftar ini?",
|
||||
"auto_clients_title": "Klien (waktu berjalan)",
|
||||
"auto_clients_desc": "Data pada klien yang menggunakan AdGuard Home, tetapi tidak disimpan dalam konfigurasi",
|
||||
"access_title": "Pengaturan akses",
|
||||
@@ -399,6 +449,8 @@
|
||||
"domain": "Domain",
|
||||
"answer": "Jawab",
|
||||
"filter_added_successfully": "Filter telah berhasil ditambahkan",
|
||||
"filter_removed_successfully": "Daftar ini telah sukses dihapus",
|
||||
"filter_updated": "Daftar telah sukses diperbarui",
|
||||
"statistics_configuration": "Konfigurasi statistik",
|
||||
"statistics_retention": "Statistik disimpan",
|
||||
"statistics_retention_desc": "Jika Anda menurunkan nilai interval, beberapa data akan hilang",
|
||||
@@ -428,13 +480,33 @@
|
||||
"whois": "Whois",
|
||||
"filtering_rules_learn_more": "<0>Pelajari lebih lanjut</0> tentang membuat daftar hitam host Anda sendiri.",
|
||||
"blocked_by_response": "Diblokir oleh CNAME atau IP sebagai respon",
|
||||
"blocked_by_cname_or_ip": "Diblokir oleh CNAME atau IP",
|
||||
"try_again": "Coba lagi",
|
||||
"domain_desc": "Masukkan nama domain atau wildcard yang ingin Anda tulis ulang.",
|
||||
"example_rewrite_domain": "tulis ulang respon hanya untuk domain ini saja.",
|
||||
"example_rewrite_wildcard": "tulis ulang respon untuk semua subdomain <0>contoh.org</0>.",
|
||||
"rewrite_ip_address": "Alamat IP: pakai IP ini dalam respons A atau AAAA",
|
||||
"rewrite_domain_name": "Nama domain: tambah ke rekaman CNAME",
|
||||
"rewrite_A": "<0>A</0>: nilai khusus, biarkan <0>A</0> merekam dari upstream",
|
||||
"rewrite_AAAA": "<0>AAAA</0>: nilai khusus, biarkan <0>AAAA</0> merekam dari upstream",
|
||||
"disable_ipv6": "Matikan IPv6",
|
||||
"disable_ipv6_desc": "Apabila fitur ini dinyalakan, semua permintaan DNS untuk alamat-alamat IPv6 (tipe AAAA) akan diputus.",
|
||||
"fastest_addr": "Alamat IP tercepat",
|
||||
"fastest_addr_desc": "Permintaan semua server DNS dan kembalinya alamat IP tercepat di antara semua respons",
|
||||
"autofix_warning_text": "Apabila anda menekan \"Perbaiki\", AdGuardHome akan mengatur sistem anda untuk menggunakan server DNS AdGuardHome.",
|
||||
"autofix_warning_list": "Ini akan melakukan tugas berikut: <0>Nonaktifkan sistem DNSStubListener</0> <0> Atur alamat server DNS ke 127.0.0.1</0> <0>Ganti target tautan simbolis /etc/resolv.conf pakai /run/systemd/resolve/resolv.conf</0> <0>Hentikan DNSStubListener (muat ulang layanan sistemd-resolve service)</0>",
|
||||
"autofix_warning_result": "Hasilnya, semua permintaan DNS dari sistem anda akan diproses oleh AdGuardHome secara standar.",
|
||||
"tags_title": "Tag",
|
||||
"tags_desc": "Anda dapat memilih tag sesuai dengan klien. Tag dapat dimasukkan dalam aturan pemfilteran dan memungkinkan Anda untuk menerapkannya lebih akurat. <0>Pelajari lebih</0>",
|
||||
"form_select_tags": "Pilih tag klien",
|
||||
"check_title": "Periksa penyaringan",
|
||||
"check_desc": "Periksa apakah nama host telah tersaring",
|
||||
"check": "Periksa",
|
||||
"form_enter_host": "Masukkan nama host",
|
||||
"filtered_custom_rules": "Tersaring oleh aturan penyaring Buatan",
|
||||
"choose_from_list": "Pilih dari daftar",
|
||||
"add_custom_list": "Tambah daftar buatan",
|
||||
"host_whitelisted": "Host didaftar putihkan",
|
||||
"check_ip": "Alamat IP: {{ip}}",
|
||||
"check_cname": "CNAME: {{cname}}",
|
||||
"check_reason": "Alasan: {{reason}}",
|
||||
@@ -446,11 +518,50 @@
|
||||
"client_blocked": "Klien \"{{ip}}\" sukses di blokir",
|
||||
"client_unblocked": "Klien \"{{ip}}\" sukses di unblock",
|
||||
"static_ip": "Alamat IP statis",
|
||||
"static_ip_desc": "AdGuard Home adalah server jadi perlu alamat IP statis agar berfungsi dengan benar. Jika tidak, pada titik tertentu, router Anda dapat menetapkan alamat IP yang berbeda untuk perangkat ini.",
|
||||
"set_static_ip": "Atur alamat IP statik",
|
||||
"install_static_ok": "Kabar baik! Alamat IP statis sudah dikonfigurasi",
|
||||
"install_static_error": "AdGuard Home tidak dapat mengonfigurasinya secara otomatis untuk antarmuka jaringan ini. Silakan cari instruksi tentang cara melakukan ini secara manual.",
|
||||
"install_static_configure": "Kami mendeteksi alamat IP dinamis digunakan - <0>{{ip}}</0>. Anda ingin menggunakannya sebagai alamat statis Anda?",
|
||||
"confirm_static_ip": "AdGuard Home akan mengonfigurasi {{ip}} menjadi alamat IP statis Anda. Anda ingin melanjutkan?",
|
||||
"list_updated": "{{count}} daftar terbarui",
|
||||
"list_updated_plural": "{{count}} daftar terbarui",
|
||||
"dnssec_enable": "Aktifkan DNSSEC",
|
||||
"dnssec_enable_desc": "Atur bendera DNSSEC di permintaan keluar DNS dan periksa hasilnya (resolver berkemampuan DNSSEC diperlukan)",
|
||||
"validated_with_dnssec": "Tervalidasi dengan DNSSEC",
|
||||
"all_queries": "Semua permintaan",
|
||||
"show_blocked_responses": "Diblokir",
|
||||
"show_whitelisted_responses": "Dalam Daftar Putih",
|
||||
"show_processed_responses": "Terproses",
|
||||
"blocked_safebrowsing": "Terblokir oleh Safebrowsing",
|
||||
"blocked_adult_websites": "Situs Dewasa Terblokir",
|
||||
"blocked_threats": "Blokir Ancaman",
|
||||
"allowed": "Dibolehkan",
|
||||
"filtered": "Tersaring",
|
||||
"rewritten": "Tulis ulang",
|
||||
"safe_search": "Pencarian aman",
|
||||
"blocklist": "Daftar blokir",
|
||||
"milliseconds_abbreviation": "ms"
|
||||
}
|
||||
"milliseconds_abbreviation": "ms",
|
||||
"cache_size": "Ukuran cache",
|
||||
"cache_size_desc": "Ukuran DNS cache (dalam bit)",
|
||||
"cache_ttl_min_override": "Tumpuk TTL minimum",
|
||||
"cache_ttl_max_override": "Tumpuk TTL maksimum",
|
||||
"enter_cache_size": "Masukkan ukuran cache",
|
||||
"enter_cache_ttl_min_override": "Masukkan TTL minimum",
|
||||
"enter_cache_ttl_max_override": "Masukkan TTL maksimum",
|
||||
"cache_ttl_min_override_desc": "Ganti nilai TTL (minimum) yang diterima dari server upstream. Nilai ini tidak boleh lebih dari 3600 (1 jam)",
|
||||
"cache_ttl_max_override_desc": "Ganti nilai TTL (maksimum) yang diterima dari server upstream",
|
||||
"min_exceeds_max_value": "Nilai minimum melebihi nilai maksimum",
|
||||
"value_not_larger_than": "Nilai tidak bisa lebih dari {{maximum}}",
|
||||
"filter_category_general": "Umum",
|
||||
"filter_category_security": "Keamanan",
|
||||
"filter_category_regional": "Wilayah",
|
||||
"filter_category_other": "Lainnya",
|
||||
"filter_category_general_desc": "Daftar yang memblokir pelacakan dan iklan di sebagian besar perangkat",
|
||||
"filter_category_security_desc": "Daftar yang khusus pada pemblokiran malware, phishing, atau domain penipuan",
|
||||
"filter_category_regional_desc": "Daftar yang berfokus pada iklan regional dan server pelacakan",
|
||||
"filter_category_other_desc": "Daftar hitam lain",
|
||||
"original_response": "Respon asli",
|
||||
"click_to_view_queries": "Klik untuk lihat permintaan",
|
||||
"port_53_faq_link": "Port 53 sering ditempati oleh layanan \"DNSStubListener\" atau \"systemd-resolved\". Silakan baca <0>instruksi ini</0> tentang cara menyelesaikan ini."
|
||||
}
|
||||
|
||||
@@ -208,8 +208,8 @@
|
||||
"page_table_footer_text": "Pagina",
|
||||
"rows_table_footer_text": "righe",
|
||||
"updated_custom_filtering_toast": "Le regole dei filtri personalizzate sono state aggiornate",
|
||||
"rule_removed_from_custom_filtering_toast": "Regola rimossa dalle regole dei filtri personalizzate",
|
||||
"rule_added_to_custom_filtering_toast": "Regola aggiunta alle regole dei filtri personalizzate",
|
||||
"rule_removed_from_custom_filtering_toast": "Regola rimossa dalle regole dei filtri personalizzate: {{rule}}",
|
||||
"rule_added_to_custom_filtering_toast": "Regola aggiunta alle regole dei filtri personalizzate: {{rule}}",
|
||||
"query_log_response_status": "Status: {{value}}",
|
||||
"query_log_filtered": "Filtrato da {{filter}}",
|
||||
"query_log_confirm_clear": "Sei sicuro di voler eliminare la query log?",
|
||||
@@ -560,4 +560,4 @@
|
||||
"filter_category_regional_desc": "Liste focalizzare su pubblicità regionali e server traccianti",
|
||||
"filter_category_other_desc": "Altre liste di blocco",
|
||||
"click_to_view_queries": "Clicca per visualizzare query"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,8 +208,8 @@
|
||||
"page_table_footer_text": "ページ",
|
||||
"rows_table_footer_text": "行",
|
||||
"updated_custom_filtering_toast": "カスタム・フィルタリングルールを更新しました",
|
||||
"rule_removed_from_custom_filtering_toast": "ルールをカスタム・フィルタリングルールから除去しました",
|
||||
"rule_added_to_custom_filtering_toast": "ルールをカスタム・フィルタリングルールに追加しました",
|
||||
"rule_removed_from_custom_filtering_toast": "ルールをカスタム・フィルタリングルールから除去しました {{rule}}",
|
||||
"rule_added_to_custom_filtering_toast": "ルールをカスタム・フィルタリングルールに追加しました {{rule}}",
|
||||
"query_log_response_status": "ステータス: {{value}}",
|
||||
"query_log_filtered": "{{filter}}によるフィルタ",
|
||||
"query_log_confirm_clear": "クエリ・ログ全体を消去してもよろしいですか?",
|
||||
@@ -562,5 +562,6 @@
|
||||
"filter_category_regional_desc": "それぞれの地域の広告と追跡サーバをターゲットするリストです。",
|
||||
"filter_category_other_desc": "その他のブロックリストです。",
|
||||
"original_response": "当初の応答",
|
||||
"click_to_view_queries": "クエリを表示するにはクリックしてください"
|
||||
}
|
||||
"click_to_view_queries": "クエリを表示するにはクリックしてください",
|
||||
"port_53_faq_link": "多くの場合、ポート53は \"DNSStubListener\" または \"systemd-resolved\" サービスによって利用されています。これを解決する方法については、<0>この手順</0>をお読みください。"
|
||||
}
|
||||
|
||||
@@ -208,8 +208,8 @@
|
||||
"page_table_footer_text": "페이지",
|
||||
"rows_table_footer_text": "행",
|
||||
"updated_custom_filtering_toast": "사용자 정의 필터링 규칙 업데이트",
|
||||
"rule_removed_from_custom_filtering_toast": "사용자 정의 필터링 규칙에서 규칙 제거",
|
||||
"rule_added_to_custom_filtering_toast": "사용자 정의 필터링 규칙에 추가된 규칙",
|
||||
"rule_removed_from_custom_filtering_toast": "사용자 정의 필터링 규칙에서 규칙 제거 {{rule}}",
|
||||
"rule_added_to_custom_filtering_toast": "사용자 정의 필터링 규칙에 추가된 규칙 {{rule}}",
|
||||
"query_log_response_status": "상태: {{value}}",
|
||||
"query_log_filtered": "필터: {{filter}}",
|
||||
"query_log_confirm_clear": "정말로 모든 쿼리 로그를 비우시겠습니까?",
|
||||
@@ -563,4 +563,4 @@
|
||||
"filter_category_other_desc": "기타 차단 목록",
|
||||
"original_response": "원래 응답",
|
||||
"click_to_view_queries": "쿼리를 보려면 클릭합니다"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,8 +208,8 @@
|
||||
"page_table_footer_text": "Pagina",
|
||||
"rows_table_footer_text": "rijen",
|
||||
"updated_custom_filtering_toast": "Aangepaste filter regels zijn bijgewerkt",
|
||||
"rule_removed_from_custom_filtering_toast": "Regel verwijderd uit de aangepaste filterregels",
|
||||
"rule_added_to_custom_filtering_toast": "Regel toegevoegd aan de aangepaste filterregels",
|
||||
"rule_removed_from_custom_filtering_toast": "Regel verwijderd uit de aangepaste filterregels: {{rule}}",
|
||||
"rule_added_to_custom_filtering_toast": "Regel toegevoegd aan de aangepaste filterregels: {{rule}}",
|
||||
"query_log_response_status": "Status: {{value}}",
|
||||
"query_log_filtered": "Gefilterd door {{filter}}",
|
||||
"query_log_confirm_clear": "Weet u zeker dat u het hele query logboek wilt legen?",
|
||||
@@ -562,5 +562,6 @@
|
||||
"filter_category_regional_desc": "Lijsten die focussen op regionale ads en tracking servers",
|
||||
"filter_category_other_desc": "Overige blokkeerlijsten",
|
||||
"original_response": "Oorspronkelijke reactie",
|
||||
"click_to_view_queries": "Klik om queries te bekijken"
|
||||
}
|
||||
"click_to_view_queries": "Klik om queries te bekijken",
|
||||
"port_53_faq_link": "Poort 53 wordt vaak gebruikt door services als DNSStubListener- of de systeem DNS-resolver. Lees a.u.b. <0>deze instructie</0> hoe dit is op te lossen."
|
||||
}
|
||||
|
||||
@@ -207,8 +207,8 @@
|
||||
"page_table_footer_text": "Side",
|
||||
"rows_table_footer_text": "rekker",
|
||||
"updated_custom_filtering_toast": "Oppdaterte de selvvalgte filtreringsreglene",
|
||||
"rule_removed_from_custom_filtering_toast": "Oppføringen ble fjernet fra de selvvalgte filtreringsreglene",
|
||||
"rule_added_to_custom_filtering_toast": "Oppføringen ble lagt til i de selvvalgte filtreringsreglene",
|
||||
"rule_removed_from_custom_filtering_toast": "Oppføringen ble fjernet fra de selvvalgte filtreringsreglene: {{rule}}",
|
||||
"rule_added_to_custom_filtering_toast": "Oppføringen ble lagt til i de selvvalgte filtreringsreglene: {{rule}}",
|
||||
"query_log_response_status": "Status: {{value}}",
|
||||
"query_log_filtered": "Filtrert av {{filter}}",
|
||||
"query_log_confirm_clear": "Er du sikker på at du vil slette hele forespørselsloggen?",
|
||||
|
||||
@@ -108,7 +108,7 @@
|
||||
"number_of_dns_query_to_safe_search": "Liczba żądań DNS do wyszukiwarek, dla których zastosowano wymuszenie bezpiecznego wyszukiwania",
|
||||
"average_processing_time": "Średni czas przetwarzania",
|
||||
"average_processing_time_hint": "Średni czas przetwarzania żądania DNS liczony w milisekundach",
|
||||
"block_domain_use_filters_and_hosts": "Blokuj domeny za pomocą filtrów i plików host",
|
||||
"block_domain_use_filters_and_hosts": "Zablokuj domeny za pomocą filtrów i plików host",
|
||||
"filters_block_toggle_hint": "Możesz skonfigurować reguły blokowania w ustawieniach <a href='#filters'>Filtry</a> ",
|
||||
"use_adguard_browsing_sec": "Użyj usługi sieciowej Bezpieczne Przeglądanie AdGuard",
|
||||
"use_adguard_browsing_sec_hint": "AdGuard Home sprawdzi, czy domena jest na czarnej liście przez serwis internetowy Bezpieczne Przeglądanie. Będzie korzystać z interfejsu API przyjaznego dla prywatności w celu przeprowadzenia kontroli: na serwer wysyłany jest tylko krótki prefiks nazwy domeny SHA256.",
|
||||
@@ -177,7 +177,7 @@
|
||||
"example_comment": "! Tutaj jest komentarz",
|
||||
"example_comment_meaning": "komentarz",
|
||||
"example_comment_hash": "# Również komentarz",
|
||||
"example_regex_meaning": "blokuj dostęp do domen pasujących do określonego wyrażenia regularnego",
|
||||
"example_regex_meaning": "zablokuj dostęp do domen pasujących do określonego wyrażenia regularnego",
|
||||
"example_upstream_regular": "normalny DNS (przez UDP)",
|
||||
"example_upstream_dot": "zaszyfrowany <0>DNS-over-TLS</0>",
|
||||
"example_upstream_doh": "zaszyfrowany <0>DNS-over-HTTPS</0>",
|
||||
@@ -208,8 +208,8 @@
|
||||
"page_table_footer_text": "Strona",
|
||||
"rows_table_footer_text": "wierszy",
|
||||
"updated_custom_filtering_toast": "Zaktualizowano niestandardowe reguły filtrowania",
|
||||
"rule_removed_from_custom_filtering_toast": "Reguła usunięta z niestandardowych reguł filtrowania",
|
||||
"rule_added_to_custom_filtering_toast": "Reguła dodana do niestandardowych reguł filtrowania",
|
||||
"rule_removed_from_custom_filtering_toast": "Reguła usunięta z niestandardowych reguł filtrowania: {{rule}}",
|
||||
"rule_added_to_custom_filtering_toast": "Reguła dodana do niestandardowych reguł filtrowania: {{rule}}",
|
||||
"query_log_response_status": "Status: {{value}}",
|
||||
"query_log_filtered": "Filtrowane przez {{filter}}",
|
||||
"query_log_confirm_clear": "Czy na pewno chcesz wyczyścić cały dziennik zapytań?",
|
||||
@@ -427,7 +427,7 @@
|
||||
"form_error_answer_format": "Nieprawidłowy format odpowiedzi",
|
||||
"configure": "Skonfiguruj",
|
||||
"main_settings": "Ustawienia główne",
|
||||
"block_services": "Blokuj określone usługi",
|
||||
"block_services": "Zablokuj określone usługi",
|
||||
"blocked_services": "Zablokowane usługi",
|
||||
"blocked_services_desc": "Pozwala szybko zablokować popularne witryny i usługi.",
|
||||
"blocked_services_saved": "Zablokowane usługi zostały pomyślnie zapisane",
|
||||
@@ -444,7 +444,7 @@
|
||||
"stats_params": "Konfiguracja statystyk",
|
||||
"config_successfully_saved": "Konfiguracja została pomyślnie zapisana",
|
||||
"interval_24_hour": "24 godziny",
|
||||
"interval_days": "{{count}} dzień",
|
||||
"interval_days": "{{count}} dni",
|
||||
"interval_days_plural": "{{count}} dni",
|
||||
"domain": "Domena",
|
||||
"answer": "Odpowiedź",
|
||||
@@ -562,5 +562,6 @@
|
||||
"filter_category_regional_desc": "Listy, które koncentrują się na reklamach regionalnych i serwerach ze skryptami śledzącymi",
|
||||
"filter_category_other_desc": "Inne listy zablokowanych",
|
||||
"original_response": "Oryginalna odpowiedź",
|
||||
"click_to_view_queries": "Kliknij, aby wyświetlić zapytania"
|
||||
}
|
||||
"click_to_view_queries": "Kliknij, aby wyświetlić zapytania",
|
||||
"port_53_faq_link": "Port 53 jest często zajęty przez usługi \"DNSStubListener\" lub \"systemd-resolved\". Przeczytaj <0>tę instrukcję</0> jak to rozwiązać."
|
||||
}
|
||||
|
||||
@@ -208,8 +208,8 @@
|
||||
"page_table_footer_text": "Página",
|
||||
"rows_table_footer_text": "linhas",
|
||||
"updated_custom_filtering_toast": "Regras de filtragem personalizadas atualizadas",
|
||||
"rule_removed_from_custom_filtering_toast": "Regra removida das regras de filtragem personalizadas",
|
||||
"rule_added_to_custom_filtering_toast": "Regra adicionada às regras de filtragem personalizadas",
|
||||
"rule_removed_from_custom_filtering_toast": "Regra removida das regras de filtragem personalizadas: {{rule}}",
|
||||
"rule_added_to_custom_filtering_toast": "Regra adicionada às regras de filtragem personalizadas: {{rule}}",
|
||||
"query_log_response_status": "Status: {{value}}",
|
||||
"query_log_filtered": "Filtrado por {{filter}}",
|
||||
"query_log_confirm_clear": "Você tem certeza que deseja limpar o registro de consulta?",
|
||||
@@ -560,4 +560,4 @@
|
||||
"filter_category_regional_desc": "Listas focadas em anúncios regionais e servidores de rastreamento",
|
||||
"filter_category_other_desc": "Outras listas negras",
|
||||
"click_to_view_queries": "Clique para ver as consultas"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,8 +168,8 @@
|
||||
"page_table_footer_text": "Página",
|
||||
"rows_table_footer_text": "linhas",
|
||||
"updated_custom_filtering_toast": "Regras de filtragem personalizadas actualizadas",
|
||||
"rule_removed_from_custom_filtering_toast": "Regra removida das regras de filtragem personalizadas",
|
||||
"rule_added_to_custom_filtering_toast": "Regra adicionada às regras de filtragem personalizadas",
|
||||
"rule_removed_from_custom_filtering_toast": "Regra removida das regras de filtragem personalizadas: {{rule}}",
|
||||
"rule_added_to_custom_filtering_toast": "Regra adicionada às regras de filtragem personalizadas: {{rule}}",
|
||||
"query_log_response_status": "Status: {{value}}",
|
||||
"query_log_filtered": "Filtrado por {{filter}}",
|
||||
"query_log_confirm_clear": "Tem a certeza de que deseja limpar todo o registo de consulta?",
|
||||
|
||||
@@ -208,8 +208,8 @@
|
||||
"page_table_footer_text": "Pagina",
|
||||
"rows_table_footer_text": "linii",
|
||||
"updated_custom_filtering_toast": "Reguli personalizate de filtrare aduse la zi",
|
||||
"rule_removed_from_custom_filtering_toast": "Regulă scoasă din regullei personalizate de filtrare",
|
||||
"rule_added_to_custom_filtering_toast": "Regulă adăugată la regulile de filtrare personalizate",
|
||||
"rule_removed_from_custom_filtering_toast": "Regulă scoasă din regullei personalizate de filtrare: {{rule}}",
|
||||
"rule_added_to_custom_filtering_toast": "Regulă adăugată la regulile de filtrare personalizate: {{rule}}",
|
||||
"query_log_response_status": "Statut: {{value}}",
|
||||
"query_log_filtered": "Filtrat de {{filter}}",
|
||||
"query_log_confirm_clear": "Sunteți sigur că doriți să ștergeți întregul jurnal de interogări?",
|
||||
@@ -562,5 +562,6 @@
|
||||
"filter_category_regional_desc": "Liste focalizate pe reclame regionale și servere de urmărire",
|
||||
"filter_category_other_desc": "Alte liste de blocări",
|
||||
"original_response": "Răspuns original",
|
||||
"click_to_view_queries": "Clicați pentru a vizualiza interogări"
|
||||
}
|
||||
"click_to_view_queries": "Clicați pentru a vizualiza interogări",
|
||||
"port_53_faq_link": "Portul 53 este adesea ocupat de serviciile \"DNSStubListener\" sau \"systemd-resolved\". Vă rugăm să citiți <0>această instrucțiune</0> despre cum să rezolvați aceasta."
|
||||
}
|
||||
|
||||
@@ -208,8 +208,8 @@
|
||||
"page_table_footer_text": "Страница",
|
||||
"rows_table_footer_text": "строк",
|
||||
"updated_custom_filtering_toast": "Внесены изменения в пользовательские правила",
|
||||
"rule_removed_from_custom_filtering_toast": "Правило удалено из авторского списка правил фильтрации",
|
||||
"rule_added_to_custom_filtering_toast": "Пользовательское правило добавлено",
|
||||
"rule_removed_from_custom_filtering_toast": "Пользовательское правило удалено: {{rule}}",
|
||||
"rule_added_to_custom_filtering_toast": "Пользовательское правило добавлено: {{rule}}",
|
||||
"query_log_response_status": "Статус: {{value}}",
|
||||
"query_log_filtered": "Отфильтровано с помощью {{filter}}",
|
||||
"query_log_confirm_clear": "Вы уверены, что хотите очистить весь журнал запросов?",
|
||||
@@ -530,7 +530,7 @@
|
||||
"dnssec_enable_desc": "Установите флаг DNSSEC в исходящих DNS-запросах и проверьте результат (требуется резолвер с поддержкой DNSSEC)",
|
||||
"validated_with_dnssec": "Подтверждено с помощью DNSSEC",
|
||||
"all_queries": "Все запросы",
|
||||
"show_blocked_responses": "Blocked",
|
||||
"show_blocked_responses": "Заблокировано",
|
||||
"show_whitelisted_responses": "В белом списке",
|
||||
"show_processed_responses": "Обработан",
|
||||
"blocked_safebrowsing": "Заблокировано согласно базе данных Safebrowsing",
|
||||
@@ -562,5 +562,6 @@
|
||||
"filter_category_regional_desc": "Списки, которые фокусируются на региональной рекламе и серверах отслеживания",
|
||||
"filter_category_other_desc": "Другие списки блокировки",
|
||||
"original_response": "Первоначальный ответ",
|
||||
"click_to_view_queries": "Нажмите, чтобы просмотреть запросы"
|
||||
}
|
||||
"click_to_view_queries": "Нажмите, чтобы просмотреть запросы",
|
||||
"port_53_faq_link": "Порт 53 часто занят службами \"DNSStubListener\" или \"systemd-resolved\". Ознакомьтесь с <0>инструкцией</0> о том, как это разрешить."
|
||||
}
|
||||
|
||||
443
client/src/__locales/si-lk.json
Normal file
443
client/src/__locales/si-lk.json
Normal file
@@ -0,0 +1,443 @@
|
||||
{
|
||||
"client_settings": "අනුග්රාහක සැකසුම්",
|
||||
"check_dhcp_servers": "DHCP සේවාදායකයන් සඳහා පරීක්ෂා කරන්න",
|
||||
"save_config": "වින්යාසය සුරකින්න",
|
||||
"enabled_dhcp": "DHCP සේවාදායකය සබල කර ඇත",
|
||||
"disabled_dhcp": "DHCP සේවාදායකය අබල කර ඇත",
|
||||
"dhcp_title": "ග.ධා.වි.කෙ. සේවාදායකය (පර්යේෂණාත්මක!)",
|
||||
"dhcp_description": "ඔබගේ මාර්ගකාරකය ග.ධා.වි.කෙ. (DHCP) සැකසුම් ලබා නොදෙන්නේ නම්, ඔබට AdGuardHome හි ඇති ග.ධා.වි.කෙ. සේවාදායකය භාවිතා කළ හැකිය.",
|
||||
"dhcp_enable": "DHCP සේවාදායකය සබල කරන්න",
|
||||
"dhcp_disable": "DHCP සේවාදායකය අබල කරන්න",
|
||||
"dhcp_config_saved": "DHCP වින්යාසය සාර්ථකව සුරකින ලදි",
|
||||
"form_error_required": "අවශ්ය ක්ෂේත්රයකි",
|
||||
"form_error_ip4_format": "වලංගු නොවන IPv4 ආකෘතියකි",
|
||||
"form_error_ip6_format": "වලංගු නොවන IPv6 ආකෘතියකි",
|
||||
"form_error_ip_format": "වලංගු නොවන අ.ජා. කෙ. (IP) ආකෘතියකි",
|
||||
"form_error_mac_format": "වලංගු නොවන MAC ආකෘතියකි",
|
||||
"form_error_client_id_format": "වලංගු නොවන අනුග්රාහක හැඳුනුම් ආකෘතියකි",
|
||||
"form_error_positive": "0 ට වඩා වැඩි විය යුතුය",
|
||||
"form_error_negative": "0 හෝ ඊට වැඩි විය යුතුය",
|
||||
"dhcp_form_range_title": "අ.ජා. කෙ. (IP) ලිපින පරාසය",
|
||||
"dhcp_form_range_start": "පරාසය ආරම්භය",
|
||||
"dhcp_form_range_end": "පරාසය අවසානය",
|
||||
"dhcp_interface_select": "ග.ධා.වි.කෙ. (DHCP) අතුරුමුහුණත තෝරන්න",
|
||||
"dhcp_hardware_address": "දෘඩාංග ලිපිනය",
|
||||
"dhcp_ip_addresses": "අ.ජා. කෙ. (IP) ලිපින",
|
||||
"ip": "අ.ජා. කෙ. (IP)",
|
||||
"dhcp_table_hostname": "ධාරක නාමය",
|
||||
"dhcp_table_expires": "කල් ඉකුත් වීම",
|
||||
"dhcp_warning": "ඔබට කෙසේ හෝ ග.ධා.වි.කෙ. (DHCP) සේවාදායකය සබල කිරීමට අවශ්ය නම්, ඔබේ ජාලයේ වෙනත් ක්රියාකාරී ග.ධා.වි.කෙ. සේවාදායකයක් නොමැති බව තහවුරු කරගන්න. එසේ නොමැති නම්, එය සම්බන්ධිත උපාංග සඳහා අන්තර්ජාලය බිඳ දැමිය හැකිය!",
|
||||
"dhcp_error": "ජාලයේ තවත් ග.ධා.වි.කෙ. (DHCP) සේවාදායකයක් තිබේද යන්න අපට තීරණය කළ නොහැකි විය.",
|
||||
"dhcp_static_ip_error": "ග.ධා.වි.කෙ. (DHCP) සේවාදායකය භාවිතා කිරීම සඳහා ස්ථිතික අන්තර්ජාල කෙටුම්පත් (IP) ලිපිනයක් සැකසිය යුතුය. මෙම ජාල අතුරුමුහුණත ස්ථිතික අ.ජා. කෙ. ලිපිනයක් භාවිතයෙන් වින්යාසගත කර තිබේද යන්න තීරණය කිරීමට අප අසමත් විය. කරුණාකර ස්ථිතික අ.ජා. කෙ. ලිපිනයක් අතින් සකසන්න.",
|
||||
"dhcp_dynamic_ip_found": "ඔබේ පද්ධතිය <0>{{interfaceName}}</0> අතුරු මුහුණත සඳහා ගතික අන්තර්ජාල කෙටුම්පත් (IP) ලිපින වින්යාසය භාවිතා කරයි. ග.ධා.වි.කෙ. (DHCP) සේවාදායකය භාවිතා කිරීම සඳහා ස්ථිතික අ.ජා. කෙ. ලිපිනයක් සැකසිය යුතුය. ඔබගේ වර්තමාන අ.ජා. කෙ. ලිපිනය <0>{{ipAddress}}</0> වේ. ඔබ ග.ධා.වි.කෙ. සබල කරන්න බොත්තම එබුවහොත් අපි ස්වයංක්රීයව මෙම අ.ජා. කෙ. ලිපිනය ස්ථිතික ලෙස සකසන්නෙමු.",
|
||||
"dhcp_reset": "ග.ධා.වි.කෙ. (DHCP) වින්යාසය යළි පිහිටුවීමට අවශ්ය බව ඔබට විශ්වාස ද?",
|
||||
"country": "රට",
|
||||
"city": "නගරය",
|
||||
"delete_confirm": "\"{{key}}\" මකා දැමීමට අවශ්ය බව ඔබට විශ්වාසද?",
|
||||
"form_enter_hostname": "ධාරක නාමය ඇතුළත් කරන්න",
|
||||
"error_details": "දෝෂ විස්තර",
|
||||
"response_details": "ප්රතිචාරයෙහි විස්තර",
|
||||
"request_details": "ඉල්ලීමෙහි විස්තර",
|
||||
"client_details": "අනුග්රාහකයේ විස්තර",
|
||||
"details": "විස්තර",
|
||||
"back": "ආපසු",
|
||||
"dashboard": "උපකරණ පුවරුව",
|
||||
"settings": "සැකසුම්",
|
||||
"filters": "පෙරහන්",
|
||||
"filter": "පෙරහන",
|
||||
"query_log": "විමසුම් ලොගය",
|
||||
"nothing_found": "කිසිවක් සොයාගත නොහැක",
|
||||
"faq": "නිති අසන පැණ",
|
||||
"version": "අනුවාදය",
|
||||
"address": "ලිපිනය",
|
||||
"protocol": "කෙටුම්පත",
|
||||
"on": "සක්රියයි",
|
||||
"off": "අක්රියයි",
|
||||
"copyright": "ප්රකාශන හිමිකම",
|
||||
"homepage": "මුල් පිටුව",
|
||||
"report_an_issue": "ගැටලුවක් වාර්තා කරන්න",
|
||||
"privacy_policy": "රහස්යතා ප්රතිපත්තිය",
|
||||
"enable_protection": "ආරක්ෂණය සබල කරන්න",
|
||||
"enabled_protection": "ආරක්ෂණය සබල කර ඇත",
|
||||
"disable_protection": "ආරක්ෂණය අබල කරන්න",
|
||||
"disabled_protection": "ආරක්ෂණය අබල කර ඇත",
|
||||
"refresh_statics": "සංඛ්යාලේඛන නැවුම් කරන්න",
|
||||
"dns_query": "ව.නා.ප. (DNS) විමසුම්",
|
||||
"blocked_by": "<0>පෙරහන් මගින් අවහිර කරන ලද</0>",
|
||||
"stats_malware_phishing": "අවහිර කළ ද්වේශාංග/තතුබෑම්",
|
||||
"stats_adult": "අවහිර කළ වැඩිහිටි වෙබ් අඩවි",
|
||||
"stats_query_domain": "ජනප්රිය විමසන ලද වසම්",
|
||||
"for_last_24_hours": "පසුගිය පැය 24 සඳහා",
|
||||
"for_last_days": "පසුගිය දින {{count}} සඳහා",
|
||||
"for_last_days_plural": "පසුගිය දින {{count}} සඳහා",
|
||||
"no_domains_found": "වසම් කිසිවක් සොයා ගත නොහැකි විය",
|
||||
"requests_count": "ඉල්ලීම් ගණන",
|
||||
"top_blocked_domains": "ජනප්රිය අවහිර කළ වසම්",
|
||||
"top_clients": "ජනප්රිය අනුග්රාහකයන්",
|
||||
"general_statistics": "පොදු සංඛ්යාලේඛන",
|
||||
"number_of_dns_query_blocked_24_hours": "දැන්වීම් වාරණ පෙරහන් සහ ධාරක වාරණ ලැයිස්තු මගින් අවහිර කරන ලද DNS ඉල්ලීම් ගණන",
|
||||
"number_of_dns_query_blocked_24_hours_by_sec": "AdGuard browsing security ඒකකය මගින් අවහිර කරන ලද DNS ඉල්ලීම් ගණන",
|
||||
"number_of_dns_query_blocked_24_hours_adult": "අවහිර කළ වැඩිහිටි වෙබ් අඩවි ගණන",
|
||||
"enforced_save_search": "ආරක්ෂිත සෙවීම බලාත්මක කරන ලද",
|
||||
"number_of_dns_query_to_safe_search": "ආරක්ෂිත සෙවීම බලාත්මක කළ සෙවුම් යන්ත්ර සඳහා DNS ඉල්ලීම් ගණන",
|
||||
"average_processing_time": "සාමාන්ය සැකසුම් කාලය",
|
||||
"average_processing_time_hint": "DNS ඉල්ලීමක් සැකසීමේ සාමාන්ය කාලය මිලි තත්පර වලින්",
|
||||
"block_domain_use_filters_and_hosts": "පෙරහන් සහ ධාරක ගොනු භාවිතා කරමින් වසම් අවහිර කරන්න",
|
||||
"filters_block_toggle_hint": "ඔබට අවහිර කිරීමේ නීති <a href='#filters'>පෙරහන්</a> තුළ පිහිටුවිය හැකිය.",
|
||||
"use_adguard_browsing_sec": "AdGuard browsing security web service භාවිතා කරන්න",
|
||||
"use_adguard_parental": "AdGuard parental control වෙබ් සේවාව භාවිතා කරන්න",
|
||||
"use_adguard_parental_hint": "වසමේ වැඩිහිටියන්ට අදාල කරුණු අඩංගු දැයි AdGuard Home විසින් පරීක්ෂා කරනු ඇත. එය browsing security වෙබ් සේවාව මෙන් රහස්යතා හිතකාමී API භාවිතා කරයි.",
|
||||
"enforce_safe_search": "ආරක්ෂිත සෙවීම බලාත්මක කරන්න",
|
||||
"enforce_save_search_hint": "AdGuard Home හට පහත සෙවුම් යන්ත්ර තුළ ආරක්ෂිත සෙවීම බලාත්මක කළ හැකිය: Google, Youtube, Bing, DuckDuckGo සහ Yandex.",
|
||||
"no_servers_specified": "සේවාදායක කිසිවක් නිශ්චිතව දක්වා නැත",
|
||||
"general_settings": "පොදු සැකසුම්",
|
||||
"dns_settings": "DNS සැකසුම්",
|
||||
"dns_blocklists": "DNS අවහිර කිරීමේ ලැයිස්තු",
|
||||
"dns_allowlists": "DNS අවසර දීමේ ලැයිස්තු",
|
||||
"dns_allowlists_desc": "DNS අවසර දීමේ ලැයිස්තුවල වසම් කිසියම් අවහිර කිරීමේ ලැයිස්තුව අඩංගු වුවද එය නොසලකා හැර අවසර දෙනු ලැබේ.",
|
||||
"custom_filtering_rules": "අභිරුචි පෙරීමේ නීති",
|
||||
"encryption_settings": "සංකේතාංකන සැකසුම්",
|
||||
"dhcp_settings": "DHCP සැකසුම්",
|
||||
"disabled_filtering_toast": "පෙරීම අබල කර ඇත",
|
||||
"enabled_filtering_toast": "පෙරීම සබල කර ඇත",
|
||||
"disabled_parental_toast": "Parental control අබල කර ඇත",
|
||||
"enabled_parental_toast": "Parental control සබල කර ඇත",
|
||||
"disabled_safe_search_toast": "ආරක්ෂිත සෙවීම අබල කර ඇත",
|
||||
"enabled_save_search_toast": "ආරක්ෂිත සෙවීම සබල කර ඇත",
|
||||
"enabled_table_header": "සබල කර ඇත",
|
||||
"name_table_header": "නම",
|
||||
"list_url_table_header": "URL ලැයිස්තුව",
|
||||
"rules_count_table_header": "නීති ගණන",
|
||||
"last_time_updated_table_header": "අවසන් වරට යාවත්කාලීන කරන ලද",
|
||||
"actions_table_header": "ක්රියාමාර්ග",
|
||||
"request_table_header": "ඉල්ලීම",
|
||||
"edit_table_action": "සංස්කරණය කරන්න",
|
||||
"delete_table_action": "මකන්න",
|
||||
"no_blocklist_added": "අවහිර කිරීමේ ලැයිස්තු එකතු කර නැත",
|
||||
"no_whitelist_added": "අවසර දීමේ ලැයිස්තු එකතු කර නැත",
|
||||
"add_blocklist": "අවහිර කිරීමේ ලැයිස්තුවක් එකතු කරන්න",
|
||||
"add_allowlist": "අවසර දීමේ ලැයිස්තුවක් එකතු කරන්න",
|
||||
"cancel_btn": "අහෝසි කරන්න",
|
||||
"enter_name_hint": "නම ඇතුළත් කරන්න",
|
||||
"enter_url_or_path_hint": "ලැයිස්තුවක URL හෝ ස්ථීර මාර්ගයක් ඇතුළත් කරන්න",
|
||||
"check_updates_btn": "යාවත්කාල පරීක්ෂා කරන්න",
|
||||
"new_blocklist": "නව අවහිර කිරීමේ ලැයිස්තුව",
|
||||
"new_allowlist": "නව අවසර දීමේ ලැයිස්තුව",
|
||||
"edit_blocklist": "අවහිර කිරීමේ ලැයිස්තුව සංස්කරණය කරන්න",
|
||||
"edit_allowlist": "අවසර දීමේ ලැයිස්තුව සංස්කරණය කරන්න",
|
||||
"enter_valid_blocklist": "අවහිර කිරීමේ ලැයිස්තුවට වලංගු URL ලිපිනයක් ඇතුළත් කරන්න.",
|
||||
"enter_valid_allowlist": "අවසර දීමේ ලැයිස්තුවට වලංගු URL ලිපිනයක් ඇතුළත් කරන්න.",
|
||||
"form_error_url_format": "වලංගු නොවන URL ආකෘතියකි",
|
||||
"form_error_url_or_path_format": "ලැයිස්තුවක වලංගු නොවන URL හෝ ස්ථීර මාර්ගයකි",
|
||||
"custom_filter_rules": "අභිරුචි පෙරීමේ නීති",
|
||||
"examples_title": "උදාහරණ",
|
||||
"example_meaning_filter_block": "example.org වසමට සහ එහි සියලුම උප වසම් වලට පිවිසීම අවහිර කරන්න",
|
||||
"example_meaning_filter_whitelist": "example.org වසමට සහ එහි සියලුම උප වසම් වලට ප්රවේශය අවහිර නොකරන්න",
|
||||
"example_meaning_host_block": "AdGuard Home දැන් example.org වසම සඳහා 127.0.0.1 ලිපිනය ලබා දෙනු ඇත (නමුත් එහි උප ලිපින නොවේ).",
|
||||
"example_comment": "! මෙතැන අදහස් දැක්වීමක්",
|
||||
"example_comment_meaning": "විස්තර කිිරීමක්",
|
||||
"example_comment_hash": "# එසේම අදහස් දැක්වීමක්",
|
||||
"example_regex_meaning": "නිශ්චිතව දක්වා ඇති නිත්ය වාක්යවිධියට ගැලපෙන වසම් වෙත පිවිසීම අවහිර කරන්න",
|
||||
"example_upstream_regular": "සාමාන්ය DNS (UDP හරහා)",
|
||||
"example_upstream_dot": "සංකේතාංකනය කළ <0>DNS-over-TLS</0>",
|
||||
"example_upstream_doh": "සංකේතාංකනය කළ <0>DNS-over-HTTPS</0>",
|
||||
"example_upstream_tcp": "සාමාන්ය DNS (TCP හරහා)",
|
||||
"all_lists_up_to_date_toast": "සියලුම ලැයිස්තු දැනටමත් යාවත්කාලීනයි",
|
||||
"dns_test_ok_toast": "සඳහන් කළ DNS සේවාදායකයන් නිවැරදිව ක්රියා කරයි",
|
||||
"dns_test_not_ok_toast": "සේවාදායකය \"{{key}}\": භාවිතා කළ නොහැකි විය, කරුණාකර ඔබ එය නිවැරදිව ලියා ඇත්දැයි පරීක්ෂා කරන්න",
|
||||
"unblock": "අනවහිර කරන්න",
|
||||
"block": "අවහිර කරන්න",
|
||||
"time_table_header": "වේලාව",
|
||||
"date": "දිනය",
|
||||
"domain_name_table_header": "වසම් නාමය",
|
||||
"domain_or_client": "වසම හෝ අනුග්රාහකය",
|
||||
"type_table_header": "වර්ගය",
|
||||
"response_table_header": "ප්රතිචාරය",
|
||||
"response_code": "ප්රතිචාර කේතය",
|
||||
"client_table_header": "අනුග්රාහකය",
|
||||
"empty_response_status": "හිස්",
|
||||
"show_all_filter_type": "සියල්ල පෙන්වන්න",
|
||||
"refresh_btn": "නැවුම් කරන්න",
|
||||
"previous_btn": "පෙර",
|
||||
"next_btn": "ඊළඟ",
|
||||
"loading_table_status": "පූරණය වෙමින්...",
|
||||
"page_table_footer_text": "පිටුව",
|
||||
"rows_table_footer_text": "පේළි",
|
||||
"updated_custom_filtering_toast": "අභිරුචි පෙරීමේ නීති යාවත්කාල කරන ලදි",
|
||||
"rule_removed_from_custom_filtering_toast": "අභිරුචි පෙරීමේ නීති තුළින් නීතියක් ඉවත් කරන ලදි",
|
||||
"rule_added_to_custom_filtering_toast": "අභිරුචි පෙරීමේ නීති තුළට මෙම නීතිය එකතු කරන ලදි",
|
||||
"query_log_response_status": "තත්ත්වය: {{value}}",
|
||||
"query_log_filtered": "{{filter}} මගින් පෙරහන් කරන ලදි",
|
||||
"query_log_confirm_clear": "සම්පූර්ණ විමසුම් ලොගය ඉවත් කිරීමට අවශ්ය යැයි ඔබට විශ්වාසද?",
|
||||
"query_log_clear": "විමසුම් ලොග ඉවත් කරන්න",
|
||||
"query_log_retention": "විමසුම් ලොග රඳවා තබා ගැනීම",
|
||||
"query_log_enable": "ලොගය සබල කරන්න",
|
||||
"query_log_configuration": "ලොග වින්යාසය",
|
||||
"query_log_disabled": "විමසුම් ලොගය අබල කර ඇති අතර එය <0>සැකසුම්</0> තුළ වින්යාසගත කළ හැකිය",
|
||||
"query_log_strict_search": "ඉතා නිවැරදිව සෙවීම සඳහා ද්විත්ව උද්ධෘතය භාවිතා කරන්න",
|
||||
"query_log_retention_confirm": "විමසුම් ලොගය රඳවා තබා ගැනීම වෙනස් කිරීමට අවශ්ය බව ඔබට විශ්වාසද? ඔබ කාල පරතරයෙහි අගය අඩු කළහොත් සමහර දත්ත නැති වී යනු ඇත",
|
||||
"anonymize_client_ip": "අනුග්රාහකයෙහි අ.ජා. කෙ. (IP) නිර්නාමික කරන්න",
|
||||
"dns_config": "DNS සේවාදායක වින්යාසය",
|
||||
"default": "සුපුරුදු",
|
||||
"nxdomain": "නොපවතින වසම",
|
||||
"null_ip": "අභිශූන්යය අ.ජා. කෙ. (IP)",
|
||||
"custom_ip": "අභිරුචි අ.ජා. කෙ. (IP)",
|
||||
"dns_over_https": "DNS-over-HTTPS",
|
||||
"dns_over_tls": "DNS-over-TLS",
|
||||
"form_enter_rate_limit": "අනුපාත සීමාව ඇතුළත් කරන්න",
|
||||
"rate_limit": "අනුපාත සීමාව",
|
||||
"edns_enable": "EDNS අනුග්රාහක අනුජාලය සබල කරන්න",
|
||||
"blocking_ipv4_desc": "අවහිර කළ A ඉල්ලීමක් සඳහා ආපසු එවිය යුතු අ.ජා. කෙ. (IP) ලිපිනය",
|
||||
"blocking_ipv6_desc": "අවහිර කළ AAAA ඉල්ලීමක් සඳහා ආපසු එවිය යුතු අ.ජා. කෙ. (IP) ලිපිනය",
|
||||
"blocking_mode_nxdomain": "නොපවතින වසම (NXDOMAIN): NXDOMAIN කේතය සමඟ ප්රතිචාර දක්වයි",
|
||||
"blocking_mode_null_ip": "අභිශූන්යය අන්තර්ජාල කෙටුම්පත: ශුන්ය අ.ජා. කෙ. (IP) ලිපිනය සමඟ ප්රතිචාර දක්වයි (A සඳහා 0.0.0.0; AAAA සඳහා ::)",
|
||||
"blocking_mode_custom_ip": "අභිරුචි අන්තර්ජාල කෙටුම්පත: අතින් සැකසූ අ.ජා. කෙ. (IP) ලිපිනයක් සමඟ ප්රතිචාර දක්වයි",
|
||||
"upstream_dns_client_desc": "ඔබ මෙම ක්ෂේත්රය හිස්ව තබා ගන්නේ නම්, AdGuard Home විසින් <0>DNS සැකසුම්</0> හි වින්යාසගත කර ඇති සේවාදායකයන් භාවිතා කරනු ඇත.",
|
||||
"source_label": "මූලාශ්රය",
|
||||
"category_label": "ප්රවර්ගය",
|
||||
"rule_label": "නීතිය",
|
||||
"list_label": "ලැයිස්තුව",
|
||||
"unknown_filter": "{{filterId}} නොදන්නා පෙරහනකි",
|
||||
"known_tracker": "දන්නා ලුහුබැඳීමක්",
|
||||
"install_welcome_title": "AdGuard Home වෙත සාදරයෙන් පිළිගනිමු!",
|
||||
"install_welcome_desc": "AdGuard Home යනු ජාලය පුරා ඇති දැන්වීම් සහ ලුහුබැඳීම අවහිර කරන DNS සේවාදායකි. ඔබගේ මුළු ජාලය සහ සියලුම උපාංග පාලනය කිරීමට ඉඩ සලසා දීම එහි පරමාර්ථය යි, එයට අනුග්රාහක පාර්ශවීය වැඩසටහනක් භාවිතා කිරීම අවශ්ය නොවේ.",
|
||||
"install_settings_title": "පරිපාලක වෙබ් අතුරු මුහුණත",
|
||||
"install_settings_listen": "සවන් දෙන අතුරු මුහුණත",
|
||||
"install_settings_port": "කවුළුව",
|
||||
"form_error_port": "වලංගු කවුළුවක අගයක් ඇතුළත් කරන්න",
|
||||
"install_settings_dns": "DNS සේවාදායකය",
|
||||
"install_settings_all_interfaces": "සියලුම අතුරුමුහුණත්",
|
||||
"install_auth_title": "සත්යාපනය",
|
||||
"install_auth_desc": "ඔබගේ AdGuard Home පරිපාලක වෙබ් අතුරු මුහුණතට මුරපද සත්යාපනය වින්යාසගත කිරීම අතිශයින් නිර්දේශ කෙරේ. එය ඔබගේ ස්ථානීය ජාලයෙන් පමණක් ප්රවේශ විය හැකි වුවද, එය තව දුරටත් සීමා රහිත ප්රවේශයකින් ආරක්ෂා කර ගැනීම වැදගත් ය.",
|
||||
"install_auth_username": "පරිශීලක නාමය",
|
||||
"install_auth_password": "මුරපදය",
|
||||
"install_auth_confirm": "මුරපදය තහවුරු කරන්න",
|
||||
"install_auth_username_enter": "පරිශීලක නාමය ඇතුළත් කරන්න",
|
||||
"install_auth_password_enter": "මුරපදය ඇතුළත් කරන්න",
|
||||
"install_step": "පියවර",
|
||||
"install_devices_title": "ඔබගේ උපාංග වින්යාසගත කරන්න",
|
||||
"install_devices_desc": "AdGuard Home භාවිතා කිරීම ආරම්භයට, ඔබගේ උපාංග එය පරිශ්රීලනයට වින්යාසගත කිරීම අවශ්ය වේ.",
|
||||
"install_submit_title": "සුභ පැතුම්!",
|
||||
"install_submit_desc": "පිහිටුවීමේ ක්රියා පටිපාටිය අවසන් වී ඇති අතර ඔබ AdGuard Home භාවිතය ආරම්භ කිරීමට සූදානම්ය.",
|
||||
"install_devices_router": "මාර්ගකාරකය",
|
||||
"install_devices_router_desc": "මෙම පිහිටුම ඔබගේ නිවසේ මාර්ගකාරකයට සම්බන්ධ සියලුම උපාංග ස්වයංක්රීයව ආවරණය කරන අතර ඔබට ඒවා අතින් වින්යාසගත කිරීමට අවශ්ය නොවනු ඇත.",
|
||||
"install_devices_router_list_3": "ඔබගේ AdGuard Home සේවාදායක ලිපින එහි ඇතුළත් කරන්න.",
|
||||
"install_devices_router_list_4": "ඔබට සමහර වර්ගයේ රවුටර වල අභිරුචි ව.නා.ප. (DNS) සේවාදායකයක් සැකසිය නොහැක. මෙම අවස්ථාවේදී AdGuard Home <0>ග.ධා.වි.කෙ. (DHCP) සේවාදායකයක්</0> ලෙස පිහිටුවන්නේ නම් එය උපකාර වනු ඇත. එසේ නොමැතිනම්, ඔබගේ විශේෂිත මාර්ගකාරක මාදිළිය සඳහා වූ ව.නා.ප. සේවාදායකයන් රිසිකරණය කරන්නේ කෙසේද යන්න පිළිබඳ අත්පොත සෙවිය යුතුය.",
|
||||
"install_devices_windows_list_1": "ආරම්භක මෙනුව හෝ වින්ඩෝස් සෙවුම හරහා පාලක පැනලය විවෘත කරන්න.",
|
||||
"install_devices_windows_list_2": "ජාල සහ අන්තර්ජාල ප්රවර්ගයට ගොස් පසුව ජාල සහ බෙදාගැනීමේ මධ්යස්ථානය වෙත යන්න.",
|
||||
"install_devices_windows_list_3": "උපයුක්තකයෙහි සැකසුම් වෙනස් කිරීම තිරයේ වම් පසින් සොයාගෙන එය මත ක්ලික් කරන්න.",
|
||||
"install_devices_windows_list_4": "ඔබගේ ක්රියාකාරී සම්බන්ධතාවය තෝරන්න, එය මත දකුණු-ක්ලික් කර ගුණාංග තෝරන්න.",
|
||||
"install_devices_windows_list_5": "ලැයිස්තුවේ ඇති අන්තර්ජාල කෙටුම්පත් අනුවාදය 4 (TCP/IP) සොයාගෙන එය තෝරා ඉන්පසු ගුණාංග මත නැවත ක්ලික් කරන්න.",
|
||||
"install_devices_windows_list_6": "'පහත දැක්වෙන DNS සේවාදායක ලිපින භාවිතා කරන්න' යන්න තෝරා ඔබගේ AdGuard Home සේවාදායක ලිපින ඇතුළත් කරන්න.",
|
||||
"install_devices_macos_list_1": "ඇපල් අයිකනය මත ක්ලික් කර පද්ධති මනාපයන් වෙත යන්න.",
|
||||
"install_devices_macos_list_2": "ජාලය මත ක්ලික් කරන්න.",
|
||||
"install_devices_macos_list_3": "ඔබගේ ලැයිස්තුවේ පළමු සම්බන්ධතාවය තෝරා උසස් මත ක්ලික් කරන්න.",
|
||||
"install_devices_macos_list_4": "DNS තීරුව තෝරා ඔබගේ AdGuard Home සේවාදායක ලිපින ඇතුළත් කරන්න.",
|
||||
"install_devices_android_list_1": "ඇන්ඩ්රොයිඩ් මෙනුවෙහි මුල් තිරයෙන්, සැකසීම් මත තට්ටු කරන්න.",
|
||||
"install_devices_android_list_2": "මෙනුවේ Wi-Fi මත තට්ටු කරන්න. පවතින සියලුම ජාල ලැයිස්තුගත කර ඇති තිරය පෙන්වනු ඇත (ජංගම සම්බන්ධතාවය සඳහා අභිරුචි DNS සැකසිය නොහැක).",
|
||||
"install_devices_android_list_3": "ඔබ සම්බන්ධ වී ඇති ජාලය මත දිගු වේලාවක් ඔබන්න, ඉන්පසුව ජාලය වෙනස් කිරීම මත තට්ටු කරන්න.",
|
||||
"install_devices_android_list_4": "ඔබට සමහර උපාංගවල වැඩිදුර සැකසුම් බැලීමට \"උසස්\" සඳහා වූ කොටුව සලකුණු කිරීමට අවශ්ය විය හැකිය. එමෙන්ම ඔබගේ ඇන්ඩ්රොයිඩ් DNS සැකසුම් වෙනස් කිරීමට, අ.ජා. කෙ. (IP) සැකසුම් ග.ධා.වි.කෙ. (DHCP) සිට ස්ථිතික වෙත මාරු කළ යුතුය.",
|
||||
"install_devices_android_list_5": "DNS 1 සහ DNS 2 පිහිටුවීම් අගයන් ඔබගේ AdGuard Home සේවාදායක ලිපින වලට වෙනස් කරන්න.",
|
||||
"install_devices_ios_list_1": "මුල් තිරයේ සිට, සැකසුම් මත තට්ටු කරන්න.",
|
||||
"install_devices_ios_list_2": "වම්පස මෙනුවෙහි Wi-Fi තෝරන්න (ජංගම දුරකථන සඳහා DNS වින්යාසගත කිරීමට නොහැකිය).",
|
||||
"install_devices_ios_list_3": "දැනට ක්රියාකාරී ජාලයයහෙි නම මත තට්ටු කරන්න.",
|
||||
"install_devices_ios_list_4": "DNS ක්ෂේත්රය තුළ ඔබගේ AdGuard Home සේවාදායක ලිපින ඇතුළත් කරන්න.",
|
||||
"get_started": "ආරම්භ කර ගන්න",
|
||||
"next": "ඊළඟ",
|
||||
"open_dashboard": "උපකරණ පුවරුව විවෘත කරන්න",
|
||||
"install_saved": "සාර්ථකව සුරකින ලදි",
|
||||
"encryption_title": "සංකේතාංකනය",
|
||||
"encryption_desc": "ගුප්තකේතනය (HTTPS/TLS) සඳහා DNS සහ පරිපාලක වෙබ් අතුරු මුහුණත සහය දක්වයි",
|
||||
"encryption_config_saved": "සංකේතාංකන වින්යාසය සුරකින ලදි",
|
||||
"encryption_server": "සේවාදායකයේ නම",
|
||||
"encryption_server_enter": "ඔබගේ වසම් නාමය ඇතුළත් කරන්න",
|
||||
"encryption_redirect": "ස්වයංක්රීයව HTTPS වෙත හරවා යවන්න",
|
||||
"encryption_redirect_desc": "සබල කර ඇත්නම්, AdGuard Home ඔබව ස්වයංක්රීයව HTTP සිට HTTPS ලිපින වෙත හරවා යවනු ඇත.",
|
||||
"encryption_https": "HTTPS කවුළුව",
|
||||
"encryption_https_desc": "HTTPS කවුළුව වින්යාසගත කර ඇත්නම්, AdGuard Home පරිපාලක අතුරුමුහුණත HTTPS හරහා ප්රවේශ විය හැකි අතර එය '/ dns-query' ස්ථානයේ DNS-over-HTTPS ද ලබා දෙනු ඇත.",
|
||||
"encryption_dot": "DNS-over-TLS කවුළුව",
|
||||
"encryption_dot_desc": "මෙම කවුළුව වින්යාසගත කර ඇත්නම්, AdGuard Home විසින් මෙම කවුළුව හරහා DNS-over-TLS සේවාදායකයක් ක්රියාත්මක කරනු ඇත.",
|
||||
"encryption_certificates": "සහතික",
|
||||
"encryption_certificates_input": "ඔබගේ PEM-කේතාංකනය කළ සහතික පිටපත් කර මෙහි අලවන්න.",
|
||||
"encryption_status": "තත්ත්වය",
|
||||
"encryption_expire": "කල් ඉකුත් වීම",
|
||||
"encryption_key": "පුද්ගලික යතුර",
|
||||
"encryption_key_input": "ඔබගේ සහතිකය සඳහා PEM-කේතාංකනය කළ පුද්ගලික යතුර පිටපත් කර මෙහි අලවන්න.",
|
||||
"encryption_enable": "සංකේතාංකනය සබල කරන්න (HTTPS, DNS-over-HTTPS සහ DNS-over-TLS)",
|
||||
"encryption_enable_desc": "සංකේතාංකනය සබල කර ඇත්නම්, AdGuard Home පරිපාලක අතුරුමුහුණත HTTPS හරහා ක්රියා කරනු ඇති අතර DNS සේවාදායකය DNS-over-HTTPS සහ DNS-over-TLS හරහා ලැබෙන ඉල්ලීම් සඳහා සවන් දෙනු ඇත.",
|
||||
"encryption_key_valid": "මෙය වලංගු {{type}} පුද්ගලික යතුරකි",
|
||||
"encryption_key_invalid": "මෙය වලංගු නොවන {{type}} පුද්ගලික යතුරකි",
|
||||
"encryption_subject": "මාතෘකාව",
|
||||
"encryption_issuer": "නිකුත් කරන්නා",
|
||||
"encryption_hostnames": "ධාරක නාම",
|
||||
"encryption_reset": "සංකේතාංකන සැකසුම් යළි පිහිටුවීමට අවශ්ය බව ඔබට විශ්වාස ද?",
|
||||
"topline_expiring_certificate": "ඔබගේ SSL සහතිකය කල් ඉකුත්වීමට ආසන්න වී ඇත. <0>සංකේතාංකන සැකසුම්</0> යාවත්කාල කරන්න.",
|
||||
"topline_expired_certificate": "ඔබගේ SSL සහතිකය කල් ඉකුත් වී ඇත. <0>සංකේතාංකන සැකසුම්</0> යාවත්කාල කරන්න.",
|
||||
"form_error_port_range": "80-65535 පරාසයෙහි කවුළුවක අගයක් ඇතුළත් කරන්න",
|
||||
"form_error_port_unsafe": "මෙය අනාරක්ෂිත කවුළුවකි",
|
||||
"form_error_equal": "සමාන නොවිය යුතුය",
|
||||
"form_error_password": "මුරපදය නොගැලපුුුුුුණි",
|
||||
"reset_settings": "සැකසුම් යළි පිහිටුවන්න",
|
||||
"update_announcement": "AdGuard Home {{version}} දැන් ලබා ගත හැකිය! වැඩි විස්තර සඳහා <0>මෙහි ක්ලික් කරන්න</0>.",
|
||||
"setup_guide": "පිහිටුවීමේ මාර්ගෝපදේශය",
|
||||
"dns_addresses": "DNS ලිපින",
|
||||
"dns_start": "DNS සේවාදායකය ආරම්භ වෙමින් පවතී",
|
||||
"down": "පහත",
|
||||
"fix": "නිරාකරණය කරන්න",
|
||||
"dns_providers": "මෙහි තෝරා ගැනීමට <0>දන්නා DNS සපයන්නන්ගේ ලැයිස්තුවක්</0> ඇත.",
|
||||
"update_now": "දැන් \tයාවත්කාල කරන්න",
|
||||
"update_failed": "ස්වයංක්රීය යාවත්කාල කිරීම අසාර්ථක විය. අතින් යාවත්කාල කිරීමට කරුණාකර <a href='https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started#update'>පියවර අනුගමනය කරන්න</a>.",
|
||||
"processing_update": "කරුණාකර රැඳී සිටින්න, AdGuard Home යාවත්කාලීන වෙමින් පවතී",
|
||||
"clients_title": "අනුග්රාහකයන්",
|
||||
"clients_desc": "AdGuard Home වෙත සම්බන්ධ කර ඇති උපාංග වින්යාසගත කරන්න",
|
||||
"settings_global": "ගෝලීය",
|
||||
"settings_custom": "අභිරුචි",
|
||||
"table_client": "අනුග්රාහකය",
|
||||
"table_name": "නම",
|
||||
"save_btn": "සුරකින්න",
|
||||
"client_add": "අනුග්රාහකයක් එකතු කරන්න",
|
||||
"client_new": "නව අනුග්රාහකය",
|
||||
"client_edit": "අනුග්රාහකය සංස්කරණය කරන්න",
|
||||
"client_identifier": "හඳුන්වනය",
|
||||
"ip_address": "අ.ජා. කෙ. (IP) ලිපිනය",
|
||||
"client_identifier_desc": "අනුග්රාහකයන් අ.ජා. කෙ. (IP) ලිපිනයක් හෝ මා.ප්ර.පා. (MAC) ලිපිනයක් මගින් හඳුනාගත හැකිය. මා.ප්ර.පා. හඳුන්වනයක් ලෙස භාවිතා කළ හැක්කේ AdGuard Home ද <0>DHCP සේවාදායකයක්</0> නම් පමණක් බව කරුණාවෙන් සලකන්න. ",
|
||||
"form_enter_ip": "අ.ජා. කෙ. (IP) ඇතුළත් කරන්න",
|
||||
"form_enter_mac": "MAC ඇතුළත් කරන්න",
|
||||
"form_enter_id": "හඳුන්වනය ඇතුළත් කරන්න",
|
||||
"form_add_id": "හඳුන්වනයක් එක් කරන්න",
|
||||
"form_client_name": "අනුග්රාහකයේ නම ඇතුළත් කරන්න",
|
||||
"name": "නම",
|
||||
"client_global_settings": "ගෝලීය සැකසුම් භාවිතා කරන්න",
|
||||
"client_deleted": "\"{{key}}\" අනුග්රාහකය සාර්ථකව ඉවත් කරන ලදි",
|
||||
"client_added": "\"{{key}}\" අනුග්රාහකය සාර්ථකව එකතු කරන ලදි",
|
||||
"client_updated": "\"{{key}}\" අනුග්රාහකය සාර්ථකව යාවත්කාල කරන ලදි",
|
||||
"client_confirm_delete": "\"{{key}}\" අනුග්රාහකය ඉවත් කිරීමට අවශ්ය බව ඔබට විශ්වාසද?",
|
||||
"list_confirm_delete": "මෙම ලැයිස්තුව ඉවත් කිරීමට අවශ්ය බව ඔබට විශ්වාස ද?",
|
||||
"auto_clients_desc": "AdGuard Home භාවිතා කරන අනුග්රාහකයන්ගේ දත්ත, නමුත් වින්යාසය තුළ ගබඩා කර නොමැති",
|
||||
"access_title": "ප්රවේශවීමට සැකසුම්",
|
||||
"access_desc": "මෙහිදී ඔබට AdGuard Home DNS සේවාදායකය සඳහා ප්රවේශ වීමේ නීති වින්යාසගත කළ හැකිය.",
|
||||
"access_allowed_title": "අවසර ලත් අනුග්රාහකයන්",
|
||||
"access_allowed_desc": "CIDR හෝ අ.ජා. කෙ. (IP) ලිපින ලැයිස්තුවක් වින්යාසගත කර ඇත්නම්, AdGuard Home විසින් එම අ.ජා. කෙ. ලිපින වලින් පමණක් ඉල්ලීම් පිළිගනු ඇත.",
|
||||
"access_disallowed_title": "අවසර නොලත් අනුග්රාහකයන්",
|
||||
"access_disallowed_desc": "CIDR හෝ අ.ජා. කෙ. (IP) ලිපින ලැයිස්තුවක් වින්යාසගත කර ඇත්නම්, AdGuard Home විසින් එම අ.ජා. කෙ. ලිපින වලින් ඉල්ලීම් අත්හරිනු ඇත.",
|
||||
"access_blocked_title": "අවහිර කළ වසම්",
|
||||
"access_settings_saved": "ප්රවේශ වීමේ සැකසුම් සාර්ථකව සුරකින ලදි",
|
||||
"updates_checked": "යාවත්කාලීන කිරීම් සාර්ථකව පරික්ෂා කර ඇත",
|
||||
"updates_version_equal": "AdGuard Home යාවත්කාලීනයි",
|
||||
"check_updates_now": "යාවත්කාල කිරීම සඳහා දැන් පරීක්ෂා කරන්න",
|
||||
"dns_privacy": "DNS රහස්යතා",
|
||||
"setup_dns_privacy_1": "<0>DNS-over-TLS:</0> <1>{{address}}</1> තන්තුව භාවිතා කරයි.",
|
||||
"setup_dns_privacy_2": "<0>DNS-over-HTTPS:</0> <1>{{address}}</1> තන්තුව භාවිතා කරයි.",
|
||||
"setup_dns_privacy_3": "<0>සංකේතාංකන ව.නා.ප. (DNS) කෙටුම්පත් සඳහා සහය දක්වන්නේ ඇන්ඩ්රොයිඩ් 9 පමණක් බව කරුණාවෙන් සලකන්න. එබැවින් ඔබ වෙනත් මෙහෙයුම් පද්ධති සඳහා අතිරේක මෘදුකාංග ස්ථාපනය කළ යුතුය.</0><0> ඔබට භාවිතා කළ හැකි මෘදුකාංග ලැයිස්තුවක් පහත දැක්වේ.</0>",
|
||||
"setup_dns_privacy_android_2": "<1>DNS-over-HTTPS</1> සහ <1>DNS-over-TLS</1> සඳහා <0>AdGuard for Android</0> සහය දක්වයි.",
|
||||
"setup_dns_privacy_ios_2": "<1>DNS-over-HTTPS</1> සහ <1>DNS-over-TLS</1> පිහිටුවීම් සඳහා <0>AdGuard for iOS</0> සහය දක්වයි.",
|
||||
"setup_dns_privacy_other_2": "<0>dnsproxy</0> දන්නා සියලුම ආරක්ෂිත DNS කෙටුම්පත් සඳහා සහය දක්වයි.",
|
||||
"setup_dns_privacy_other_3": "<1>DNS-over-HTTPS</1> සඳහා <0>dnscrypt-පෙරකලාසිය</0> සහය දක්වයි.",
|
||||
"setup_dns_privacy_other_4": "<1>DNS-over-HTTPS</1> සඳහා <0>මොසිල්ලා ෆයර්ෆොක්ස්</0> සහය දක්වයි.",
|
||||
"setup_dns_notice": "ඔබට <1>DNS-over-HTTPS</1> හෝ <1>DNS-over-TLS</1> භාවිතා කිරීම සඳහා AdGuard Home සැකසුම් තුළ <0>සංකේතාංකනය වින්යාසගත</0> කිරීමට අවශ්ය වේ.",
|
||||
"rewrite_add": "DNS නැවත ලිවීමක් එකතු කරන්න",
|
||||
"rewrite_confirm_delete": "\"{{key}}\" සඳහා DNS නැවත ලිවීම ඉවත් කිරීමට අවශ්ය බව ඔබට විශ්වාසද?",
|
||||
"dns_rewrites": "DNS නැවත ලිවීම්",
|
||||
"form_domain": "වසම ඇතුළත් කරන්න",
|
||||
"form_answer": "අ.ජා. කෙ. (IP) ලිපිනය හෝ වසම ඇතුළත් කරන්න ",
|
||||
"form_error_domain_format": "වලංගු නොවන වසම් ආකෘතියකි",
|
||||
"form_error_answer_format": "වලංගු නොවන පිළිතුරු ආකෘතියකි",
|
||||
"configure": "වින්යාසගත කරන්න",
|
||||
"main_settings": "ප්රධාන සැකසුම්",
|
||||
"block_services": "විශේෂිත සේවාවන් අවහිර කරන්න",
|
||||
"blocked_services": "අවහිර කළ සේවාවන්",
|
||||
"blocked_services_desc": "ජනප්රිය අඩවි සහ සේවාවන් ඉක්මනින් අවහිර කිරීමට ඉඩ දෙයි.",
|
||||
"blocked_services_saved": "අවහිර කළ සේවාවන් සාර්ථකව සුරකින ලදි",
|
||||
"blocked_services_global": "ගෝලීය අවහිර කළ සේවාවන් භාවිතා කරන්න",
|
||||
"blocked_service": "අවහිර කළ සේවාව",
|
||||
"block_all": "සියල්ල අවහිර කරන්න",
|
||||
"unblock_all": "සියල්ල අනවහිර කරන්න",
|
||||
"encryption_certificate_path": "සහතිකයේ මාර්ගය",
|
||||
"encryption_private_key_path": "පුද්ගලික යතුරෙහි මාර්ගය",
|
||||
"encryption_certificates_source_path": "සහතික ගොනුවෙහි මාර්ගය සකසන්න",
|
||||
"encryption_certificates_source_content": "සහතිකවල අන්තර්ගත අලවන්න",
|
||||
"encryption_key_source_path": "පුද්ගලික යතුරක ගොනුවක් සකසන්න",
|
||||
"encryption_key_source_content": "පුද්ගලික යතුරෙහි අන්තර්ගත අලවන්න",
|
||||
"stats_params": "සංඛ්යාලේඛන වින්යාසය",
|
||||
"config_successfully_saved": "වින්යාසය සාර්ථකව සුරකින ලදි",
|
||||
"interval_24_hour": "පැය 24",
|
||||
"interval_days": "{{count}} දිනය",
|
||||
"interval_days_plural": "දින {{count}}",
|
||||
"domain": "වසම",
|
||||
"answer": "පිළිතුර",
|
||||
"filter_added_successfully": "පෙරහන සාර්ථකව එකතු කරන ලදි",
|
||||
"filter_updated": "ලැයිස්තුව සාර්ථකව යාවත්කාලීන කර ඇත",
|
||||
"statistics_configuration": "සංඛ්යාලේඛන වින්යාසය",
|
||||
"statistics_retention": "සංඛ්යාලේඛන රඳවා තබා ගැනීම",
|
||||
"statistics_retention_desc": "ඔබ කාල පරතරය අඩු කළහොත් සමහර දත්ත නැති වනු ඇත",
|
||||
"statistics_clear": " සංඛ්යාලේඛන ඉවත් කරන්න",
|
||||
"statistics_clear_confirm": "සංඛ්යාලේඛන ඉවත් කිරීමට අවශ්ය බව ඔබට විශ්වාස ද?",
|
||||
"statistics_retention_confirm": "සංඛ්යාලේඛන රඳවා තබා ගැනීම වෙනස් කිරීමට අවශ්ය බව ඔබට විශ්වාසද? ඔබ කාල පරතරයෙහි අගය අඩු කළහොත් සමහර දත්ත නැති වී යනු ඇත",
|
||||
"statistics_cleared": "සංඛ්යාලේඛන සාර්ථකව ඉවත් කරන ලදි",
|
||||
"interval_hours": "පැය {{count}}",
|
||||
"interval_hours_plural": "පැය {{count}}",
|
||||
"filters_configuration": "පෙරහන් වින්යාසය",
|
||||
"filters_enable": "පෙරහන් සබල කරන්න",
|
||||
"filters_interval": "පෙරහන් යාවත්කාල කාල පරතරය",
|
||||
"disabled": "අබල කර ඇත",
|
||||
"username_label": "පරිශීලක නාමය",
|
||||
"username_placeholder": "පරිශීලක නාමය ඇතුළත් කරන්න",
|
||||
"password_label": "මුරපදය",
|
||||
"password_placeholder": "මුරපදය ඇතුළත් කරන්න",
|
||||
"sign_in": "පුරන්න",
|
||||
"sign_out": "වරන්න",
|
||||
"forgot_password": "මුරපදය අමතක වුණා ද?",
|
||||
"forgot_password_desc": "ඔබගේ පරිශීලක ගිණුම සඳහා නව මුරපදයක් සෑදීමට කරුණාකර <0>මෙම පියවර</0> අනුගමනය කරන්න.",
|
||||
"location": "ස්ථානය",
|
||||
"orgname": "සංවිධානයේ නම",
|
||||
"netname": "ජාලයේ නම",
|
||||
"network": "ජාලය",
|
||||
"descr": "විස්තරය",
|
||||
"whois": "Whois",
|
||||
"blocked_by_response": "ප්රතිචාරය අන්වර්ථ නාමයක් (CNAME) හෝ අ.ජා. කෙ. (IP) මගින් අවහිර කර ඇත",
|
||||
"try_again": "නැවත උත්සහා කරන්න",
|
||||
"example_rewrite_domain": "මෙම වසම් නාමය සඳහා පමණක් ප්රතිචාර නැවත ලියන්න.",
|
||||
"example_rewrite_wildcard": "<0>example.org</0> සහ එහි සියලුම උප වසම් සඳහා ප්රතිචාර නැවත ලියන්න.",
|
||||
"disable_ipv6": "IPv6 අබල කරන්න",
|
||||
"disable_ipv6_desc": "මෙම අංගය සක්රීය කර ඇත්නම්, IPv6 ලිපින සඳහා වන සියලුම DNS විමසුම් (AAAA වර්ගය) අතහැර දමනු ලැබේ.",
|
||||
"fastest_addr": "වේගවත්ම අන්තර්ජාල කෙටුම්පත් (IP) ලිපිනය",
|
||||
"autofix_warning_text": "ඔබ \"නිරාකරණය කරන්න\" මත ක්ලික් කළහොත්, AdGuard Home ඔබගේ පද්ධතිය AdGuard Home DNS සේවාදායකය භාවිතා කිරීමට වින්යාසගත කරනු ඇත.",
|
||||
"tags_title": "හැඳුනුම් සංකේත",
|
||||
"tags_desc": "අනුග්රාහකයට අනුරූප වන හැඳුනුම් සංකේත ඔබට තෝරා ගත හැකිය. පෙරහන් නීති වලට හැඳුනුම් සංකේත ඇතුළත් කළ හැකි අතර ඒවා වඩාත් නිවැරදිව යෙදීමට ඔබට ඉඩ සලසයි. <0>වැඩිදුර ඉගෙන ගන්න</0>",
|
||||
"form_select_tags": "අනුග්රාහක හැඳුනුම් සංකේත",
|
||||
"check_title": "පෙරීම පරීක්ෂා කරන්න",
|
||||
"check": "පරීක්ෂා කරන්න",
|
||||
"form_enter_host": "ධාරක නාමයක් ඇතුළත් කරන්න",
|
||||
"filtered_custom_rules": "අභිරුචි පෙරීමේ නීති මගින් පෙරහන් කරන ලදි",
|
||||
"check_ip": "අ.ජා. කෙ. (IP) ලිපින: {{ip}}",
|
||||
"check_cname": "CNAME: {{cname}}",
|
||||
"check_reason": "හේතුව: {{reason}}",
|
||||
"check_rule": "නීතිය: {{rule}}",
|
||||
"check_service": "සේවාවෙහි නම: {{service}}",
|
||||
"check_not_found": "ඔබගේ පෙරහන් ලැයිස්තු තුළ සොයා ගත නොහැක",
|
||||
"client_confirm_block": "{{ip}} අනුග්රාහකය අවහිර කිරීමට අවශ්ය බව ඔබට විශ්වාසද?",
|
||||
"client_confirm_unblock": "{{ip}} අනුග්රාහකය අනවහිර කිරීමට අවශ්ය බව ඔබට විශ්වාසද?",
|
||||
"client_blocked": "අනුග්රාහකය \"{{ip}}\" සාර්ථකව අවහිර කරන ලදි",
|
||||
"client_unblocked": "අනුග්රාහකය \"{{ip}}\" සාර්ථකව අනවහිර කරන ලදි",
|
||||
"static_ip": "ස්ථිතික අ.ජා. කෙ. (IP) ලිපිනය",
|
||||
"static_ip_desc": "AdGuard Home යනු සේවාදායකයක් බැවින් එය නිසි ලෙස ක්රියා කිරීමට ස්ථිතික අන්තර්ජාල කෙටුම්පත් (IP) ලිපිනයක් අවශ්ය වේ. එසේ නොමැතිනම්, යම් අවස්ථාවක දී ඔබගේ මාර්ගකාරකය මෙම උපාංගයට වෙනත් අ.ජා. කෙ. ලිපිනයක් ලබා දිය හැකිය.",
|
||||
"set_static_ip": "ස්ථිතික අ.ජා. කෙ. (IP) ලිපිනයක් සකසන්න",
|
||||
"install_static_ok": "සුභ තොරතුරක්! ස්ථිතික අන්තර්ජාල කෙටුම්පත් (IP) ලිපිනය දැනටමත් වින්යාසගත කර ඇත",
|
||||
"install_static_error": "මෙම ජාල අතුරුමුහුණත සඳහා AdGuard Home හට එය ස්වයංක්රීයව වින්යාසගත කළ නොහැක. කරුණාකර මෙය අතින් කරන්නේ කෙසේද යන්න පිළිබඳ උපදෙස් සොයා ගන්න.",
|
||||
"install_static_configure": "ගතික අ.ජා. කෙ. (IP) ලිපිනයක් භාවිතා කරන බව අපි අනාවරණය කර ගෙන ඇත්තෙමු - <0>{{ip}}</0>. එය ඔබගේ ස්ථිතික ලිපිනය ලෙස භාවිතා කිරීමට අවශ්යද?",
|
||||
"confirm_static_ip": "AdGuard Home ඔබේ ස්ථිතික අ.ජා. කෙ. (IP) ලිපිනය ලෙස {{ip}} වින්යාසගත කරනු ඇත. ඔබට ඉදිරියට යාමට අවශ්යද?",
|
||||
"list_updated": "{{count}} ලැයිස්තුව යාවත්කාලීන කරන ලදි",
|
||||
"list_updated_plural": "ලැයිස්තු {{count}} ක් යාවත්කාලීන කරන ලදි",
|
||||
"dnssec_enable": "DNSSEC සබල කරන්න",
|
||||
"validated_with_dnssec": "DNSSEC සමඟ තහවුරු කර ඇත",
|
||||
"show_blocked_responses": "අවහිර කර ඇත",
|
||||
"blocked_safebrowsing": "ආරක්ෂිත සෙවීම මගින් අවහිර කරන ලද",
|
||||
"blocked_adult_websites": "අවහිර කළ වැඩිහිටි වෙබ් අඩවි",
|
||||
"blocked_threats": "අවහිර කළ තර්ජන",
|
||||
"allowed": "අවසර ලත්",
|
||||
"filtered": "පෙරහන් කරන ලද",
|
||||
"rewritten": "නැවත ලියන ලද",
|
||||
"safe_search": "ආරක්ෂිත සෙවීම",
|
||||
"blocklist": "අවහිර කිරීමේ ලැයිස්තුව",
|
||||
"milliseconds_abbreviation": "මිලි තත්."
|
||||
}
|
||||
@@ -208,8 +208,8 @@
|
||||
"page_table_footer_text": "Stránka",
|
||||
"rows_table_footer_text": "riadky",
|
||||
"updated_custom_filtering_toast": "Aktualizované vlastné filtračné pravidlá",
|
||||
"rule_removed_from_custom_filtering_toast": "Pravidlo odstránené z vlastných filtračných pravidiel",
|
||||
"rule_added_to_custom_filtering_toast": "Pravidlo pridané do vlastných filtračných pravidiel",
|
||||
"rule_removed_from_custom_filtering_toast": "Pravidlo odstránené z vlastných filtračných pravidiel: {{rule}}",
|
||||
"rule_added_to_custom_filtering_toast": "Pravidlo pridané do vlastných filtračných pravidiel: {{rule}}",
|
||||
"query_log_response_status": "Stav: {{value}}",
|
||||
"query_log_filtered": "Vyfiltrované pomocou {{filter}}",
|
||||
"query_log_confirm_clear": "Naozaj chcete vymazať celý denník dopytov?",
|
||||
@@ -562,5 +562,6 @@
|
||||
"filter_category_regional_desc": "Zoznamy zamerané na regionálne reklamy a sledovacie servery",
|
||||
"filter_category_other_desc": "Iné blokovacie zoznamy",
|
||||
"original_response": "Pôvodná odozva",
|
||||
"click_to_view_queries": "Kliknite pre zobrazenie dopytov"
|
||||
}
|
||||
"click_to_view_queries": "Kliknite pre zobrazenie dopytov",
|
||||
"port_53_faq_link": "Port 53 je často obsadený službami \"DNSStubListener\" alebo \"systemd-resolved\". Prečítajte si <0>tento návod</0> o tom, ako to vyriešiť."
|
||||
}
|
||||
|
||||
@@ -208,8 +208,8 @@
|
||||
"page_table_footer_text": "Stran",
|
||||
"rows_table_footer_text": "vrstic",
|
||||
"updated_custom_filtering_toast": "Posodobljena pravila filtriranja po meri",
|
||||
"rule_removed_from_custom_filtering_toast": "Pravilo je odstranjeno iz pravil filtriranja po meri",
|
||||
"rule_added_to_custom_filtering_toast": "Pravilo je dodano pravilom filtriranja po meri",
|
||||
"rule_removed_from_custom_filtering_toast": "Pravilo je odstranjeno iz pravil filtriranja po meri: {{rule}}",
|
||||
"rule_added_to_custom_filtering_toast": "Pravilo je dodano pravilom filtriranja po meri: {{rule}}",
|
||||
"query_log_response_status": "Stanje: {{value}}",
|
||||
"query_log_filtered": "Filtriran z {{filter}}",
|
||||
"query_log_confirm_clear": "Ali ste prepričani, da želite počistiti celoten dnevnik poizvedb?",
|
||||
@@ -562,5 +562,6 @@
|
||||
"filter_category_regional_desc": "Seznami, ki so osredotočeni na področne oglase in strežnike za sledenje",
|
||||
"filter_category_other_desc": "Drugi seznami za zaviranje",
|
||||
"original_response": "Izviren odgovor",
|
||||
"click_to_view_queries": "Kliknite za prikaz poizvedb"
|
||||
}
|
||||
"click_to_view_queries": "Kliknite za prikaz poizvedb",
|
||||
"port_53_faq_link": "Vrata 53 pogosto zasedajo storitve 'DNSStubListener' ali 'Sistemsko razrešene storitve'. Preberite <0>to navodilo</0> o tem, kako to rešiti."
|
||||
}
|
||||
|
||||
@@ -208,8 +208,8 @@
|
||||
"page_table_footer_text": "Stranica",
|
||||
"rows_table_footer_text": "redovi",
|
||||
"updated_custom_filtering_toast": "Ažurirana prilagođena pravila filtriranja",
|
||||
"rule_removed_from_custom_filtering_toast": "Pravilo uklonjeno iz prilagođenih pravila filtriranja",
|
||||
"rule_added_to_custom_filtering_toast": "Pravilo dodato u prilagođena pravila filtriranja",
|
||||
"rule_removed_from_custom_filtering_toast": "Pravilo uklonjeno iz prilagođenih pravila filtriranja: {{rule}}",
|
||||
"rule_added_to_custom_filtering_toast": "Pravilo dodato u prilagođena pravila filtriranja: {{rule}}",
|
||||
"query_log_response_status": "Stanje: {{value}}",
|
||||
"query_log_filtered": "Filtrirano od {{filter}}",
|
||||
"query_log_confirm_clear": "Jeste li sigurni da želite da očistite ceo dnevnik unosa?",
|
||||
@@ -562,5 +562,6 @@
|
||||
"filter_category_regional_desc": "Lista koja se usredsređuje na regionalne reklame i servere praćenja",
|
||||
"filter_category_other_desc": "Ostale liste blokiranja",
|
||||
"original_response": "Izvorni odgovor",
|
||||
"click_to_view_queries": "Kliknite da pogledate zahteve"
|
||||
}
|
||||
"click_to_view_queries": "Kliknite da pogledate zahteve",
|
||||
"port_53_faq_link": "Port 53 je najčešće zauzet od \"DNSStubListener\" ili \"systemd-resolved\" usluga. Pročitajte <0>ovo uputstvo</0> kako da to rešite."
|
||||
}
|
||||
|
||||
@@ -162,8 +162,8 @@
|
||||
"page_table_footer_text": "Sida",
|
||||
"rows_table_footer_text": "rader",
|
||||
"updated_custom_filtering_toast": "Uppdaterade de egna filterreglerna",
|
||||
"rule_removed_from_custom_filtering_toast": "Regel borttagen från de egna filterreglerna",
|
||||
"rule_added_to_custom_filtering_toast": "Regel tillagd till de egna filterreglerna",
|
||||
"rule_removed_from_custom_filtering_toast": "Regel borttagen från de egna filterreglerna: {{rule}}",
|
||||
"rule_added_to_custom_filtering_toast": "Regel tillagd till de egna filterreglerna: {{rule}}",
|
||||
"query_log_response_status": "Status: {{value}}",
|
||||
"query_log_filtered": "Filtrerat av {{filter}}",
|
||||
"query_log_confirm_clear": "Är du säker på att du vill rensa hela förfrågningsloggen?",
|
||||
|
||||
@@ -167,8 +167,8 @@
|
||||
"page_table_footer_text": "หน้า",
|
||||
"rows_table_footer_text": "ตาราง",
|
||||
"updated_custom_filtering_toast": "อัปเดตกฎการกรองที่กำหนดเอง",
|
||||
"rule_removed_from_custom_filtering_toast": "ลบกฎออกจากกฎการกรองที่กำหนดเองแล้ว",
|
||||
"rule_added_to_custom_filtering_toast": "เพิ่มกฎในกฎการกรองที่กำหนดเองแล้ว",
|
||||
"rule_removed_from_custom_filtering_toast": "ลบกฎออกจากกฎการกรองที่กำหนดเองแล้ว {{rule}}",
|
||||
"rule_added_to_custom_filtering_toast": "เพิ่มกฎในกฎการกรองที่กำหนดเองแล้ว {{rule}}",
|
||||
"query_log_response_status": "สถานะ: {{value}}",
|
||||
"query_log_filtered": "กรองโดย {{filter}}",
|
||||
"query_log_confirm_clear": "คุณแน่ใจหรือไม่ว่าต้องการลบบันทึกการใช้งานทั้งหมด?",
|
||||
|
||||
@@ -197,8 +197,8 @@
|
||||
"page_table_footer_text": "Sayfa",
|
||||
"rows_table_footer_text": "satır",
|
||||
"updated_custom_filtering_toast": "İsteğe bağlı filtreleme kuralları güncellendi",
|
||||
"rule_removed_from_custom_filtering_toast": "Kural isteğe bağlı filtreleme kurallarından kaldırıldı",
|
||||
"rule_added_to_custom_filtering_toast": "Kural isteğe bağlı filtreleme kurallarına eklendi",
|
||||
"rule_removed_from_custom_filtering_toast": "Özel filtreleme kurallarından kural kaldırıldı: {{rule}}",
|
||||
"rule_added_to_custom_filtering_toast": "Özel filtreleme kurallarına kural eklendi: {{rule}}",
|
||||
"query_log_response_status": "Durum: {{value}}",
|
||||
"query_log_filtered": "{{filter}} tarafından filtrelendi",
|
||||
"query_log_confirm_clear": "Tüm sorgu günlüğünü temizlemek istediğinizden emin misiniz?",
|
||||
@@ -239,7 +239,7 @@
|
||||
"rule_label": "Kural",
|
||||
"list_label": "Liste",
|
||||
"unknown_filter": "Bilinmeyen filtre {{filterId}}",
|
||||
"install_welcome_title": "AdGuard Home'a hoşgeldiniz!",
|
||||
"install_welcome_title": "AdGuard Home'a hoş geldiniz!",
|
||||
"install_welcome_desc": "AdGuard Home, ağ genelinde reklam ve izleyicileri engelleyen bir DNS sunucusudur. Tüm ağınızı ve tüm cihazlarınızı kontrol etmenize yarayan bir araçtır, istemci tarafında bir program kullanmanıza gerek duymaz.",
|
||||
"install_settings_title": "Yönetici Web Arayüzü",
|
||||
"install_settings_listen": "Dinleme arayüzü",
|
||||
@@ -505,5 +505,6 @@
|
||||
"blocked_adult_websites": "Yetişkin içerikli site engellendi",
|
||||
"blocked_threats": "Engellenen Tehditler",
|
||||
"allowed": "İzin verildi",
|
||||
"blocklist": "Engellenen listesi"
|
||||
}
|
||||
"blocklist": "Engellenen listesi",
|
||||
"port_53_faq_link": "Port 53 genellikle \"DNSStubListener\" veya \"sistemd-resolved\" hizmetler tarafından kullanılır. Lütfen problemin nasıl çözüleceğine ilişkin <0>bu talimatı</0> okuyun."
|
||||
}
|
||||
|
||||
@@ -172,8 +172,8 @@
|
||||
"page_table_footer_text": "Trang",
|
||||
"rows_table_footer_text": "hàng",
|
||||
"updated_custom_filtering_toast": "Đã cập nhật quy tắc lọc tuỳ chỉnh",
|
||||
"rule_removed_from_custom_filtering_toast": "Quy tắc đã được xoá khỏi quy tắc lọc tuỳ chỉnh",
|
||||
"rule_added_to_custom_filtering_toast": "Quy tắc đã được thêm vào quy tắc lọc tuỳ chỉnh",
|
||||
"rule_removed_from_custom_filtering_toast": "Quy tắc đã được xoá khỏi quy tắc lọc tuỳ chỉnh {{rule}}",
|
||||
"rule_added_to_custom_filtering_toast": "Quy tắc đã được thêm vào quy tắc lọc tuỳ chỉnh: {{rule}}",
|
||||
"query_log_response_status": "Trạng thái: {{value}}",
|
||||
"query_log_filtered": "Được lọc bởi {{filter}}",
|
||||
"query_log_confirm_clear": "Bạn có chắc chắn muốn xóa toàn bộ nhật ký truy vấn không?",
|
||||
@@ -445,4 +445,4 @@
|
||||
"blocked_threats": "Mối nguy hiểm đã chặn",
|
||||
"allowed": "Được phép",
|
||||
"safe_search": "Tìm kiếm an toàn"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,8 +208,8 @@
|
||||
"page_table_footer_text": "页",
|
||||
"rows_table_footer_text": "行",
|
||||
"updated_custom_filtering_toast": "自定义过滤规则已更新",
|
||||
"rule_removed_from_custom_filtering_toast": "规则已从自定义过滤规则列表中移除",
|
||||
"rule_added_to_custom_filtering_toast": "规则已添加到自定义过滤规则列表中",
|
||||
"rule_removed_from_custom_filtering_toast": "规则已从自定义过滤规则列表中移除 {{rule}}",
|
||||
"rule_added_to_custom_filtering_toast": "规则已添加到自定义过滤规则列表中 {{rule}}",
|
||||
"query_log_response_status": "状态: {{value}}",
|
||||
"query_log_filtered": "被 {{filter}} 过滤",
|
||||
"query_log_confirm_clear": "你确定想要清除全部查询日志吗?",
|
||||
@@ -562,5 +562,6 @@
|
||||
"filter_category_regional_desc": "专注于区域广告和跟踪服务器的列表",
|
||||
"filter_category_other_desc": "其他阻止列表",
|
||||
"original_response": "原始响应",
|
||||
"click_to_view_queries": "点击查看查询"
|
||||
}
|
||||
"click_to_view_queries": "点击查看查询",
|
||||
"port_53_faq_link": "53端口常被DNSStubListener或systemdn解析的服务占用。请阅读<0>这份关于如何解决这一问题的说明</0>"
|
||||
}
|
||||
|
||||
@@ -208,8 +208,8 @@
|
||||
"page_table_footer_text": "頁面",
|
||||
"rows_table_footer_text": "列",
|
||||
"updated_custom_filtering_toast": "已更新自訂的過濾規則",
|
||||
"rule_removed_from_custom_filtering_toast": "規則從自訂的過濾規則中被移除",
|
||||
"rule_added_to_custom_filtering_toast": "規則被加至自訂的過濾規則中",
|
||||
"rule_removed_from_custom_filtering_toast": "規則從自訂的過濾規則中被移除 {{rule}}",
|
||||
"rule_added_to_custom_filtering_toast": "規則被加至自訂的過濾規則中 {{rule}}",
|
||||
"query_log_response_status": "狀態:{{value}}",
|
||||
"query_log_filtered": "被 {{filter}} 過濾",
|
||||
"query_log_confirm_clear": "您確定您想要清除整個查詢記錄嗎?",
|
||||
@@ -544,8 +544,8 @@
|
||||
"milliseconds_abbreviation": "ms",
|
||||
"cache_size": "快取大小",
|
||||
"cache_size_desc": "DNS 快取大小(以位元組)",
|
||||
"cache_ttl_min_override": "覆寫最小的存活時間(TTL)",
|
||||
"cache_ttl_max_override": "覆寫最大的存活時間(TTL)",
|
||||
"cache_ttl_min_override": "覆寫最小的存活時間(TTL)(以秒數)",
|
||||
"cache_ttl_max_override": "覆寫最大的存活時間(TTL)(以秒數)",
|
||||
"enter_cache_size": "輸入快取大小",
|
||||
"enter_cache_ttl_min_override": "輸入最小的存活時間(TTL)",
|
||||
"enter_cache_ttl_max_override": "輸入最大的存活時間(TTL)",
|
||||
@@ -562,5 +562,6 @@
|
||||
"filter_category_regional_desc": "專注於區域性的廣告和追蹤伺服器之清單",
|
||||
"filter_category_other_desc": "其它的封鎖清單",
|
||||
"original_response": "原始的回應",
|
||||
"click_to_view_queries": "點擊以檢視查詢"
|
||||
}
|
||||
"click_to_view_queries": "點擊以檢視查詢",
|
||||
"port_53_faq_link": "連接埠 53 常被 \"DNSStubListener\" 或 \"systemd-resolved\" 服務佔用。請閱讀有關如何解決這個的<0>用法說明</0>。"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getIpMatchListStatus } from '../helpers/helpers';
|
||||
import { getIpMatchListStatus, sortIp } from '../helpers/helpers';
|
||||
import { IP_MATCH_LIST_STATUS } from '../helpers/constants';
|
||||
|
||||
describe('getIpMatchListStatus', () => {
|
||||
@@ -129,3 +129,356 @@ describe('getIpMatchListStatus', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('sortIp', () => {
|
||||
describe('ipv4', () => {
|
||||
test('one octet differ', () => {
|
||||
const arr = [
|
||||
'127.0.2.0',
|
||||
'127.0.3.0',
|
||||
'127.0.1.0',
|
||||
];
|
||||
const sortedArr = [
|
||||
'127.0.1.0',
|
||||
'127.0.2.0',
|
||||
'127.0.3.0',
|
||||
];
|
||||
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
|
||||
});
|
||||
test('few octets differ', () => {
|
||||
const arr = [
|
||||
'192.168.11.10',
|
||||
'192.168.10.0',
|
||||
'192.168.11.11',
|
||||
'192.168.10.10',
|
||||
'192.168.1.10',
|
||||
'192.168.0.1',
|
||||
'192.168.1.0',
|
||||
'192.168.1.1',
|
||||
'192.168.11.0',
|
||||
'192.168.0.10',
|
||||
'192.168.10.11',
|
||||
'192.168.0.11',
|
||||
'192.168.1.11',
|
||||
'192.168.0.0',
|
||||
'192.168.10.1',
|
||||
'192.168.11.1',
|
||||
];
|
||||
const sortedArr = [
|
||||
'192.168.0.0',
|
||||
'192.168.0.1',
|
||||
'192.168.0.10',
|
||||
'192.168.0.11',
|
||||
'192.168.1.0',
|
||||
'192.168.1.1',
|
||||
'192.168.1.10',
|
||||
'192.168.1.11',
|
||||
'192.168.10.0',
|
||||
'192.168.10.1',
|
||||
'192.168.10.10',
|
||||
'192.168.10.11',
|
||||
'192.168.11.0',
|
||||
'192.168.11.1',
|
||||
'192.168.11.10',
|
||||
'192.168.11.11',
|
||||
];
|
||||
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
|
||||
|
||||
// Example from issue https://github.com/AdguardTeam/AdGuardHome/issues/1778#issuecomment-640937599
|
||||
const arr2 = [
|
||||
'192.168.2.11',
|
||||
'192.168.3.1',
|
||||
'192.168.2.100',
|
||||
'192.168.2.2',
|
||||
'192.168.2.1',
|
||||
'192.168.2.10',
|
||||
'192.168.2.99',
|
||||
'192.168.2.200',
|
||||
'192.168.2.199',
|
||||
];
|
||||
const sortedArr2 = [
|
||||
'192.168.2.1',
|
||||
'192.168.2.2',
|
||||
'192.168.2.10',
|
||||
'192.168.2.11',
|
||||
'192.168.2.99',
|
||||
'192.168.2.100',
|
||||
'192.168.2.199',
|
||||
'192.168.2.200',
|
||||
'192.168.3.1',
|
||||
];
|
||||
expect(arr2.sort(sortIp)).toStrictEqual(sortedArr2);
|
||||
});
|
||||
});
|
||||
describe('ipv6', () => {
|
||||
test('only long form', () => {
|
||||
const arr = [
|
||||
'2001:db8:11a3:9d7:0:0:0:2',
|
||||
'2001:db8:11a3:9d7:0:0:0:3',
|
||||
'2001:db8:11a3:9d7:0:0:0:1',
|
||||
];
|
||||
const sortedArr = [
|
||||
'2001:db8:11a3:9d7:0:0:0:1',
|
||||
'2001:db8:11a3:9d7:0:0:0:2',
|
||||
'2001:db8:11a3:9d7:0:0:0:3',
|
||||
];
|
||||
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
|
||||
});
|
||||
test('only short form', () => {
|
||||
const arr = [
|
||||
'2001:db8::',
|
||||
'2001:db7::',
|
||||
'2001:db9::',
|
||||
];
|
||||
const sortedArr = [
|
||||
'2001:db7::',
|
||||
'2001:db8::',
|
||||
'2001:db9::',
|
||||
];
|
||||
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
|
||||
});
|
||||
test('long and short forms', () => {
|
||||
const arr = [
|
||||
'2001:db8::',
|
||||
'2001:db7:11a3:9d7:0:0:0:2',
|
||||
'2001:db6:11a3:9d7:0:0:0:1',
|
||||
'2001:db6::',
|
||||
'2001:db7:11a3:9d7:0:0:0:1',
|
||||
'2001:db7::',
|
||||
];
|
||||
const sortedArr = [
|
||||
'2001:db6::',
|
||||
'2001:db6:11a3:9d7:0:0:0:1',
|
||||
'2001:db7::',
|
||||
'2001:db7:11a3:9d7:0:0:0:1',
|
||||
'2001:db7:11a3:9d7:0:0:0:2',
|
||||
'2001:db8::',
|
||||
];
|
||||
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
|
||||
});
|
||||
});
|
||||
describe('ipv4 and ipv6', () => {
|
||||
test('ipv6 long form', () => {
|
||||
const arr = [
|
||||
'127.0.0.3',
|
||||
'2001:db8:11a3:9d7:0:0:0:1',
|
||||
'2001:db8:11a3:9d7:0:0:0:3',
|
||||
'127.0.0.1',
|
||||
'2001:db8:11a3:9d7:0:0:0:2',
|
||||
'127.0.0.2',
|
||||
];
|
||||
const sortedArr = [
|
||||
'127.0.0.1',
|
||||
'127.0.0.2',
|
||||
'127.0.0.3',
|
||||
'2001:db8:11a3:9d7:0:0:0:1',
|
||||
'2001:db8:11a3:9d7:0:0:0:2',
|
||||
'2001:db8:11a3:9d7:0:0:0:3',
|
||||
];
|
||||
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
|
||||
});
|
||||
test('ipv6 short form', () => {
|
||||
const arr = [
|
||||
'2001:db8:11a3:9d7::1',
|
||||
'127.0.0.3',
|
||||
'2001:db8:11a3:9d7::3',
|
||||
'127.0.0.1',
|
||||
'2001:db8:11a3:9d7::2',
|
||||
'127.0.0.2',
|
||||
];
|
||||
const sortedArr = [
|
||||
'127.0.0.1',
|
||||
'127.0.0.2',
|
||||
'127.0.0.3',
|
||||
'2001:db8:11a3:9d7::1',
|
||||
'2001:db8:11a3:9d7::2',
|
||||
'2001:db8:11a3:9d7::3',
|
||||
];
|
||||
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
|
||||
});
|
||||
test('ipv6 long and short forms', () => {
|
||||
const arr = [
|
||||
'2001:db8:11a3:9d7::1',
|
||||
'127.0.0.3',
|
||||
'2001:db8:11a3:9d7:0:0:0:2',
|
||||
'127.0.0.1',
|
||||
'2001:db8:11a3:9d7::3',
|
||||
'127.0.0.2',
|
||||
];
|
||||
const sortedArr = [
|
||||
'127.0.0.1',
|
||||
'127.0.0.2',
|
||||
'127.0.0.3',
|
||||
'2001:db8:11a3:9d7::1',
|
||||
'2001:db8:11a3:9d7:0:0:0:2',
|
||||
'2001:db8:11a3:9d7::3',
|
||||
];
|
||||
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
|
||||
});
|
||||
test('always put ipv4 before ipv6', () => {
|
||||
const arr = [
|
||||
'::1',
|
||||
'0.0.0.2',
|
||||
'127.0.0.1',
|
||||
'::2',
|
||||
'2001:db8:11a3:9d7:0:0:0:2',
|
||||
'0.0.0.1',
|
||||
'2001:db8:11a3:9d7::1',
|
||||
];
|
||||
const sortedArr = [
|
||||
'0.0.0.1',
|
||||
'0.0.0.2',
|
||||
'127.0.0.1',
|
||||
'::1',
|
||||
'::2',
|
||||
'2001:db8:11a3:9d7::1',
|
||||
'2001:db8:11a3:9d7:0:0:0:2',
|
||||
];
|
||||
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
|
||||
});
|
||||
});
|
||||
describe('cidr', () => {
|
||||
test('only ipv4 cidr', () => {
|
||||
const arr = [
|
||||
'192.168.0.1/9',
|
||||
'192.168.0.1/7',
|
||||
'192.168.0.1/8',
|
||||
];
|
||||
const sortedArr = [
|
||||
'192.168.0.1/7',
|
||||
'192.168.0.1/8',
|
||||
'192.168.0.1/9',
|
||||
];
|
||||
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
|
||||
});
|
||||
test('ipv4 and cidr ipv4', () => {
|
||||
const arr = [
|
||||
'192.168.0.1/9',
|
||||
'192.168.0.1',
|
||||
'192.168.0.1/32',
|
||||
'192.168.0.1/7',
|
||||
'192.168.0.1/8',
|
||||
];
|
||||
const sortedArr = [
|
||||
'192.168.0.1/7',
|
||||
'192.168.0.1/8',
|
||||
'192.168.0.1/9',
|
||||
'192.168.0.1/32',
|
||||
'192.168.0.1',
|
||||
];
|
||||
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
|
||||
});
|
||||
test('only ipv6 cidr', () => {
|
||||
const arr = [
|
||||
'2001:db8:11a3:9d7::1/32',
|
||||
'2001:db8:11a3:9d7::1/64',
|
||||
'2001:db8:11a3:9d7::1/128',
|
||||
'2001:db8:11a3:9d7::1/24',
|
||||
];
|
||||
const sortedArr = [
|
||||
'2001:db8:11a3:9d7::1/24',
|
||||
'2001:db8:11a3:9d7::1/32',
|
||||
'2001:db8:11a3:9d7::1/64',
|
||||
'2001:db8:11a3:9d7::1/128',
|
||||
];
|
||||
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
|
||||
});
|
||||
test('ipv6 and cidr ipv6', () => {
|
||||
const arr = [
|
||||
'2001:db8:11a3:9d7::1/32',
|
||||
'2001:db8:11a3:9d7::1',
|
||||
'2001:db8:11a3:9d7::1/64',
|
||||
'2001:db8:11a3:9d7::1/128',
|
||||
'2001:db8:11a3:9d7::1/24',
|
||||
];
|
||||
const sortedArr = [
|
||||
'2001:db8:11a3:9d7::1/24',
|
||||
'2001:db8:11a3:9d7::1/32',
|
||||
'2001:db8:11a3:9d7::1/64',
|
||||
'2001:db8:11a3:9d7::1/128',
|
||||
'2001:db8:11a3:9d7::1',
|
||||
];
|
||||
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
|
||||
});
|
||||
});
|
||||
describe('invalid input', () => {
|
||||
const originalError = console.error;
|
||||
|
||||
beforeEach(() => {
|
||||
console.error = jest.fn();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
expect(console.error).toHaveBeenCalled();
|
||||
console.error = originalError;
|
||||
});
|
||||
|
||||
test('invalid strings', () => {
|
||||
const arr = ['invalid ip', 'invalid cidr'];
|
||||
expect(arr.sort(sortIp)).toStrictEqual(arr);
|
||||
});
|
||||
test('invalid ip', () => {
|
||||
const arr = ['127.0.0.2.', '.127.0.0.1.', '.2001:db8:11a3:9d7:0:0:0:0'];
|
||||
expect(arr.sort(sortIp)).toStrictEqual(arr);
|
||||
});
|
||||
test('invalid cidr', () => {
|
||||
const arr = ['127.0.0.2/33', '2001:db8:11a3:9d7:0:0:0:0/129'];
|
||||
expect(arr.sort(sortIp)).toStrictEqual(arr);
|
||||
});
|
||||
test('valid and invalid ip', () => {
|
||||
const arr = ['127.0.0.4.', '127.0.0.1', '.127.0.0.3', '127.0.0.2'];
|
||||
expect(arr.sort(sortIp)).toStrictEqual(arr);
|
||||
});
|
||||
});
|
||||
describe('mixed', () => {
|
||||
test('ipv4, ipv6 in short and long forms and cidr', () => {
|
||||
const arr = [
|
||||
'2001:db8:11a3:9d7:0:0:0:1/32',
|
||||
'192.168.1.2',
|
||||
'127.0.0.2',
|
||||
'2001:db8:11a3:9d7::1/128',
|
||||
'2001:db8:11a3:9d7:0:0:0:1',
|
||||
'127.0.0.1/12',
|
||||
'192.168.1.1',
|
||||
'2001:db8::/32',
|
||||
'2001:db8:11a3:9d7::1/24',
|
||||
'192.168.1.2/12',
|
||||
'2001:db7::/32',
|
||||
'127.0.0.1',
|
||||
'2001:db8:11a3:9d7:0:0:0:2',
|
||||
'192.168.1.1/24',
|
||||
'2001:db7::/64',
|
||||
'2001:db7::',
|
||||
'2001:db8::',
|
||||
'2001:db8:11a3:9d7:0:0:0:1/128',
|
||||
'192.168.1.1/12',
|
||||
'127.0.0.1/32',
|
||||
'::1',
|
||||
];
|
||||
const sortedArr = [
|
||||
'127.0.0.1/12',
|
||||
'127.0.0.1/32',
|
||||
'127.0.0.1',
|
||||
'127.0.0.2',
|
||||
'192.168.1.1/12',
|
||||
'192.168.1.1/24',
|
||||
'192.168.1.1',
|
||||
'192.168.1.2/12',
|
||||
'192.168.1.2',
|
||||
'::1',
|
||||
'2001:db7::/32',
|
||||
'2001:db7::/64',
|
||||
'2001:db7::',
|
||||
'2001:db8::/32',
|
||||
'2001:db8::',
|
||||
'2001:db8:11a3:9d7::1/24',
|
||||
'2001:db8:11a3:9d7:0:0:0:1/32',
|
||||
'2001:db8:11a3:9d7::1/128',
|
||||
'2001:db8:11a3:9d7:0:0:0:1/128',
|
||||
'2001:db8:11a3:9d7:0:0:0:1',
|
||||
'2001:db8:11a3:9d7:0:0:0:2',
|
||||
];
|
||||
expect(arr.sort(sortIp)).toStrictEqual(sortedArr);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,11 +2,18 @@ import { createAction } from 'redux-actions';
|
||||
import i18next from 'i18next';
|
||||
import axios from 'axios';
|
||||
|
||||
import { isVersionGreater, splitByNewLine, sortClients } from '../helpers/helpers';
|
||||
import { CHECK_TIMEOUT, SETTINGS_NAMES } from '../helpers/constants';
|
||||
import endsWith from 'lodash/endsWith';
|
||||
import escapeRegExp from 'lodash/escapeRegExp';
|
||||
import React from 'react';
|
||||
import { splitByNewLine, sortClients } from '../helpers/helpers';
|
||||
import {
|
||||
BLOCK_ACTIONS, CHECK_TIMEOUT, STATUS_RESPONSE, SETTINGS_NAMES, FORM_NAME, GETTING_STARTED_LINK,
|
||||
} from '../helpers/constants';
|
||||
import { areEqualVersions } from '../helpers/version';
|
||||
import { getTlsStatus } from './encryption';
|
||||
import apiClient from '../api/Api';
|
||||
import { addErrorToast, addNoticeToast, addSuccessToast } from './toasts';
|
||||
import { getFilteringStatus, setRules } from './filtering';
|
||||
|
||||
export const toggleSettingStatus = createAction('SETTING_STATUS_TOGGLE');
|
||||
export const showSettingsFailure = createAction('SETTINGS_FAILURE_SHOW');
|
||||
@@ -121,7 +128,7 @@ export const getVersion = (recheck = false) => async (dispatch, getState) => {
|
||||
const { dnsVersion } = getState().dashboard;
|
||||
const currentVersion = dnsVersion === 'undefined' ? 0 : dnsVersion;
|
||||
|
||||
if (data && isVersionGreater(currentVersion, data.new_version)) {
|
||||
if (data && !areEqualVersions(currentVersion, data.new_version)) {
|
||||
dispatch(addSuccessToast('updates_checked'));
|
||||
} else {
|
||||
dispatch(addSuccessToast('updates_version_equal'));
|
||||
@@ -147,7 +154,7 @@ const checkStatus = async (handleRequestSuccess, handleRequestError, attempts =
|
||||
const rmTimeout = (t) => t && clearTimeout(t);
|
||||
|
||||
try {
|
||||
const response = await axios.get('control/status');
|
||||
const response = await axios.get(`${apiClient.baseUrl}/status`);
|
||||
rmTimeout(timeout);
|
||||
if (response?.status === 200) {
|
||||
handleRequestSuccess(response);
|
||||
@@ -178,7 +185,14 @@ export const getUpdate = () => async (dispatch, getState) => {
|
||||
|
||||
dispatch(getUpdateRequest());
|
||||
const handleRequestError = () => {
|
||||
dispatch(addNoticeToast({ error: 'update_failed' }));
|
||||
const options = {
|
||||
components: {
|
||||
a: <a href={GETTING_STARTED_LINK} target="_blank"
|
||||
rel="noopener noreferrer" />,
|
||||
},
|
||||
};
|
||||
|
||||
dispatch(addNoticeToast({ error: 'update_failed', options }));
|
||||
dispatch(getUpdateFailure());
|
||||
};
|
||||
|
||||
@@ -341,6 +355,8 @@ export const getDhcpStatus = () => async (dispatch) => {
|
||||
dispatch(getDhcpStatusRequest());
|
||||
try {
|
||||
const status = await apiClient.getDhcpStatus();
|
||||
const globalStatus = await apiClient.getGlobalStatus();
|
||||
status.dhcp_available = globalStatus.dhcp_available;
|
||||
dispatch(getDhcpStatusSuccess(status));
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
@@ -367,11 +383,69 @@ export const findActiveDhcpRequest = createAction('FIND_ACTIVE_DHCP_REQUEST');
|
||||
export const findActiveDhcpSuccess = createAction('FIND_ACTIVE_DHCP_SUCCESS');
|
||||
export const findActiveDhcpFailure = createAction('FIND_ACTIVE_DHCP_FAILURE');
|
||||
|
||||
export const findActiveDhcp = (name) => async (dispatch) => {
|
||||
export const findActiveDhcp = (name) => async (dispatch, getState) => {
|
||||
dispatch(findActiveDhcpRequest());
|
||||
try {
|
||||
const activeDhcp = await apiClient.findActiveDhcp(name);
|
||||
dispatch(findActiveDhcpSuccess(activeDhcp));
|
||||
const { check, interface_name, interfaces } = getState().dhcp;
|
||||
const selectedInterface = getState().form[FORM_NAME.DHCP_INTERFACES].values.interface_name;
|
||||
const v4 = check?.v4 ?? { static_ip: {}, other_server: {} };
|
||||
const v6 = check?.v6 ?? { other_server: {} };
|
||||
|
||||
let isError = false;
|
||||
let isStaticIPError = false;
|
||||
|
||||
const hasV4Interface = !!interfaces[selectedInterface]?.ipv4_addresses;
|
||||
const hasV6Interface = !!interfaces[selectedInterface]?.ipv6_addresses;
|
||||
|
||||
if (hasV4Interface && v4.other_server.found === STATUS_RESPONSE.ERROR) {
|
||||
isError = true;
|
||||
if (v4.other_server.error) {
|
||||
dispatch(addErrorToast({ error: v4.other_server.error }));
|
||||
}
|
||||
}
|
||||
|
||||
if (hasV6Interface && v6.other_server.found === STATUS_RESPONSE.ERROR) {
|
||||
isError = true;
|
||||
if (v6.other_server.error) {
|
||||
dispatch(addErrorToast({ error: v6.other_server.error }));
|
||||
}
|
||||
}
|
||||
|
||||
if (hasV4Interface && v4.static_ip.static === STATUS_RESPONSE.ERROR) {
|
||||
isStaticIPError = true;
|
||||
dispatch(addErrorToast({ error: 'dhcp_static_ip_error' }));
|
||||
}
|
||||
|
||||
|
||||
if (isError) {
|
||||
dispatch(addErrorToast({ error: 'dhcp_error' }));
|
||||
}
|
||||
|
||||
if (isStaticIPError || isError) {
|
||||
// No need to proceed if there was an error discovering DHCP server
|
||||
return;
|
||||
}
|
||||
|
||||
if ((hasV4Interface && v4.other_server.found === STATUS_RESPONSE.YES)
|
||||
|| (hasV6Interface && v6.other_server.found === STATUS_RESPONSE.YES)) {
|
||||
dispatch(addErrorToast({ error: 'dhcp_found' }));
|
||||
} else if (hasV4Interface && v4.static_ip.static === STATUS_RESPONSE.NO
|
||||
&& v4.static_ip.ip
|
||||
&& interface_name) {
|
||||
const warning = i18next.t('dhcp_dynamic_ip_found', {
|
||||
interfaceName: interface_name,
|
||||
ipAddress: v4.static_ip.ip,
|
||||
interpolation: {
|
||||
prefix: '<0>{{',
|
||||
suffix: '}}</0>',
|
||||
},
|
||||
});
|
||||
dispatch(addErrorToast({ error: warning }));
|
||||
} else {
|
||||
dispatch(addSuccessToast('dhcp_not_found'));
|
||||
}
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(findActiveDhcpFailure());
|
||||
@@ -382,14 +456,11 @@ export const setDhcpConfigRequest = createAction('SET_DHCP_CONFIG_REQUEST');
|
||||
export const setDhcpConfigSuccess = createAction('SET_DHCP_CONFIG_SUCCESS');
|
||||
export const setDhcpConfigFailure = createAction('SET_DHCP_CONFIG_FAILURE');
|
||||
|
||||
export const setDhcpConfig = (values) => async (dispatch, getState) => {
|
||||
const { config } = getState().dhcp;
|
||||
const updatedConfig = { ...config, ...values };
|
||||
export const setDhcpConfig = (values) => async (dispatch) => {
|
||||
dispatch(setDhcpConfigRequest());
|
||||
dispatch(findActiveDhcp(values.interface_name));
|
||||
try {
|
||||
await apiClient.setDhcpConfig(updatedConfig);
|
||||
dispatch(setDhcpConfigSuccess(updatedConfig));
|
||||
await apiClient.setDhcpConfig(values);
|
||||
dispatch(setDhcpConfigSuccess(values));
|
||||
dispatch(addSuccessToast('dhcp_config_saved'));
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
@@ -415,7 +486,6 @@ export const toggleDhcp = (values) => async (dispatch) => {
|
||||
enabled: true,
|
||||
};
|
||||
successMessage = 'enabled_dhcp';
|
||||
dispatch(findActiveDhcp(values.interface_name));
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -482,3 +552,44 @@ export const removeStaticLease = (config) => async (dispatch) => {
|
||||
};
|
||||
|
||||
export const removeToast = createAction('REMOVE_TOAST');
|
||||
|
||||
export const toggleBlocking = (
|
||||
type, domain, baseRule, baseUnblocking,
|
||||
) => async (dispatch, getState) => {
|
||||
const baseBlockingRule = baseRule || `||${domain}^$important`;
|
||||
const baseUnblockingRule = baseUnblocking || `@@${baseBlockingRule}`;
|
||||
const { userRules } = getState().filtering;
|
||||
|
||||
const lineEnding = !endsWith(userRules, '\n') ? '\n' : '';
|
||||
|
||||
const blockingRule = type === BLOCK_ACTIONS.BLOCK ? baseUnblockingRule : baseBlockingRule;
|
||||
const unblockingRule = type === BLOCK_ACTIONS.BLOCK ? baseBlockingRule : baseUnblockingRule;
|
||||
const preparedBlockingRule = new RegExp(`(^|\n)${escapeRegExp(blockingRule)}($|\n)`);
|
||||
const preparedUnblockingRule = new RegExp(`(^|\n)${escapeRegExp(unblockingRule)}($|\n)`);
|
||||
|
||||
const matchPreparedBlockingRule = userRules.match(preparedBlockingRule);
|
||||
const matchPreparedUnblockingRule = userRules.match(preparedUnblockingRule);
|
||||
|
||||
if (matchPreparedBlockingRule) {
|
||||
dispatch(setRules(userRules.replace(`${blockingRule}`, '')));
|
||||
dispatch(addSuccessToast(i18next.t('rule_removed_from_custom_filtering_toast', { rule: blockingRule })));
|
||||
} else if (!matchPreparedUnblockingRule) {
|
||||
dispatch(setRules(`${userRules}${lineEnding}${unblockingRule}\n`));
|
||||
dispatch(addSuccessToast(i18next.t('rule_added_to_custom_filtering_toast', { rule: unblockingRule })));
|
||||
} else if (matchPreparedUnblockingRule) {
|
||||
dispatch(addSuccessToast(i18next.t('rule_added_to_custom_filtering_toast', { rule: unblockingRule })));
|
||||
return;
|
||||
} else if (!matchPreparedBlockingRule) {
|
||||
dispatch(addSuccessToast(i18next.t('rule_removed_from_custom_filtering_toast', { rule: blockingRule })));
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(getFilteringStatus());
|
||||
};
|
||||
|
||||
export const toggleBlockingForClient = (type, domain, client) => {
|
||||
const baseRule = `||${domain}^$client='${client.replace(/'/g, '/\'')}'`;
|
||||
const baseUnblocking = `@@${baseRule}`;
|
||||
|
||||
return toggleBlocking(type, domain, baseRule, baseUnblocking);
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ import { createAction } from 'redux-actions';
|
||||
|
||||
import apiClient from '../api/Api';
|
||||
import { addErrorToast } from './toasts';
|
||||
import { HTML_PAGES } from '../helpers/constants';
|
||||
|
||||
export const processLoginRequest = createAction('PROCESS_LOGIN_REQUEST');
|
||||
export const processLoginFailure = createAction('PROCESS_LOGIN_FAILURE');
|
||||
@@ -11,7 +12,8 @@ export const processLogin = (values) => async (dispatch) => {
|
||||
dispatch(processLoginRequest());
|
||||
try {
|
||||
await apiClient.login(values);
|
||||
const dashboardUrl = window.location.origin + window.location.pathname.replace('/login.html', '/');
|
||||
const dashboardUrl = window.location.origin
|
||||
+ window.location.pathname.replace(HTML_PAGES.LOGIN, HTML_PAGES.MAIN);
|
||||
window.location.replace(dashboardUrl);
|
||||
dispatch(processLoginSuccess());
|
||||
} catch (error) {
|
||||
|
||||
@@ -3,9 +3,7 @@ import { createAction } from 'redux-actions';
|
||||
import apiClient from '../api/Api';
|
||||
import { normalizeLogs, getParamsForClientsSearch, addClientInfo } from '../helpers/helpers';
|
||||
import {
|
||||
DEFAULT_LOGS_FILTER,
|
||||
TABLE_DEFAULT_PAGE_SIZE,
|
||||
TABLE_FIRST_PAGE,
|
||||
DEFAULT_LOGS_FILTER, FORM_NAME, QUERY_LOGS_PAGE_LIMIT,
|
||||
} from '../helpers/constants';
|
||||
import { addErrorToast, addSuccessToast } from './toasts';
|
||||
|
||||
@@ -37,15 +35,22 @@ export const getAdditionalLogsRequest = createAction('GET_ADDITIONAL_LOGS_REQUES
|
||||
export const getAdditionalLogsFailure = createAction('GET_ADDITIONAL_LOGS_FAILURE');
|
||||
export const getAdditionalLogsSuccess = createAction('GET_ADDITIONAL_LOGS_SUCCESS');
|
||||
|
||||
const checkFilteredLogs = async (data, filter, dispatch, total) => {
|
||||
const shortPollQueryLogs = async (data, filter, dispatch, getState, total) => {
|
||||
const { logs, oldest } = data;
|
||||
const totalData = total || { logs };
|
||||
|
||||
const needToGetAdditionalLogs = (logs.length < TABLE_DEFAULT_PAGE_SIZE
|
||||
|| totalData.logs.length < TABLE_DEFAULT_PAGE_SIZE)
|
||||
&& oldest !== '';
|
||||
const queryForm = getState().form[FORM_NAME.LOGS_FILTER];
|
||||
const currentQuery = queryForm && queryForm.values.search;
|
||||
const previousQuery = filter?.search;
|
||||
const isQueryTheSame = typeof previousQuery === 'string'
|
||||
&& typeof currentQuery === 'string'
|
||||
&& previousQuery === currentQuery;
|
||||
|
||||
if (needToGetAdditionalLogs) {
|
||||
const isShortPollingNeeded = (logs.length < QUERY_LOGS_PAGE_LIMIT
|
||||
|| totalData.logs.length < QUERY_LOGS_PAGE_LIMIT)
|
||||
&& oldest !== '' && isQueryTheSame;
|
||||
|
||||
if (isShortPollingNeeded) {
|
||||
dispatch(getAdditionalLogsRequest());
|
||||
|
||||
try {
|
||||
@@ -54,7 +59,7 @@ const checkFilteredLogs = async (data, filter, dispatch, total) => {
|
||||
filter,
|
||||
});
|
||||
if (additionalLogs.oldest.length > 0) {
|
||||
return await checkFilteredLogs(additionalLogs, filter, dispatch, {
|
||||
return await shortPollQueryLogs(additionalLogs, filter, dispatch, getState, {
|
||||
logs: [...totalData.logs, ...additionalLogs.logs],
|
||||
oldest: additionalLogs.oldest,
|
||||
});
|
||||
@@ -71,31 +76,25 @@ const checkFilteredLogs = async (data, filter, dispatch, total) => {
|
||||
return totalData;
|
||||
};
|
||||
|
||||
export const setLogsPagination = createAction('LOGS_PAGINATION');
|
||||
export const setLogsPage = createAction('SET_LOG_PAGE');
|
||||
export const toggleDetailedLogs = createAction('TOGGLE_DETAILED_LOGS');
|
||||
|
||||
export const getLogsRequest = createAction('GET_LOGS_REQUEST');
|
||||
export const getLogsFailure = createAction('GET_LOGS_FAILURE');
|
||||
export const getLogsSuccess = createAction('GET_LOGS_SUCCESS');
|
||||
|
||||
export const getLogs = (config) => async (dispatch, getState) => {
|
||||
export const getLogs = () => async (dispatch, getState) => {
|
||||
dispatch(getLogsRequest());
|
||||
try {
|
||||
const { isFiltered, filter, page } = getState().queryLogs;
|
||||
const { isFiltered, filter, oldest } = getState().queryLogs;
|
||||
const data = await getLogsWithParams({
|
||||
...config,
|
||||
older_than: oldest,
|
||||
filter,
|
||||
});
|
||||
|
||||
if (isFiltered) {
|
||||
const additionalData = await checkFilteredLogs(data, filter, dispatch);
|
||||
const additionalData = await shortPollQueryLogs(data, filter, dispatch, getState);
|
||||
const updatedData = additionalData.logs ? { ...data, ...additionalData } : data;
|
||||
dispatch(getLogsSuccess(updatedData));
|
||||
dispatch(setLogsPagination({
|
||||
page,
|
||||
pageSize: TABLE_DEFAULT_PAGE_SIZE,
|
||||
}));
|
||||
} else {
|
||||
dispatch(getLogsSuccess(data));
|
||||
}
|
||||
@@ -111,7 +110,7 @@ export const setLogsFilterRequest = createAction('SET_LOGS_FILTER_REQUEST');
|
||||
*
|
||||
* @param filter
|
||||
* @param {string} filter.search
|
||||
* @param {string} filter.response_status query field of RESPONSE_FILTER object
|
||||
* @param {string} filter.response_status 'QUERY' field of RESPONSE_FILTER object
|
||||
* @returns function
|
||||
*/
|
||||
export const setLogsFilter = (filter) => setLogsFilterRequest(filter);
|
||||
@@ -120,21 +119,20 @@ export const setFilteredLogsRequest = createAction('SET_FILTERED_LOGS_REQUEST');
|
||||
export const setFilteredLogsFailure = createAction('SET_FILTERED_LOGS_FAILURE');
|
||||
export const setFilteredLogsSuccess = createAction('SET_FILTERED_LOGS_SUCCESS');
|
||||
|
||||
export const setFilteredLogs = (filter) => async (dispatch) => {
|
||||
export const setFilteredLogs = (filter) => async (dispatch, getState) => {
|
||||
dispatch(setFilteredLogsRequest());
|
||||
try {
|
||||
const data = await getLogsWithParams({
|
||||
older_than: '',
|
||||
filter,
|
||||
});
|
||||
const additionalData = await checkFilteredLogs(data, filter, dispatch);
|
||||
const additionalData = await shortPollQueryLogs(data, filter, dispatch, getState);
|
||||
const updatedData = additionalData.logs ? { ...data, ...additionalData } : data;
|
||||
|
||||
dispatch(setFilteredLogsSuccess({
|
||||
...updatedData,
|
||||
filter,
|
||||
}));
|
||||
dispatch(setLogsPage(TABLE_FIRST_PAGE));
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(setFilteredLogsFailure(error));
|
||||
|
||||
@@ -1,37 +1,44 @@
|
||||
import axios from 'axios';
|
||||
|
||||
import { getPathWithQueryString } from '../helpers/helpers';
|
||||
import { R_PATH_LAST_PART } from '../helpers/constants';
|
||||
import { QUERY_LOGS_PAGE_LIMIT, HTML_PAGES, R_PATH_LAST_PART } from '../helpers/constants';
|
||||
import { BASE_URL } from '../../constants';
|
||||
|
||||
class Api {
|
||||
baseUrl = 'control';
|
||||
baseUrl = BASE_URL;
|
||||
|
||||
async makeRequest(path, method = 'POST', config) {
|
||||
const url = `${this.baseUrl}/${path}`;
|
||||
|
||||
try {
|
||||
const response = await axios({
|
||||
url: `${this.baseUrl}/${path}`,
|
||||
url,
|
||||
method,
|
||||
...config,
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
const errorPath = `${this.baseUrl}/${path}`;
|
||||
const errorPath = url;
|
||||
if (error.response) {
|
||||
if (error.response.status === 403) {
|
||||
const loginPageUrl = window.location.href.replace(R_PATH_LAST_PART, '/login.html');
|
||||
const { pathname } = document.location;
|
||||
const shouldRedirect = pathname !== HTML_PAGES.LOGIN
|
||||
&& pathname !== HTML_PAGES.INSTALL;
|
||||
|
||||
if (error.response.status === 403 && shouldRedirect) {
|
||||
const loginPageUrl = window.location.href
|
||||
.replace(R_PATH_LAST_PART, HTML_PAGES.LOGIN);
|
||||
window.location.replace(loginPageUrl);
|
||||
return false;
|
||||
}
|
||||
|
||||
throw new Error(`${errorPath} | ${error.response.data} | ${error.response.status}`);
|
||||
}
|
||||
throw new Error(`${errorPath} | ${error.message ? error.message : error}`);
|
||||
throw new Error(`${errorPath} | ${error.message || error}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Global methods
|
||||
GLOBAL_STATUS = { path: 'status', method: 'GET' };
|
||||
GLOBAL_STATUS = { path: 'status', method: 'GET' }
|
||||
|
||||
GLOBAL_TEST_UPSTREAM_DNS = { path: 'test_upstream_dns', method: 'POST' };
|
||||
|
||||
@@ -529,6 +536,8 @@ class Api {
|
||||
|
||||
getQueryLog(params) {
|
||||
const { path, method } = this.GET_QUERY_LOG;
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
params.limit = QUERY_LOGS_PAGE_LIMIT;
|
||||
const url = getPathWithQueryString(path, params);
|
||||
return this.makeRequest(url, method);
|
||||
}
|
||||
|
||||
@@ -28,10 +28,6 @@ body {
|
||||
}
|
||||
|
||||
@media screen and (max-width: 992px) {
|
||||
.container {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.container--wrap {
|
||||
min-height: calc(100vh);
|
||||
}
|
||||
@@ -46,13 +42,6 @@ body {
|
||||
background: linear-gradient(45deg, rgba(99, 125, 120, 1) 0%, rgba(88, 177, 101, 1) 100%);
|
||||
}
|
||||
|
||||
@media (max-width: 575px) {
|
||||
.container {
|
||||
padding-right: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-body--medium {
|
||||
max-height: 20rem;
|
||||
overflow-y: scroll;
|
||||
@@ -69,3 +58,20 @@ body {
|
||||
.mw-75 {
|
||||
max-width: 75% !important;
|
||||
}
|
||||
|
||||
.cursor--not-allowed {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.select--no-warning {
|
||||
margin-bottom: 1.375rem;
|
||||
}
|
||||
|
||||
.button-action {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.logs__row:hover .button-action,
|
||||
.button-action--active {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
@@ -1,30 +1,16 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import { HashRouter, Route } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withTranslation } from 'react-i18next';
|
||||
import LoadingBar from 'react-redux-loading-bar';
|
||||
import { hot } from 'react-hot-loader/root';
|
||||
|
||||
import 'react-table/react-table.css';
|
||||
import '../ui/Tabler.css';
|
||||
import '../ui/ReactTable.css';
|
||||
import './index.css';
|
||||
|
||||
import Header from '../../containers/Header';
|
||||
import Dashboard from '../../containers/Dashboard';
|
||||
import Settings from '../../containers/Settings';
|
||||
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import CustomRules from '../../containers/CustomRules';
|
||||
import DnsBlocklist from '../../containers/DnsBlocklist';
|
||||
import DnsAllowlist from '../../containers/DnsAllowlist';
|
||||
import DnsRewrites from '../../containers/DnsRewrites';
|
||||
|
||||
import Dns from '../../containers/Dns';
|
||||
import Encryption from '../../containers/Encryption';
|
||||
import Dhcp from '../../containers/Dhcp';
|
||||
import Clients from '../../containers/Clients';
|
||||
|
||||
import Logs from '../../containers/Logs';
|
||||
import SetupGuide from '../../containers/SetupGuide';
|
||||
import propTypes from 'prop-types';
|
||||
import Toasts from '../Toasts';
|
||||
import Footer from '../ui/Footer';
|
||||
import Status from '../ui/Status';
|
||||
@@ -35,31 +21,108 @@ import Icons from '../ui/Icons';
|
||||
import i18n from '../../i18n';
|
||||
import Loading from '../ui/Loading';
|
||||
import { FILTERS_URLS, MENU_URLS, SETTINGS_URLS } from '../../helpers/constants';
|
||||
import Services from '../Filters/Services';
|
||||
import { getLogsUrlParams, setHtmlLangAttr } from '../../helpers/helpers';
|
||||
import Header from '../Header';
|
||||
import { changeLanguage, getDnsStatus } from '../../actions';
|
||||
|
||||
class App extends Component {
|
||||
componentDidMount() {
|
||||
this.props.getDnsStatus();
|
||||
}
|
||||
import Dashboard from '../../containers/Dashboard';
|
||||
import SetupGuide from '../../containers/SetupGuide';
|
||||
import Settings from '../../containers/Settings';
|
||||
import Dns from '../../containers/Dns';
|
||||
import Encryption from '../../containers/Encryption';
|
||||
import Dhcp from '../Settings/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';
|
||||
import Logs from '../Logs';
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this.props.dashboard.language !== prevProps.dashboard.language) {
|
||||
this.setLanguage();
|
||||
}
|
||||
}
|
||||
|
||||
reloadPage = () => {
|
||||
window.location.reload();
|
||||
};
|
||||
const ROUTES = [
|
||||
{
|
||||
path: MENU_URLS.root,
|
||||
component: Dashboard,
|
||||
exact: true,
|
||||
},
|
||||
{
|
||||
path: [`${MENU_URLS.logs}${getLogsUrlParams(':search?', ':response_status?')}`, MENU_URLS.logs],
|
||||
component: Logs,
|
||||
},
|
||||
{
|
||||
path: MENU_URLS.guide,
|
||||
component: SetupGuide,
|
||||
},
|
||||
{
|
||||
path: SETTINGS_URLS.settings,
|
||||
component: Settings,
|
||||
},
|
||||
{
|
||||
path: SETTINGS_URLS.dns,
|
||||
component: Dns,
|
||||
},
|
||||
{
|
||||
path: SETTINGS_URLS.encryption,
|
||||
component: Encryption,
|
||||
},
|
||||
{
|
||||
path: SETTINGS_URLS.dhcp,
|
||||
component: Dhcp,
|
||||
},
|
||||
{
|
||||
path: SETTINGS_URLS.clients,
|
||||
component: Clients,
|
||||
},
|
||||
{
|
||||
path: FILTERS_URLS.dns_blocklists,
|
||||
component: DnsBlocklist,
|
||||
},
|
||||
{
|
||||
path: FILTERS_URLS.dns_allowlists,
|
||||
component: DnsAllowlist,
|
||||
},
|
||||
{
|
||||
path: FILTERS_URLS.dns_rewrites,
|
||||
component: DnsRewrites,
|
||||
},
|
||||
{
|
||||
path: FILTERS_URLS.custom_rules,
|
||||
component: CustomRules,
|
||||
},
|
||||
{
|
||||
path: FILTERS_URLS.blocked_services,
|
||||
component: Services,
|
||||
},
|
||||
];
|
||||
|
||||
handleUpdate = () => {
|
||||
this.props.getUpdate();
|
||||
};
|
||||
const renderRoute = ({ path, component, exact }, idx) => <Route
|
||||
key={idx}
|
||||
exact={exact}
|
||||
path={path}
|
||||
component={component}
|
||||
/>;
|
||||
|
||||
setLanguage = () => {
|
||||
const { processing, language } = this.props.dashboard;
|
||||
const App = () => {
|
||||
const dispatch = useDispatch();
|
||||
const {
|
||||
language,
|
||||
isCoreRunning,
|
||||
isUpdateAvailable,
|
||||
processing,
|
||||
} = useSelector((state) => state.dashboard, shallowEqual);
|
||||
|
||||
const { processing: processingEncryption } = useSelector((
|
||||
state,
|
||||
) => state.encryption, shallowEqual);
|
||||
|
||||
const updateAvailable = isCoreRunning && isUpdateAvailable;
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getDnsStatus());
|
||||
}, []);
|
||||
|
||||
const setLanguage = () => {
|
||||
if (!processing) {
|
||||
if (language) {
|
||||
i18n.changeLanguage(language);
|
||||
@@ -68,93 +131,46 @@ class App extends Component {
|
||||
}
|
||||
|
||||
i18n.on('languageChanged', (lang) => {
|
||||
this.props.changeLanguage(lang);
|
||||
dispatch(changeLanguage(lang));
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { dashboard, encryption, getVersion } = this.props;
|
||||
const updateAvailable = dashboard.isCoreRunning && dashboard.isUpdateAvailable;
|
||||
useEffect(() => {
|
||||
setLanguage();
|
||||
}, [language]);
|
||||
|
||||
return (
|
||||
<HashRouter hashType="noslash">
|
||||
<Fragment>
|
||||
{updateAvailable && (
|
||||
<Fragment>
|
||||
<UpdateTopline
|
||||
url={dashboard.announcementUrl}
|
||||
version={dashboard.newVersion}
|
||||
canAutoUpdate={dashboard.canAutoUpdate}
|
||||
getUpdate={this.handleUpdate}
|
||||
processingUpdate={dashboard.processingUpdate}
|
||||
/>
|
||||
<UpdateOverlay processingUpdate={dashboard.processingUpdate} />
|
||||
</Fragment>
|
||||
)}
|
||||
{!encryption.processing && (
|
||||
<EncryptionTopline notAfter={encryption.not_after} />
|
||||
)}
|
||||
<LoadingBar className="loading-bar" updateTime={1000} />
|
||||
<Route component={Header} />
|
||||
<div className="container container--wrap pb-5">
|
||||
{dashboard.processing && <Loading />}
|
||||
{!dashboard.isCoreRunning && (
|
||||
<div className="row row-cards">
|
||||
<div className="col-lg-12">
|
||||
<Status reloadPage={this.reloadPage}
|
||||
message="dns_start"
|
||||
/>
|
||||
<Loading />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!dashboard.processing && dashboard.isCoreRunning && (
|
||||
<>
|
||||
<Route path={MENU_URLS.root} exact component={Dashboard} />
|
||||
<Route
|
||||
path={[`${MENU_URLS.logs}${getLogsUrlParams(':search?', ':response_status?')}`, MENU_URLS.logs]}
|
||||
component={Logs} />
|
||||
<Route path={MENU_URLS.guide} component={SetupGuide} />
|
||||
<Route path={SETTINGS_URLS.settings} component={Settings} />
|
||||
<Route path={SETTINGS_URLS.dns} component={Dns} />
|
||||
<Route path={SETTINGS_URLS.encryption} component={Encryption} />
|
||||
<Route path={SETTINGS_URLS.dhcp} component={Dhcp} />
|
||||
<Route path={SETTINGS_URLS.clients} component={Clients} />
|
||||
<Route path={FILTERS_URLS.dns_blocklists}
|
||||
component={DnsBlocklist} />
|
||||
<Route path={FILTERS_URLS.dns_allowlists}
|
||||
component={DnsAllowlist} />
|
||||
<Route path={FILTERS_URLS.dns_rewrites} component={DnsRewrites} />
|
||||
<Route path={FILTERS_URLS.custom_rules} component={CustomRules} />
|
||||
<Route path={FILTERS_URLS.blocked_services} component={Services} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<Footer
|
||||
dnsVersion={dashboard.dnsVersion}
|
||||
dnsPort={dashboard.dnsPort}
|
||||
processingVersion={dashboard.processingVersion}
|
||||
getVersion={getVersion}
|
||||
checkUpdateFlag={dashboard.checkUpdateFlag}
|
||||
/>
|
||||
<Toasts />
|
||||
<Icons />
|
||||
</Fragment>
|
||||
</HashRouter>
|
||||
);
|
||||
}
|
||||
}
|
||||
const reloadPage = () => {
|
||||
window.location.reload();
|
||||
};
|
||||
|
||||
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,
|
||||
return <HashRouter hashType="noslash">
|
||||
{updateAvailable && <>
|
||||
<UpdateTopline />
|
||||
<UpdateOverlay />
|
||||
</>}
|
||||
{!processingEncryption && <EncryptionTopline />}
|
||||
<LoadingBar className="loading-bar" updateTime={1000} />
|
||||
<Header />
|
||||
<div className="container container--wrap pb-5">
|
||||
{processing && <Loading />}
|
||||
{!isCoreRunning && <div className="row row-cards">
|
||||
<div className="col-lg-12">
|
||||
<Status reloadPage={reloadPage} message="dns_start" />
|
||||
<Loading />
|
||||
</div>
|
||||
</div>}
|
||||
{!processing && isCoreRunning && ROUTES.map(renderRoute)}
|
||||
</div>
|
||||
<Footer />
|
||||
<Toasts />
|
||||
<Icons />
|
||||
</HashRouter>;
|
||||
};
|
||||
|
||||
export default withTranslation()(App);
|
||||
renderRoute.propTypes = {
|
||||
path: propTypes.oneOfType([propTypes.string, propTypes.arrayOf(propTypes.string)]).isRequired,
|
||||
component: propTypes.element.isRequired,
|
||||
exact: propTypes.bool,
|
||||
};
|
||||
|
||||
export default hot(App);
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import React from 'react';
|
||||
import ReactTable from 'react-table';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Trans, withTranslation } from 'react-i18next';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
|
||||
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||
import classNames from 'classnames';
|
||||
import Card from '../ui/Card';
|
||||
import Cell from '../ui/Cell';
|
||||
|
||||
import { getPercent, getIpMatchListStatus } from '../../helpers/helpers';
|
||||
import { IP_MATCH_LIST_STATUS, STATUS_COLORS } from '../../helpers/constants';
|
||||
import { formatClientCell } from '../../helpers/formatClientCell';
|
||||
import { getPercent, getIpMatchListStatus, sortIp } from '../../helpers/helpers';
|
||||
import { BLOCK_ACTIONS, IP_MATCH_LIST_STATUS, STATUS_COLORS } from '../../helpers/constants';
|
||||
import { toggleClientBlock } from '../../actions/access';
|
||||
import { renderFormattedClientCell } from '../../helpers/renderFormattedClientCell';
|
||||
|
||||
const getClientsPercentColor = (percent) => {
|
||||
if (percent > 50) {
|
||||
@@ -20,126 +23,132 @@ const getClientsPercentColor = (percent) => {
|
||||
return STATUS_COLORS.red;
|
||||
};
|
||||
|
||||
const countCell = (dnsQueries) => function cell(row) {
|
||||
const { value } = row;
|
||||
const percent = getPercent(dnsQueries, value);
|
||||
const CountCell = (row) => {
|
||||
const { value, original: { ip } } = row;
|
||||
const numDnsQueries = useSelector((state) => state.stats.numDnsQueries, shallowEqual);
|
||||
|
||||
const percent = getPercent(numDnsQueries, value);
|
||||
const percentColor = getClientsPercentColor(percent);
|
||||
|
||||
return <Cell value={value} percent={percent} color={percentColor} search={row.original.ip} />;
|
||||
return <Cell value={value} percent={percent} color={percentColor} search={ip} />;
|
||||
};
|
||||
|
||||
const renderBlockingButton = (ipMatchListStatus, ip, handleClick, processing) => {
|
||||
const buttonProps = ipMatchListStatus === IP_MATCH_LIST_STATUS.NOT_FOUND
|
||||
? {
|
||||
className: 'btn-outline-danger',
|
||||
text: 'block',
|
||||
type: 'block',
|
||||
const renderBlockingButton = (ip) => {
|
||||
const dispatch = useDispatch();
|
||||
const { t } = useTranslation();
|
||||
const processingSet = useSelector((state) => state.access.processingSet);
|
||||
const disallowed_clients = useSelector(
|
||||
(state) => state.access.disallowed_clients, shallowEqual,
|
||||
);
|
||||
|
||||
const ipMatchListStatus = getIpMatchListStatus(ip, disallowed_clients);
|
||||
|
||||
if (ipMatchListStatus === IP_MATCH_LIST_STATUS.CIDR) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isNotFound = ipMatchListStatus === IP_MATCH_LIST_STATUS.NOT_FOUND;
|
||||
const type = isNotFound ? BLOCK_ACTIONS.BLOCK : BLOCK_ACTIONS.UNBLOCK;
|
||||
const text = type;
|
||||
|
||||
const buttonClass = classNames('button-action button-action--main', {
|
||||
'button-action--unblock': !isNotFound,
|
||||
});
|
||||
|
||||
const toggleClientStatus = (type, ip) => {
|
||||
const confirmMessage = type === BLOCK_ACTIONS.BLOCK
|
||||
? `${t('adg_will_drop_dns_queries')} ${t('client_confirm_block', { ip })}`
|
||||
: t('client_confirm_unblock', { ip });
|
||||
|
||||
if (window.confirm(confirmMessage)) {
|
||||
dispatch(toggleClientBlock(type, ip));
|
||||
}
|
||||
: {
|
||||
className: 'btn-outline-secondary',
|
||||
text: 'unblock',
|
||||
type: 'unblock',
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="table__action button__action">
|
||||
<button
|
||||
type="button"
|
||||
className={`btn btn-sm ${buttonProps.className}`}
|
||||
onClick={() => handleClick(buttonProps.type, ip)}
|
||||
disabled={processing}
|
||||
>
|
||||
<Trans>{buttonProps.text}</Trans>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
const onClick = () => toggleClientStatus(type, ip);
|
||||
|
||||
return <div className="table__action pl-4">
|
||||
<button
|
||||
type="button"
|
||||
className={buttonClass}
|
||||
onClick={onClick}
|
||||
disabled={processingSet}
|
||||
>
|
||||
<Trans>{text}</Trans>
|
||||
</button>
|
||||
</div>;
|
||||
};
|
||||
|
||||
const clientCell = (t, toggleClientStatus, processing, disallowedClients) => function cell(row) {
|
||||
const { value } = row;
|
||||
const ipMatchListStatus = getIpMatchListStatus(value, disallowedClients);
|
||||
const ClientCell = (row) => {
|
||||
const { value, original: { info } } = row;
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="logs__row logs__row--overflow logs__row--column">
|
||||
{formatClientCell(row, true)}
|
||||
</div>
|
||||
{ipMatchListStatus !== IP_MATCH_LIST_STATUS.CIDR
|
||||
&& renderBlockingButton(ipMatchListStatus, value, toggleClientStatus, processing)}
|
||||
</>
|
||||
);
|
||||
return <>
|
||||
<div className="logs__row logs__row--overflow logs__row--column d-flex align-items-center">
|
||||
{renderFormattedClientCell(value, info, true)}
|
||||
{renderBlockingButton(value)}
|
||||
</div>
|
||||
</>;
|
||||
};
|
||||
|
||||
const Clients = ({
|
||||
t,
|
||||
refreshButton,
|
||||
topClients,
|
||||
subtitle,
|
||||
dnsQueries,
|
||||
toggleClientStatus,
|
||||
processingAccessSet,
|
||||
disallowedClients,
|
||||
}) => (
|
||||
<Card
|
||||
title={t('top_clients')}
|
||||
subtitle={subtitle}
|
||||
bodyType="card-table"
|
||||
refresh={refreshButton}
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const topClients = useSelector((state) => state.stats.topClients, shallowEqual);
|
||||
const disallowedClients = useSelector((state) => state.access.disallowed_clients, shallowEqual);
|
||||
|
||||
return <Card
|
||||
title={t('top_clients')}
|
||||
subtitle={subtitle}
|
||||
bodyType="card-table"
|
||||
refresh={refreshButton}
|
||||
>
|
||||
<ReactTable
|
||||
data={topClients.map(({
|
||||
name: ip, count, info, blocked,
|
||||
}) => ({
|
||||
ip,
|
||||
count,
|
||||
info,
|
||||
blocked,
|
||||
}))}
|
||||
columns={[
|
||||
{
|
||||
Header: 'IP',
|
||||
accessor: 'ip',
|
||||
sortMethod: (a, b) => parseInt(a.replace(/\./g, ''), 10) - parseInt(b.replace(/\./g, ''), 10),
|
||||
Cell: clientCell(t, toggleClientStatus, processingAccessSet, disallowedClients),
|
||||
},
|
||||
{
|
||||
Header: <Trans>requests_count</Trans>,
|
||||
accessor: 'count',
|
||||
minWidth: 180,
|
||||
maxWidth: 200,
|
||||
Cell: countCell(dnsQueries),
|
||||
},
|
||||
]}
|
||||
showPagination={false}
|
||||
noDataText={t('no_clients_found')}
|
||||
minRows={6}
|
||||
defaultPageSize={100}
|
||||
className="-highlight card-table-overflow--limited clients__table"
|
||||
getTrProps={(_state, rowInfo) => {
|
||||
if (!rowInfo) {
|
||||
return {};
|
||||
}
|
||||
data={topClients.map(({
|
||||
name: ip, count, info, blocked,
|
||||
}) => ({
|
||||
ip,
|
||||
count,
|
||||
info,
|
||||
blocked,
|
||||
}))}
|
||||
columns={[
|
||||
{
|
||||
Header: 'IP',
|
||||
accessor: 'ip',
|
||||
sortMethod: sortIp,
|
||||
Cell: ClientCell,
|
||||
},
|
||||
{
|
||||
Header: <Trans>requests_count</Trans>,
|
||||
accessor: 'count',
|
||||
minWidth: 180,
|
||||
maxWidth: 200,
|
||||
Cell: CountCell,
|
||||
},
|
||||
]}
|
||||
showPagination={false}
|
||||
noDataText={t('no_clients_found')}
|
||||
minRows={6}
|
||||
defaultPageSize={100}
|
||||
className="-highlight card-table-overflow--limited clients__table"
|
||||
getTrProps={(_state, rowInfo) => {
|
||||
if (!rowInfo) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const { ip } = rowInfo.original;
|
||||
const { ip } = rowInfo.original;
|
||||
|
||||
return getIpMatchListStatus(ip, disallowedClients)
|
||||
=== IP_MATCH_LIST_STATUS.NOT_FOUND ? {} : { className: 'red' };
|
||||
}}
|
||||
return getIpMatchListStatus(ip, disallowedClients) === IP_MATCH_LIST_STATUS.NOT_FOUND ? {} : { className: 'red' };
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
|
||||
Clients.propTypes = {
|
||||
topClients: PropTypes.array.isRequired,
|
||||
dnsQueries: PropTypes.number.isRequired,
|
||||
refreshButton: PropTypes.node.isRequired,
|
||||
clients: PropTypes.array.isRequired,
|
||||
autoClients: PropTypes.array.isRequired,
|
||||
subtitle: PropTypes.string.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
toggleClientStatus: PropTypes.func.isRequired,
|
||||
processingAccessSet: PropTypes.bool.isRequired,
|
||||
disallowedClients: PropTypes.string.isRequired,
|
||||
</Card>;
|
||||
};
|
||||
|
||||
export default withTranslation()(Clients);
|
||||
Clients.propTypes = {
|
||||
refreshButton: PropTypes.node.isRequired,
|
||||
subtitle: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default Clients;
|
||||
|
||||
@@ -21,7 +21,7 @@ const Row = ({
|
||||
<Trans components={translationComponents}>{label}</Trans>
|
||||
<Tooltip content={tooltipTitle} placement="top"
|
||||
className="tooltip-container tooltip-custom--narrow text-center">
|
||||
<svg className="icons icon--20 icon--lightgray ml-1">
|
||||
<svg className="icons icon--20 icon--lightgray ml-2">
|
||||
<use xlinkHref="#question" />
|
||||
</svg>
|
||||
</Tooltip>
|
||||
@@ -47,32 +47,32 @@ const Counters = ({ refreshButton, subtitle }) => {
|
||||
label: 'dns_query',
|
||||
count: numDnsQueries,
|
||||
tooltipTitle: interval === 1 ? 'number_of_dns_query_24_hours' : t('number_of_dns_query_days', { count: interval }),
|
||||
response_status: RESPONSE_FILTER.ALL.query,
|
||||
response_status: RESPONSE_FILTER.ALL.QUERY,
|
||||
},
|
||||
{
|
||||
label: 'blocked_by',
|
||||
count: numBlockedFiltering,
|
||||
tooltipTitle: 'number_of_dns_query_blocked_24_hours',
|
||||
response_status: RESPONSE_FILTER.BLOCKED.query,
|
||||
response_status: RESPONSE_FILTER.BLOCKED.QUERY,
|
||||
translationComponents: [<a href="#filters" key="0">link</a>],
|
||||
},
|
||||
{
|
||||
label: 'stats_malware_phishing',
|
||||
count: numReplacedSafebrowsing,
|
||||
tooltipTitle: 'number_of_dns_query_blocked_24_hours_by_sec',
|
||||
response_status: RESPONSE_FILTER.BLOCKED_THREATS.query,
|
||||
response_status: RESPONSE_FILTER.BLOCKED_THREATS.QUERY,
|
||||
},
|
||||
{
|
||||
label: 'stats_adult',
|
||||
count: numReplacedParental,
|
||||
tooltipTitle: 'number_of_dns_query_blocked_24_hours_adult',
|
||||
response_status: RESPONSE_FILTER.BLOCKED_ADULT_WEBSITES.query,
|
||||
response_status: RESPONSE_FILTER.BLOCKED_ADULT_WEBSITES.QUERY,
|
||||
},
|
||||
{
|
||||
label: 'enforced_save_search',
|
||||
count: numReplacedSafesearch,
|
||||
tooltipTitle: 'number_of_dns_query_to_safe_search',
|
||||
response_status: RESPONSE_FILTER.SAFE_SEARCH.query,
|
||||
response_status: RESPONSE_FILTER.SAFE_SEARCH.QUERY,
|
||||
},
|
||||
{
|
||||
label: 'average_processing_time',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Trans, withTranslation } from 'react-i18next';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
|
||||
import Statistics from './Statistics';
|
||||
import Counters from './Counters';
|
||||
@@ -10,147 +10,133 @@ import BlockedDomains from './BlockedDomains';
|
||||
|
||||
import PageTitle from '../ui/PageTitle';
|
||||
import Loading from '../ui/Loading';
|
||||
import { BLOCK_ACTIONS } from '../../helpers/constants';
|
||||
import './Dashboard.css';
|
||||
|
||||
class Dashboard extends Component {
|
||||
componentDidMount() {
|
||||
this.getAllStats();
|
||||
}
|
||||
const Dashboard = ({
|
||||
getAccessList,
|
||||
getStats,
|
||||
getStatsConfig,
|
||||
dashboard,
|
||||
toggleProtection,
|
||||
stats,
|
||||
access,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
getAllStats = () => {
|
||||
this.props.getAccessList();
|
||||
this.props.getStats();
|
||||
this.props.getStatsConfig();
|
||||
const getAllStats = () => {
|
||||
getAccessList();
|
||||
getStats();
|
||||
getStatsConfig();
|
||||
};
|
||||
|
||||
getToggleFilteringButton = () => {
|
||||
const { protectionEnabled, processingProtection } = this.props.dashboard;
|
||||
useEffect(() => {
|
||||
getAllStats();
|
||||
}, []);
|
||||
|
||||
const getToggleFilteringButton = () => {
|
||||
const { protectionEnabled, processingProtection } = dashboard;
|
||||
const buttonText = protectionEnabled ? 'disable_protection' : 'enable_protection';
|
||||
const buttonClass = protectionEnabled ? 'btn-gray' : 'btn-success';
|
||||
|
||||
return (
|
||||
<button
|
||||
return <button
|
||||
type="button"
|
||||
className={`btn btn-sm mr-2 ${buttonClass}`}
|
||||
onClick={() => this.props.toggleProtection(protectionEnabled)}
|
||||
onClick={() => toggleProtection(protectionEnabled)}
|
||||
disabled={processingProtection}
|
||||
>
|
||||
<Trans>{buttonText}</Trans>
|
||||
</button>
|
||||
);
|
||||
>
|
||||
<Trans>{buttonText}</Trans>
|
||||
</button>;
|
||||
};
|
||||
|
||||
toggleClientStatus = (type, ip) => {
|
||||
const confirmMessage = type === BLOCK_ACTIONS.BLOCK ? 'client_confirm_block' : 'client_confirm_unblock';
|
||||
const refreshButton = <button
|
||||
type="button"
|
||||
className="btn btn-icon btn-outline-primary btn-sm"
|
||||
onClick={() => getAllStats()}
|
||||
>
|
||||
<svg className="icons">
|
||||
<use xlinkHref="#refresh" />
|
||||
</svg>
|
||||
</button>;
|
||||
|
||||
if (window.confirm(this.props.t(confirmMessage, { ip }))) {
|
||||
this.props.toggleClientBlock(type, ip);
|
||||
}
|
||||
};
|
||||
const subtitle = stats.interval === 1
|
||||
? t('for_last_24_hours')
|
||||
: t('for_last_days', { count: stats.interval });
|
||||
|
||||
render() {
|
||||
const {
|
||||
dashboard, stats, access, t,
|
||||
} = this.props;
|
||||
const statsProcessing = stats.processingStats
|
||||
const refreshFullButton = <button
|
||||
type="button"
|
||||
className="btn btn-outline-primary btn-sm"
|
||||
onClick={() => getAllStats()}
|
||||
>
|
||||
<Trans>refresh_statics</Trans>
|
||||
</button>;
|
||||
|
||||
const statsProcessing = stats.processingStats
|
||||
|| stats.processingGetConfig
|
||||
|| access.processing;
|
||||
|
||||
const subtitle = stats.interval === 1
|
||||
? t('for_last_24_hours')
|
||||
: t('for_last_days', { count: stats.interval });
|
||||
return <>
|
||||
<PageTitle title={t('dashboard')}>
|
||||
<div className="page-title__actions">
|
||||
{getToggleFilteringButton()}
|
||||
{refreshFullButton}
|
||||
</div>
|
||||
</PageTitle>
|
||||
{statsProcessing && <Loading />}
|
||||
{!statsProcessing && <div className="row row-cards">
|
||||
<div className="col-lg-12">
|
||||
<Statistics
|
||||
interval={stats.interval}
|
||||
dnsQueries={stats.dnsQueries}
|
||||
blockedFiltering={stats.blockedFiltering}
|
||||
replacedSafebrowsing={stats.replacedSafebrowsing}
|
||||
replacedParental={stats.replacedParental}
|
||||
numDnsQueries={stats.numDnsQueries}
|
||||
numBlockedFiltering={stats.numBlockedFiltering}
|
||||
numReplacedSafebrowsing={stats.numReplacedSafebrowsing}
|
||||
numReplacedParental={stats.numReplacedParental}
|
||||
refreshButton={refreshButton}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-lg-6">
|
||||
<Counters
|
||||
subtitle={subtitle}
|
||||
|
||||
const refreshFullButton = (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-outline-primary btn-sm"
|
||||
onClick={() => this.getAllStats()}
|
||||
>
|
||||
<Trans>refresh_statics</Trans>
|
||||
</button>
|
||||
);
|
||||
|
||||
const refreshButton = (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-icon btn-outline-primary btn-sm"
|
||||
onClick={() => this.getAllStats()}
|
||||
>
|
||||
<svg className="icons">
|
||||
<use xlinkHref="#refresh" />
|
||||
</svg>
|
||||
</button>
|
||||
);
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<PageTitle title={t('dashboard')}>
|
||||
<div className="page-title__actions">
|
||||
{this.getToggleFilteringButton()}
|
||||
{refreshFullButton}
|
||||
</div>
|
||||
</PageTitle>
|
||||
{statsProcessing && <Loading />}
|
||||
{!statsProcessing && (
|
||||
<div className="row row-cards">
|
||||
<div className="col-lg-12">
|
||||
<Statistics
|
||||
interval={stats.interval}
|
||||
dnsQueries={stats.dnsQueries}
|
||||
blockedFiltering={stats.blockedFiltering}
|
||||
replacedSafebrowsing={stats.replacedSafebrowsing}
|
||||
replacedParental={stats.replacedParental}
|
||||
numDnsQueries={stats.numDnsQueries}
|
||||
numBlockedFiltering={stats.numBlockedFiltering}
|
||||
numReplacedSafebrowsing={stats.numReplacedSafebrowsing}
|
||||
numReplacedParental={stats.numReplacedParental}
|
||||
refreshButton={refreshButton}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-lg-6">
|
||||
<Counters
|
||||
subtitle={subtitle}
|
||||
refreshButton={refreshButton}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-lg-6">
|
||||
<Clients
|
||||
subtitle={subtitle}
|
||||
dnsQueries={stats.numDnsQueries}
|
||||
topClients={stats.topClients}
|
||||
clients={dashboard.clients}
|
||||
autoClients={dashboard.autoClients}
|
||||
refreshButton={refreshButton}
|
||||
toggleClientStatus={this.toggleClientStatus}
|
||||
processingAccessSet={access.processingSet}
|
||||
disallowedClients={access.disallowed_clients}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-lg-6">
|
||||
<QueriedDomains
|
||||
subtitle={subtitle}
|
||||
dnsQueries={stats.numDnsQueries}
|
||||
topQueriedDomains={stats.topQueriedDomains}
|
||||
refreshButton={refreshButton}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-lg-6">
|
||||
<BlockedDomains
|
||||
subtitle={subtitle}
|
||||
topBlockedDomains={stats.topBlockedDomains}
|
||||
blockedFiltering={stats.numBlockedFiltering}
|
||||
replacedSafebrowsing={stats.numReplacedSafebrowsing}
|
||||
replacedParental={stats.numReplacedParental}
|
||||
refreshButton={refreshButton}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
refreshButton={refreshButton}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-lg-6">
|
||||
<Clients
|
||||
subtitle={subtitle}
|
||||
dnsQueries={stats.numDnsQueries}
|
||||
topClients={stats.topClients}
|
||||
clients={dashboard.clients}
|
||||
autoClients={dashboard.autoClients}
|
||||
refreshButton={refreshButton}
|
||||
processingAccessSet={access.processingSet}
|
||||
disallowedClients={access.disallowed_clients}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-lg-6">
|
||||
<QueriedDomains
|
||||
subtitle={subtitle}
|
||||
dnsQueries={stats.numDnsQueries}
|
||||
topQueriedDomains={stats.topQueriedDomains}
|
||||
refreshButton={refreshButton}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-lg-6">
|
||||
<BlockedDomains
|
||||
subtitle={subtitle}
|
||||
topBlockedDomains={stats.topBlockedDomains}
|
||||
blockedFiltering={stats.numBlockedFiltering}
|
||||
replacedSafebrowsing={stats.numReplacedSafebrowsing}
|
||||
replacedParental={stats.numReplacedParental}
|
||||
refreshButton={refreshButton}
|
||||
/>
|
||||
</div>
|
||||
</div>}
|
||||
</>;
|
||||
};
|
||||
|
||||
Dashboard.propTypes = {
|
||||
dashboard: PropTypes.object.isRequired,
|
||||
@@ -160,9 +146,7 @@ Dashboard.propTypes = {
|
||||
getStatsConfig: PropTypes.func.isRequired,
|
||||
toggleProtection: PropTypes.func.isRequired,
|
||||
getClients: PropTypes.func.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
toggleClientBlock: PropTypes.func.isRequired,
|
||||
getAccessList: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default withTranslation()(Dashboard);
|
||||
export default Dashboard;
|
||||
|
||||
@@ -11,25 +11,10 @@ import {
|
||||
checkWhiteList,
|
||||
checkSafeSearch,
|
||||
checkSafeBrowsing,
|
||||
checkParental,
|
||||
checkParental, getFilterName,
|
||||
} from '../../../helpers/helpers';
|
||||
import { FILTERED } from '../../../helpers/constants';
|
||||
|
||||
const getFilterName = (id, filters, whitelistFilters, t) => {
|
||||
if (id === 0) {
|
||||
return t('filtered_custom_rules');
|
||||
}
|
||||
|
||||
const filter = filters.find((filter) => filter.id === id)
|
||||
|| whitelistFilters.find((filter) => filter.id === id);
|
||||
|
||||
if (filter && filter.name) {
|
||||
return t('query_log_filtered', { filter: filter.name });
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
const getTitle = (reason, filterName, t, onlyFiltered) => {
|
||||
if (checkNotFilteredNotFound(reason)) {
|
||||
return t('check_not_found');
|
||||
@@ -101,7 +86,12 @@ const Info = ({
|
||||
ip_addrs,
|
||||
t,
|
||||
}) => {
|
||||
const filterName = getFilterName(filter_id, filters, whitelistFilters, t);
|
||||
const filterName = getFilterName(filters,
|
||||
whitelistFilters,
|
||||
filter_id,
|
||||
'filtered_custom_rules',
|
||||
(filter) => (filter?.name ? t('query_log_filtered', { filter: filter.name }) : ''));
|
||||
|
||||
const onlyFiltered = checkSafeSearch(reason)
|
||||
|| checkSafeBrowsing(reason)
|
||||
|| checkParental(reason);
|
||||
|
||||
@@ -5,7 +5,7 @@ import { withTranslation } from 'react-i18next';
|
||||
import flow from 'lodash/flow';
|
||||
import classNames from 'classnames';
|
||||
import { validatePath, validateRequiredValue } from '../../helpers/validators';
|
||||
import { renderInputField, renderSelectField } from '../../helpers/form';
|
||||
import { renderCheckboxField, renderInputField } from '../../helpers/form';
|
||||
import { MODAL_OPEN_TIMEOUT, MODAL_TYPE, FORM_NAME } from '../../helpers/constants';
|
||||
|
||||
const getIconsData = (homepage, source) => ([
|
||||
@@ -60,7 +60,7 @@ const renderFilters = ({ categories, filters }, selectedSources, t) => Object.ke
|
||||
<Field
|
||||
name={`${filter.id}`}
|
||||
type="checkbox"
|
||||
component={renderSelectField}
|
||||
component={renderCheckboxField}
|
||||
placeholder={t(name)}
|
||||
disabled={isSelected}
|
||||
checked={isSelected}
|
||||
@@ -148,13 +148,13 @@ const Form = (props) => {
|
||||
>
|
||||
{t('cancel_btn')}
|
||||
</button>
|
||||
<button
|
||||
{modalType !== MODAL_TYPE.SELECT_MODAL_TYPE && <button
|
||||
type="submit"
|
||||
className="btn btn-success"
|
||||
disabled={processingAddFilter || processingConfigFilter}
|
||||
>
|
||||
{t('save_btn')}
|
||||
</button>
|
||||
</button>}
|
||||
</div>
|
||||
</form>;
|
||||
};
|
||||
|
||||
@@ -62,23 +62,13 @@ class Table extends Component {
|
||||
showPagination
|
||||
defaultPageSize={10}
|
||||
minRows={5}
|
||||
previousText={
|
||||
<svg className="icons icon--24 icon--gray">
|
||||
<use xlinkHref="#arrow-left" />
|
||||
</svg>}
|
||||
nextText={
|
||||
<svg className="icons icon--24 icon--gray">
|
||||
<use xlinkHref="#arrow-right" />
|
||||
</svg>}
|
||||
loadingText={t('loading_table_status')}
|
||||
pageText=''
|
||||
ofText=''
|
||||
ofText="/"
|
||||
previousText={t('previous_btn')}
|
||||
nextText={t('next_btn')}
|
||||
pageText={t('page_table_footer_text')}
|
||||
rowsText={t('rows_table_footer_text')}
|
||||
loadingText={t('loading_table_status')}
|
||||
noDataText={t('rewrite_not_found')}
|
||||
showPageSizeOptions={false}
|
||||
showPageJump={false}
|
||||
renderTotalPagesCount={() => false}
|
||||
getPaginationProps={() => ({ className: 'custom-pagination' })}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -59,6 +59,4 @@ const Services = () => {
|
||||
);
|
||||
};
|
||||
|
||||
Services.propTypes = {};
|
||||
|
||||
export default Services;
|
||||
|
||||
@@ -48,7 +48,7 @@ class Table extends Component {
|
||||
accessor: 'url',
|
||||
minWidth: 200,
|
||||
Cell: ({ value }) => (
|
||||
<div className="logs__row o-hidden">
|
||||
<div className="logs__row">
|
||||
{isValidAbsolutePath(value) ? value
|
||||
: <a
|
||||
href={value}
|
||||
@@ -128,24 +128,15 @@ class Table extends Component {
|
||||
columns={this.columns}
|
||||
showPagination
|
||||
defaultPageSize={10}
|
||||
showPageSizeOptions={false}
|
||||
showPageJump={false}
|
||||
renderTotalPagesCount={() => false}
|
||||
loading={loading}
|
||||
minRows={6}
|
||||
pageText=''
|
||||
ofText=''
|
||||
ofText="/"
|
||||
previousText={t('previous_btn')}
|
||||
nextText={t('next_btn')}
|
||||
pageText={t('page_table_footer_text')}
|
||||
rowsText={t('rows_table_footer_text')}
|
||||
loadingText={t('loading_table_status')}
|
||||
noDataText={whitelist ? t('no_whitelist_added') : t('no_blocklist_added')}
|
||||
getPaginationProps={() => ({ className: 'custom-pagination' })}
|
||||
previousText={
|
||||
<svg className="icons icon--24 icon--gray w-100 h-100">
|
||||
<use xlinkHref="#arrow-left" />
|
||||
</svg>}
|
||||
nextText={
|
||||
<svg className="icons icon--24 icon--gray w-100 h-100">
|
||||
<use xlinkHref="#arrow-right" />
|
||||
</svg>}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -164,6 +164,10 @@
|
||||
color: #9aa0ac;
|
||||
}
|
||||
|
||||
.nav-icon--white {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.header-brand-img {
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
@@ -90,9 +90,8 @@ class Menu extends Component {
|
||||
};
|
||||
|
||||
getActiveClassForDropdown = (URLS) => {
|
||||
const { pathname } = this.props.location;
|
||||
const isActivePage = Object.values(URLS)
|
||||
.some((item) => item === pathname);
|
||||
.some((item) => item === this.props.pathname);
|
||||
|
||||
return isActivePage ? 'active' : '';
|
||||
};
|
||||
@@ -180,9 +179,9 @@ class Menu extends Component {
|
||||
}
|
||||
|
||||
Menu.propTypes = {
|
||||
isMenuOpen: PropTypes.bool,
|
||||
closeMenu: PropTypes.func,
|
||||
location: PropTypes.object,
|
||||
isMenuOpen: PropTypes.bool.isRequired,
|
||||
closeMenu: PropTypes.func.isRequired,
|
||||
pathname: PropTypes.string.isRequired,
|
||||
t: PropTypes.func,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,83 +1,75 @@
|
||||
import React, { Component } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { useState } from 'react';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
import { shallowEqual, useSelector } from 'react-redux';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import classnames from 'classnames';
|
||||
import { Trans, withTranslation } from 'react-i18next';
|
||||
|
||||
import Menu from './Menu';
|
||||
import logo from '../ui/svg/logo.svg';
|
||||
import './Header.css';
|
||||
|
||||
class Header extends Component {
|
||||
state = {
|
||||
isMenuOpen: false,
|
||||
const Header = () => {
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const {
|
||||
protectionEnabled,
|
||||
processing,
|
||||
isCoreRunning,
|
||||
processingProfile,
|
||||
name,
|
||||
} = useSelector((state) => state.dashboard, shallowEqual);
|
||||
|
||||
const { pathname } = useLocation();
|
||||
|
||||
const toggleMenuOpen = () => {
|
||||
setIsMenuOpen((isMenuOpen) => !isMenuOpen);
|
||||
};
|
||||
|
||||
toggleMenuOpen = () => {
|
||||
this.setState((prevState) => ({ isMenuOpen: !prevState.isMenuOpen }));
|
||||
const closeMenu = () => {
|
||||
setIsMenuOpen(false);
|
||||
};
|
||||
|
||||
closeMenu = () => {
|
||||
this.setState({ isMenuOpen: false });
|
||||
};
|
||||
const badgeClass = classnames('badge dns-status', {
|
||||
'badge-success': protectionEnabled,
|
||||
'badge-danger': !protectionEnabled,
|
||||
});
|
||||
|
||||
render() {
|
||||
const { dashboard, location } = this.props;
|
||||
const { isMenuOpen } = this.state;
|
||||
const badgeClass = classnames({
|
||||
'badge dns-status': true,
|
||||
'badge-success': dashboard.protectionEnabled,
|
||||
'badge-danger': !dashboard.protectionEnabled,
|
||||
});
|
||||
|
||||
return (
|
||||
<div className="header">
|
||||
<div className="header__container">
|
||||
<div className="header__row">
|
||||
<div
|
||||
className="header-toggler d-lg-none ml-lg-0 collapsed"
|
||||
onClick={this.toggleMenuOpen}
|
||||
>
|
||||
<span className="header-toggler-icon" />
|
||||
</div>
|
||||
<div className="header__column">
|
||||
<div className="d-flex align-items-center">
|
||||
<Link to="/" className="nav-link pl-0 pr-1">
|
||||
<img src={logo} alt="" className="header-brand-img" />
|
||||
</Link>
|
||||
{!dashboard.processing && dashboard.isCoreRunning && (
|
||||
<span className={badgeClass}>
|
||||
<Trans>{dashboard.protectionEnabled ? 'on' : 'off'}</Trans>
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<Menu
|
||||
location={location}
|
||||
isMenuOpen={isMenuOpen}
|
||||
closeMenu={this.closeMenu}
|
||||
/>
|
||||
<div className="header__column">
|
||||
<div className="header__right">
|
||||
{!dashboard.processingProfile && dashboard.name
|
||||
&& <a href="control/logout" className="btn btn-sm btn-outline-secondary">
|
||||
<Trans>sign_out</Trans>
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
return <div className="header">
|
||||
<div className="header__container">
|
||||
<div className="header__row">
|
||||
<div
|
||||
className="header-toggler d-lg-none ml-lg-0 collapsed"
|
||||
onClick={toggleMenuOpen}
|
||||
>
|
||||
<span className="header-toggler-icon" />
|
||||
</div>
|
||||
<div className="header__column">
|
||||
<div className="d-flex align-items-center">
|
||||
<Link to="/" className="nav-link pl-0 pr-1">
|
||||
<img src={logo} alt="" className="header-brand-img" />
|
||||
</Link>
|
||||
{!processing && isCoreRunning
|
||||
&& <span className={badgeClass}
|
||||
>{t(protectionEnabled ? 'on' : 'off')}
|
||||
</span>}
|
||||
</div>
|
||||
</div>
|
||||
<Menu
|
||||
pathname={pathname}
|
||||
isMenuOpen={isMenuOpen}
|
||||
closeMenu={closeMenu}
|
||||
/>
|
||||
<div className="header__column">
|
||||
<div className="header__right">
|
||||
{!processingProfile && name
|
||||
&& <a href="control/logout" className="btn btn-sm btn-outline-secondary">
|
||||
{t('sign_out')}
|
||||
</a>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Header.propTypes = {
|
||||
dashboard: PropTypes.object.isRequired,
|
||||
location: PropTypes.object.isRequired,
|
||||
getVersion: PropTypes.func.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
</div>
|
||||
</div>;
|
||||
};
|
||||
|
||||
export default withTranslation()(Header);
|
||||
export default Header;
|
||||
|
||||
174
client/src/components/Logs/Cells/ClientCell.js
Normal file
174
client/src/components/Logs/Cells/ClientCell.js
Normal file
@@ -0,0 +1,174 @@
|
||||
import React, { useState } from 'react';
|
||||
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||
import { nanoid } from 'nanoid';
|
||||
import classNames from 'classnames';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import propTypes from 'prop-types';
|
||||
import { checkFiltered, getBlockingClientName } from '../../../helpers/helpers';
|
||||
import { BLOCK_ACTIONS } from '../../../helpers/constants';
|
||||
import { toggleBlocking, toggleBlockingForClient } from '../../../actions';
|
||||
import IconTooltip from './IconTooltip';
|
||||
import { renderFormattedClientCell } from '../../../helpers/renderFormattedClientCell';
|
||||
import { toggleClientBlock } from '../../../actions/access';
|
||||
import { getBlockClientInfo } from './helpers';
|
||||
|
||||
const ClientCell = ({
|
||||
client,
|
||||
domain,
|
||||
info,
|
||||
info: { name, whois_info },
|
||||
reason,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const autoClients = useSelector((state) => state.dashboard.autoClients, shallowEqual);
|
||||
const processingRules = useSelector((state) => state.filtering.processingRules);
|
||||
const isDetailed = useSelector((state) => state.queryLogs.isDetailed);
|
||||
const [isOptionsOpened, setOptionsOpened] = useState(false);
|
||||
|
||||
const disallowed_clients = useSelector(
|
||||
(state) => state.access.disallowed_clients,
|
||||
shallowEqual,
|
||||
);
|
||||
|
||||
const autoClient = autoClients.find((autoClient) => autoClient.name === client);
|
||||
const source = autoClient?.source;
|
||||
const whoisAvailable = whois_info && Object.keys(whois_info).length > 0;
|
||||
|
||||
const id = nanoid();
|
||||
|
||||
const data = {
|
||||
address: client,
|
||||
name,
|
||||
country: whois_info?.country,
|
||||
city: whois_info?.city,
|
||||
network: whois_info?.orgname,
|
||||
source_label: source,
|
||||
};
|
||||
|
||||
const processedData = Object.entries(data);
|
||||
|
||||
const isFiltered = checkFiltered(reason);
|
||||
|
||||
const nameClass = classNames('w-90 o-hidden d-flex flex-column', {
|
||||
'mt-2': isDetailed && !name && !whoisAvailable,
|
||||
'white-space--nowrap': isDetailed,
|
||||
});
|
||||
|
||||
const hintClass = classNames('icons mr-4 icon--24 icon--lightgray', {
|
||||
'my-3': isDetailed,
|
||||
});
|
||||
|
||||
const renderBlockingButton = (isFiltered, domain) => {
|
||||
const buttonType = isFiltered ? BLOCK_ACTIONS.UNBLOCK : BLOCK_ACTIONS.BLOCK;
|
||||
const clients = useSelector((state) => state.dashboard.clients);
|
||||
|
||||
const {
|
||||
confirmMessage,
|
||||
buttonKey: blockingClientKey,
|
||||
type,
|
||||
} = getBlockClientInfo(client, disallowed_clients);
|
||||
|
||||
const blockingForClientKey = isFiltered ? 'unblock_for_this_client_only' : 'block_for_this_client_only';
|
||||
const clientNameBlockingFor = getBlockingClientName(clients, client);
|
||||
|
||||
const BUTTON_OPTIONS_TO_ACTION_MAP = {
|
||||
[blockingForClientKey]: () => {
|
||||
dispatch(toggleBlockingForClient(buttonType, domain, clientNameBlockingFor));
|
||||
},
|
||||
[blockingClientKey]: () => {
|
||||
const message = `${type === BLOCK_ACTIONS.BLOCK ? t('adg_will_drop_dns_queries') : ''} ${t(confirmMessage, { ip: client })}`;
|
||||
if (window.confirm(message)) {
|
||||
dispatch(toggleClientBlock(type, client));
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const onClick = () => dispatch(toggleBlocking(buttonType, domain));
|
||||
|
||||
const getOptions = (optionToActionMap) => {
|
||||
const options = Object.entries(optionToActionMap);
|
||||
if (options.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return <>{options
|
||||
.map(([name, onClick]) => <div
|
||||
key={name}
|
||||
className="button-action--arrow-option px-4 py-2"
|
||||
onClick={onClick}
|
||||
>{t(name)}
|
||||
</div>)}</>;
|
||||
};
|
||||
|
||||
const content = getOptions(BUTTON_OPTIONS_TO_ACTION_MAP);
|
||||
|
||||
const buttonClass = classNames('button-action button-action--main', {
|
||||
'button-action--unblock': isFiltered,
|
||||
'button-action--with-options': content,
|
||||
'button-action--active': isOptionsOpened,
|
||||
});
|
||||
|
||||
const buttonArrowClass = classNames('button-action button-action--arrow', {
|
||||
'button-action--unblock': isFiltered,
|
||||
'button-action--active': isOptionsOpened,
|
||||
});
|
||||
|
||||
const containerClass = classNames('button-action__container', {
|
||||
'button-action__container--detailed': isDetailed,
|
||||
});
|
||||
|
||||
return <div className={containerClass}>
|
||||
<button type="button"
|
||||
className={buttonClass}
|
||||
onClick={onClick}
|
||||
disabled={processingRules}
|
||||
>
|
||||
{t(buttonType)}
|
||||
</button>
|
||||
{content && <button className={buttonArrowClass} disabled={processingRules}>
|
||||
<IconTooltip
|
||||
className='h-100'
|
||||
tooltipClass='button-action--arrow-option-container'
|
||||
xlinkHref='chevron-down'
|
||||
triggerClass='button-action--icon'
|
||||
content={content} placement="bottom-end" trigger="click"
|
||||
onVisibilityChange={setOptionsOpened}
|
||||
/>
|
||||
</button>}
|
||||
</div>;
|
||||
};
|
||||
|
||||
return <div className="o-hidden h-100 logs__cell logs__cell--client" role="gridcell">
|
||||
<IconTooltip className={hintClass} columnClass='grid grid--limited' tooltipClass='px-5 pb-5 pt-4 mw-75'
|
||||
xlinkHref='question' contentItemClass="contentItemClass" title="client_details"
|
||||
content={processedData} placement="bottom" />
|
||||
<div className={nameClass}>
|
||||
<div data-tip={true} data-for={id}>
|
||||
{renderFormattedClientCell(client, info, isDetailed, true)}
|
||||
</div>
|
||||
{isDetailed && name && !whoisAvailable
|
||||
&& <div className="detailed-info d-none d-sm-block logs__text"
|
||||
title={name}>{name}</div>}
|
||||
</div>
|
||||
{renderBlockingButton(isFiltered, domain)}
|
||||
</div>;
|
||||
};
|
||||
|
||||
ClientCell.propTypes = {
|
||||
client: propTypes.string.isRequired,
|
||||
domain: propTypes.string.isRequired,
|
||||
info: propTypes.oneOfType([
|
||||
propTypes.string,
|
||||
propTypes.shape({
|
||||
name: propTypes.string.isRequired,
|
||||
whois_info: propTypes.shape({
|
||||
country: propTypes.string,
|
||||
city: propTypes.string,
|
||||
orgname: propTypes.string,
|
||||
}),
|
||||
}),
|
||||
]),
|
||||
reason: propTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default ClientCell;
|
||||
29
client/src/components/Logs/Cells/DateCell.js
Normal file
29
client/src/components/Logs/Cells/DateCell.js
Normal file
@@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import propTypes from 'prop-types';
|
||||
import { formatDateTime, formatTime } from '../../../helpers/helpers';
|
||||
import { DEFAULT_SHORT_DATE_FORMAT_OPTIONS, DEFAULT_TIME_FORMAT } from '../../../helpers/constants';
|
||||
|
||||
const DateCell = ({ time }) => {
|
||||
const isDetailed = useSelector((state) => state.queryLogs.isDetailed);
|
||||
|
||||
if (!time) {
|
||||
return '–';
|
||||
}
|
||||
|
||||
const formattedTime = formatTime(time, DEFAULT_TIME_FORMAT);
|
||||
const formattedDate = formatDateTime(time, DEFAULT_SHORT_DATE_FORMAT_OPTIONS);
|
||||
|
||||
return <div className="logs__cell logs__cell logs__cell--date text-truncate" role="gridcell">
|
||||
<div className="logs__time" title={formattedTime}>{formattedTime}</div>
|
||||
{isDetailed
|
||||
&& <div className="detailed-info d-none d-sm-block text-truncate"
|
||||
title={formattedDate}>{formattedDate}</div>}
|
||||
</div>;
|
||||
};
|
||||
|
||||
DateCell.propTypes = {
|
||||
time: propTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default DateCell;
|
||||
@@ -1,7 +1,8 @@
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import getIconTooltip from './getIconTooltip';
|
||||
import propTypes from 'prop-types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
DEFAULT_SHORT_DATE_FORMAT_OPTIONS,
|
||||
LONG_TIME_FORMAT,
|
||||
@@ -9,15 +10,20 @@ import {
|
||||
} from '../../../helpers/constants';
|
||||
import { captitalizeWords, formatDateTime, formatTime } from '../../../helpers/helpers';
|
||||
import { getSourceData } from '../../../helpers/trackers/trackers';
|
||||
import IconTooltip from './IconTooltip';
|
||||
|
||||
const getDomainCell = (props) => {
|
||||
const {
|
||||
row, t, isDetailed, dnssec_enabled,
|
||||
} = props;
|
||||
|
||||
const {
|
||||
tracker, type, answer_dnssec, client_proto, domain, time,
|
||||
} = row.original;
|
||||
const DomainCell = ({
|
||||
answer_dnssec,
|
||||
service_name,
|
||||
client_proto,
|
||||
domain,
|
||||
time,
|
||||
tracker,
|
||||
type,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const dnssec_enabled = useSelector((state) => state.dnsConfig.dnssec_enabled);
|
||||
const isDetailed = useSelector((state) => state.queryLogs.isDetailed);
|
||||
|
||||
const hasTracker = !!tracker;
|
||||
|
||||
@@ -44,14 +50,18 @@ const getDomainCell = (props) => {
|
||||
protocol,
|
||||
};
|
||||
|
||||
if (service_name) {
|
||||
requestDetailsObj.check_service = service_name;
|
||||
}
|
||||
|
||||
const sourceData = getSourceData(tracker);
|
||||
|
||||
const knownTrackerDataObj = {
|
||||
name_table_header: tracker?.name,
|
||||
category_label: hasTracker && captitalizeWords(tracker.category),
|
||||
source_label: sourceData
|
||||
&& <a href={sourceData.url} target="_blank" rel="noopener noreferrer"
|
||||
className="link--green">{sourceData.name}</a>,
|
||||
&& <a href={sourceData.url} target="_blank" rel="noopener noreferrer"
|
||||
className="link--green">{sourceData.name}</a>,
|
||||
};
|
||||
|
||||
const renderGrid = (content, idx) => {
|
||||
@@ -72,51 +82,43 @@ const getDomainCell = (props) => {
|
||||
|
||||
const renderContent = hasTracker ? requestDetails.concat(getGrid(knownTrackerDataObj, 'known_tracker', 'pt-4')) : requestDetails;
|
||||
|
||||
const trackerHint = getIconTooltip({
|
||||
className: privacyIconClass,
|
||||
tooltipClass: 'pt-4 pb-5 px-5 mw-75',
|
||||
xlinkHref: 'privacy',
|
||||
contentItemClass: 'key-colon',
|
||||
renderContent,
|
||||
place: 'bottom',
|
||||
});
|
||||
|
||||
const valueClass = classNames('w-100', {
|
||||
const valueClass = classNames('w-100 text-truncate', {
|
||||
'px-2 d-flex justify-content-center flex-column': isDetailed,
|
||||
});
|
||||
|
||||
const details = [ip, protocol].filter(Boolean)
|
||||
.join(', ');
|
||||
|
||||
return (
|
||||
<div className="logs__row o-hidden">
|
||||
{dnssec_enabled && getIconTooltip({
|
||||
className: lockIconClass,
|
||||
tooltipClass: 'py-4 px-5 pb-45',
|
||||
canShowTooltip: answer_dnssec,
|
||||
xlinkHref: 'lock',
|
||||
columnClass: 'w-100',
|
||||
content: 'validated_with_dnssec',
|
||||
placement: 'bottom',
|
||||
})}
|
||||
{trackerHint}
|
||||
<div className={valueClass}>
|
||||
<div className="text-truncate" title={domain}>{domain}</div>
|
||||
{details && isDetailed
|
||||
&& <div className="detailed-info d-none d-sm-block text-truncate"
|
||||
title={details}>{details}</div>}
|
||||
</div>
|
||||
return <div className="d-flex o-hidden logs__cell logs__cell logs__cell--domain" role="gridcell">
|
||||
{dnssec_enabled && <IconTooltip
|
||||
className={lockIconClass}
|
||||
tooltipClass='py-4 px-5 pb-45'
|
||||
canShowTooltip={!!answer_dnssec}
|
||||
xlinkHref='lock'
|
||||
columnClass='w-100'
|
||||
content='validated_with_dnssec'
|
||||
placement='bottom'
|
||||
/>}
|
||||
<IconTooltip className={privacyIconClass} tooltipClass='pt-4 pb-5 px-5 mw-75'
|
||||
xlinkHref='privacy' contentItemClass='key-colon' renderContent={renderContent}
|
||||
place='bottom' />
|
||||
<div className={valueClass}>
|
||||
<div className="text-truncate" title={domain}>{service_name || domain}</div>
|
||||
{details && isDetailed
|
||||
&& <div className="detailed-info d-none d-sm-block text-truncate"
|
||||
title={details}>{details}</div>}
|
||||
</div>
|
||||
);
|
||||
</div>;
|
||||
};
|
||||
|
||||
getDomainCell.propTypes = {
|
||||
row: PropTypes.object.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
isDetailed: PropTypes.bool.isRequired,
|
||||
toggleBlocking: PropTypes.func.isRequired,
|
||||
autoClients: PropTypes.array.isRequired,
|
||||
dnssec_enabled: PropTypes.bool.isRequired,
|
||||
DomainCell.propTypes = {
|
||||
answer_dnssec: propTypes.bool.isRequired,
|
||||
client_proto: propTypes.string.isRequired,
|
||||
domain: propTypes.string.isRequired,
|
||||
time: propTypes.string.isRequired,
|
||||
type: propTypes.string.isRequired,
|
||||
service_name: propTypes.string,
|
||||
tracker: propTypes.object,
|
||||
};
|
||||
|
||||
export default getDomainCell;
|
||||
export default DomainCell;
|
||||
54
client/src/components/Logs/Cells/Header.js
Normal file
54
client/src/components/Logs/Cells/Header.js
Normal file
@@ -0,0 +1,54 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
import { toggleDetailedLogs } from '../../../actions/queryLogs';
|
||||
import HeaderCell from './HeaderCell';
|
||||
|
||||
const Header = () => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const isDetailed = useSelector((state) => state.queryLogs.isDetailed);
|
||||
const disableDetailedMode = () => dispatch(toggleDetailedLogs(false));
|
||||
const enableDetailedMode = () => dispatch(toggleDetailedLogs(true));
|
||||
|
||||
const HEADERS = [
|
||||
{
|
||||
className: 'logs__cell--date',
|
||||
content: 'time_table_header',
|
||||
},
|
||||
{
|
||||
className: 'logs__cell--domain',
|
||||
content: 'request_table_header',
|
||||
},
|
||||
{
|
||||
className: 'logs__cell--response',
|
||||
content: 'response_table_header',
|
||||
},
|
||||
{
|
||||
className: 'logs__cell--client',
|
||||
content: <>
|
||||
{t('client_table_header')}
|
||||
{<span>
|
||||
<svg className={classNames('icons icon--24 icon--green cursor--pointer mr-2', { 'icon--selected': !isDetailed })}
|
||||
onClick={disableDetailedMode}
|
||||
>
|
||||
<title>{t('compact')}</title>
|
||||
<use xlinkHref='#list' /></svg>
|
||||
<svg className={classNames('icons icon--24 icon--green cursor--pointer', { 'icon--selected': isDetailed })}
|
||||
onClick={enableDetailedMode}
|
||||
>
|
||||
<title>{t('default')}</title>
|
||||
<use xlinkHref='#detailed_list' />
|
||||
</svg>
|
||||
</span>}
|
||||
</>,
|
||||
},
|
||||
];
|
||||
|
||||
return <div className="logs__cell--header__container px-5" role="row">
|
||||
{HEADERS.map(HeaderCell)}
|
||||
</div>;
|
||||
};
|
||||
|
||||
export default Header;
|
||||
22
client/src/components/Logs/Cells/HeaderCell.js
Normal file
22
client/src/components/Logs/Cells/HeaderCell.js
Normal file
@@ -0,0 +1,22 @@
|
||||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
import propTypes from 'prop-types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const HeaderCell = ({ content, className }, idx) => {
|
||||
const { t } = useTranslation();
|
||||
return <div
|
||||
key={idx}
|
||||
className={classNames('logs__cell--header__item logs__cell logs__text--bold', className)}
|
||||
role="columnheader"
|
||||
>
|
||||
{typeof content === 'string' ? t(content) : content}
|
||||
</div>;
|
||||
};
|
||||
|
||||
HeaderCell.propTypes = {
|
||||
content: propTypes.oneOfType([propTypes.string, propTypes.element]).isRequired,
|
||||
className: propTypes.string,
|
||||
};
|
||||
|
||||
export default HeaderCell;
|
||||
@@ -6,17 +6,21 @@ import { processContent } from '../../../helpers/helpers';
|
||||
import Tooltip from '../../ui/Tooltip';
|
||||
import 'react-popper-tooltip/dist/styles.css';
|
||||
import './IconTooltip.css';
|
||||
import { SHOW_TOOLTIP_DELAY } from '../../../helpers/constants';
|
||||
|
||||
const getIconTooltip = ({
|
||||
const IconTooltip = ({
|
||||
className,
|
||||
contentItemClass,
|
||||
columnClass,
|
||||
triggerClass,
|
||||
canShowTooltip = true,
|
||||
xlinkHref,
|
||||
title,
|
||||
placement,
|
||||
tooltipClass,
|
||||
content,
|
||||
trigger,
|
||||
onVisibilityChange,
|
||||
renderContent = content ? React.Children.map(
|
||||
processContent(content),
|
||||
(item, idx) => <div key={idx} className={contentItemClass}>
|
||||
@@ -36,6 +40,10 @@ const getIconTooltip = ({
|
||||
className={tooltipClassName}
|
||||
content={tooltipContent}
|
||||
placement={placement}
|
||||
triggerClass={triggerClass}
|
||||
trigger={trigger}
|
||||
onVisibilityChange={onVisibilityChange}
|
||||
delayShow={trigger === 'click' ? 0 : SHOW_TOOLTIP_DELAY}
|
||||
>
|
||||
{xlinkHref && <svg className={className}>
|
||||
<use xlinkHref={`#${xlinkHref}`} />
|
||||
@@ -43,20 +51,20 @@ const getIconTooltip = ({
|
||||
</Tooltip>;
|
||||
};
|
||||
|
||||
getIconTooltip.propTypes = {
|
||||
IconTooltip.propTypes = {
|
||||
className: PropTypes.string,
|
||||
trigger: PropTypes.string,
|
||||
triggerClass: PropTypes.string,
|
||||
contentItemClass: PropTypes.string,
|
||||
columnClass: PropTypes.string,
|
||||
tooltipClass: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
placement: PropTypes.string,
|
||||
canShowTooltip: PropTypes.string,
|
||||
canShowTooltip: PropTypes.bool,
|
||||
xlinkHref: PropTypes.string,
|
||||
content: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.array,
|
||||
]),
|
||||
content: PropTypes.node,
|
||||
renderContent: PropTypes.arrayOf(PropTypes.element),
|
||||
onVisibilityChange: PropTypes.func,
|
||||
};
|
||||
|
||||
export default getIconTooltip;
|
||||
export default IconTooltip;
|
||||
101
client/src/components/Logs/Cells/ResponseCell.js
Normal file
101
client/src/components/Logs/Cells/ResponseCell.js
Normal file
@@ -0,0 +1,101 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { shallowEqual, useSelector } from 'react-redux';
|
||||
import classNames from 'classnames';
|
||||
import React from 'react';
|
||||
import propTypes from 'prop-types';
|
||||
import { formatElapsedMs, getFilterName } from '../../../helpers/helpers';
|
||||
import { FILTERED_STATUS, FILTERED_STATUS_TO_META_MAP } from '../../../helpers/constants';
|
||||
import IconTooltip from './IconTooltip';
|
||||
|
||||
const ResponseCell = ({
|
||||
elapsedMs,
|
||||
originalResponse,
|
||||
reason,
|
||||
response,
|
||||
status,
|
||||
upstream,
|
||||
rule,
|
||||
filterId,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const filters = useSelector((state) => state.filtering.filters, shallowEqual);
|
||||
const whitelistFilters = useSelector((state) => state.filtering.whitelistFilters, shallowEqual);
|
||||
const isDetailed = useSelector((state) => state.queryLogs.isDetailed);
|
||||
|
||||
const formattedElapsedMs = formatElapsedMs(elapsedMs, t);
|
||||
|
||||
const isBlocked = reason === FILTERED_STATUS.FILTERED_BLACK_LIST
|
||||
|| reason === FILTERED_STATUS.FILTERED_BLOCKED_SERVICE;
|
||||
|
||||
const isBlockedByResponse = originalResponse.length > 0 && isBlocked;
|
||||
|
||||
const statusLabel = t(isBlockedByResponse ? 'blocked_by_cname_or_ip' : FILTERED_STATUS_TO_META_MAP[reason]?.LABEL || reason);
|
||||
const boldStatusLabel = <span className="font-weight-bold">{statusLabel}</span>;
|
||||
const filter = getFilterName(filters, whitelistFilters, filterId);
|
||||
|
||||
const renderResponses = (responseArr) => {
|
||||
if (!responseArr || responseArr.length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return <div>{responseArr.map((response) => {
|
||||
const className = classNames('white-space--nowrap', {
|
||||
'overflow-break': response.length > 100,
|
||||
});
|
||||
|
||||
return <div key={response} className={className}>{`${response}\n`}</div>;
|
||||
})}</div>;
|
||||
};
|
||||
|
||||
const COMMON_CONTENT = {
|
||||
encryption_status: boldStatusLabel,
|
||||
install_settings_dns: upstream,
|
||||
elapsed: formattedElapsedMs,
|
||||
response_code: status,
|
||||
filter,
|
||||
rule_label: rule,
|
||||
response_table_header: renderResponses(response),
|
||||
original_response: renderResponses(originalResponse),
|
||||
};
|
||||
|
||||
const content = rule
|
||||
? Object.entries(COMMON_CONTENT)
|
||||
: Object.entries({
|
||||
...COMMON_CONTENT,
|
||||
filter: '',
|
||||
});
|
||||
const detailedInfo = isBlocked ? filter : formattedElapsedMs;
|
||||
|
||||
|
||||
return <div className="logs__cell logs__cell--response" role="gridcell">
|
||||
<IconTooltip
|
||||
className={classNames('icons mr-4 icon--24 icon--lightgray', { 'my-3': isDetailed })}
|
||||
columnClass='grid grid--limited'
|
||||
tooltipClass='px-5 pb-5 pt-4 mw-75 custom-tooltip__response-details'
|
||||
contentItemClass='text-truncate key-colon o-hidden'
|
||||
xlinkHref='question'
|
||||
title='response_details'
|
||||
content={content}
|
||||
placement='bottom'
|
||||
/>
|
||||
<div className="text-truncate">
|
||||
<div className="text-truncate" title={statusLabel}>{statusLabel}</div>
|
||||
{isDetailed && <div
|
||||
className="detailed-info d-none d-sm-block pt-1 text-truncate"
|
||||
title={detailedInfo}>{detailedInfo}</div>}
|
||||
</div>
|
||||
</div>;
|
||||
};
|
||||
|
||||
ResponseCell.propTypes = {
|
||||
elapsedMs: propTypes.string.isRequired,
|
||||
originalResponse: propTypes.array.isRequired,
|
||||
reason: propTypes.string.isRequired,
|
||||
response: propTypes.array.isRequired,
|
||||
status: propTypes.string.isRequired,
|
||||
upstream: propTypes.string.isRequired,
|
||||
rule: propTypes.string,
|
||||
filterId: propTypes.number,
|
||||
};
|
||||
|
||||
export default ResponseCell;
|
||||
@@ -1,102 +0,0 @@
|
||||
import React from 'react';
|
||||
import { nanoid } from 'nanoid';
|
||||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import { formatClientCell } from '../../../helpers/formatClientCell';
|
||||
import getIconTooltip from './getIconTooltip';
|
||||
import { checkFiltered } from '../../../helpers/helpers';
|
||||
import { BLOCK_ACTIONS } from '../../../helpers/constants';
|
||||
|
||||
const getClientCell = ({
|
||||
row, t, isDetailed, toggleBlocking, autoClients, processingRules,
|
||||
}) => {
|
||||
const {
|
||||
reason, client, domain, info: { name, whois_info },
|
||||
} = row.original;
|
||||
|
||||
const autoClient = autoClients.find((autoClient) => autoClient.name === client);
|
||||
const source = autoClient?.source;
|
||||
|
||||
const id = nanoid();
|
||||
|
||||
const data = {
|
||||
address: client,
|
||||
name,
|
||||
country: whois_info?.country,
|
||||
city: whois_info?.city,
|
||||
network: whois_info?.orgname,
|
||||
source_label: source,
|
||||
};
|
||||
|
||||
const processedData = Object.entries(data);
|
||||
|
||||
const isFiltered = checkFiltered(reason);
|
||||
|
||||
const nameClass = classNames('w-90 o-hidden d-flex flex-column', {
|
||||
'mt-2': isDetailed && !name && !whois_info,
|
||||
'white-space--nowrap': isDetailed,
|
||||
});
|
||||
|
||||
const hintClass = classNames('icons mr-4 icon--24 icon--lightgray', {
|
||||
'my-3': isDetailed,
|
||||
});
|
||||
|
||||
const renderBlockingButton = (isFiltered, domain) => {
|
||||
const buttonType = isFiltered ? BLOCK_ACTIONS.UNBLOCK : BLOCK_ACTIONS.BLOCK;
|
||||
|
||||
const buttonClass = classNames('logs__action button__action', {
|
||||
'btn-outline-secondary': isFiltered,
|
||||
'btn-outline-danger': !isFiltered,
|
||||
'logs__action--detailed': isDetailed,
|
||||
});
|
||||
|
||||
const onClick = () => toggleBlocking(buttonType, domain);
|
||||
|
||||
return (
|
||||
<div className={buttonClass}>
|
||||
<button
|
||||
type="button"
|
||||
className={`btn btn-sm ${buttonClass}`}
|
||||
onClick={onClick}
|
||||
disabled={processingRules}
|
||||
>
|
||||
{t(buttonType)}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="logs__row o-hidden h-100">
|
||||
{getIconTooltip({
|
||||
className: hintClass,
|
||||
columnClass: 'grid grid--limited',
|
||||
tooltipClass: 'px-5 pb-5 pt-4 mw-75',
|
||||
xlinkHref: 'question',
|
||||
contentItemClass: 'text-truncate key-colon',
|
||||
title: 'client_details',
|
||||
content: processedData,
|
||||
placement: 'bottom',
|
||||
})}
|
||||
<div
|
||||
className={nameClass}>
|
||||
<div data-tip={true} data-for={id}>{formatClientCell(row, isDetailed)}</div>
|
||||
{isDetailed && name
|
||||
&& !whois_info && <div className="detailed-info d-none d-sm-block logs__text"
|
||||
title={name}>{name}</div>}
|
||||
</div>
|
||||
{renderBlockingButton(isFiltered, domain)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
getClientCell.propTypes = {
|
||||
row: PropTypes.object.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
isDetailed: PropTypes.bool.isRequired,
|
||||
toggleBlocking: PropTypes.func.isRequired,
|
||||
autoClients: PropTypes.array.isRequired,
|
||||
processingRules: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default getClientCell;
|
||||
@@ -1,28 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { formatTime, formatDateTime } from '../../../helpers/helpers';
|
||||
import {
|
||||
DEFAULT_SHORT_DATE_FORMAT_OPTIONS,
|
||||
DEFAULT_TIME_FORMAT,
|
||||
} from '../../../helpers/constants';
|
||||
|
||||
const getDateCell = (row, isDetailed) => {
|
||||
const { time } = row.original;
|
||||
|
||||
if (!time) {
|
||||
return '–';
|
||||
}
|
||||
|
||||
const formattedTime = formatTime(time, DEFAULT_TIME_FORMAT);
|
||||
const formattedDate = formatDateTime(time, DEFAULT_SHORT_DATE_FORMAT_OPTIONS);
|
||||
|
||||
return (
|
||||
<div className="logs__cell">
|
||||
<div className="logs__time" title={formattedTime}>{formattedTime}</div>
|
||||
{isDetailed && <div className="detailed-info d-none d-sm-block text-truncate"
|
||||
title={formattedDate}>{formattedDate}</div>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default getDateCell;
|
||||
@@ -1,118 +0,0 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { formatElapsedMs } from '../../../helpers/helpers';
|
||||
import {
|
||||
FILTERED_STATUS,
|
||||
FILTERED_STATUS_TO_META_MAP,
|
||||
} from '../../../helpers/constants';
|
||||
import getIconTooltip from './getIconTooltip';
|
||||
|
||||
const getResponseCell = (row, filtering, t, isDetailed, getFilterName) => {
|
||||
const {
|
||||
reason, filterId, rule, status, upstream, elapsedMs, response, originalResponse,
|
||||
} = row.original;
|
||||
|
||||
const { filters, whitelistFilters } = filtering;
|
||||
const formattedElapsedMs = formatElapsedMs(elapsedMs, t);
|
||||
|
||||
const isBlocked = reason === FILTERED_STATUS.FILTERED_BLACK_LIST
|
||||
|| reason === FILTERED_STATUS.FILTERED_BLOCKED_SERVICE;
|
||||
|
||||
const isBlockedByResponse = originalResponse.length > 0 && isBlocked;
|
||||
|
||||
const statusLabel = t(isBlockedByResponse ? 'blocked_by_cname_or_ip' : FILTERED_STATUS_TO_META_MAP[reason]?.label || reason);
|
||||
const boldStatusLabel = <span className="font-weight-bold">{statusLabel}</span>;
|
||||
const filter = getFilterName(filters, whitelistFilters, filterId, t);
|
||||
|
||||
const renderResponses = (responseArr) => {
|
||||
if (responseArr?.length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return <div>{responseArr.map((response) => {
|
||||
const className = classNames('white-space--nowrap', {
|
||||
'overflow-break': response.length > 100,
|
||||
});
|
||||
|
||||
return <div key={response} className={className}>{`${response}\n`}</div>;
|
||||
})}</div>;
|
||||
};
|
||||
|
||||
const FILTERED_STATUS_TO_FIELDS_MAP = {
|
||||
[FILTERED_STATUS.NOT_FILTERED_NOT_FOUND]: {
|
||||
encryption_status: boldStatusLabel,
|
||||
install_settings_dns: upstream,
|
||||
elapsed: formattedElapsedMs,
|
||||
response_code: status,
|
||||
response_table_header: renderResponses(response),
|
||||
},
|
||||
[FILTERED_STATUS.FILTERED_BLOCKED_SERVICE]: {
|
||||
encryption_status: boldStatusLabel,
|
||||
install_settings_dns: upstream,
|
||||
elapsed: formattedElapsedMs,
|
||||
filter,
|
||||
rule_label: rule,
|
||||
response_code: status,
|
||||
original_response: renderResponses(originalResponse),
|
||||
},
|
||||
[FILTERED_STATUS.NOT_FILTERED_WHITE_LIST]: {
|
||||
encryption_status: boldStatusLabel,
|
||||
install_settings_dns: upstream,
|
||||
elapsed: formattedElapsedMs,
|
||||
filter,
|
||||
rule_label: rule,
|
||||
response_code: status,
|
||||
},
|
||||
[FILTERED_STATUS.NOT_FILTERED_WHITE_LIST]: {
|
||||
encryption_status: boldStatusLabel,
|
||||
filter,
|
||||
rule_label: rule,
|
||||
response_code: status,
|
||||
},
|
||||
[FILTERED_STATUS.FILTERED_SAFE_SEARCH]: {
|
||||
encryption_status: boldStatusLabel,
|
||||
install_settings_dns: upstream,
|
||||
elapsed: formattedElapsedMs,
|
||||
response_code: status,
|
||||
response_table_header: renderResponses(response),
|
||||
},
|
||||
[FILTERED_STATUS.FILTERED_BLACK_LIST]: {
|
||||
encryption_status: boldStatusLabel,
|
||||
filter,
|
||||
rule_label: rule,
|
||||
install_settings_dns: upstream,
|
||||
elapsed: formattedElapsedMs,
|
||||
response_code: status,
|
||||
original_response: renderResponses(originalResponse),
|
||||
},
|
||||
};
|
||||
|
||||
const content = FILTERED_STATUS_TO_FIELDS_MAP[reason]
|
||||
? Object.entries(FILTERED_STATUS_TO_FIELDS_MAP[reason])
|
||||
: Object.entries(FILTERED_STATUS_TO_FIELDS_MAP.NotFilteredNotFound);
|
||||
|
||||
const detailedInfo = isBlocked ? filter : formattedElapsedMs;
|
||||
|
||||
return (
|
||||
<div className="logs__row">
|
||||
{getIconTooltip({
|
||||
className: classNames('icons mr-4 icon--24 icon--lightgray', { 'my-3': isDetailed }),
|
||||
columnClass: 'grid grid--limited',
|
||||
tooltipClass: 'px-5 pb-5 pt-4 mw-75 custom-tooltip__response-details',
|
||||
contentItemClass: 'text-truncate key-colon o-hidden',
|
||||
xlinkHref: 'question',
|
||||
title: 'response_details',
|
||||
content,
|
||||
placement: 'bottom',
|
||||
})}
|
||||
<div className="text-truncate">
|
||||
<div className="text-truncate" title={statusLabel}>{statusLabel}</div>
|
||||
{isDetailed && <div
|
||||
className="detailed-info d-none d-sm-block pt-1 text-truncate"
|
||||
title={detailedInfo}>{detailedInfo}</div>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default getResponseCell;
|
||||
19
client/src/components/Logs/Cells/helpers/index.js
Normal file
19
client/src/components/Logs/Cells/helpers/index.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import { getIpMatchListStatus } from '../../../../helpers/helpers';
|
||||
import { BLOCK_ACTIONS, IP_MATCH_LIST_STATUS } from '../../../../helpers/constants';
|
||||
|
||||
export const BUTTON_PREFIX = 'btn_';
|
||||
|
||||
export const getBlockClientInfo = (client, disallowed_clients) => {
|
||||
const ipMatchListStatus = getIpMatchListStatus(client, disallowed_clients);
|
||||
|
||||
const isNotFound = ipMatchListStatus === IP_MATCH_LIST_STATUS.NOT_FOUND;
|
||||
const type = isNotFound ? BLOCK_ACTIONS.BLOCK : BLOCK_ACTIONS.UNBLOCK;
|
||||
|
||||
const confirmMessage = isNotFound ? 'client_confirm_block' : 'client_confirm_unblock';
|
||||
const buttonKey = isNotFound ? 'disallow_this_client' : 'allow_this_client';
|
||||
return {
|
||||
confirmMessage,
|
||||
buttonKey,
|
||||
type,
|
||||
};
|
||||
};
|
||||
229
client/src/components/Logs/Cells/index.js
Normal file
229
client/src/components/Logs/Cells/index.js
Normal file
@@ -0,0 +1,229 @@
|
||||
import React, { memo } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||
import propTypes from 'prop-types';
|
||||
import {
|
||||
captitalizeWords,
|
||||
checkFiltered,
|
||||
formatDateTime,
|
||||
formatElapsedMs,
|
||||
formatTime,
|
||||
getBlockingClientName,
|
||||
getFilterName,
|
||||
processContent,
|
||||
} from '../../../helpers/helpers';
|
||||
import {
|
||||
BLOCK_ACTIONS,
|
||||
DEFAULT_SHORT_DATE_FORMAT_OPTIONS,
|
||||
FILTERED_STATUS,
|
||||
FILTERED_STATUS_TO_META_MAP,
|
||||
LONG_TIME_FORMAT,
|
||||
QUERY_STATUS_COLORS,
|
||||
SCHEME_TO_PROTOCOL_MAP,
|
||||
} from '../../../helpers/constants';
|
||||
import { getSourceData } from '../../../helpers/trackers/trackers';
|
||||
import { toggleBlocking, toggleBlockingForClient } from '../../../actions';
|
||||
import DateCell from './DateCell';
|
||||
import DomainCell from './DomainCell';
|
||||
import ResponseCell from './ResponseCell';
|
||||
import ClientCell from './ClientCell';
|
||||
import '../Logs.css';
|
||||
import { toggleClientBlock } from '../../../actions/access';
|
||||
import { getBlockClientInfo, BUTTON_PREFIX } from './helpers';
|
||||
|
||||
const Row = memo(({
|
||||
style,
|
||||
rowProps,
|
||||
rowProps: { reason },
|
||||
isSmallScreen,
|
||||
setDetailedDataCurrent,
|
||||
setButtonType,
|
||||
setModalOpened,
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
const { t } = useTranslation();
|
||||
const dnssec_enabled = useSelector((state) => state.dnsConfig.dnssec_enabled);
|
||||
const filters = useSelector((state) => state.filtering.filters, shallowEqual);
|
||||
const whitelistFilters = useSelector((state) => state.filtering.whitelistFilters, shallowEqual);
|
||||
const autoClients = useSelector((state) => state.dashboard.autoClients, shallowEqual);
|
||||
|
||||
const disallowed_clients = useSelector(
|
||||
(state) => state.access.disallowed_clients,
|
||||
shallowEqual,
|
||||
);
|
||||
|
||||
const clients = useSelector((state) => state.dashboard.clients);
|
||||
|
||||
const onClick = () => {
|
||||
if (!isSmallScreen) { return; }
|
||||
const {
|
||||
answer_dnssec,
|
||||
client,
|
||||
domain,
|
||||
elapsedMs,
|
||||
info,
|
||||
reason,
|
||||
response,
|
||||
time,
|
||||
tracker,
|
||||
upstream,
|
||||
type,
|
||||
client_proto,
|
||||
filterId,
|
||||
rule,
|
||||
originalResponse,
|
||||
status,
|
||||
} = rowProps;
|
||||
|
||||
const hasTracker = !!tracker;
|
||||
|
||||
const autoClient = autoClients
|
||||
.find((autoClient) => autoClient.name === client);
|
||||
|
||||
const { whois_info } = info;
|
||||
const country = whois_info?.country;
|
||||
const city = whois_info?.city;
|
||||
const network = whois_info?.orgname;
|
||||
|
||||
const source = autoClient?.source;
|
||||
|
||||
const formattedElapsedMs = formatElapsedMs(elapsedMs, t);
|
||||
const isFiltered = checkFiltered(reason);
|
||||
|
||||
const isBlocked = reason === FILTERED_STATUS.FILTERED_BLACK_LIST
|
||||
|| reason === FILTERED_STATUS.FILTERED_BLOCKED_SERVICE;
|
||||
|
||||
const buttonType = isFiltered ? BLOCK_ACTIONS.UNBLOCK : BLOCK_ACTIONS.BLOCK;
|
||||
const onToggleBlock = () => {
|
||||
dispatch(toggleBlocking(buttonType, domain));
|
||||
};
|
||||
|
||||
const isBlockedByResponse = originalResponse.length > 0 && isBlocked;
|
||||
const requestStatus = t(isBlockedByResponse ? 'blocked_by_cname_or_ip' : FILTERED_STATUS_TO_META_MAP[reason]?.LABEL || reason);
|
||||
|
||||
const protocol = t(SCHEME_TO_PROTOCOL_MAP[client_proto]) || '';
|
||||
|
||||
const sourceData = getSourceData(tracker);
|
||||
|
||||
const filter = getFilterName(filters, whitelistFilters, filterId);
|
||||
|
||||
const {
|
||||
confirmMessage,
|
||||
buttonKey: blockingClientKey,
|
||||
type: blockType,
|
||||
} = getBlockClientInfo(client, disallowed_clients);
|
||||
|
||||
const blockingForClientKey = isFiltered ? 'unblock_for_this_client_only' : 'block_for_this_client_only';
|
||||
const clientNameBlockingFor = getBlockingClientName(clients, client);
|
||||
|
||||
const onBlockingForClientClick = () => {
|
||||
dispatch(toggleBlockingForClient(buttonType, domain, clientNameBlockingFor));
|
||||
};
|
||||
|
||||
const onBlockingClientClick = () => {
|
||||
const message = `${blockType === BLOCK_ACTIONS.BLOCK ? t('adg_will_drop_dns_queries') : ''} ${t(confirmMessage, { ip: client })}`;
|
||||
if (window.confirm(message)) {
|
||||
dispatch(toggleClientBlock(blockType, client));
|
||||
}
|
||||
};
|
||||
|
||||
const detailedData = {
|
||||
time_table_header: formatTime(time, LONG_TIME_FORMAT),
|
||||
date: formatDateTime(time, DEFAULT_SHORT_DATE_FORMAT_OPTIONS),
|
||||
encryption_status: isBlocked
|
||||
? <div className="bg--danger">{requestStatus}</div> : requestStatus,
|
||||
domain,
|
||||
type_table_header: type,
|
||||
protocol,
|
||||
known_tracker: hasTracker && 'title',
|
||||
table_name: tracker?.name,
|
||||
category_label: hasTracker && captitalizeWords(tracker.category),
|
||||
tracker_source: hasTracker && sourceData
|
||||
&& <a
|
||||
href={sourceData.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="link--green">{sourceData.name}
|
||||
</a>,
|
||||
response_details: 'title',
|
||||
install_settings_dns: upstream,
|
||||
elapsed: formattedElapsedMs,
|
||||
filter: rule ? filter : null,
|
||||
rule_label: rule,
|
||||
response_table_header: response?.join('\n'),
|
||||
response_code: status,
|
||||
client_details: 'title',
|
||||
ip_address: client,
|
||||
name: info?.name,
|
||||
country,
|
||||
city,
|
||||
network,
|
||||
source_label: source,
|
||||
validated_with_dnssec: dnssec_enabled ? Boolean(answer_dnssec) : false,
|
||||
original_response: originalResponse?.join('\n'),
|
||||
[BUTTON_PREFIX + buttonType]: <div onClick={onToggleBlock}
|
||||
className={classNames('title--border text-center', {
|
||||
'bg--danger': isBlocked,
|
||||
})}>{t(buttonType)}</div>,
|
||||
[BUTTON_PREFIX + blockingForClientKey]: <div onClick={onBlockingForClientClick} className='text-center font-weight-bold py-2'>{t(blockingForClientKey)}</div>,
|
||||
[BUTTON_PREFIX + blockingClientKey]: <div onClick={onBlockingClientClick} className='text-center font-weight-bold py-2'>{t(blockingClientKey)}</div>,
|
||||
};
|
||||
|
||||
setDetailedDataCurrent(processContent(detailedData));
|
||||
setButtonType(buttonType);
|
||||
setModalOpened(true);
|
||||
};
|
||||
|
||||
const isDetailed = useSelector((state) => state.queryLogs.isDetailed);
|
||||
|
||||
const className = classNames('d-flex px-5 logs__row',
|
||||
`logs__row--${FILTERED_STATUS_TO_META_MAP?.[reason]?.COLOR ?? QUERY_STATUS_COLORS.WHITE}`, {
|
||||
'logs__cell--detailed': isDetailed,
|
||||
});
|
||||
|
||||
return <div style={style} className={className} onClick={onClick} role="row">
|
||||
<DateCell {...rowProps} />
|
||||
<DomainCell {...rowProps} />
|
||||
<ResponseCell {...rowProps} />
|
||||
<ClientCell {...rowProps} />
|
||||
</div>;
|
||||
});
|
||||
|
||||
Row.displayName = 'Row';
|
||||
|
||||
Row.propTypes = {
|
||||
style: propTypes.object,
|
||||
rowProps: propTypes.shape({
|
||||
reason: propTypes.string.isRequired,
|
||||
answer_dnssec: propTypes.bool.isRequired,
|
||||
client: propTypes.string.isRequired,
|
||||
domain: propTypes.string.isRequired,
|
||||
elapsedMs: propTypes.string.isRequired,
|
||||
info: propTypes.oneOfType([
|
||||
propTypes.string,
|
||||
propTypes.shape({
|
||||
whois_info: propTypes.shape({
|
||||
country: propTypes.string,
|
||||
city: propTypes.string,
|
||||
orgname: propTypes.string,
|
||||
}),
|
||||
})]),
|
||||
response: propTypes.array.isRequired,
|
||||
time: propTypes.string.isRequired,
|
||||
tracker: propTypes.object,
|
||||
upstream: propTypes.string.isRequired,
|
||||
type: propTypes.string.isRequired,
|
||||
client_proto: propTypes.string.isRequired,
|
||||
filterId: propTypes.number,
|
||||
rule: propTypes.string,
|
||||
originalResponse: propTypes.array,
|
||||
status: propTypes.string.isRequired,
|
||||
}).isRequired,
|
||||
isSmallScreen: propTypes.bool.isRequired,
|
||||
setDetailedDataCurrent: propTypes.func.isRequired,
|
||||
setButtonType: propTypes.func.isRequired,
|
||||
setModalOpened: propTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default Row;
|
||||
@@ -107,7 +107,7 @@ const Form = (props) => {
|
||||
|
||||
const {
|
||||
response_status, search,
|
||||
} = useSelector((state) => state.form[FORM_NAME.LOGS_FILTER].values, shallowEqual);
|
||||
} = useSelector((state) => state?.form[FORM_NAME.LOGS_FILTER].values, shallowEqual);
|
||||
|
||||
const [
|
||||
debouncedSearch,
|
||||
@@ -143,10 +143,11 @@ const Form = (props) => {
|
||||
const normalizeOnBlur = (data) => data.trim();
|
||||
|
||||
return (
|
||||
<form className="d-flex flex-wrap form-control--container"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
<form
|
||||
className="d-flex flex-wrap form-control--container"
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
<div className="field__search">
|
||||
<Field
|
||||
@@ -166,13 +167,21 @@ const Form = (props) => {
|
||||
<Field
|
||||
name={FORM_NAMES.response_status}
|
||||
component="select"
|
||||
className={classNames('form-control custom-select custom-select--logs custom-select__arrow--left ml-small form-control--transparent', responseStatusClass)}
|
||||
className={classNames('form-control custom-select custom-select--logs custom-select__arrow--left form-control--transparent', responseStatusClass)}
|
||||
>
|
||||
{Object.values(RESPONSE_FILTER)
|
||||
.map(({
|
||||
query, label, disabled,
|
||||
}) => <option key={label} value={query}
|
||||
disabled={disabled}>{t(label)}</option>)}
|
||||
QUERY, LABEL, disabled,
|
||||
}) => (
|
||||
<option
|
||||
key={LABEL}
|
||||
value={QUERY}
|
||||
disabled={disabled}
|
||||
>
|
||||
{t(LABEL)}
|
||||
</option>
|
||||
))
|
||||
}
|
||||
</Field>
|
||||
</div>
|
||||
</form>
|
||||
@@ -188,5 +197,4 @@ Form.propTypes = {
|
||||
|
||||
export default reduxForm({
|
||||
form: FORM_NAME.LOGS_FILTER,
|
||||
enableReinitialize: true,
|
||||
})(Form);
|
||||
|
||||
@@ -1,33 +1,45 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Trans } from 'react-i18next';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import Form from './Form';
|
||||
import { refreshFilteredLogs } from '../../../actions/queryLogs';
|
||||
import { addSuccessToast } from '../../../actions/toasts';
|
||||
|
||||
const Filters = ({ filter, refreshLogs, setIsLoading }) => (
|
||||
<div className="page-header page-header--logs">
|
||||
<h1 className="page-title page-title--large">
|
||||
<Trans>query_log</Trans>
|
||||
<button
|
||||
const Filters = ({ filter, setIsLoading }) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const refreshLogs = async () => {
|
||||
setIsLoading(true);
|
||||
await dispatch(refreshFilteredLogs());
|
||||
dispatch(addSuccessToast('query_log_updated'));
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
return <div className="page-header page-header--logs">
|
||||
<h1 className="page-title page-title--large">
|
||||
{t('query_log')}
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-icon--green ml-3 bg-transparent"
|
||||
className="btn btn-icon--green logs__refresh"
|
||||
onClick={refreshLogs}
|
||||
>
|
||||
<svg className="icons icon--24">
|
||||
<use xlinkHref="#update" />
|
||||
</svg>
|
||||
</button>
|
||||
</h1>
|
||||
<Form
|
||||
>
|
||||
<svg className="icons icon--24">
|
||||
<use xlinkHref="#update" />
|
||||
</svg>
|
||||
</button>
|
||||
</h1>
|
||||
<Form
|
||||
responseStatusClass="d-sm-block"
|
||||
initialValues={filter}
|
||||
setIsLoading={setIsLoading}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
/>
|
||||
</div>;
|
||||
};
|
||||
|
||||
Filters.propTypes = {
|
||||
filter: PropTypes.object.isRequired,
|
||||
refreshLogs: PropTypes.func.isRequired,
|
||||
processingGetLogs: PropTypes.bool.isRequired,
|
||||
setIsLoading: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
87
client/src/components/Logs/InfiniteTable.js
Normal file
87
client/src/components/Logs/InfiniteTable.js
Normal file
@@ -0,0 +1,87 @@
|
||||
import React, {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
} from 'react';
|
||||
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import propTypes from 'prop-types';
|
||||
import throttle from 'lodash/throttle';
|
||||
import Loading from '../ui/Loading';
|
||||
import Header from './Cells/Header';
|
||||
import { getLogs } from '../../actions/queryLogs';
|
||||
import Row from './Cells';
|
||||
import { isScrolledIntoView } from '../../helpers/helpers';
|
||||
import { QUERY_LOGS_PAGE_LIMIT } from '../../helpers/constants';
|
||||
|
||||
const InfiniteTable = ({
|
||||
isLoading,
|
||||
items,
|
||||
isSmallScreen,
|
||||
setDetailedDataCurrent,
|
||||
setButtonType,
|
||||
setModalOpened,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const loader = useRef(null);
|
||||
|
||||
const {
|
||||
isEntireLog,
|
||||
processingGetLogs,
|
||||
} = useSelector((state) => state.queryLogs, shallowEqual);
|
||||
|
||||
const loading = isLoading || processingGetLogs;
|
||||
|
||||
const listener = useCallback(() => {
|
||||
if (loader.current && isScrolledIntoView(loader.current)) {
|
||||
dispatch(getLogs());
|
||||
}
|
||||
}, [loader.current, isScrolledIntoView, getLogs]);
|
||||
|
||||
useEffect(() => {
|
||||
listener();
|
||||
}, [items.length < QUERY_LOGS_PAGE_LIMIT]);
|
||||
|
||||
useEffect(() => {
|
||||
const THROTTLE_TIME = 100;
|
||||
const throttledListener = throttle(listener, THROTTLE_TIME);
|
||||
|
||||
window.addEventListener('scroll', throttledListener);
|
||||
return () => {
|
||||
window.removeEventListener('scroll', throttledListener);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const renderRow = (row, idx) => <Row
|
||||
key={idx}
|
||||
rowProps={row}
|
||||
isSmallScreen={isSmallScreen}
|
||||
setDetailedDataCurrent={setDetailedDataCurrent}
|
||||
setButtonType={setButtonType}
|
||||
setModalOpened={setModalOpened}
|
||||
/>;
|
||||
|
||||
const isNothingFound = items.length === 0 && !processingGetLogs;
|
||||
|
||||
return <div className='logs__table' role='grid'>
|
||||
{loading && <Loading />}
|
||||
<Header />
|
||||
{isNothingFound
|
||||
? <label className="logs__no-data">{t('nothing_found')}</label>
|
||||
: <>{items.map(renderRow)}
|
||||
{!isEntireLog && <div ref={loader} className="logs__loading text-center">{t('loading_table_status')}</div>}
|
||||
</>}
|
||||
</div>;
|
||||
};
|
||||
|
||||
InfiniteTable.propTypes = {
|
||||
isLoading: propTypes.bool.isRequired,
|
||||
items: propTypes.array.isRequired,
|
||||
isSmallScreen: propTypes.bool.isRequired,
|
||||
setDetailedDataCurrent: propTypes.func.isRequired,
|
||||
setButtonType: propTypes.func.isRequired,
|
||||
setModalOpened: propTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default InfiniteTable;
|
||||
@@ -1,44 +1,32 @@
|
||||
:root {
|
||||
--blue: #e5effd;
|
||||
--green-pale: rgba(103, 178, 121, 0.1);
|
||||
--red: rgba(223, 56, 18, 0.05);
|
||||
--white: #fff;
|
||||
--yellow: rgba(247, 181, 0, 0.1);
|
||||
--size-date: 70;
|
||||
--size-domain: 180;
|
||||
--size-response: 150;
|
||||
--size-client: 123;
|
||||
--gray-216: rgba(216, 216, 216, 0.23);
|
||||
--gray-4d: #4D4D4D;
|
||||
--gray-f3: #F3F3F3;
|
||||
--gray-8: #888;
|
||||
--danger: #DF3812;
|
||||
--white80: rgba(255, 255, 255, 0.8);
|
||||
|
||||
--btn-block: #C23814;
|
||||
--btn-block-disabled: #E3B3A6;
|
||||
--btn-block-active: #A62200;
|
||||
|
||||
--btn-unblock: #888888;
|
||||
--btn-unblock-disabled: #D8D8D8;
|
||||
--btn-unblock-active: #4D4D4D;
|
||||
|
||||
--option-border-radius: 4px;
|
||||
}
|
||||
|
||||
.logs__row {
|
||||
position: relative;
|
||||
display: flex;
|
||||
min-height: 26px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.card-table .logs__row {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.logs__row--center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.logs__row--column {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.logs__row--icons {
|
||||
max-width: 180px;
|
||||
flex-flow: row wrap;
|
||||
}
|
||||
|
||||
.logs__row .list-unstyled {
|
||||
margin-bottom: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.logs__text,
|
||||
.logs__row .list-unstyled li {
|
||||
.logs__text {
|
||||
padding: 0 1px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
@@ -54,237 +42,6 @@
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.logs__text--full {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.logs__text--wrap,
|
||||
.logs__text--whois {
|
||||
line-height: 1.4;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.logs__text--nowrap {
|
||||
line-height: 1.4;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.logs__text--whois {
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.logs__row .tooltip-custom {
|
||||
top: 0;
|
||||
margin-left: 0;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.tooltip__option {
|
||||
height: 2.5rem !important;
|
||||
width: 10.5rem;
|
||||
padding: 0.3125rem 1.5rem 0.6875rem;
|
||||
}
|
||||
|
||||
.tooltip__option:hover {
|
||||
background-color: var(--gray-f3);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.button__action {
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
transition: opacity 0.2s ease, visibility 0.2s ease;
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.table__action {
|
||||
position: absolute;
|
||||
top: 11px;
|
||||
right: 15px;
|
||||
}
|
||||
|
||||
.logs__action {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 1rem;
|
||||
}
|
||||
|
||||
.logs__action--detailed {
|
||||
top: 5px;
|
||||
}
|
||||
|
||||
.logs__table .rt-td,
|
||||
.clients__table .rt-td {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.logs__table .rt-thead, .logs__table .rt-tbody {
|
||||
min-width: 100% !important;
|
||||
}
|
||||
|
||||
.logs__table .rt-tr:hover .logs__action,
|
||||
.clients__table .rt-tr:hover .table__action {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.logs__table .rt-tr-group:first-child .tooltip-custom:before {
|
||||
top: calc(100% + 12px);
|
||||
bottom: initial;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.logs__table .rt-tr-group:first-child .tooltip-custom:after {
|
||||
top: initial;
|
||||
bottom: -4px;
|
||||
border-top: 6px solid transparent;
|
||||
border-bottom: 6px solid #585965;
|
||||
}
|
||||
|
||||
.logs__table .rt-tr-group:first-child .popover__body {
|
||||
top: calc(100% + 5px);
|
||||
bottom: initial;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.logs__table .rt-tr-group:first-child .popover__body:after {
|
||||
top: -11px;
|
||||
border-top: 6px solid transparent;
|
||||
border-bottom: 6px solid #585965;
|
||||
}
|
||||
|
||||
.logs__table .rt-thead.-filters input,
|
||||
.logs__table .rt-thead.-filters select {
|
||||
padding: 6px 7px;
|
||||
border-radius: 3px;
|
||||
font-size: 0.9375rem;
|
||||
line-height: 1.6;
|
||||
color: #495057;
|
||||
border: 1px solid rgba(0, 40, 100, 0.12);
|
||||
}
|
||||
|
||||
.logs__table .rt-thead.-filters select {
|
||||
background: #fff url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAxMCA1Jz48cGF0aCBmaWxsPScjOTk5JyBkPSdNMCAwTDEwIDBMNSA1TDAgMCcvPjwvc3ZnPg==") no-repeat right 0.75rem center;
|
||||
background-size: 8px 10px;
|
||||
}
|
||||
|
||||
.logs__table .rt-thead.-filters input:focus,
|
||||
.logs__table .rt-thead.-filters select:focus {
|
||||
border-color: #1991eb;
|
||||
box-shadow: 0 0 0 2px rgba(70, 127, 207, 0.25);
|
||||
}
|
||||
|
||||
.logs__text-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.logs__list-wrap {
|
||||
display: flex;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.logs__list-item {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.logs__input-wrap {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.logs__whois {
|
||||
display: inline;
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.logs__whois::after {
|
||||
content: "|";
|
||||
padding: 0 5px;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.logs__whois:last-child::after {
|
||||
content: "";
|
||||
}
|
||||
|
||||
.logs__whois-icon.icons {
|
||||
position: relative;
|
||||
top: -2px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
margin-right: 1px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/* New logs */
|
||||
.logs__table {
|
||||
background-color: #fff;
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
min-height: 42rem;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.logs__table--detailed {
|
||||
min-height: 50rem;
|
||||
}
|
||||
|
||||
.logs__table .rt-thead.-header {
|
||||
box-shadow: none;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.logs__table .rt-thead .rt-th {
|
||||
padding: 0.9375rem 0.9375rem 0.875rem 0;
|
||||
text-align: left;
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
.logs__table .rt-tbody .rt-td {
|
||||
padding: 1rem 1rem 0.5rem 0;
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
.logs__table .rt-thead .rt-th:last-child,
|
||||
.logs__table .rt-tbody .rt-td:last-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.logs__table .rt-tbody .rt-tr-group {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.logs__table .rt-tr {
|
||||
position: relative;
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
.logs__table .rt-tr {
|
||||
position: relative;
|
||||
padding: 0 1.5rem;
|
||||
}
|
||||
|
||||
.logs__table .rt-tr-group:not(:first-child) .rt-tr:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 1.5rem;
|
||||
right: 1.5rem;
|
||||
top: 0;
|
||||
width: calc(100% - 3rem);
|
||||
height: 2px;
|
||||
background-color: rgba(216, 216, 216, 0.23);
|
||||
}
|
||||
|
||||
.logs__table .rt-tr-group:last-child .rt-tr:after,
|
||||
.logs__table .rt-thead .rt-tr:after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.logs__time {
|
||||
font-size: 1rem;
|
||||
line-height: 1.5;
|
||||
@@ -302,132 +59,24 @@
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* Hide 3 and 4 column on mobile */
|
||||
.logs__table .rt-thead .rt-th:nth-child(3),
|
||||
.logs__table .rt-thead .rt-th:nth-child(4),
|
||||
.logs__table .rt-tbody .rt-td:nth-child(3),
|
||||
.logs__table .rt-tbody .rt-td:nth-child(4) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.logs__table .rt-thead .rt-th:nth-child(3),
|
||||
.logs__table .rt-thead .rt-th:nth-child(4),
|
||||
.logs__table .rt-tbody .rt-td:nth-child(3),
|
||||
.logs__table .rt-tbody .rt-td:nth-child(4) {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.text-pre {
|
||||
white-space: pre-wrap !important;
|
||||
overflow-wrap: break-word;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.custom-pagination {
|
||||
width: 11.875rem !important;
|
||||
background-color: transparent;
|
||||
box-shadow: none !important;
|
||||
border: none !important;
|
||||
align-items: center !important;
|
||||
}
|
||||
|
||||
.custom-pagination--padding {
|
||||
padding: 2.5rem 0 2.5rem !important;
|
||||
}
|
||||
|
||||
.custom-pagination .-btn {
|
||||
--side-size: 2rem;
|
||||
background-color: transparent !important;
|
||||
border: 1px solid var(--gray-d8) !important;
|
||||
border-radius: 4px !important;
|
||||
width: var(--side-size) !important;
|
||||
height: var(--side-size) !important;
|
||||
}
|
||||
|
||||
.custom-pagination .-btn:enabled:hover {
|
||||
background-color: var(--gray-f3) !important;
|
||||
}
|
||||
|
||||
.custom-pagination .-previous {
|
||||
flex: 0 1 !important;
|
||||
}
|
||||
|
||||
.custom-pagination .-next {
|
||||
flex: 0 1 !important;
|
||||
}
|
||||
|
||||
.custom-pagination .-btn {
|
||||
display: flex !important;
|
||||
}
|
||||
|
||||
.-pageInfo {
|
||||
--side-size: 2rem;
|
||||
font-variant-numeric: tabular-nums !important;
|
||||
background-color: transparent !important;
|
||||
border: 1px solid var(--gray-d8) !important;
|
||||
border-radius: 4px !important;
|
||||
width: var(--side-size) !important;
|
||||
height: var(--side-size) !important;
|
||||
margin: 0 !important;
|
||||
display: flex !important;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.pagination-bottom {
|
||||
justify-content: center !important;
|
||||
display: flex !important;
|
||||
}
|
||||
|
||||
.-center:before {
|
||||
content: '...';
|
||||
transform: translateY(-0.25rem);
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.-center:after {
|
||||
content: '...';
|
||||
transform: translateY(-0.25rem);
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.icon--detailed-info {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0.5rem;
|
||||
}
|
||||
|
||||
.link--green {
|
||||
color: var(--green79);
|
||||
}
|
||||
|
||||
.row--detailed {
|
||||
height: 4.9rem
|
||||
}
|
||||
|
||||
.w-90 {
|
||||
max-width: 90% !important;
|
||||
}
|
||||
|
||||
.h-85 {
|
||||
height: 85% !important;
|
||||
}
|
||||
|
||||
.pt-45 {
|
||||
padding-top: 1.25rem !important;
|
||||
}
|
||||
|
||||
.pb-45 {
|
||||
padding-bottom: 1.25rem !important;
|
||||
}
|
||||
|
||||
.py-45 {
|
||||
padding-top: 1.25rem !important;
|
||||
padding-bottom: 1.25rem !important;
|
||||
}
|
||||
|
||||
.mh-100 {
|
||||
max-height: 100% !important;
|
||||
}
|
||||
@@ -437,12 +86,13 @@
|
||||
}
|
||||
|
||||
.custom-select__arrow--left {
|
||||
background: #fff url('./chevron-down.svg') no-repeat left 0.2rem center;
|
||||
background-size: 1.5rem;
|
||||
background: var(--white) url('../ui/svg/chevron-down.svg') no-repeat;
|
||||
background-position: 5px 9px;
|
||||
background-size: 22px;
|
||||
}
|
||||
|
||||
.custom-select--logs {
|
||||
padding: 0.5rem 0.75rem 0.5rem 1.75rem !important;
|
||||
padding: 0.5rem 0.75rem 0.5rem 2rem !important;
|
||||
}
|
||||
|
||||
.bg--danger {
|
||||
@@ -492,14 +142,6 @@
|
||||
}
|
||||
|
||||
@media (max-width: 767.98px) {
|
||||
.rt-tr .logs__row .logs__text {
|
||||
max-width: calc(100% - 1.5rem);
|
||||
}
|
||||
|
||||
.ml-small {
|
||||
margin-left: 1.5rem;
|
||||
}
|
||||
|
||||
.form-control--container {
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
@@ -511,28 +153,263 @@
|
||||
|
||||
.field__select {
|
||||
margin-top: 1.5rem;
|
||||
padding-left: 24px;
|
||||
padding-right: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 575px) {
|
||||
.logs__table .rt-tr {
|
||||
height: 3.125rem;
|
||||
}
|
||||
|
||||
.logs__table .rt-tbody .rt-td {
|
||||
padding: 0.625rem 1rem 0.875rem 0;
|
||||
}
|
||||
|
||||
.logs__table {
|
||||
min-height: 42rem;
|
||||
@media screen and (max-width: 767.98px) {
|
||||
.logs__table .logs__cell--response,
|
||||
.logs__table .logs__cell--client {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
.loading__container > .-loading-inner {
|
||||
top: 10rem !important;
|
||||
bottom: initial !important;
|
||||
.logs__refresh {
|
||||
--size: 2.5rem;
|
||||
position: relative;
|
||||
top: 3px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
padding: 0;
|
||||
margin-left: 0.9375rem;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.loading__text {
|
||||
transform: translateY(3rem);
|
||||
.logs__cell {
|
||||
padding: 1rem 1rem 0.5rem 0;
|
||||
}
|
||||
|
||||
.logs__cell--date {
|
||||
width: 4.375rem;
|
||||
flex: var(--size-date) 0 auto;
|
||||
}
|
||||
|
||||
.logs__cell--domain {
|
||||
width: 11.25rem;
|
||||
flex: var(--size-domain) 0 auto;
|
||||
}
|
||||
|
||||
.logs__cell--response {
|
||||
width: 9.375rem;
|
||||
flex: var(--size-response) 0 auto;
|
||||
}
|
||||
|
||||
.logs__cell--client {
|
||||
width: 7.6875rem;
|
||||
flex: var(--size-client) 0 auto;
|
||||
padding-right: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.logs__cell--header__container > .logs__cell--header__item {
|
||||
border-right: 0;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.logs__cell--header__container > .logs__cell--header__item:last-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.button-action__container {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0.5rem;
|
||||
height: 1.6rem;
|
||||
}
|
||||
|
||||
.button-action__container--detailed {
|
||||
bottom: 1.3rem;
|
||||
}
|
||||
|
||||
.button-action {
|
||||
outline: 0 !important;
|
||||
background: var(--btn-block);
|
||||
border-radius: var(--option-border-radius);
|
||||
font-size: 0.8rem;
|
||||
color: var(--white);
|
||||
letter-spacing: 0;
|
||||
text-align: center;
|
||||
line-height: 28px;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.button-action--unblock {
|
||||
background: var(--btn-unblock);
|
||||
}
|
||||
|
||||
.button-action--main {
|
||||
padding: 0 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.button-action--with-options {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
.button-action--arrow {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
border-left: 1px solid var(--white);
|
||||
width: 1.5625rem;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.button-action:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.button-action--arrow .button-action--icon {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.button-action:active {
|
||||
background: var(--btn-block-active);
|
||||
}
|
||||
|
||||
.button-action--unblock:active {
|
||||
background: var(--btn-unblock-active);
|
||||
}
|
||||
|
||||
.button-action:disabled {
|
||||
background: var(--btn-block-disabled);
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.button-action--unblock:disabled {
|
||||
background: var(--btn-unblock-disabled);
|
||||
}
|
||||
|
||||
.button-action--arrow-option:hover {
|
||||
cursor: pointer;
|
||||
background: var(--gray-f3);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.button-action--arrow-option-container {
|
||||
overflow: visible;
|
||||
transform-origin: left;
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
.logs__row {
|
||||
position: relative;
|
||||
display: flex;
|
||||
min-height: 26px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.logs__table .logs__row {
|
||||
border-bottom: 2px solid var(--gray-216);
|
||||
}
|
||||
|
||||
/* QUERY_STATUS_COLORS */
|
||||
.logs__row--blue {
|
||||
background-color: var(--blue);
|
||||
}
|
||||
|
||||
.logs__row--green {
|
||||
background-color: var(--green-pale);
|
||||
}
|
||||
|
||||
.logs__row--red {
|
||||
background-color: var(--red);
|
||||
}
|
||||
|
||||
.logs__row--white {
|
||||
background-color: var(--white);
|
||||
}
|
||||
|
||||
.logs__row--yellow {
|
||||
background-color: var(--yellow);
|
||||
}
|
||||
|
||||
.logs__no-data {
|
||||
color: var(--gray-4d);
|
||||
background-color: var(--white80);
|
||||
pointer-events: none;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
padding-top: 21rem;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.logs__loading {
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
.logs__table {
|
||||
background-color: var(--white);
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
min-height: 43rem;
|
||||
max-width: 100%;
|
||||
align-items: stretch;
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
contain: layout;
|
||||
overflow-x: hidden;
|
||||
overflow-y: scroll;
|
||||
will-change: scroll-position;
|
||||
}
|
||||
|
||||
.logs__table .logs__cell--response,
|
||||
.logs__table .logs__cell--client {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.logs__cell--header__container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.logs__table > .logs__cell--header__container > .logs__cell--client {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.logs__table .loading:after {
|
||||
top: 10%;
|
||||
}
|
||||
|
||||
.logs__table .loading:before {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.logs__whois {
|
||||
display: inline;
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.logs__whois::after {
|
||||
content: "|";
|
||||
padding: 0 5px;
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.logs__whois:last-child::after {
|
||||
content: "";
|
||||
}
|
||||
|
||||
.logs__whois-icon.icons {
|
||||
position: relative;
|
||||
top: -2px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
margin-right: 1px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
@@ -1,414 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useTranslation, Trans } from 'react-i18next';
|
||||
import ReactTable from 'react-table';
|
||||
import classNames from 'classnames';
|
||||
import endsWith from 'lodash/endsWith';
|
||||
import escapeRegExp from 'lodash/escapeRegExp';
|
||||
import {
|
||||
BLOCK_ACTIONS,
|
||||
DEFAULT_SHORT_DATE_FORMAT_OPTIONS,
|
||||
LONG_TIME_FORMAT,
|
||||
FILTERED_STATUS_TO_META_MAP,
|
||||
TABLE_DEFAULT_PAGE_SIZE,
|
||||
SCHEME_TO_PROTOCOL_MAP,
|
||||
CUSTOM_FILTERING_RULES_ID, FILTERED_STATUS,
|
||||
} from '../../helpers/constants';
|
||||
import getDateCell from './Cells/getDateCell';
|
||||
import getDomainCell from './Cells/getDomainCell';
|
||||
import getClientCell from './Cells/getClientCell';
|
||||
import getResponseCell from './Cells/getResponseCell';
|
||||
|
||||
import {
|
||||
captitalizeWords,
|
||||
checkFiltered,
|
||||
formatDateTime,
|
||||
formatElapsedMs,
|
||||
formatTime,
|
||||
processContent,
|
||||
} from '../../helpers/helpers';
|
||||
import Loading from '../ui/Loading';
|
||||
import { getSourceData } from '../../helpers/trackers/trackers';
|
||||
|
||||
const Table = (props) => {
|
||||
const {
|
||||
setDetailedDataCurrent,
|
||||
setButtonType,
|
||||
setModalOpened,
|
||||
isSmallScreen,
|
||||
setIsLoading,
|
||||
filtering,
|
||||
isDetailed,
|
||||
toggleDetailedLogs,
|
||||
setLogsPage,
|
||||
setLogsPagination,
|
||||
processingGetLogs,
|
||||
logs,
|
||||
pages,
|
||||
page,
|
||||
isLoading,
|
||||
} = props;
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const toggleBlocking = (type, domain) => {
|
||||
const {
|
||||
setRules, getFilteringStatus, addSuccessToast,
|
||||
} = props;
|
||||
const { userRules } = filtering;
|
||||
|
||||
const lineEnding = !endsWith(userRules, '\n') ? '\n' : '';
|
||||
const baseRule = `||${domain}^$important`;
|
||||
const baseUnblocking = `@@${baseRule}`;
|
||||
|
||||
const blockingRule = type === BLOCK_ACTIONS.BLOCK ? baseUnblocking : baseRule;
|
||||
const unblockingRule = type === BLOCK_ACTIONS.BLOCK ? baseRule : baseUnblocking;
|
||||
const preparedBlockingRule = new RegExp(`(^|\n)${escapeRegExp(blockingRule)}($|\n)`);
|
||||
const preparedUnblockingRule = new RegExp(`(^|\n)${escapeRegExp(unblockingRule)}($|\n)`);
|
||||
|
||||
const matchPreparedBlockingRule = userRules.match(preparedBlockingRule);
|
||||
const matchPreparedUnblockingRule = userRules.match(preparedUnblockingRule);
|
||||
|
||||
if (matchPreparedBlockingRule) {
|
||||
setRules(userRules.replace(`${blockingRule}`, ''));
|
||||
addSuccessToast(`${t('rule_removed_from_custom_filtering_toast')}: ${blockingRule}`);
|
||||
} else if (!matchPreparedUnblockingRule) {
|
||||
setRules(`${userRules}${lineEnding}${unblockingRule}\n`);
|
||||
addSuccessToast(`${t('rule_added_to_custom_filtering_toast')}: ${unblockingRule}`);
|
||||
} else if (matchPreparedUnblockingRule) {
|
||||
addSuccessToast(`${t('rule_added_to_custom_filtering_toast')}: ${unblockingRule}`);
|
||||
return;
|
||||
} else if (!matchPreparedBlockingRule) {
|
||||
addSuccessToast(`${t('rule_removed_from_custom_filtering_toast')}: ${blockingRule}`);
|
||||
return;
|
||||
}
|
||||
|
||||
getFilteringStatus();
|
||||
};
|
||||
|
||||
const getFilterName = (filters, whitelistFilters, filterId, t) => {
|
||||
if (filterId === CUSTOM_FILTERING_RULES_ID) {
|
||||
return t('custom_filter_rules');
|
||||
}
|
||||
|
||||
const filter = filters.find((filter) => filter.id === filterId)
|
||||
|| whitelistFilters.find((filter) => filter.id === filterId);
|
||||
let filterName = '';
|
||||
|
||||
if (filter) {
|
||||
filterName = filter.name;
|
||||
}
|
||||
|
||||
if (!filterName) {
|
||||
filterName = t('unknown_filter', { filterId });
|
||||
}
|
||||
|
||||
return filterName;
|
||||
};
|
||||
|
||||
|
||||
const columns = [
|
||||
{
|
||||
Header: t('time_table_header'),
|
||||
accessor: 'time',
|
||||
Cell: (row) => getDateCell(row, isDetailed),
|
||||
minWidth: 70,
|
||||
maxHeight: 60,
|
||||
headerClassName: 'logs__text',
|
||||
},
|
||||
{
|
||||
Header: t('request_table_header'),
|
||||
accessor: 'domain',
|
||||
Cell: (row) => {
|
||||
const {
|
||||
isDetailed,
|
||||
autoClients,
|
||||
dnssec_enabled,
|
||||
} = props;
|
||||
|
||||
return getDomainCell({
|
||||
row,
|
||||
t,
|
||||
isDetailed,
|
||||
toggleBlocking,
|
||||
autoClients,
|
||||
dnssec_enabled,
|
||||
});
|
||||
},
|
||||
minWidth: 180,
|
||||
maxHeight: 60,
|
||||
headerClassName: 'logs__text',
|
||||
},
|
||||
{
|
||||
Header: t('response_table_header'),
|
||||
accessor: 'response',
|
||||
Cell: (row) => getResponseCell(
|
||||
row,
|
||||
filtering,
|
||||
t,
|
||||
isDetailed,
|
||||
getFilterName,
|
||||
),
|
||||
minWidth: 150,
|
||||
maxHeight: 60,
|
||||
headerClassName: 'logs__text',
|
||||
},
|
||||
{
|
||||
Header: function Header() {
|
||||
return <div className="d-flex justify-content-between">
|
||||
{t('client_table_header')}
|
||||
{<span>
|
||||
<svg
|
||||
className={classNames('icons icon--24 icon--green mr-2 cursor--pointer', {
|
||||
'icon--selected': !isDetailed,
|
||||
})}
|
||||
onClick={() => toggleDetailedLogs(false)}
|
||||
>
|
||||
<title><Trans>compact</Trans></title>
|
||||
<use xlinkHref='#list' />
|
||||
</svg>
|
||||
<svg
|
||||
className={classNames('icons icon--24 icon--green cursor--pointer', {
|
||||
'icon--selected': isDetailed,
|
||||
})}
|
||||
onClick={() => toggleDetailedLogs(true)}
|
||||
>
|
||||
<title><Trans>default</Trans></title>
|
||||
<use xlinkHref='#detailed_list' />
|
||||
</svg>
|
||||
</span>}
|
||||
</div>;
|
||||
},
|
||||
accessor: 'client',
|
||||
Cell: (row) => {
|
||||
const {
|
||||
isDetailed,
|
||||
autoClients,
|
||||
filtering: { processingRules },
|
||||
} = props;
|
||||
|
||||
return getClientCell({
|
||||
row,
|
||||
t,
|
||||
isDetailed,
|
||||
toggleBlocking,
|
||||
autoClients,
|
||||
processingRules,
|
||||
});
|
||||
},
|
||||
minWidth: 123,
|
||||
maxHeight: 60,
|
||||
headerClassName: 'logs__text',
|
||||
className: 'pb-0',
|
||||
},
|
||||
];
|
||||
|
||||
const changePage = async (page) => {
|
||||
setIsLoading(true);
|
||||
|
||||
const { oldest, getLogs, pages } = props;
|
||||
const isLastPage = pages && (page + 1 === pages);
|
||||
|
||||
await Promise.all([
|
||||
setLogsPage(page),
|
||||
setLogsPagination({
|
||||
page,
|
||||
pageSize: TABLE_DEFAULT_PAGE_SIZE,
|
||||
}),
|
||||
].concat(isLastPage ? getLogs(oldest, page) : []));
|
||||
|
||||
setIsLoading(false);
|
||||
};
|
||||
|
||||
const tableClass = classNames('logs__table', {
|
||||
'logs__table--detailed': isDetailed,
|
||||
});
|
||||
|
||||
return (
|
||||
<ReactTable
|
||||
manual
|
||||
minRows={0}
|
||||
page={page}
|
||||
pages={pages}
|
||||
columns={columns}
|
||||
filterable={false}
|
||||
sortable={false}
|
||||
resizable={false}
|
||||
data={logs || []}
|
||||
loading={isLoading || processingGetLogs}
|
||||
showPageJump={false}
|
||||
showPageSizeOptions={false}
|
||||
onPageChange={changePage}
|
||||
className={tableClass}
|
||||
defaultPageSize={TABLE_DEFAULT_PAGE_SIZE}
|
||||
loadingText={
|
||||
<>
|
||||
<Loading />
|
||||
<h6 className="loading__text">{t('loading_table_status')}</h6>
|
||||
</>
|
||||
}
|
||||
getLoadingProps={() => ({ className: 'loading__container' })}
|
||||
rowsText={t('rows_table_footer_text')}
|
||||
noDataText={!processingGetLogs
|
||||
&& <label className="logs__text logs__text--bold">{t('nothing_found')}</label>}
|
||||
pageText=''
|
||||
ofText=''
|
||||
showPagination={logs.length > 0}
|
||||
getPaginationProps={() => ({ className: 'custom-pagination custom-pagination--padding' })}
|
||||
getTbodyProps={() => ({ className: 'd-block' })}
|
||||
previousText={
|
||||
<svg className="icons icon--24 icon--gray w-100 h-100 cursor--pointer">
|
||||
<title><Trans>previous_btn</Trans></title>
|
||||
<use xlinkHref="#arrow-left" />
|
||||
</svg>}
|
||||
nextText={
|
||||
<svg className="icons icon--24 icon--gray w-100 h-100 cursor--pointer">
|
||||
<title><Trans>next_btn</Trans></title>
|
||||
<use xlinkHref="#arrow-right" />
|
||||
</svg>}
|
||||
renderTotalPagesCount={() => false}
|
||||
getTrGroupProps={(_state, rowInfo) => {
|
||||
if (!rowInfo) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const { reason } = rowInfo.original;
|
||||
const colorClass = FILTERED_STATUS_TO_META_MAP[reason] ? FILTERED_STATUS_TO_META_MAP[reason].color : 'white';
|
||||
|
||||
return { className: colorClass };
|
||||
}}
|
||||
getTrProps={(state, rowInfo) => ({
|
||||
className: isDetailed ? 'row--detailed' : '',
|
||||
onClick: () => {
|
||||
if (isSmallScreen) {
|
||||
const { dnssec_enabled, autoClients } = props;
|
||||
const {
|
||||
answer_dnssec,
|
||||
client,
|
||||
domain,
|
||||
elapsedMs,
|
||||
info,
|
||||
reason,
|
||||
response,
|
||||
time,
|
||||
tracker,
|
||||
upstream,
|
||||
type,
|
||||
client_proto,
|
||||
filterId,
|
||||
rule,
|
||||
originalResponse,
|
||||
status,
|
||||
} = rowInfo.original;
|
||||
|
||||
const hasTracker = !!tracker;
|
||||
|
||||
const autoClient = autoClients
|
||||
.find((autoClient) => autoClient.name === client);
|
||||
|
||||
const { whois_info } = info;
|
||||
const country = whois_info?.country;
|
||||
const city = whois_info?.city;
|
||||
const network = whois_info?.orgname;
|
||||
|
||||
const source = autoClient?.source;
|
||||
|
||||
const formattedElapsedMs = formatElapsedMs(elapsedMs, t);
|
||||
const isFiltered = checkFiltered(reason);
|
||||
|
||||
const isBlocked = reason === FILTERED_STATUS.FILTERED_BLACK_LIST
|
||||
|| reason === FILTERED_STATUS.FILTERED_BLOCKED_SERVICE;
|
||||
|
||||
const buttonType = isFiltered ? BLOCK_ACTIONS.UNBLOCK : BLOCK_ACTIONS.BLOCK;
|
||||
const onToggleBlock = () => {
|
||||
toggleBlocking(buttonType, domain);
|
||||
};
|
||||
|
||||
const isBlockedByResponse = originalResponse.length > 0 && isBlocked;
|
||||
const requestStatus = t(isBlockedByResponse ? 'blocked_by_cname_or_ip' : FILTERED_STATUS_TO_META_MAP[reason]?.label || reason);
|
||||
|
||||
const protocol = t(SCHEME_TO_PROTOCOL_MAP[client_proto]) || '';
|
||||
|
||||
const sourceData = getSourceData(tracker);
|
||||
|
||||
const { filters, whitelistFilters } = filtering;
|
||||
const filter = getFilterName(filters, whitelistFilters, filterId, t);
|
||||
|
||||
const detailedData = {
|
||||
time_table_header: formatTime(time, LONG_TIME_FORMAT),
|
||||
date: formatDateTime(time, DEFAULT_SHORT_DATE_FORMAT_OPTIONS),
|
||||
encryption_status: isBlocked
|
||||
? <div className="bg--danger">{requestStatus}</div> : requestStatus,
|
||||
domain,
|
||||
type_table_header: type,
|
||||
protocol,
|
||||
known_tracker: hasTracker && 'title',
|
||||
table_name: tracker?.name,
|
||||
category_label: hasTracker && captitalizeWords(tracker.category),
|
||||
tracker_source: hasTracker && sourceData
|
||||
&& <a
|
||||
href={sourceData.url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="link--green">{sourceData.name}
|
||||
</a>,
|
||||
response_details: 'title',
|
||||
install_settings_dns: upstream,
|
||||
elapsed: formattedElapsedMs,
|
||||
filter: isBlocked ? filter : null,
|
||||
rule_label: rule,
|
||||
response_table_header: response?.join('\n'),
|
||||
response_code: status,
|
||||
client_details: 'title',
|
||||
ip_address: client,
|
||||
name: info?.name,
|
||||
country,
|
||||
city,
|
||||
network,
|
||||
source_label: source,
|
||||
validated_with_dnssec: dnssec_enabled ? Boolean(answer_dnssec) : false,
|
||||
original_response: originalResponse?.join('\n'),
|
||||
[buttonType]: <div onClick={onToggleBlock}
|
||||
className={classNames('title--border text-center', {
|
||||
'bg--danger': isBlocked,
|
||||
})}>{t(buttonType)}</div>,
|
||||
};
|
||||
|
||||
setDetailedDataCurrent(processContent(detailedData));
|
||||
setButtonType(buttonType);
|
||||
setModalOpened(true);
|
||||
}
|
||||
},
|
||||
})}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
Table.propTypes = {
|
||||
logs: PropTypes.array.isRequired,
|
||||
pages: PropTypes.number.isRequired,
|
||||
page: PropTypes.number.isRequired,
|
||||
autoClients: PropTypes.array.isRequired,
|
||||
defaultPageSize: PropTypes.number,
|
||||
oldest: PropTypes.string.isRequired,
|
||||
filtering: PropTypes.object.isRequired,
|
||||
processingGetLogs: PropTypes.bool.isRequired,
|
||||
processingGetConfig: PropTypes.bool.isRequired,
|
||||
isDetailed: PropTypes.bool.isRequired,
|
||||
setLogsPage: PropTypes.func.isRequired,
|
||||
setLogsPagination: PropTypes.func.isRequired,
|
||||
getLogs: PropTypes.func.isRequired,
|
||||
toggleDetailedLogs: PropTypes.func.isRequired,
|
||||
setRules: PropTypes.func.isRequired,
|
||||
addSuccessToast: PropTypes.func.isRequired,
|
||||
getFilteringStatus: PropTypes.func.isRequired,
|
||||
isLoading: PropTypes.bool.isRequired,
|
||||
setIsLoading: PropTypes.func.isRequired,
|
||||
dnssec_enabled: PropTypes.bool.isRequired,
|
||||
setDetailedDataCurrent: PropTypes.func.isRequired,
|
||||
setButtonType: PropTypes.func.isRequired,
|
||||
setModalOpened: PropTypes.func.isRequired,
|
||||
isSmallScreen: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default Table;
|
||||
@@ -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 |
@@ -1,5 +1,4 @@
|
||||
import React, { Fragment, useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Trans } from 'react-i18next';
|
||||
import Modal from 'react-modal';
|
||||
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||
@@ -8,34 +7,32 @@ import queryString from 'query-string';
|
||||
import classNames from 'classnames';
|
||||
import {
|
||||
BLOCK_ACTIONS,
|
||||
TABLE_DEFAULT_PAGE_SIZE,
|
||||
TABLE_FIRST_PAGE,
|
||||
smallScreenSize,
|
||||
SMALL_SCREEN_SIZE,
|
||||
} from '../../helpers/constants';
|
||||
import Loading from '../ui/Loading';
|
||||
import Filters from './Filters';
|
||||
import Table from './Table';
|
||||
import Disabled from './Disabled';
|
||||
import { getFilteringStatus } from '../../actions/filtering';
|
||||
import { getClients } from '../../actions';
|
||||
import { getDnsConfig } from '../../actions/dnsConfig';
|
||||
import {
|
||||
getLogsConfig,
|
||||
refreshFilteredLogs,
|
||||
resetFilteredLogs,
|
||||
setFilteredLogs,
|
||||
toggleDetailedLogs,
|
||||
} from '../../actions/queryLogs';
|
||||
import { addSuccessToast } from '../../actions/toasts';
|
||||
import InfiniteTable from './InfiniteTable';
|
||||
import './Logs.css';
|
||||
import { BUTTON_PREFIX } from './Cells/helpers';
|
||||
|
||||
const processContent = (data, buttonType) => Object.entries(data)
|
||||
const processContent = (data) => Object.entries(data)
|
||||
.map(([key, value]) => {
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const isTitle = value === 'title';
|
||||
const isButton = key === buttonType;
|
||||
const isButton = key.startsWith(BUTTON_PREFIX);
|
||||
const isBoolean = typeof value === 'boolean';
|
||||
const isHidden = isBoolean && value === false;
|
||||
|
||||
@@ -48,21 +45,20 @@ const processContent = (data, buttonType) => Object.entries(data)
|
||||
keyClass = '';
|
||||
}
|
||||
|
||||
return isHidden ? null : <Fragment key={key}>
|
||||
return isHidden ? null : <div key={key}>
|
||||
<div
|
||||
className={classNames(`key__${key}`, keyClass, {
|
||||
'font-weight-bold': isBoolean && value === true,
|
||||
})}>
|
||||
className={classNames(`key__${key}`, keyClass, {
|
||||
'font-weight-bold': isBoolean && value === true,
|
||||
})}>
|
||||
<Trans>{isButton ? value : key}</Trans>
|
||||
</div>
|
||||
<div className={`value__${key} text-pre text-truncate`}>
|
||||
<Trans>{(isTitle || isButton || isBoolean) ? '' : value || '—'}</Trans>
|
||||
</div>
|
||||
</Fragment>;
|
||||
</div>;
|
||||
});
|
||||
|
||||
|
||||
const Logs = (props) => {
|
||||
const Logs = () => {
|
||||
const dispatch = useDispatch();
|
||||
const history = useHistory();
|
||||
|
||||
@@ -71,17 +67,25 @@ const Logs = (props) => {
|
||||
search: search_url_param = '',
|
||||
} = queryString.parse(history.location.search);
|
||||
|
||||
const { filter } = useSelector((state) => state.queryLogs, shallowEqual);
|
||||
const {
|
||||
enabled,
|
||||
processingGetConfig,
|
||||
processingAdditionalLogs,
|
||||
processingGetLogs,
|
||||
} = useSelector((state) => state.queryLogs, shallowEqual);
|
||||
const filter = useSelector((state) => state.queryLogs.filter, shallowEqual);
|
||||
const logs = useSelector((state) => state.queryLogs.logs, shallowEqual);
|
||||
|
||||
const search = filter?.search || search_url_param;
|
||||
const response_status = filter?.response_status || response_status_url_param;
|
||||
|
||||
const [isSmallScreen, setIsSmallScreen] = useState(window.innerWidth < smallScreenSize);
|
||||
const [isSmallScreen, setIsSmallScreen] = useState(window.innerWidth < SMALL_SCREEN_SIZE);
|
||||
const [detailedDataCurrent, setDetailedDataCurrent] = useState({});
|
||||
const [buttonType, setButtonType] = useState(BLOCK_ACTIONS.BLOCK);
|
||||
const [isModalOpened, setModalOpened] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const closeModal = () => setModalOpened(false);
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
@@ -94,44 +98,11 @@ const Logs = (props) => {
|
||||
})();
|
||||
}, [response_status, search]);
|
||||
|
||||
const {
|
||||
filtering,
|
||||
setLogsPage,
|
||||
setLogsPagination,
|
||||
toggleDetailedLogs,
|
||||
dashboard,
|
||||
dnsConfig,
|
||||
queryLogs: {
|
||||
enabled,
|
||||
processingGetConfig,
|
||||
processingAdditionalLogs,
|
||||
processingGetLogs,
|
||||
oldest,
|
||||
logs,
|
||||
pages,
|
||||
page,
|
||||
isDetailed,
|
||||
},
|
||||
} = props;
|
||||
|
||||
const mediaQuery = window.matchMedia(`(max-width: ${smallScreenSize}px)`);
|
||||
const mediaQuery = window.matchMedia(`(max-width: ${SMALL_SCREEN_SIZE}px)`);
|
||||
const mediaQueryHandler = (e) => {
|
||||
setIsSmallScreen(e.matches);
|
||||
if (e.matches) {
|
||||
toggleDetailedLogs(false);
|
||||
}
|
||||
};
|
||||
|
||||
const closeModal = () => setModalOpened(false);
|
||||
|
||||
const getLogs = (older_than, page, initial) => {
|
||||
if (enabled) {
|
||||
props.getLogs({
|
||||
older_than,
|
||||
page,
|
||||
pageSize: TABLE_DEFAULT_PAGE_SIZE,
|
||||
initial,
|
||||
});
|
||||
dispatch(toggleDetailedLogs(false));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -149,7 +120,6 @@ const Logs = (props) => {
|
||||
|
||||
(async () => {
|
||||
setIsLoading(true);
|
||||
dispatch(setLogsPage(TABLE_FIRST_PAGE));
|
||||
dispatch(getFilteringStatus());
|
||||
dispatch(getClients());
|
||||
try {
|
||||
@@ -169,6 +139,7 @@ const Logs = (props) => {
|
||||
mediaQuery.removeEventListener('change', mediaQueryHandler);
|
||||
} catch (e1) {
|
||||
try {
|
||||
// Safari 13.1 do not support mediaQuery.addEventListener('change', handler)
|
||||
mediaQuery.removeListener(mediaQueryHandler);
|
||||
} catch (e2) {
|
||||
console.error(e2);
|
||||
@@ -179,99 +150,53 @@ const Logs = (props) => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
const refreshLogs = async () => {
|
||||
setIsLoading(true);
|
||||
await Promise.all([
|
||||
dispatch(setLogsPage(TABLE_FIRST_PAGE)),
|
||||
dispatch(refreshFilteredLogs()),
|
||||
]);
|
||||
dispatch(addSuccessToast('query_log_updated'));
|
||||
setIsLoading(false);
|
||||
};
|
||||
const renderPage = () => <>
|
||||
<Filters
|
||||
filter={{
|
||||
response_status,
|
||||
search,
|
||||
}}
|
||||
setIsLoading={setIsLoading}
|
||||
processingGetLogs={processingGetLogs}
|
||||
processingAdditionalLogs={processingAdditionalLogs}
|
||||
/>
|
||||
<InfiniteTable
|
||||
isLoading={isLoading}
|
||||
items={logs}
|
||||
isSmallScreen={isSmallScreen}
|
||||
setDetailedDataCurrent={setDetailedDataCurrent}
|
||||
setButtonType={setButtonType}
|
||||
setModalOpened={setModalOpened}
|
||||
/>
|
||||
<Modal portalClassName='grid' isOpen={isSmallScreen && isModalOpened}
|
||||
onRequestClose={closeModal}
|
||||
style={{
|
||||
content: {
|
||||
width: '100%',
|
||||
height: 'fit-content',
|
||||
left: 0,
|
||||
top: 47,
|
||||
padding: '1rem 1.5rem 1rem',
|
||||
},
|
||||
overlay: {
|
||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
className="icon icon--24 icon-cross d-block d-md-none cursor--pointer"
|
||||
onClick={closeModal}>
|
||||
<use xlinkHref="#cross" />
|
||||
</svg>
|
||||
{processContent(detailedDataCurrent, buttonType)}
|
||||
</Modal>
|
||||
</>;
|
||||
|
||||
return (
|
||||
<>
|
||||
{enabled && processingGetConfig && <Loading />}
|
||||
{enabled && !processingGetConfig && (
|
||||
<>
|
||||
<Filters
|
||||
filter={{
|
||||
response_status,
|
||||
search,
|
||||
}}
|
||||
setIsLoading={setIsLoading}
|
||||
processingGetLogs={processingGetLogs}
|
||||
processingAdditionalLogs={processingAdditionalLogs}
|
||||
refreshLogs={refreshLogs}
|
||||
/>
|
||||
<Table
|
||||
isLoading={isLoading}
|
||||
setIsLoading={setIsLoading}
|
||||
logs={logs}
|
||||
pages={pages}
|
||||
page={page}
|
||||
autoClients={dashboard.autoClients}
|
||||
oldest={oldest}
|
||||
filtering={filtering}
|
||||
processingGetLogs={processingGetLogs}
|
||||
processingGetConfig={processingGetConfig}
|
||||
isDetailed={isDetailed}
|
||||
setLogsPagination={setLogsPagination}
|
||||
setLogsPage={setLogsPage}
|
||||
toggleDetailedLogs={toggleDetailedLogs}
|
||||
getLogs={getLogs}
|
||||
setRules={props.setRules}
|
||||
addSuccessToast={props.addSuccessToast}
|
||||
getFilteringStatus={props.getFilteringStatus}
|
||||
dnssec_enabled={dnsConfig.dnssec_enabled}
|
||||
setDetailedDataCurrent={setDetailedDataCurrent}
|
||||
setButtonType={setButtonType}
|
||||
setModalOpened={setModalOpened}
|
||||
isSmallScreen={isSmallScreen}
|
||||
/>
|
||||
<Modal portalClassName='grid' isOpen={isSmallScreen && isModalOpened}
|
||||
onRequestClose={closeModal}
|
||||
style={{
|
||||
content: {
|
||||
width: '100%',
|
||||
height: 'fit-content',
|
||||
left: 0,
|
||||
top: 47,
|
||||
padding: '1rem 1.5rem 1rem',
|
||||
},
|
||||
overlay: {
|
||||
backgroundColor: 'rgba(0,0,0,0.5)',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<svg
|
||||
className="icon icon--24 icon-cross d-block d-md-none cursor--pointer"
|
||||
onClick={closeModal}>
|
||||
<use xlinkHref="#cross" />
|
||||
</svg>
|
||||
{processContent(detailedDataCurrent, buttonType)}
|
||||
</Modal>
|
||||
</>
|
||||
)}
|
||||
{!enabled && !processingGetConfig && (
|
||||
<Disabled />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Logs.propTypes = {
|
||||
getLogs: PropTypes.func.isRequired,
|
||||
queryLogs: PropTypes.object.isRequired,
|
||||
dashboard: PropTypes.object.isRequired,
|
||||
getFilteringStatus: PropTypes.func.isRequired,
|
||||
filtering: PropTypes.object.isRequired,
|
||||
setRules: PropTypes.func.isRequired,
|
||||
addSuccessToast: PropTypes.func.isRequired,
|
||||
setLogsPagination: PropTypes.func.isRequired,
|
||||
setLogsPage: PropTypes.func.isRequired,
|
||||
toggleDetailedLogs: PropTypes.func.isRequired,
|
||||
dnsConfig: PropTypes.object.isRequired,
|
||||
return <>
|
||||
{enabled && processingGetConfig && <Loading />}
|
||||
{enabled && !processingGetConfig && renderPage()}
|
||||
{!enabled && !processingGetConfig && <Disabled />}
|
||||
</>;
|
||||
};
|
||||
|
||||
export default Logs;
|
||||
|
||||
@@ -8,6 +8,7 @@ import CellWrap from '../../ui/CellWrap';
|
||||
|
||||
import whoisCell from './whoisCell';
|
||||
import LogsSearchLink from '../../ui/LogsSearchLink';
|
||||
import { sortIp } from '../../../helpers/helpers';
|
||||
|
||||
const COLUMN_MIN_WIDTH = 200;
|
||||
|
||||
@@ -18,6 +19,7 @@ class AutoClients extends Component {
|
||||
accessor: 'ip',
|
||||
minWidth: COLUMN_MIN_WIDTH,
|
||||
Cell: CellWrap,
|
||||
sortMethod: sortIp,
|
||||
},
|
||||
{
|
||||
Header: this.props.t('table_name'),
|
||||
@@ -85,23 +87,13 @@ class AutoClients extends Component {
|
||||
showPagination
|
||||
defaultPageSize={10}
|
||||
minRows={5}
|
||||
showPageSizeOptions={false}
|
||||
showPageJump={false}
|
||||
renderTotalPagesCount={() => false}
|
||||
previousText={
|
||||
<svg className="icons icon--24 icon--gray w-100 h-100">
|
||||
<use xlinkHref="#arrow-left" />
|
||||
</svg>}
|
||||
nextText={
|
||||
<svg className="icons icon--24 icon--gray w-100 h-100">
|
||||
<use xlinkHref="#arrow-right" />
|
||||
</svg>}
|
||||
loadingText={t('loading_table_status')}
|
||||
pageText=''
|
||||
ofText=''
|
||||
ofText="/"
|
||||
previousText={t('previous_btn')}
|
||||
nextText={t('next_btn')}
|
||||
pageText={t('page_table_footer_text')}
|
||||
rowsText={t('rows_table_footer_text')}
|
||||
loadingText={t('loading_table_status')}
|
||||
noDataText={t('clients_not_found')}
|
||||
getPaginationProps={() => ({ className: 'custom-pagination' })}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
|
||||
@@ -297,23 +297,13 @@ class ClientsTable extends Component {
|
||||
showPagination
|
||||
defaultPageSize={10}
|
||||
minRows={5}
|
||||
showPageSizeOptions={false}
|
||||
showPageJump={false}
|
||||
renderTotalPagesCount={() => false}
|
||||
previousText={
|
||||
<svg className="icons icon--24 icon--gray w-100 h-100">
|
||||
<use xlinkHref="#arrow-left" />
|
||||
</svg>}
|
||||
nextText={
|
||||
<svg className="icons icon--24 icon--gray w-100 h-100">
|
||||
<use xlinkHref="#arrow-right" />
|
||||
</svg>}
|
||||
loadingText={t('loading_table_status')}
|
||||
pageText=''
|
||||
ofText=''
|
||||
ofText="/"
|
||||
previousText={t('previous_btn')}
|
||||
nextText={t('next_btn')}
|
||||
pageText={t('page_table_footer_text')}
|
||||
rowsText={t('rows_table_footer_text')}
|
||||
loadingText={t('loading_table_status')}
|
||||
noDataText={t('clients_not_found')}
|
||||
getPaginationProps={() => ({ className: 'custom-pagination' })}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -15,7 +15,7 @@ import { toggleAllServices } from '../../../helpers/helpers';
|
||||
import {
|
||||
renderInputField,
|
||||
renderGroupField,
|
||||
renderSelectField,
|
||||
renderCheckboxField,
|
||||
renderServiceField,
|
||||
} from '../../../helpers/form';
|
||||
import { validateClientId, validateRequiredValue } from '../../../helpers/validators';
|
||||
@@ -151,7 +151,7 @@ let Form = (props) => {
|
||||
<Field
|
||||
name={setting.name}
|
||||
type="checkbox"
|
||||
component={renderSelectField}
|
||||
component={renderCheckboxField}
|
||||
placeholder={t(setting.placeholder)}
|
||||
disabled={
|
||||
setting.name !== 'use_global_settings'
|
||||
|
||||
@@ -14,7 +14,7 @@ const getFormattedWhois = (value, t) => {
|
||||
<div key={key} title={t(key)}>
|
||||
{icon && (
|
||||
<Fragment>
|
||||
<svg className="logs__whois-icon text-muted-dark icons">
|
||||
<svg className="logs__whois-icon text-muted-dark icons icon--24">
|
||||
<use xlinkHref={`#${icon}`} />
|
||||
</svg>
|
||||
|
||||
|
||||
@@ -1,235 +0,0 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Field, reduxForm, formValueSelector } from 'redux-form';
|
||||
import { Trans, withTranslation } from 'react-i18next';
|
||||
import flow from 'lodash/flow';
|
||||
import { renderInputField, toNumber } from '../../../helpers/form';
|
||||
import { FORM_NAME } from '../../../helpers/constants';
|
||||
import { validateIpv4, validateIsPositiveValue, validateRequiredValue } from '../../../helpers/validators';
|
||||
|
||||
const renderInterfaces = ((interfaces) => (
|
||||
Object.keys(interfaces).map((item) => {
|
||||
const option = interfaces[item];
|
||||
const { name } = option;
|
||||
const onlyIPv6 = option.ip_addresses.every((ip) => ip.includes(':'));
|
||||
let interfaceIP = option.ip_addresses[0];
|
||||
|
||||
if (!onlyIPv6) {
|
||||
option.ip_addresses.forEach((ip) => {
|
||||
if (!ip.includes(':')) {
|
||||
interfaceIP = ip;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<option value={name} key={name} disabled={onlyIPv6}>
|
||||
{name} - {interfaceIP}
|
||||
</option>
|
||||
);
|
||||
})
|
||||
));
|
||||
|
||||
const renderInterfaceValues = ((interfaceValues) => (
|
||||
<ul className="list-unstyled mt-1 mb-0">
|
||||
<li>
|
||||
<span className="interface__title">MTU: </span>
|
||||
{interfaceValues.mtu}
|
||||
</li>
|
||||
<li>
|
||||
<span className="interface__title"><Trans>dhcp_hardware_address</Trans>: </span>
|
||||
{interfaceValues.hardware_address}
|
||||
</li>
|
||||
<li>
|
||||
<span className="interface__title"><Trans>dhcp_ip_addresses</Trans>: </span>
|
||||
{
|
||||
interfaceValues.ip_addresses
|
||||
.map((ip) => <span key={ip} className="interface__ip">{ip}</span>)
|
||||
}
|
||||
</li>
|
||||
</ul>
|
||||
));
|
||||
|
||||
const clearFields = (change, resetDhcp, t) => {
|
||||
const fields = {
|
||||
interface_name: '',
|
||||
gateway_ip: '',
|
||||
subnet_mask: '',
|
||||
range_start: '',
|
||||
range_end: '',
|
||||
lease_duration: 86400,
|
||||
};
|
||||
|
||||
// eslint-disable-next-line no-alert
|
||||
if (window.confirm(t('dhcp_reset'))) {
|
||||
Object.keys(fields).forEach((field) => change(field, fields[field]));
|
||||
resetDhcp();
|
||||
}
|
||||
};
|
||||
|
||||
let Form = (props) => {
|
||||
const {
|
||||
t,
|
||||
handleSubmit,
|
||||
submitting,
|
||||
invalid,
|
||||
enabled,
|
||||
interfaces,
|
||||
interfaceValue,
|
||||
processingConfig,
|
||||
processingInterfaces,
|
||||
resetDhcp,
|
||||
change,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
{!processingInterfaces && interfaces
|
||||
&& <div className="row">
|
||||
<div className="col-sm-12 col-md-6">
|
||||
<div className="form__group form__group--settings">
|
||||
<label>{t('dhcp_interface_select')}</label>
|
||||
<Field
|
||||
name="interface_name"
|
||||
component="select"
|
||||
className="form-control custom-select"
|
||||
validate={[validateRequiredValue]}
|
||||
>
|
||||
<option value="" disabled={enabled}>
|
||||
{t('dhcp_interface_select')}
|
||||
</option>
|
||||
{renderInterfaces(interfaces)}
|
||||
</Field>
|
||||
</div>
|
||||
</div>
|
||||
{interfaceValue
|
||||
&& <div className="col-sm-12 col-md-6">
|
||||
{interfaces[interfaceValue]
|
||||
&& renderInterfaceValues(interfaces[interfaceValue])}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<hr/>
|
||||
<div className="row">
|
||||
<div className="col-lg-6">
|
||||
<div className="form__group form__group--settings">
|
||||
<label>{t('dhcp_form_gateway_input')}</label>
|
||||
<Field
|
||||
id="gateway_ip"
|
||||
name="gateway_ip"
|
||||
component={renderInputField}
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder={t('dhcp_form_gateway_input')}
|
||||
validate={[validateIpv4, validateRequiredValue]}
|
||||
/>
|
||||
</div>
|
||||
<div className="form__group form__group--settings">
|
||||
<label>{t('dhcp_form_subnet_input')}</label>
|
||||
<Field
|
||||
id="subnet_mask"
|
||||
name="subnet_mask"
|
||||
component={renderInputField}
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder={t('dhcp_form_subnet_input')}
|
||||
validate={[validateIpv4, validateRequiredValue]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-6">
|
||||
<div className="form__group form__group--settings">
|
||||
<div className="row">
|
||||
<div className="col-12">
|
||||
<label>{t('dhcp_form_range_title')}</label>
|
||||
</div>
|
||||
<div className="col">
|
||||
<Field
|
||||
id="range_start"
|
||||
name="range_start"
|
||||
component={renderInputField}
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder={t('dhcp_form_range_start')}
|
||||
validate={[validateIpv4, validateRequiredValue]}
|
||||
/>
|
||||
</div>
|
||||
<div className="col">
|
||||
<Field
|
||||
id="range_end"
|
||||
name="range_end"
|
||||
component={renderInputField}
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder={t('dhcp_form_range_end')}
|
||||
validate={[validateIpv4, validateRequiredValue]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form__group form__group--settings">
|
||||
<label>{t('dhcp_form_lease_title')}</label>
|
||||
<Field
|
||||
name="lease_duration"
|
||||
component={renderInputField}
|
||||
type="number"
|
||||
className="form-control"
|
||||
placeholder={t('dhcp_form_lease_input')}
|
||||
validate={[validateRequiredValue, validateIsPositiveValue]}
|
||||
normalize={toNumber}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="btn-list">
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-success btn-standard"
|
||||
disabled={submitting || invalid || processingConfig}
|
||||
>
|
||||
{t('save_config')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary btn-standart"
|
||||
disabled={submitting || processingConfig}
|
||||
onClick={() => clearFields(change, resetDhcp, t)}
|
||||
>
|
||||
<Trans>reset_settings</Trans>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
Form.propTypes = {
|
||||
handleSubmit: PropTypes.func.isRequired,
|
||||
submitting: PropTypes.bool.isRequired,
|
||||
invalid: PropTypes.bool.isRequired,
|
||||
interfaces: PropTypes.object.isRequired,
|
||||
interfaceValue: PropTypes.string,
|
||||
initialValues: PropTypes.object.isRequired,
|
||||
processingConfig: PropTypes.bool.isRequired,
|
||||
processingInterfaces: PropTypes.bool.isRequired,
|
||||
enabled: PropTypes.bool.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
resetDhcp: PropTypes.func.isRequired,
|
||||
change: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
const selector = formValueSelector(FORM_NAME.DHCP);
|
||||
|
||||
Form = connect((state) => {
|
||||
const interfaceValue = selector(state, 'interface_name');
|
||||
return {
|
||||
interfaceValue,
|
||||
};
|
||||
})(Form);
|
||||
|
||||
export default flow([
|
||||
withTranslation(),
|
||||
reduxForm({ form: FORM_NAME.DHCP }),
|
||||
])(Form);
|
||||
145
client/src/components/Settings/Dhcp/FormDHCPv4.js
Normal file
145
client/src/components/Settings/Dhcp/FormDHCPv4.js
Normal file
@@ -0,0 +1,145 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { shallowEqual, useSelector } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Field, reduxForm } from 'redux-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import {
|
||||
renderInputField,
|
||||
toNumber,
|
||||
} from '../../../helpers/form';
|
||||
import { FORM_NAME } from '../../../helpers/constants';
|
||||
import {
|
||||
validateIpv4,
|
||||
validateIsPositiveValue,
|
||||
validateRequiredValue,
|
||||
validateIpv4RangeEnd,
|
||||
} from '../../../helpers/validators';
|
||||
|
||||
const FormDHCPv4 = ({
|
||||
handleSubmit,
|
||||
submitting,
|
||||
processingConfig,
|
||||
ipv4placeholders,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const dhcp = useSelector((state) => state.form[FORM_NAME.DHCPv4], shallowEqual);
|
||||
const interfaces = useSelector((state) => state.form[FORM_NAME.DHCP_INTERFACES], shallowEqual);
|
||||
const interface_name = interfaces?.values?.interface_name;
|
||||
|
||||
const isInterfaceIncludesIpv4 = useSelector(
|
||||
(state) => !!state.dhcp?.interfaces?.[interface_name]?.ipv4_addresses,
|
||||
);
|
||||
|
||||
const isEmptyConfig = !Object.values(dhcp?.values?.v4 ?? {})
|
||||
.some(Boolean);
|
||||
|
||||
const invalid = dhcp?.syncErrors || interfaces?.syncErrors || !isInterfaceIncludesIpv4
|
||||
|| isEmptyConfig || submitting || processingConfig;
|
||||
|
||||
const validateRequired = useCallback((value) => {
|
||||
if (isEmptyConfig) {
|
||||
return undefined;
|
||||
}
|
||||
return validateRequiredValue(value);
|
||||
}, [isEmptyConfig]);
|
||||
|
||||
return <form onSubmit={handleSubmit}>
|
||||
<div className="row">
|
||||
<div className="col-lg-6">
|
||||
<div className="form__group form__group--settings">
|
||||
<label>{t('dhcp_form_gateway_input')}</label>
|
||||
<Field
|
||||
name="v4.gateway_ip"
|
||||
component={renderInputField}
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder={t(ipv4placeholders.gateway_ip)}
|
||||
validate={[validateIpv4, validateRequired]}
|
||||
disabled={!isInterfaceIncludesIpv4}
|
||||
/>
|
||||
</div>
|
||||
<div className="form__group form__group--settings">
|
||||
<label>{t('dhcp_form_subnet_input')}</label>
|
||||
<Field
|
||||
name="v4.subnet_mask"
|
||||
component={renderInputField}
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder={t(ipv4placeholders.subnet_mask)}
|
||||
validate={[validateIpv4, validateRequired]}
|
||||
disabled={!isInterfaceIncludesIpv4}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-6">
|
||||
<div className="form__group form__group--settings">
|
||||
<div className="row">
|
||||
<div className="col-12">
|
||||
<label>{t('dhcp_form_range_title')}</label>
|
||||
</div>
|
||||
<div className="col">
|
||||
<Field
|
||||
name="v4.range_start"
|
||||
component={renderInputField}
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder={t(ipv4placeholders.range_start)}
|
||||
validate={[validateIpv4]}
|
||||
disabled={!isInterfaceIncludesIpv4}
|
||||
/>
|
||||
</div>
|
||||
<div className="col">
|
||||
<Field
|
||||
name="v4.range_end"
|
||||
component={renderInputField}
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder={t(ipv4placeholders.range_end)}
|
||||
validate={[validateIpv4, validateIpv4RangeEnd]}
|
||||
disabled={!isInterfaceIncludesIpv4}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form__group form__group--settings">
|
||||
<label>{t('dhcp_form_lease_title')}</label>
|
||||
<Field
|
||||
name="v4.lease_duration"
|
||||
component={renderInputField}
|
||||
type="number"
|
||||
className="form-control"
|
||||
placeholder={t(ipv4placeholders.lease_duration)}
|
||||
validate={[validateIsPositiveValue, validateRequired]}
|
||||
normalize={toNumber}
|
||||
min={0}
|
||||
disabled={!isInterfaceIncludesIpv4}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="btn-list">
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-success btn-standard"
|
||||
disabled={invalid}
|
||||
>
|
||||
{t('save_config')}
|
||||
</button>
|
||||
</div>
|
||||
</form>;
|
||||
};
|
||||
|
||||
FormDHCPv4.propTypes = {
|
||||
handleSubmit: PropTypes.func.isRequired,
|
||||
submitting: PropTypes.bool.isRequired,
|
||||
initialValues: PropTypes.object.isRequired,
|
||||
processingConfig: PropTypes.bool.isRequired,
|
||||
change: PropTypes.func.isRequired,
|
||||
reset: PropTypes.func.isRequired,
|
||||
ipv4placeholders: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default reduxForm({
|
||||
form: FORM_NAME.DHCPv4,
|
||||
})(FormDHCPv4);
|
||||
120
client/src/components/Settings/Dhcp/FormDHCPv6.js
Normal file
120
client/src/components/Settings/Dhcp/FormDHCPv6.js
Normal file
@@ -0,0 +1,120 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { shallowEqual, useSelector } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Field, reduxForm } from 'redux-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import {
|
||||
renderInputField,
|
||||
toNumber,
|
||||
} from '../../../helpers/form';
|
||||
import { FORM_NAME } from '../../../helpers/constants';
|
||||
import {
|
||||
validateIpv6,
|
||||
validateIsPositiveValue,
|
||||
validateRequiredValue,
|
||||
} from '../../../helpers/validators';
|
||||
|
||||
const FormDHCPv6 = ({
|
||||
handleSubmit,
|
||||
submitting,
|
||||
processingConfig,
|
||||
ipv6placeholders,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const dhcp = useSelector((state) => state.form[FORM_NAME.DHCPv6], shallowEqual);
|
||||
const interfaces = useSelector((state) => state.form[FORM_NAME.DHCP_INTERFACES], shallowEqual);
|
||||
const interface_name = interfaces?.values?.interface_name;
|
||||
|
||||
const isInterfaceIncludesIpv6 = useSelector(
|
||||
(state) => !!state.dhcp?.interfaces?.[interface_name]?.ipv6_addresses,
|
||||
);
|
||||
|
||||
const isEmptyConfig = !Object.values(dhcp?.values?.v6 ?? {})
|
||||
.some(Boolean);
|
||||
|
||||
const invalid = dhcp?.syncErrors || interfaces?.syncErrors || !isInterfaceIncludesIpv6
|
||||
|| isEmptyConfig || submitting || processingConfig;
|
||||
|
||||
const validateRequired = useCallback((value) => {
|
||||
if (isEmptyConfig) {
|
||||
return undefined;
|
||||
}
|
||||
return validateRequiredValue(value);
|
||||
}, [isEmptyConfig]);
|
||||
|
||||
return <form onSubmit={handleSubmit}>
|
||||
<div className="row">
|
||||
<div className="col-lg-6">
|
||||
<div className="form__group form__group--settings">
|
||||
<div className="row">
|
||||
<div className="col-12">
|
||||
<label>{t('dhcp_form_range_title')}</label>
|
||||
</div>
|
||||
<div className="col">
|
||||
<Field
|
||||
name="v6.range_start"
|
||||
component={renderInputField}
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder={t(ipv6placeholders.range_start)}
|
||||
validate={[validateIpv6, validateRequired]}
|
||||
disabled={!isInterfaceIncludesIpv6}
|
||||
/>
|
||||
</div>
|
||||
<div className="col">
|
||||
<Field
|
||||
name="v6.range_end"
|
||||
component="input"
|
||||
type="text"
|
||||
className="form-control disabled cursor--not-allowed"
|
||||
placeholder={t(ipv6placeholders.range_end)}
|
||||
value={t(ipv6placeholders.range_end)}
|
||||
disabled
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col-lg-6 form__group form__group--settings">
|
||||
<label>{t('dhcp_form_lease_title')}</label>
|
||||
<Field
|
||||
name="v6.lease_duration"
|
||||
component={renderInputField}
|
||||
type="number"
|
||||
className="form-control"
|
||||
placeholder={t(ipv6placeholders.lease_duration)}
|
||||
validate={[validateIsPositiveValue, validateRequired]}
|
||||
normalizeOnBlur={toNumber}
|
||||
min={0}
|
||||
disabled={!isInterfaceIncludesIpv6}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="btn-list">
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-success btn-standard"
|
||||
disabled={invalid}
|
||||
>
|
||||
{t('save_config')}
|
||||
</button>
|
||||
</div>
|
||||
</form>;
|
||||
};
|
||||
|
||||
FormDHCPv6.propTypes = {
|
||||
handleSubmit: PropTypes.func.isRequired,
|
||||
submitting: PropTypes.bool.isRequired,
|
||||
initialValues: PropTypes.object.isRequired,
|
||||
processingConfig: PropTypes.bool.isRequired,
|
||||
change: PropTypes.func.isRequired,
|
||||
reset: PropTypes.func.isRequired,
|
||||
ipv6placeholders: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
export default reduxForm({
|
||||
form: FORM_NAME.DHCPv6,
|
||||
})(FormDHCPv6);
|
||||
109
client/src/components/Settings/Dhcp/Interfaces.js
Normal file
109
client/src/components/Settings/Dhcp/Interfaces.js
Normal file
@@ -0,0 +1,109 @@
|
||||
import React from 'react';
|
||||
import { shallowEqual, useSelector } from 'react-redux';
|
||||
import { Field, reduxForm } from 'redux-form';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import propTypes from 'prop-types';
|
||||
import { renderSelectField } from '../../../helpers/form';
|
||||
import { validateRequiredValue } from '../../../helpers/validators';
|
||||
import { FORM_NAME } from '../../../helpers/constants';
|
||||
|
||||
const renderInterfaces = (interfaces) => Object.keys(interfaces)
|
||||
.map((item) => {
|
||||
const option = interfaces[item];
|
||||
const { name } = option;
|
||||
|
||||
const [interfaceIPv4] = option?.ipv4_addresses ?? [];
|
||||
const [interfaceIPv6] = option?.ipv6_addresses ?? [];
|
||||
|
||||
const optionContent = [name, interfaceIPv4, interfaceIPv6].filter(Boolean).join(' - ');
|
||||
|
||||
return <option value={name} key={name}>{optionContent}</option>;
|
||||
});
|
||||
|
||||
|
||||
const getInterfaceValues = ({
|
||||
gateway_ip,
|
||||
hardware_address,
|
||||
ip_addresses,
|
||||
}) => [
|
||||
{
|
||||
name: 'dhcp_form_gateway_input',
|
||||
value: gateway_ip,
|
||||
},
|
||||
{
|
||||
name: 'dhcp_hardware_address',
|
||||
value: hardware_address,
|
||||
},
|
||||
{
|
||||
name: 'dhcp_ip_addresses',
|
||||
value: ip_addresses,
|
||||
render: (ip_addresses) => ip_addresses
|
||||
.map((ip) => <span key={ip} className="interface__ip">{ip}</span>),
|
||||
},
|
||||
];
|
||||
|
||||
const renderInterfaceValues = ({
|
||||
gateway_ip,
|
||||
hardware_address,
|
||||
ip_addresses,
|
||||
}) => <div className='d-flex align-items-end col-6'>
|
||||
<ul className="list-unstyled m-0">
|
||||
{getInterfaceValues({
|
||||
gateway_ip,
|
||||
hardware_address,
|
||||
ip_addresses,
|
||||
}).map(({ name, value, render }) => value && <li key={name}>
|
||||
<span className="interface__title"><Trans>{name}</Trans>: </span>
|
||||
{render?.(value) || value}
|
||||
</li>)}
|
||||
</ul>
|
||||
</div>;
|
||||
|
||||
const Interfaces = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const {
|
||||
processingInterfaces,
|
||||
interfaces,
|
||||
enabled,
|
||||
} = useSelector((store) => store.dhcp, shallowEqual);
|
||||
|
||||
const interface_name = useSelector(
|
||||
(store) => store.form[FORM_NAME.DHCP_INTERFACES]?.values?.interface_name,
|
||||
);
|
||||
|
||||
const interfaceValue = interface_name && interfaces[interface_name];
|
||||
|
||||
return !processingInterfaces
|
||||
&& interfaces
|
||||
&& <>
|
||||
<div className="row align-items-center pb-2">
|
||||
<div className="col-6">
|
||||
<Field
|
||||
name="interface_name"
|
||||
component={renderSelectField}
|
||||
className="form-control custom-select"
|
||||
validate={[validateRequiredValue]}
|
||||
label='dhcp_interface_select'
|
||||
>
|
||||
<option value='' disabled={enabled}>
|
||||
{t('dhcp_interface_select')}
|
||||
</option>
|
||||
{renderInterfaces(interfaces)}
|
||||
</Field>
|
||||
</div>
|
||||
{interfaceValue
|
||||
&& renderInterfaceValues(interfaceValue)}
|
||||
</div>
|
||||
</>;
|
||||
};
|
||||
|
||||
renderInterfaceValues.propTypes = {
|
||||
gateway_ip: propTypes.string.isRequired,
|
||||
hardware_address: propTypes.string.isRequired,
|
||||
ip_addresses: propTypes.arrayOf(propTypes.string).isRequired,
|
||||
};
|
||||
|
||||
export default reduxForm({
|
||||
form: FORM_NAME.DHCP_INTERFACES,
|
||||
})(Interfaces);
|
||||
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
|
||||
import ReactTable from 'react-table';
|
||||
import { Trans, withTranslation } from 'react-i18next';
|
||||
import { LEASES_TABLE_DEFAULT_PAGE_SIZE } from '../../../helpers/constants';
|
||||
import { sortIp } from '../../../helpers/helpers';
|
||||
|
||||
class Leases extends Component {
|
||||
cellWrap = ({ value }) => (
|
||||
@@ -27,6 +28,7 @@ class Leases extends Component {
|
||||
Header: 'IP',
|
||||
accessor: 'ip',
|
||||
Cell: this.cellWrap,
|
||||
sortMethod: sortIp,
|
||||
}, {
|
||||
Header: <Trans>dhcp_table_hostname</Trans>,
|
||||
accessor: 'hostname',
|
||||
|
||||
@@ -1,22 +1,27 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Field, reduxForm } from 'redux-form';
|
||||
import { Trans, withTranslation } from 'react-i18next';
|
||||
import flow from 'lodash/flow';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { renderInputField } from '../../../../helpers/form';
|
||||
import { validateIpv4, validateMac, validateRequiredValue } from '../../../../helpers/validators';
|
||||
import { FORM_NAME } from '../../../../helpers/constants';
|
||||
import { toggleLeaseModal } from '../../../../actions';
|
||||
|
||||
const Form = (props) => {
|
||||
const {
|
||||
t,
|
||||
handleSubmit,
|
||||
reset,
|
||||
pristine,
|
||||
submitting,
|
||||
toggleLeaseModal,
|
||||
processingAdding,
|
||||
} = props;
|
||||
const Form = ({
|
||||
handleSubmit,
|
||||
reset,
|
||||
pristine,
|
||||
submitting,
|
||||
processingAdding,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const onClick = () => {
|
||||
reset();
|
||||
dispatch(toggleLeaseModal());
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
@@ -61,10 +66,7 @@ const Form = (props) => {
|
||||
type="button"
|
||||
className="btn btn-secondary btn-standard"
|
||||
disabled={submitting}
|
||||
onClick={() => {
|
||||
reset();
|
||||
toggleLeaseModal();
|
||||
}}
|
||||
onClick={onClick}
|
||||
>
|
||||
<Trans>cancel_btn</Trans>
|
||||
</button>
|
||||
@@ -86,12 +88,7 @@ Form.propTypes = {
|
||||
handleSubmit: PropTypes.func.isRequired,
|
||||
reset: PropTypes.func.isRequired,
|
||||
submitting: PropTypes.bool.isRequired,
|
||||
toggleLeaseModal: PropTypes.func.isRequired,
|
||||
processingAdding: PropTypes.bool.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default flow([
|
||||
withTranslation(),
|
||||
reduxForm({ form: FORM_NAME.LEASE }),
|
||||
])(Form);
|
||||
export default reduxForm({ form: FORM_NAME.LEASE })(Form);
|
||||
|
||||
@@ -2,36 +2,37 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Trans, withTranslation } from 'react-i18next';
|
||||
import ReactModal from 'react-modal';
|
||||
|
||||
import { useDispatch } from 'react-redux';
|
||||
import Form from './Form';
|
||||
import { toggleLeaseModal } from '../../../../actions';
|
||||
|
||||
const Modal = (props) => {
|
||||
const {
|
||||
isModalOpen,
|
||||
handleSubmit,
|
||||
toggleLeaseModal,
|
||||
processingAdding,
|
||||
} = props;
|
||||
const Modal = ({
|
||||
isModalOpen,
|
||||
handleSubmit,
|
||||
processingAdding,
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const toggleModal = () => dispatch(toggleLeaseModal());
|
||||
|
||||
return (
|
||||
<ReactModal
|
||||
className="Modal__Bootstrap modal-dialog modal-dialog-centered modal-dialog--clients"
|
||||
closeTimeoutMS={0}
|
||||
isOpen={isModalOpen}
|
||||
onRequestClose={() => toggleLeaseModal()}
|
||||
onRequestClose={toggleModal}
|
||||
>
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<h4 className="modal-title">
|
||||
<Trans>dhcp_new_static_lease</Trans>
|
||||
</h4>
|
||||
<button type="button" className="close" onClick={() => toggleLeaseModal()}>
|
||||
<button type="button" className="close" onClick={toggleModal}>
|
||||
<span className="sr-only">Close</span>
|
||||
</button>
|
||||
</div>
|
||||
<Form
|
||||
onSubmit={handleSubmit}
|
||||
toggleLeaseModal={toggleLeaseModal}
|
||||
processingAdding={processingAdding}
|
||||
/>
|
||||
</div>
|
||||
@@ -42,7 +43,6 @@ const Modal = (props) => {
|
||||
Modal.propTypes = {
|
||||
isModalOpen: PropTypes.bool.isRequired,
|
||||
handleSubmit: PropTypes.func.isRequired,
|
||||
toggleLeaseModal: PropTypes.func.isRequired,
|
||||
processingAdding: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,114 +1,116 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ReactTable from 'react-table';
|
||||
import { Trans, withTranslation } from 'react-i18next';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { LEASES_TABLE_DEFAULT_PAGE_SIZE } from '../../../../helpers/constants';
|
||||
|
||||
import { sortIp } from '../../../../helpers/helpers';
|
||||
import Modal from './Modal';
|
||||
import { addStaticLease, removeStaticLease } from '../../../../actions';
|
||||
|
||||
class StaticLeases extends Component {
|
||||
cellWrap = ({ value }) => (
|
||||
<div className="logs__row o-hidden">
|
||||
const cellWrap = ({ value }) => (
|
||||
<div className="logs__row o-hidden">
|
||||
<span className="logs__text" title={value}>
|
||||
{value}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
</div>
|
||||
);
|
||||
|
||||
handleSubmit = (data) => {
|
||||
this.props.addStaticLease(data);
|
||||
const StaticLeases = ({
|
||||
isModalOpen,
|
||||
processingAdding,
|
||||
processingDeleting,
|
||||
staticLeases,
|
||||
}) => {
|
||||
const [t] = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handleSubmit = (data) => {
|
||||
dispatch(addStaticLease(data));
|
||||
};
|
||||
|
||||
handleDelete = (ip, mac, hostname = '') => {
|
||||
const handleDelete = (ip, mac, hostname = '') => {
|
||||
const name = hostname || ip;
|
||||
// eslint-disable-next-line no-alert
|
||||
if (window.confirm(this.props.t('delete_confirm', { key: name }))) {
|
||||
this.props.removeStaticLease({ ip, mac, hostname });
|
||||
if (window.confirm(t('delete_confirm', { key: name }))) {
|
||||
dispatch(removeStaticLease({
|
||||
ip,
|
||||
mac,
|
||||
hostname,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
isModalOpen,
|
||||
toggleLeaseModal,
|
||||
processingAdding,
|
||||
processingDeleting,
|
||||
staticLeases,
|
||||
t,
|
||||
} = this.props;
|
||||
return (
|
||||
<Fragment>
|
||||
<ReactTable
|
||||
data={staticLeases || []}
|
||||
columns={[
|
||||
{
|
||||
Header: 'MAC',
|
||||
accessor: 'mac',
|
||||
Cell: this.cellWrap,
|
||||
},
|
||||
{
|
||||
Header: 'IP',
|
||||
accessor: 'ip',
|
||||
Cell: this.cellWrap,
|
||||
},
|
||||
{
|
||||
Header: <Trans>dhcp_table_hostname</Trans>,
|
||||
accessor: 'hostname',
|
||||
Cell: this.cellWrap,
|
||||
},
|
||||
{
|
||||
Header: <Trans>actions_table_header</Trans>,
|
||||
accessor: 'actions',
|
||||
maxWidth: 150,
|
||||
Cell: (row) => {
|
||||
const { ip, mac, hostname } = row.original;
|
||||
return (
|
||||
<>
|
||||
<ReactTable
|
||||
data={staticLeases || []}
|
||||
columns={[
|
||||
{
|
||||
Header: 'MAC',
|
||||
accessor: 'mac',
|
||||
Cell: cellWrap,
|
||||
},
|
||||
{
|
||||
Header: 'IP',
|
||||
accessor: 'ip',
|
||||
sortMethod: sortIp,
|
||||
Cell: cellWrap,
|
||||
},
|
||||
{
|
||||
Header: <Trans>dhcp_table_hostname</Trans>,
|
||||
accessor: 'hostname',
|
||||
Cell: cellWrap,
|
||||
},
|
||||
{
|
||||
Header: <Trans>actions_table_header</Trans>,
|
||||
accessor: 'actions',
|
||||
maxWidth: 150,
|
||||
// eslint-disable-next-line react/display-name
|
||||
Cell: (row) => {
|
||||
const { ip, mac, hostname } = row.original;
|
||||
|
||||
return (
|
||||
<div className="logs__row logs__row--center">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-icon btn-icon--green btn-outline-secondary btn-sm"
|
||||
title={t('delete_table_action')}
|
||||
disabled={processingDeleting}
|
||||
onClick={() => this.handleDelete(ip, mac, hostname)
|
||||
}
|
||||
return <div className="logs__row logs__row--center">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-icon btn-icon--green btn-outline-secondary btn-sm"
|
||||
title={t('delete_table_action')}
|
||||
disabled={processingDeleting}
|
||||
onClick={() => handleDelete(ip, mac, hostname)}
|
||||
>
|
||||
<svg className="icons">
|
||||
<use xlinkHref="#delete"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
</div>;
|
||||
},
|
||||
]}
|
||||
pageSize={LEASES_TABLE_DEFAULT_PAGE_SIZE}
|
||||
showPageSizeOptions={false}
|
||||
showPagination={staticLeases.length > LEASES_TABLE_DEFAULT_PAGE_SIZE}
|
||||
noDataText={t('dhcp_static_leases_not_found')}
|
||||
className="-striped -highlight card-table-overflow"
|
||||
minRows={6}
|
||||
/>
|
||||
<Modal
|
||||
isModalOpen={isModalOpen}
|
||||
toggleLeaseModal={toggleLeaseModal}
|
||||
handleSubmit={this.handleSubmit}
|
||||
processingAdding={processingAdding}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
]}
|
||||
pageSize={LEASES_TABLE_DEFAULT_PAGE_SIZE}
|
||||
showPageSizeOptions={false}
|
||||
showPagination={staticLeases.length > LEASES_TABLE_DEFAULT_PAGE_SIZE}
|
||||
noDataText={t('dhcp_static_leases_not_found')}
|
||||
className="-striped -highlight card-table-overflow"
|
||||
minRows={6}
|
||||
/>
|
||||
<Modal
|
||||
isModalOpen={isModalOpen}
|
||||
handleSubmit={handleSubmit}
|
||||
processingAdding={processingAdding}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
StaticLeases.propTypes = {
|
||||
staticLeases: PropTypes.array.isRequired,
|
||||
isModalOpen: PropTypes.bool.isRequired,
|
||||
toggleLeaseModal: PropTypes.func.isRequired,
|
||||
removeStaticLease: PropTypes.func.isRequired,
|
||||
addStaticLease: PropTypes.func.isRequired,
|
||||
processingAdding: PropTypes.bool.isRequired,
|
||||
processingDeleting: PropTypes.bool.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default withTranslation()(StaticLeases);
|
||||
cellWrap.propTypes = {
|
||||
value: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default StaticLeases;
|
||||
|
||||
@@ -1,281 +1,277 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { Trans, withTranslation } from 'react-i18next';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
|
||||
import { DHCP_STATUS_RESPONSE } from '../../../helpers/constants';
|
||||
import Form from './Form';
|
||||
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||
import classNames from 'classnames';
|
||||
import { destroy } from 'redux-form';
|
||||
import {
|
||||
DHCP_DESCRIPTION_PLACEHOLDERS,
|
||||
DHCP_FORM_NAMES,
|
||||
STATUS_RESPONSE,
|
||||
FORM_NAME,
|
||||
} from '../../../helpers/constants';
|
||||
import Leases from './Leases';
|
||||
import StaticLeases from './StaticLeases/index';
|
||||
import Card from '../../ui/Card';
|
||||
import Accordion from '../../ui/Accordion';
|
||||
import PageTitle from '../../ui/PageTitle';
|
||||
import Loading from '../../ui/Loading';
|
||||
import {
|
||||
findActiveDhcp,
|
||||
getDhcpInterfaces,
|
||||
getDhcpStatus,
|
||||
resetDhcp,
|
||||
setDhcpConfig,
|
||||
toggleDhcp,
|
||||
toggleLeaseModal,
|
||||
} from '../../../actions';
|
||||
import FormDHCPv4 from './FormDHCPv4';
|
||||
import FormDHCPv6 from './FormDHCPv6';
|
||||
import Interfaces from './Interfaces';
|
||||
import {
|
||||
calculateDhcpPlaceholdersIpv4,
|
||||
calculateDhcpPlaceholdersIpv6,
|
||||
} from '../../../helpers/helpers';
|
||||
|
||||
class Dhcp extends Component {
|
||||
componentDidMount() {
|
||||
this.props.getDhcpStatus();
|
||||
this.props.getDhcpInterfaces();
|
||||
}
|
||||
const Dhcp = () => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const {
|
||||
processingStatus,
|
||||
processingConfig,
|
||||
processing,
|
||||
processingInterfaces,
|
||||
check,
|
||||
leases,
|
||||
staticLeases,
|
||||
isModalOpen,
|
||||
processingAdding,
|
||||
processingDeleting,
|
||||
processingDhcp,
|
||||
v4,
|
||||
v6,
|
||||
interface_name: interfaceName,
|
||||
enabled,
|
||||
dhcp_available,
|
||||
interfaces,
|
||||
} = useSelector((state) => state.dhcp, shallowEqual);
|
||||
|
||||
handleFormSubmit = (values) => {
|
||||
if (values.interface_name) {
|
||||
this.props.setDhcpConfig(values);
|
||||
const interface_name = useSelector(
|
||||
(state) => state.form[FORM_NAME.DHCP_INTERFACES]?.values?.interface_name,
|
||||
);
|
||||
|
||||
const [ipv4placeholders, setIpv4Placeholders] = useState(DHCP_DESCRIPTION_PLACEHOLDERS.ipv4);
|
||||
const [ipv6placeholders, setIpv6Placeholders] = useState(DHCP_DESCRIPTION_PLACEHOLDERS.ipv6);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(getDhcpStatus());
|
||||
dispatch(getDhcpInterfaces());
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const [ipv4] = interfaces?.[interface_name]?.ipv4_addresses ?? [];
|
||||
const [ipv6] = interfaces?.[interface_name]?.ipv6_addresses ?? [];
|
||||
const gateway_ip = interfaces?.[interface_name]?.gateway_ip;
|
||||
|
||||
const v4placeholders = ipv4
|
||||
? calculateDhcpPlaceholdersIpv4(ipv4, gateway_ip)
|
||||
: DHCP_DESCRIPTION_PLACEHOLDERS.ipv4;
|
||||
|
||||
const v6placeholders = ipv6
|
||||
? calculateDhcpPlaceholdersIpv6()
|
||||
: DHCP_DESCRIPTION_PLACEHOLDERS.ipv6;
|
||||
|
||||
setIpv4Placeholders(v4placeholders);
|
||||
setIpv6Placeholders(v6placeholders);
|
||||
}, [interface_name]);
|
||||
|
||||
const clear = () => {
|
||||
// eslint-disable-next-line no-alert
|
||||
if (window.confirm(t('dhcp_reset'))) {
|
||||
Object.values(DHCP_FORM_NAMES)
|
||||
.forEach((formName) => dispatch(destroy(formName)));
|
||||
dispatch(resetDhcp());
|
||||
}
|
||||
};
|
||||
|
||||
handleToggle = (config) => {
|
||||
this.props.toggleDhcp(config);
|
||||
const handleSubmit = (values) => {
|
||||
dispatch(setDhcpConfig({
|
||||
interface_name,
|
||||
...values,
|
||||
}));
|
||||
};
|
||||
|
||||
getToggleDhcpButton = () => {
|
||||
const {
|
||||
config, check, processingDhcp, processingConfig,
|
||||
} = this.props.dhcp;
|
||||
const otherDhcpFound = check?.otherServer
|
||||
&& check.otherServer.found === DHCP_STATUS_RESPONSE.YES;
|
||||
const filledConfig = Object.keys(config).every((key) => {
|
||||
if (key === 'enabled' || key === 'icmp_timeout_msec') {
|
||||
return true;
|
||||
}
|
||||
const enteredSomeV4Value = Object.values(v4)
|
||||
.some(Boolean);
|
||||
const enteredSomeV6Value = Object.values(v6)
|
||||
.some(Boolean);
|
||||
const enteredSomeValue = enteredSomeV4Value || enteredSomeV6Value || interfaceName;
|
||||
|
||||
return config[key];
|
||||
const getToggleDhcpButton = () => {
|
||||
const otherDhcpFound = check && (check.v4.other_server.found === STATUS_RESPONSE.YES
|
||||
|| check.v6.other_server.found === STATUS_RESPONSE.YES);
|
||||
|
||||
const filledConfig = interface_name && (Object.values(v4)
|
||||
.every(Boolean) || Object.values(v6)
|
||||
.every(Boolean));
|
||||
|
||||
const className = classNames('btn btn-sm mr-2', {
|
||||
'btn-gray': enabled,
|
||||
'btn-outline-success': !enabled,
|
||||
});
|
||||
|
||||
if (config.enabled) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-standard mr-2 btn-gray"
|
||||
onClick={() => this.props.toggleDhcp(config)}
|
||||
disabled={processingDhcp || processingConfig}
|
||||
>
|
||||
<Trans>dhcp_disable</Trans>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
const onClickDisable = () => dispatch(toggleDhcp({ enabled }));
|
||||
const onClickEnable = () => {
|
||||
const values = {
|
||||
enabled,
|
||||
interface_name,
|
||||
v4: enteredSomeV4Value ? v4 : {},
|
||||
v6: enteredSomeV6Value ? v6 : {},
|
||||
};
|
||||
dispatch(toggleDhcp(values));
|
||||
};
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-standard mr-2 btn-success"
|
||||
onClick={() => this.handleToggle(config)}
|
||||
disabled={
|
||||
!filledConfig || !check || otherDhcpFound || processingDhcp || processingConfig
|
||||
}
|
||||
>
|
||||
<Trans>dhcp_enable</Trans>
|
||||
</button>
|
||||
);
|
||||
return <button
|
||||
type="button"
|
||||
className={className}
|
||||
onClick={enabled ? onClickDisable : onClickEnable}
|
||||
disabled={processingDhcp || processingConfig
|
||||
|| (!enabled && (!filledConfig || !check || otherDhcpFound))}
|
||||
>
|
||||
<Trans>{enabled ? 'dhcp_disable' : 'dhcp_enable'}</Trans>
|
||||
</button>;
|
||||
};
|
||||
|
||||
getActiveDhcpMessage = (t, check) => {
|
||||
const { found } = check.otherServer;
|
||||
const statusButtonClass = classNames('btn btn-sm mx-2', {
|
||||
'btn-loading btn-primary': processingStatus,
|
||||
'btn-outline-primary': !processingStatus,
|
||||
});
|
||||
|
||||
if (found === DHCP_STATUS_RESPONSE.ERROR) {
|
||||
return (
|
||||
<div className="text-danger mb-2">
|
||||
<Trans>dhcp_error</Trans>
|
||||
<div className="mt-2 mb-2">
|
||||
<Accordion label={t('error_details')}>
|
||||
<span>{check.otherServer.error}</span>
|
||||
</Accordion>
|
||||
const onClick = () => dispatch(findActiveDhcp(interface_name));
|
||||
|
||||
const toggleModal = () => dispatch(toggleLeaseModal());
|
||||
|
||||
const initialV4 = enteredSomeV4Value ? v4 : {};
|
||||
const initialV6 = enteredSomeV6Value ? v6 : {};
|
||||
|
||||
if (processing || processingInterfaces) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
if (!processing && !dhcp_available) {
|
||||
return <div className="text-center pt-5">
|
||||
<h2>
|
||||
<Trans>unavailable_dhcp</Trans>
|
||||
</h2>
|
||||
<h4>
|
||||
<Trans>unavailable_dhcp_desc</Trans>
|
||||
</h4>
|
||||
</div>;
|
||||
}
|
||||
|
||||
const toggleDhcpButton = getToggleDhcpButton();
|
||||
|
||||
return <>
|
||||
<PageTitle title={t('dhcp_settings')} subtitle={t('dhcp_description')}>
|
||||
<div className="page-title__actions">
|
||||
<div className="mb-3">
|
||||
{toggleDhcpButton}
|
||||
<button
|
||||
type="button"
|
||||
className={statusButtonClass}
|
||||
onClick={onClick}
|
||||
disabled={enabled || !interface_name || processingConfig}
|
||||
>
|
||||
<Trans>check_dhcp_servers</Trans>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className='btn btn-sm mx-2 btn-outline-secondary'
|
||||
disabled={!enteredSomeValue || processingConfig}
|
||||
onClick={clear}
|
||||
>
|
||||
<Trans>reset_settings</Trans>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</PageTitle>
|
||||
{!processing && !processingInterfaces
|
||||
&& <>
|
||||
{!enabled
|
||||
&& check
|
||||
&& (check.v4.other_server.found !== STATUS_RESPONSE.NO
|
||||
|| check.v6.other_server.found !== STATUS_RESPONSE.NO)
|
||||
&& <div className="mb-5">
|
||||
<hr />
|
||||
<div className="text-danger">
|
||||
<Trans>dhcp_warning</Trans>
|
||||
</div>
|
||||
</div>}
|
||||
<Interfaces
|
||||
initialValues={{ interface_name: interfaceName }}
|
||||
/>
|
||||
<Card
|
||||
title={t('dhcp_ipv4_settings')}
|
||||
bodyType="card-body box-body--settings"
|
||||
>
|
||||
<div>
|
||||
<FormDHCPv4
|
||||
onSubmit={handleSubmit}
|
||||
initialValues={{ v4: initialV4 }}
|
||||
processingConfig={processingConfig}
|
||||
ipv4placeholders={ipv4placeholders}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
<Card
|
||||
title={t('dhcp_ipv6_settings')}
|
||||
bodyType="card-body box-body--settings"
|
||||
>
|
||||
<div>
|
||||
<FormDHCPv6
|
||||
onSubmit={handleSubmit}
|
||||
initialValues={{ v6: initialV6 }}
|
||||
processingConfig={processingConfig}
|
||||
ipv6placeholders={ipv6placeholders}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
{enabled
|
||||
&& <Card
|
||||
title={t('dhcp_leases')}
|
||||
bodyType="card-body box-body--settings"
|
||||
>
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
<Leases leases={leases} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mb-2">
|
||||
{found === DHCP_STATUS_RESPONSE.YES ? (
|
||||
<div className="text-danger">
|
||||
<Trans>dhcp_found</Trans>
|
||||
</Card>}
|
||||
<Card
|
||||
title={t('dhcp_static_leases')}
|
||||
bodyType="card-body box-body--settings"
|
||||
>
|
||||
<div className="row">
|
||||
<div className="col-12">
|
||||
<StaticLeases
|
||||
staticLeases={staticLeases}
|
||||
isModalOpen={isModalOpen}
|
||||
processingAdding={processingAdding}
|
||||
processingDeleting={processingDeleting}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-secondary">
|
||||
<Trans>dhcp_not_found</Trans>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
getDhcpWarning = (check) => {
|
||||
if (check.otherServer.found === DHCP_STATUS_RESPONSE.NO) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="text-danger">
|
||||
<Trans>dhcp_warning</Trans>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
getStaticIpWarning = (t, check, interfaceName) => {
|
||||
if (check.staticIP.static === DHCP_STATUS_RESPONSE.ERROR) {
|
||||
return (
|
||||
<Fragment>
|
||||
<div className="text-danger mb-2">
|
||||
<Trans>dhcp_static_ip_error</Trans>
|
||||
<div className="mt-2 mb-2">
|
||||
<Accordion label={t('error_details')}>
|
||||
<span>{check.staticIP.error}</span>
|
||||
</Accordion>
|
||||
</div>
|
||||
</div>
|
||||
<hr className="mt-4 mb-4" />
|
||||
</Fragment>
|
||||
);
|
||||
} if (
|
||||
check.staticIP.static === DHCP_STATUS_RESPONSE.NO
|
||||
&& check.staticIP.ip
|
||||
&& interfaceName
|
||||
) {
|
||||
return (
|
||||
<Fragment>
|
||||
<div className="text-secondary mb-2">
|
||||
<Trans
|
||||
components={[<strong key="0">example</strong>]}
|
||||
values={{
|
||||
interfaceName,
|
||||
ipAddress: check.staticIP.ip,
|
||||
}}
|
||||
<div className="col-12">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-success btn-standard mt-3"
|
||||
onClick={toggleModal}
|
||||
>
|
||||
dhcp_dynamic_ip_found
|
||||
</Trans>
|
||||
<Trans>dhcp_add_static_lease</Trans>
|
||||
</button>
|
||||
</div>
|
||||
<hr className="mt-4 mb-4" />
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
t,
|
||||
dhcp,
|
||||
resetDhcp,
|
||||
findActiveDhcp,
|
||||
addStaticLease,
|
||||
removeStaticLease,
|
||||
toggleLeaseModal,
|
||||
} = this.props;
|
||||
const statusButtonClass = classnames({
|
||||
'btn btn-primary btn-standard': true,
|
||||
'btn btn-primary btn-standard btn-loading': dhcp.processingStatus,
|
||||
});
|
||||
const { enabled, interface_name, ...values } = dhcp.config;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<PageTitle title={t('dhcp_settings')} />
|
||||
{(dhcp.processing || dhcp.processingInterfaces) && <Loading />}
|
||||
{!dhcp.processing && !dhcp.processingInterfaces && (
|
||||
<Fragment>
|
||||
<Card
|
||||
title={t('dhcp_title')}
|
||||
subtitle={t('dhcp_description')}
|
||||
bodyType="card-body box-body--settings"
|
||||
>
|
||||
<div className="dhcp">
|
||||
<Fragment>
|
||||
<Form
|
||||
onSubmit={this.handleFormSubmit}
|
||||
initialValues={{
|
||||
interface_name,
|
||||
...values,
|
||||
}}
|
||||
interfaces={dhcp.interfaces}
|
||||
processingConfig={dhcp.processingConfig}
|
||||
processingInterfaces={dhcp.processingInterfaces}
|
||||
enabled={enabled}
|
||||
resetDhcp={resetDhcp}
|
||||
/>
|
||||
<hr />
|
||||
<div className="card-actions mb-3">
|
||||
{this.getToggleDhcpButton()}
|
||||
<button
|
||||
type="button"
|
||||
className={statusButtonClass}
|
||||
onClick={() => findActiveDhcp(interface_name)}
|
||||
disabled={
|
||||
enabled || !interface_name || dhcp.processingConfig
|
||||
}
|
||||
>
|
||||
<Trans>check_dhcp_servers</Trans>
|
||||
</button>
|
||||
</div>
|
||||
{!enabled && dhcp.check && (
|
||||
<Fragment>
|
||||
{this.getStaticIpWarning(t, dhcp.check, interface_name)}
|
||||
{this.getActiveDhcpMessage(t, dhcp.check)}
|
||||
{this.getDhcpWarning(dhcp.check)}
|
||||
</Fragment>
|
||||
)}
|
||||
</Fragment>
|
||||
</div>
|
||||
</Card>
|
||||
{dhcp.config.enabled && (
|
||||
<Card
|
||||
title={t('dhcp_leases')}
|
||||
bodyType="card-body box-body--settings"
|
||||
>
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
<Leases leases={dhcp.leases} />
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
)}
|
||||
<Card
|
||||
title={t('dhcp_static_leases')}
|
||||
bodyType="card-body box-body--settings"
|
||||
>
|
||||
<div className="row">
|
||||
<div className="col-12">
|
||||
<StaticLeases
|
||||
staticLeases={dhcp.staticLeases}
|
||||
isModalOpen={dhcp.isModalOpen}
|
||||
addStaticLease={addStaticLease}
|
||||
removeStaticLease={removeStaticLease}
|
||||
toggleLeaseModal={toggleLeaseModal}
|
||||
processingAdding={dhcp.processingAdding}
|
||||
processingDeleting={dhcp.processingDeleting}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-success btn-standard mt-3"
|
||||
onClick={() => toggleLeaseModal()}
|
||||
>
|
||||
<Trans>dhcp_add_static_lease</Trans>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</Fragment>
|
||||
)}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Dhcp.propTypes = {
|
||||
dhcp: PropTypes.object.isRequired,
|
||||
toggleDhcp: PropTypes.func.isRequired,
|
||||
getDhcpStatus: PropTypes.func.isRequired,
|
||||
setDhcpConfig: PropTypes.func.isRequired,
|
||||
findActiveDhcp: PropTypes.func.isRequired,
|
||||
addStaticLease: PropTypes.func.isRequired,
|
||||
removeStaticLease: PropTypes.func.isRequired,
|
||||
toggleLeaseModal: PropTypes.func.isRequired,
|
||||
getDhcpInterfaces: PropTypes.func.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
resetDhcp: PropTypes.func.isRequired,
|
||||
</div>
|
||||
</Card>
|
||||
</>}
|
||||
</>;
|
||||
};
|
||||
|
||||
export default withTranslation()(Dhcp);
|
||||
export default Dhcp;
|
||||
|
||||
@@ -1,40 +1,36 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withTranslation } from 'react-i18next';
|
||||
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||
import Form from './Form';
|
||||
import Card from '../../../ui/Card';
|
||||
import { setAccessList } from '../../../../actions/access';
|
||||
|
||||
class Access extends Component {
|
||||
handleFormSubmit = (values) => {
|
||||
this.props.setAccessList(values);
|
||||
const Access = () => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const {
|
||||
processing,
|
||||
processingSet,
|
||||
...values
|
||||
} = useSelector((state) => state.access, shallowEqual);
|
||||
|
||||
const handleFormSubmit = (values) => {
|
||||
dispatch(setAccessList(values));
|
||||
};
|
||||
|
||||
render() {
|
||||
const { t, access } = this.props;
|
||||
|
||||
const { processing, processingSet, ...values } = access;
|
||||
|
||||
return (
|
||||
<Card
|
||||
title={t('access_title')}
|
||||
subtitle={t('access_desc')}
|
||||
bodyType="card-body box-body--settings"
|
||||
>
|
||||
<Form
|
||||
initialValues={values}
|
||||
onSubmit={this.handleFormSubmit}
|
||||
processingSet={processingSet}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Access.propTypes = {
|
||||
access: PropTypes.object.isRequired,
|
||||
setAccessList: PropTypes.func.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
return (
|
||||
<Card
|
||||
title={t('access_title')}
|
||||
subtitle={t('access_desc')}
|
||||
bodyType="card-body box-body--settings"
|
||||
>
|
||||
<Form
|
||||
initialValues={values}
|
||||
onSubmit={handleFormSubmit}
|
||||
processingSet={processingSet}
|
||||
/>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default withTranslation()(Access);
|
||||
export default Access;
|
||||
|
||||
@@ -53,7 +53,7 @@ const Form = ({
|
||||
{INPUTS_FIELDS.map(({
|
||||
name, title, description, placeholder, validate, max,
|
||||
}) => <div className="col-12" key={name}>
|
||||
<div className="col-7 p-0">
|
||||
<div className="col-12 col-md-7 p-0">
|
||||
<div className="form__group form__group--settings">
|
||||
<label htmlFor={name}
|
||||
className="form__label form__label--with-desc">{t(title)}</label>
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { Field, reduxForm, formValueSelector } from 'redux-form';
|
||||
import { Trans, withTranslation } from 'react-i18next';
|
||||
import flow from 'lodash/flow';
|
||||
import { shallowEqual, useSelector } from 'react-redux';
|
||||
import { Field, reduxForm } from 'redux-form';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import {
|
||||
renderInputField,
|
||||
renderRadioField,
|
||||
renderSelectField,
|
||||
renderCheckboxField,
|
||||
toNumber,
|
||||
} from '../../../../helpers/form';
|
||||
import {
|
||||
@@ -18,32 +17,36 @@ import {
|
||||
} from '../../../../helpers/validators';
|
||||
import { BLOCKING_MODES, FORM_NAME } from '../../../../helpers/constants';
|
||||
|
||||
const checkboxes = [{
|
||||
name: 'edns_cs_enabled',
|
||||
placeholder: 'edns_enable',
|
||||
subtitle: 'edns_cs_desc',
|
||||
},
|
||||
{
|
||||
name: 'dnssec_enabled',
|
||||
placeholder: 'dnssec_enable',
|
||||
subtitle: 'dnssec_enable_desc',
|
||||
},
|
||||
{
|
||||
name: 'disable_ipv6',
|
||||
placeholder: 'disable_ipv6',
|
||||
subtitle: 'disable_ipv6_desc',
|
||||
}];
|
||||
const checkboxes = [
|
||||
{
|
||||
name: 'edns_cs_enabled',
|
||||
placeholder: 'edns_enable',
|
||||
subtitle: 'edns_cs_desc',
|
||||
},
|
||||
{
|
||||
name: 'dnssec_enabled',
|
||||
placeholder: 'dnssec_enable',
|
||||
subtitle: 'dnssec_enable_desc',
|
||||
},
|
||||
{
|
||||
name: 'disable_ipv6',
|
||||
placeholder: 'disable_ipv6',
|
||||
subtitle: 'disable_ipv6_desc',
|
||||
},
|
||||
];
|
||||
|
||||
const customIps = [{
|
||||
description: 'blocking_ipv4_desc',
|
||||
name: 'blocking_ipv4',
|
||||
validateIp: validateIpv4,
|
||||
},
|
||||
{
|
||||
description: 'blocking_ipv6_desc',
|
||||
name: 'blocking_ipv6',
|
||||
validateIp: validateIpv6,
|
||||
}];
|
||||
const customIps = [
|
||||
{
|
||||
description: 'blocking_ipv4_desc',
|
||||
name: 'blocking_ipv4',
|
||||
validateIp: validateIpv4,
|
||||
},
|
||||
{
|
||||
description: 'blocking_ipv6_desc',
|
||||
name: 'blocking_ipv6',
|
||||
validateIp: validateIpv6,
|
||||
},
|
||||
];
|
||||
|
||||
const getFields = (processing, t) => Object.values(BLOCKING_MODES)
|
||||
.map((mode) => (
|
||||
@@ -58,114 +61,107 @@ const getFields = (processing, t) => Object.values(BLOCKING_MODES)
|
||||
/>
|
||||
));
|
||||
|
||||
let Form = ({
|
||||
handleSubmit, submitting, invalid, processing, blockingMode, t,
|
||||
}) => <form onSubmit={handleSubmit}>
|
||||
<div className="row">
|
||||
<div className="col-12 col-sm-6">
|
||||
<div className="form__group form__group--settings">
|
||||
<label htmlFor="ratelimit"
|
||||
className="form__label form__label--with-desc">
|
||||
<Trans>rate_limit</Trans>
|
||||
</label>
|
||||
<div className="form__desc form__desc--top">
|
||||
<Trans>rate_limit_desc</Trans>
|
||||
</div>
|
||||
<Field
|
||||
name="ratelimit"
|
||||
type="number"
|
||||
component={renderInputField}
|
||||
className="form-control"
|
||||
placeholder={t('form_enter_rate_limit')}
|
||||
normalize={toNumber}
|
||||
validate={[validateRequiredValue, validateBiggerOrEqualZeroValue]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{checkboxes.map(({ name, placeholder, subtitle }) => <div className="col-12" key={name}>
|
||||
<div className="form__group form__group--settings">
|
||||
<Field
|
||||
name={name}
|
||||
type="checkbox"
|
||||
component={renderSelectField}
|
||||
placeholder={t(placeholder)}
|
||||
disabled={processing}
|
||||
subtitle={t(subtitle)}
|
||||
/>
|
||||
</div>
|
||||
</div>)}
|
||||
<div className="col-12">
|
||||
<div className="form__group form__group--settings mb-4">
|
||||
<label className="form__label form__label--with-desc">
|
||||
<Trans>blocking_mode</Trans>
|
||||
</label>
|
||||
<div className="form__desc form__desc--top">
|
||||
{Object.values(BLOCKING_MODES)
|
||||
.map((mode) => (
|
||||
<li key={mode}>
|
||||
<Trans>{`blocking_mode_${mode}`}</Trans>
|
||||
</li>
|
||||
))}
|
||||
</div>
|
||||
<div className="custom-controls-stacked">
|
||||
{getFields(processing, t)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{blockingMode === BLOCKING_MODES.custom_ip && (
|
||||
<Fragment>
|
||||
{customIps.map(({
|
||||
description,
|
||||
name,
|
||||
validateIp,
|
||||
}) => <div className="col-12 col-sm-6" key={name}>
|
||||
<div className="form__group form__group--settings">
|
||||
<label className="form__label form__label--with-desc"
|
||||
htmlFor={name}><Trans>{name}</Trans>
|
||||
</label>
|
||||
<div className="form__desc form__desc--top">
|
||||
<Trans>{description}</Trans>
|
||||
</div>
|
||||
<Field
|
||||
name={name}
|
||||
component={renderInputField}
|
||||
className="form-control"
|
||||
placeholder={t('form_enter_ip')}
|
||||
validate={[validateIp, validateRequiredValue]}
|
||||
/>
|
||||
const Form = ({
|
||||
handleSubmit, submitting, invalid, processing,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
blocking_mode,
|
||||
} = useSelector((state) => state.form[FORM_NAME.BLOCKING_MODE].values ?? {}, shallowEqual);
|
||||
|
||||
return <form onSubmit={handleSubmit}>
|
||||
<div className="row">
|
||||
<div className="col-12 col-sm-6">
|
||||
<div className="form__group form__group--settings">
|
||||
<label htmlFor="ratelimit"
|
||||
className="form__label form__label--with-desc">
|
||||
<Trans>rate_limit</Trans>
|
||||
</label>
|
||||
<div className="form__desc form__desc--top">
|
||||
<Trans>rate_limit_desc</Trans>
|
||||
</div>
|
||||
</div>)}
|
||||
</Fragment>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-success btn-standard btn-large"
|
||||
disabled={submitting || invalid || processing}
|
||||
>
|
||||
<Trans>save_btn</Trans>
|
||||
</button>
|
||||
</form>;
|
||||
<Field
|
||||
name="ratelimit"
|
||||
type="number"
|
||||
component={renderInputField}
|
||||
className="form-control"
|
||||
placeholder={t('form_enter_rate_limit')}
|
||||
normalize={toNumber}
|
||||
validate={[validateRequiredValue, validateBiggerOrEqualZeroValue]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{checkboxes.map(({ name, placeholder, subtitle }) => <div className="col-12" key={name}>
|
||||
<div className="form__group form__group--settings">
|
||||
<Field
|
||||
name={name}
|
||||
type="checkbox"
|
||||
component={renderCheckboxField}
|
||||
placeholder={t(placeholder)}
|
||||
disabled={processing}
|
||||
subtitle={t(subtitle)}
|
||||
/>
|
||||
</div>
|
||||
</div>)}
|
||||
<div className="col-12">
|
||||
<div className="form__group form__group--settings mb-4">
|
||||
<label className="form__label form__label--with-desc">
|
||||
<Trans>blocking_mode</Trans>
|
||||
</label>
|
||||
<div className="form__desc form__desc--top">
|
||||
{Object.values(BLOCKING_MODES)
|
||||
.map((mode) => (
|
||||
<li key={mode}>
|
||||
<Trans>{`blocking_mode_${mode}`}</Trans>
|
||||
</li>
|
||||
))}
|
||||
</div>
|
||||
<div className="custom-controls-stacked">
|
||||
{getFields(processing, t)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{blocking_mode === BLOCKING_MODES.custom_ip && (
|
||||
<>
|
||||
{customIps.map(({
|
||||
description,
|
||||
name,
|
||||
validateIp,
|
||||
}) => <div className="col-12 col-sm-6" key={name}>
|
||||
<div className="form__group form__group--settings">
|
||||
<label className="form__label form__label--with-desc"
|
||||
htmlFor={name}><Trans>{name}</Trans>
|
||||
</label>
|
||||
<div className="form__desc form__desc--top">
|
||||
<Trans>{description}</Trans>
|
||||
</div>
|
||||
<Field
|
||||
name={name}
|
||||
component={renderInputField}
|
||||
className="form-control"
|
||||
placeholder={t('form_enter_ip')}
|
||||
validate={[validateIp, validateRequiredValue]}
|
||||
/>
|
||||
</div>
|
||||
</div>)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-success btn-standard btn-large"
|
||||
disabled={submitting || invalid || processing}
|
||||
>
|
||||
<Trans>save_btn</Trans>
|
||||
</button>
|
||||
</form>;
|
||||
};
|
||||
|
||||
Form.propTypes = {
|
||||
blockingMode: PropTypes.string.isRequired,
|
||||
handleSubmit: PropTypes.func.isRequired,
|
||||
submitting: PropTypes.bool.isRequired,
|
||||
invalid: PropTypes.bool.isRequired,
|
||||
processing: PropTypes.bool.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
const selector = formValueSelector(FORM_NAME.BLOCKING_MODE);
|
||||
|
||||
Form = connect((state) => {
|
||||
const blockingMode = selector(state, 'blocking_mode');
|
||||
return {
|
||||
blockingMode,
|
||||
};
|
||||
})(Form);
|
||||
|
||||
export default flow([
|
||||
withTranslation(),
|
||||
reduxForm({ form: FORM_NAME.BLOCKING_MODE }),
|
||||
])(Form);
|
||||
export default reduxForm({ form: FORM_NAME.BLOCKING_MODE })(Form);
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withTranslation } from 'react-i18next';
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||
import Card from '../../../ui/Card';
|
||||
import Form from './Form';
|
||||
import { setDnsConfig } from '../../../../actions/dnsConfig';
|
||||
|
||||
const Config = ({ t, dnsConfig, setDnsConfig }) => {
|
||||
const handleFormSubmit = (values) => {
|
||||
setDnsConfig(values);
|
||||
};
|
||||
|
||||
const Config = () => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const {
|
||||
blocking_mode,
|
||||
ratelimit,
|
||||
@@ -19,7 +17,11 @@ const Config = ({ t, dnsConfig, setDnsConfig }) => {
|
||||
dnssec_enabled,
|
||||
disable_ipv6,
|
||||
processingSetConfig,
|
||||
} = dnsConfig;
|
||||
} = useSelector((state) => state.dnsConfig, shallowEqual);
|
||||
|
||||
const handleFormSubmit = (values) => {
|
||||
dispatch(setDnsConfig(values));
|
||||
};
|
||||
|
||||
return (
|
||||
<Card
|
||||
@@ -46,10 +48,4 @@ const Config = ({ t, dnsConfig, setDnsConfig }) => {
|
||||
);
|
||||
};
|
||||
|
||||
Config.propTypes = {
|
||||
dnsConfig: PropTypes.object.isRequired,
|
||||
setDnsConfig: PropTypes.func.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default withTranslation()(Config);
|
||||
export default Config;
|
||||
|
||||
@@ -52,10 +52,11 @@ const Form = ({
|
||||
submitting, invalid, processingSetConfig, processingTestUpstream, handleSubmit,
|
||||
}) => {
|
||||
const dispatch = useDispatch();
|
||||
const [t] = useTranslation();
|
||||
const { t } = useTranslation();
|
||||
const upstream_dns = useSelector((store) => store.form[FORM_NAME.UPSTREAM].values.upstream_dns);
|
||||
const bootstrap_dns = useSelector((store) => store.form[FORM_NAME.UPSTREAM]
|
||||
.values.bootstrap_dns);
|
||||
const bootstrap_dns = useSelector(
|
||||
(store) => store.form[FORM_NAME.UPSTREAM].values.bootstrap_dns,
|
||||
);
|
||||
|
||||
const handleUpstreamTest = () => dispatch(testUpstream({
|
||||
upstream_dns,
|
||||
|
||||
@@ -1,56 +1,46 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||
import Form from './Form';
|
||||
import Card from '../../../ui/Card';
|
||||
import { setDnsConfig } from '../../../../actions/dnsConfig';
|
||||
|
||||
const Upstream = (props) => {
|
||||
const [t] = useTranslation();
|
||||
const Upstream = () => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const {
|
||||
upstream_dns,
|
||||
bootstrap_dns,
|
||||
upstream_mode,
|
||||
processingSetConfig,
|
||||
} = useSelector((state) => state.dnsConfig, shallowEqual);
|
||||
|
||||
const { processingTestUpstream } = useSelector((state) => state.settings, shallowEqual);
|
||||
|
||||
const handleSubmit = (values) => {
|
||||
dispatch(setDnsConfig(values));
|
||||
};
|
||||
|
||||
const {
|
||||
processingTestUpstream,
|
||||
dnsConfig: {
|
||||
upstream_dns,
|
||||
bootstrap_dns,
|
||||
processingSetConfig,
|
||||
upstream_mode,
|
||||
},
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<Card
|
||||
title={t('upstream_dns')}
|
||||
subtitle={t('upstream_dns_hint')}
|
||||
bodyType="card-body box-body--settings"
|
||||
>
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
<Form
|
||||
initialValues={{
|
||||
upstream_dns,
|
||||
bootstrap_dns,
|
||||
upstream_mode,
|
||||
}}
|
||||
onSubmit={handleSubmit}
|
||||
processingTestUpstream={processingTestUpstream}
|
||||
processingSetConfig={processingSetConfig}
|
||||
/>
|
||||
</div>
|
||||
return <Card
|
||||
title={t('upstream_dns')}
|
||||
subtitle={t('upstream_dns_hint')}
|
||||
bodyType="card-body box-body--settings"
|
||||
>
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
<Form
|
||||
initialValues={{
|
||||
upstream_dns,
|
||||
bootstrap_dns,
|
||||
upstream_mode,
|
||||
}}
|
||||
onSubmit={handleSubmit}
|
||||
processingTestUpstream={processingTestUpstream}
|
||||
processingSetConfig={processingSetConfig}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
Upstream.propTypes = {
|
||||
processingTestUpstream: PropTypes.bool.isRequired,
|
||||
dnsConfig: PropTypes.object.isRequired,
|
||||
</div>
|
||||
</Card>;
|
||||
};
|
||||
|
||||
export default Upstream;
|
||||
|
||||
@@ -1,67 +1,40 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import Upstream from './Upstream';
|
||||
import Access from './Access';
|
||||
import Config from './Config';
|
||||
import PageTitle from '../../ui/PageTitle';
|
||||
import Loading from '../../ui/Loading';
|
||||
import CacheConfig from './Cache';
|
||||
import { getDnsConfig } from '../../../actions/dnsConfig';
|
||||
import { getAccessList } from '../../../actions/access';
|
||||
|
||||
const Dns = (props) => {
|
||||
const Dns = () => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useDispatch();
|
||||
const processing = useSelector((state) => state.access.processing);
|
||||
const processingGetConfig = useSelector((state) => state.dnsConfig.processingGetConfig);
|
||||
|
||||
const isDataLoading = processing || processingGetConfig;
|
||||
|
||||
useEffect(() => {
|
||||
props.getAccessList();
|
||||
props.getDnsConfig();
|
||||
dispatch(getAccessList());
|
||||
dispatch(getDnsConfig());
|
||||
}, []);
|
||||
|
||||
const {
|
||||
settings,
|
||||
access,
|
||||
setAccessList,
|
||||
dnsConfig,
|
||||
setDnsConfig,
|
||||
} = props;
|
||||
|
||||
const isDataLoading = access.processing || dnsConfig.processingGetConfig;
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageTitle title={t('dns_settings')} />
|
||||
{isDataLoading
|
||||
? <Loading />
|
||||
: <>
|
||||
<Upstream
|
||||
processingTestUpstream={settings.processingTestUpstream}
|
||||
dnsConfig={dnsConfig}
|
||||
/>
|
||||
<Config
|
||||
dnsConfig={dnsConfig}
|
||||
setDnsConfig={setDnsConfig}
|
||||
/>
|
||||
<CacheConfig
|
||||
dnsConfig={dnsConfig}
|
||||
setDnsConfig={setDnsConfig}
|
||||
/>
|
||||
<Access
|
||||
access={access}
|
||||
setAccessList={setAccessList}
|
||||
/>
|
||||
</>}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Dns.propTypes = {
|
||||
settings: PropTypes.object.isRequired,
|
||||
getAccessList: PropTypes.func.isRequired,
|
||||
setAccessList: PropTypes.func.isRequired,
|
||||
access: PropTypes.object.isRequired,
|
||||
dnsConfig: PropTypes.object.isRequired,
|
||||
setDnsConfig: PropTypes.func.isRequired,
|
||||
getDnsConfig: PropTypes.func.isRequired,
|
||||
return <>
|
||||
<PageTitle title={t('dns_settings')} />
|
||||
{isDataLoading
|
||||
? <Loading />
|
||||
: <>
|
||||
<Upstream />
|
||||
<Config />
|
||||
<CacheConfig />
|
||||
<Access />
|
||||
</>}
|
||||
</>;
|
||||
};
|
||||
|
||||
export default Dns;
|
||||
|
||||
@@ -7,7 +7,7 @@ import flow from 'lodash/flow';
|
||||
|
||||
import {
|
||||
renderInputField,
|
||||
renderSelectField,
|
||||
renderCheckboxField,
|
||||
renderRadioField,
|
||||
toNumber,
|
||||
} from '../../../helpers/form';
|
||||
@@ -15,7 +15,7 @@ import { validateIsSafePort, validatePort, validatePortTLS } from '../../../help
|
||||
import i18n from '../../../i18n';
|
||||
import KeyStatus from './KeyStatus';
|
||||
import CertificateStatus from './CertificateStatus';
|
||||
import { FORM_NAME } from '../../../helpers/constants';
|
||||
import { DNS_OVER_TLS_PORT, FORM_NAME, STANDARD_HTTPS_PORT } from '../../../helpers/constants';
|
||||
|
||||
const validate = (values) => {
|
||||
const errors = {};
|
||||
@@ -36,8 +36,8 @@ const clearFields = (change, setTlsConfig, t) => {
|
||||
certificate_chain: '',
|
||||
private_key_path: '',
|
||||
certificate_path: '',
|
||||
port_https: 443,
|
||||
port_dns_over_tls: 853,
|
||||
port_https: STANDARD_HTTPS_PORT,
|
||||
port_dns_over_tls: DNS_OVER_TLS_PORT,
|
||||
server_name: '',
|
||||
force_https: false,
|
||||
enabled: false,
|
||||
@@ -96,7 +96,7 @@ let Form = (props) => {
|
||||
<Field
|
||||
name="enabled"
|
||||
type="checkbox"
|
||||
component={renderSelectField}
|
||||
component={renderCheckboxField}
|
||||
placeholder={t('encryption_enable')}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
@@ -133,7 +133,7 @@ let Form = (props) => {
|
||||
<Field
|
||||
name="force_https"
|
||||
type="checkbox"
|
||||
component={renderSelectField}
|
||||
component={renderCheckboxField}
|
||||
placeholder={t('encryption_redirect')}
|
||||
onChange={handleChange}
|
||||
disabled={!isEnabled}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user