Compare commits
337 Commits
v0.92-hotf
...
v0.94
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
395ddb7b3c | ||
|
|
1448b7ab16 | ||
|
|
abd58004b8 | ||
|
|
800cb177f3 | ||
|
|
31a0dde515 | ||
|
|
6edfe1bb8e | ||
|
|
7ebfaccc6e | ||
|
|
bc0b0af06b | ||
|
|
5210d214ec | ||
|
|
ed942f3e31 | ||
|
|
5b417d9f17 | ||
|
|
f7860c893d | ||
|
|
a01ba5dd4d | ||
|
|
b6d0d94990 | ||
|
|
9ea5c1abe1 | ||
|
|
91ec996ffb | ||
|
|
9bc5a4570e | ||
|
|
8defb3b39e | ||
|
|
d5e57248a0 | ||
|
|
0647f3fe86 | ||
|
|
d664a9de1d | ||
|
|
b54f540f71 | ||
|
|
0884116de3 | ||
|
|
eefdf8449a | ||
|
|
ae2c7d00a9 | ||
|
|
afa54a1339 | ||
|
|
56271819ea | ||
|
|
a9b329daf6 | ||
|
|
61b1a30aa1 | ||
|
|
b4732c83c5 | ||
|
|
783ac967a1 | ||
|
|
c091d10a41 | ||
|
|
756c5ac0d3 | ||
|
|
ef789acee4 | ||
|
|
b1c11a718e | ||
|
|
d7b1825cf5 | ||
|
|
b5eb840d22 | ||
|
|
6f56eb4c12 | ||
|
|
6b223e2992 | ||
|
|
43e5d42070 | ||
|
|
a58bf0e24e | ||
|
|
f432eb3609 | ||
|
|
6e16654344 | ||
|
|
ef637e1313 | ||
|
|
9494b87ca5 | ||
|
|
d68600c5d0 | ||
|
|
8fa2f48136 | ||
|
|
542a67b84e | ||
|
|
d832d7ce95 | ||
|
|
92cf7c1aca | ||
|
|
b5f0d48e7f | ||
|
|
6f69fb73af | ||
|
|
67014c40f7 | ||
|
|
a2e9d69452 | ||
|
|
e164cff02b | ||
|
|
08bf9b0acb | ||
|
|
60fa3b2e95 | ||
|
|
3f93cb3397 | ||
|
|
10daf29e2f | ||
|
|
70c56f7a18 | ||
|
|
e9d20651e9 | ||
|
|
466807638c | ||
|
|
991717e150 | ||
|
|
b1f707d18c | ||
|
|
f857ed74ec | ||
|
|
2f497cf5d0 | ||
|
|
21db166be0 | ||
|
|
d06cc0f8ee | ||
|
|
b74eded414 | ||
|
|
ecf0b0c5c1 | ||
|
|
64c2f2d89c | ||
|
|
0b96bd17c4 | ||
|
|
392c16cd27 | ||
|
|
10fcfefcfd | ||
|
|
2bd9923691 | ||
|
|
c8c663f3f0 | ||
|
|
bf82ccede9 | ||
|
|
982f8dc1e1 | ||
|
|
83877fb871 | ||
|
|
ac131923a2 | ||
|
|
bc4c2e2ff7 | ||
|
|
1b15bee2b0 | ||
|
|
8e09424774 | ||
|
|
89b6323f03 | ||
|
|
cdc568d8d9 | ||
|
|
bda6f777c4 | ||
|
|
bf2781d465 | ||
|
|
f2e547a54e | ||
|
|
ceaa1e4ebf | ||
|
|
5d6c980ac7 | ||
|
|
e973c4b174 | ||
|
|
d9d641941c | ||
|
|
d318545913 | ||
|
|
7ec4aa91ea | ||
|
|
681fb4e705 | ||
|
|
c443672e35 | ||
|
|
53d680a5df | ||
|
|
6b7a8078b0 | ||
|
|
91f8ab0549 | ||
|
|
a8812908c1 | ||
|
|
b8595b87c4 | ||
|
|
acb4a98466 | ||
|
|
3929f0da44 | ||
|
|
224c2a891d | ||
|
|
81e88472cb | ||
|
|
6b2baba3c7 | ||
|
|
967a1e6b87 | ||
|
|
241e7ca20c | ||
|
|
bc325de13f | ||
|
|
ffa4429818 | ||
|
|
24edf7eeb6 | ||
|
|
99c8cd06c9 | ||
|
|
9a6266588d | ||
|
|
f21daae023 | ||
|
|
7b64f9ff42 | ||
|
|
1626b6bd5a | ||
|
|
eb22d9cdd9 | ||
|
|
1ed3a9673d | ||
|
|
5ad9f8ead2 | ||
|
|
523c5ef10a | ||
|
|
a9839e95a0 | ||
|
|
1223965cd4 | ||
|
|
141b14c94a | ||
|
|
3a9d436f8a | ||
|
|
01548c236e | ||
|
|
5cb6d97cd7 | ||
|
|
f4a6ca726c | ||
|
|
766fbab071 | ||
|
|
87c8114291 | ||
|
|
d218e047a3 | ||
|
|
bf893d488a | ||
|
|
bd31151c1f | ||
|
|
4476262357 | ||
|
|
2a93e45b67 | ||
|
|
5ba43a59f4 | ||
|
|
dc05556c5a | ||
|
|
5bc6d00aa0 | ||
|
|
2dc2a0946a | ||
|
|
aa30728cda | ||
|
|
71ab95f12f | ||
|
|
c71d6ed433 | ||
|
|
77348e746f | ||
|
|
27c33b2fa9 | ||
|
|
86279f19b0 | ||
|
|
3d901a82ad | ||
|
|
d351ed82c1 | ||
|
|
8e13f22aa5 | ||
|
|
d0f4f22e0d | ||
|
|
4bbc503709 | ||
|
|
1b305d94a7 | ||
|
|
a7478255a1 | ||
|
|
84604e292b | ||
|
|
a04923a4f3 | ||
|
|
1da954fa97 | ||
|
|
ad4b58472f | ||
|
|
4e1c1618cb | ||
|
|
3916f1073d | ||
|
|
623c3bba09 | ||
|
|
e8898811fe | ||
|
|
71df659dc9 | ||
|
|
158f2f6100 | ||
|
|
8e993cd788 | ||
|
|
12f8590228 | ||
|
|
2814c393ad | ||
|
|
37431735fd | ||
|
|
251beb24d3 | ||
|
|
37a1a98c49 | ||
|
|
5ac775aa4a | ||
|
|
c53a132072 | ||
|
|
8e7ceec1a1 | ||
|
|
89446fccd5 | ||
|
|
4f45f2c3e3 | ||
|
|
9c8e4c64ea | ||
|
|
2c2295c161 | ||
|
|
a2dd7c32d5 | ||
|
|
b3f33b4b0b | ||
|
|
de08b53ae1 | ||
|
|
a60eeb55f1 | ||
|
|
9d4b829fb6 | ||
|
|
1515c353f8 | ||
|
|
f0536b6347 | ||
|
|
340a4fb58e | ||
|
|
e873149bee | ||
|
|
77793e5f21 | ||
|
|
24154f0033 | ||
|
|
8c406427af | ||
|
|
885e4e16c8 | ||
|
|
0b7f0396de | ||
|
|
cca6998efe | ||
|
|
3c374b5940 | ||
|
|
ba103f9825 | ||
|
|
2748d4c889 | ||
|
|
b8c0ed9335 | ||
|
|
ff012cf0a3 | ||
|
|
2b0addd505 | ||
|
|
2de0f82bbc | ||
|
|
1fc5f15aaa | ||
|
|
954d923975 | ||
|
|
05cce8b107 | ||
|
|
d44f68e844 | ||
|
|
cb97c221fd | ||
|
|
81bb4aea78 | ||
|
|
8da90a7f4a | ||
|
|
b4b800565c | ||
|
|
e8280c60d8 | ||
|
|
571be68733 | ||
|
|
bdec98f18e | ||
|
|
28df187012 | ||
|
|
f0569af367 | ||
|
|
e2956cae82 | ||
|
|
110434c2d5 | ||
|
|
f417f6257f | ||
|
|
1d2958f4aa | ||
|
|
3e67c8d79a | ||
|
|
57a33654f7 | ||
|
|
30050bf278 | ||
|
|
5cbaeb82a8 | ||
|
|
876bec5a65 | ||
|
|
4b4faad9e8 | ||
|
|
c061bec6d8 | ||
|
|
229ef78085 | ||
|
|
0aeca6bbf5 | ||
|
|
35b5f4b48b | ||
|
|
0d3aa00956 | ||
|
|
cb9ffe4de9 | ||
|
|
351673c060 | ||
|
|
4a14c199d8 | ||
|
|
1dd548c36c | ||
|
|
d42718465d | ||
|
|
93847bd309 | ||
|
|
4da55dc2aa | ||
|
|
3d3e0784ea | ||
|
|
3898309778 | ||
|
|
c19416bf8e | ||
|
|
c025c845d2 | ||
|
|
c5b1105fc1 | ||
|
|
38869b22a6 | ||
|
|
ab11c912db | ||
|
|
7451eb1346 | ||
|
|
8725c1df7a | ||
|
|
0820983d81 | ||
|
|
a5b61459cc | ||
|
|
dd3621bcf6 | ||
|
|
571370ab16 | ||
|
|
e33c8a3cde | ||
|
|
0d5f24927c | ||
|
|
27ea739cfd | ||
|
|
899b26725e | ||
|
|
26f2207b5c | ||
|
|
6d7d10ec38 | ||
|
|
c1f6da2b52 | ||
|
|
a40ddb094b | ||
|
|
b477b67428 | ||
|
|
cd9db6440b | ||
|
|
9ff420bb52 | ||
|
|
9a03190a62 | ||
|
|
6b6eacaa2b | ||
|
|
5ca33e44d8 | ||
|
|
68c8a4d484 | ||
|
|
6e5731ab02 | ||
|
|
548f539566 | ||
|
|
853582dade | ||
|
|
3a94080491 | ||
|
|
ba161e9a6f | ||
|
|
91eaf72051 | ||
|
|
826529e73e | ||
|
|
c466f8cc73 | ||
|
|
f9d1948f6a | ||
|
|
bb8d7c37bb | ||
|
|
f2d7f8161b | ||
|
|
39b2e345c3 | ||
|
|
a7a38413fe | ||
|
|
fe671152c2 | ||
|
|
ba678ffa82 | ||
|
|
672ff33879 | ||
|
|
398312cd80 | ||
|
|
06a28a461d | ||
|
|
31b855f9ab | ||
|
|
f379d34813 | ||
|
|
5abe5af707 | ||
|
|
daae040f9c | ||
|
|
f2b3c3a14c | ||
|
|
d3e81c47f6 | ||
|
|
c14aff3dba | ||
|
|
d97c426646 | ||
|
|
34e14930de | ||
|
|
924afea22b | ||
|
|
302c3a767a | ||
|
|
c494e17df5 | ||
|
|
7c25c0febe | ||
|
|
5f7fc0f041 | ||
|
|
beb94741cf | ||
|
|
24be7ce4ed | ||
|
|
6e41897323 | ||
|
|
b5e7237169 | ||
|
|
7e95ce9136 | ||
|
|
a7416f9c34 | ||
|
|
2bd4840ba5 | ||
|
|
5349ec76fd | ||
|
|
71259c5f19 | ||
|
|
f21aebd1cf | ||
|
|
c36a7895ad | ||
|
|
5fed5c0718 | ||
|
|
f437d53c1c | ||
|
|
bfe25ba014 | ||
|
|
a8cdc5b01c | ||
|
|
0fbfa057b1 | ||
|
|
93ea27077f | ||
|
|
aab8da4c7c | ||
|
|
448a6caeb8 | ||
|
|
a4dc4c61d8 | ||
|
|
277415124e | ||
|
|
b216475c20 | ||
|
|
c776ad21b7 | ||
|
|
b56dcc9de1 | ||
|
|
05cab6fde0 | ||
|
|
09b49d0145 | ||
|
|
98bfb82787 | ||
|
|
d238e1feb3 | ||
|
|
911250cfbe | ||
|
|
4b4cb99b30 | ||
|
|
0161509b5f | ||
|
|
ec6b1f7c42 | ||
|
|
69a75fbcaa | ||
|
|
a0157e39c6 | ||
|
|
d078851246 | ||
|
|
c9d627ea71 | ||
|
|
297a1c7fa5 | ||
|
|
f1c3fecfb2 | ||
|
|
79eff5f260 | ||
|
|
8d5d37c281 | ||
|
|
e1bb428a6a | ||
|
|
f1b6da93cf | ||
|
|
61f4b6f1ae | ||
|
|
f678eaf9c0 | ||
|
|
607089cd25 | ||
|
|
df94d76a8b |
8
.codecov.yml
Normal file
8
.codecov.yml
Normal file
@@ -0,0 +1,8 @@
|
||||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
target: 40%
|
||||
threshold: null
|
||||
patch: false
|
||||
changes: false
|
||||
8
.gitignore
vendored
8
.gitignore
vendored
@@ -2,15 +2,19 @@
|
||||
/.vscode
|
||||
/.idea
|
||||
/AdGuardHome
|
||||
/AdGuardHome.exe
|
||||
/AdGuardHome.yaml
|
||||
/AdGuardHome.log
|
||||
/data/
|
||||
/build/
|
||||
/dist/
|
||||
/client/node_modules/
|
||||
/querylog.json
|
||||
/querylog.json.1
|
||||
/scripts/translations/node_modules
|
||||
/scripts/translations/oneskyapp.json
|
||||
/a_main-packr.go
|
||||
|
||||
# Test output
|
||||
dnsfilter/dnsfilter.TestLotsOfRules*.pprof
|
||||
tests/top-1m.csv
|
||||
dnsfilter/tests/top-1m.csv
|
||||
dnsfilter/tests/dnsfilter.TestLotsOfRules*.pprof
|
||||
56
.golangci.yml
Normal file
56
.golangci.yml
Normal file
@@ -0,0 +1,56 @@
|
||||
# options for analysis running
|
||||
run:
|
||||
# default concurrency is a available CPU number
|
||||
concurrency: 4
|
||||
|
||||
# timeout for analysis, e.g. 30s, 5m, default is 1m
|
||||
deadline: 2m
|
||||
|
||||
# which files to skip: they will be analyzed, but issues from them
|
||||
# won't be reported. Default value is empty list, but there is
|
||||
# no need to include all autogenerated files, we confidently recognize
|
||||
# autogenerated files. If it's not please let us know.
|
||||
skip-files:
|
||||
- ".*generated.*"
|
||||
- dnsfilter/rule_to_regexp.go
|
||||
|
||||
|
||||
# all available settings of specific linters
|
||||
linters-settings:
|
||||
errcheck:
|
||||
# [deprecated] comma-separated list of pairs of the form pkg:regex
|
||||
# the regex is used to ignore names within pkg. (default "fmt:.*").
|
||||
# see https://github.com/kisielk/errcheck#the-deprecated-method for details
|
||||
ignore: fmt:.*,net:SetReadDeadline,net/http:^Write
|
||||
gocyclo:
|
||||
min-complexity: 20
|
||||
lll:
|
||||
line-length: 200
|
||||
|
||||
linters:
|
||||
enable-all: true
|
||||
disable:
|
||||
- interfacer
|
||||
- gocritic
|
||||
- scopelint
|
||||
- gochecknoglobals
|
||||
- gochecknoinits
|
||||
- prealloc
|
||||
- maligned
|
||||
- goconst # disabled until it's possible to configure
|
||||
fast: true
|
||||
|
||||
issues:
|
||||
# List of regexps of issue texts to exclude, empty list by default.
|
||||
# But independently from this option we use default exclude patterns,
|
||||
# it can be disabled by `exclude-use-default: false`. To list all
|
||||
# excluded by default patterns execute `golangci-lint run --help`
|
||||
exclude:
|
||||
# structcheck cannot detect usages while they're there
|
||||
- .parentalServer. is unused
|
||||
- .safeBrowsingServer. is unused
|
||||
# errcheck
|
||||
- Error return value of .s.closeConn. is not checked
|
||||
- Error return value of ..*.Shutdown.
|
||||
# goconst
|
||||
- string .forcesafesearch.google.com. has 3 occurrences
|
||||
29
.gometalinter.json
Normal file
29
.gometalinter.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"Vendor": true,
|
||||
"Test": true,
|
||||
"Deadline": "2m",
|
||||
"Sort": ["linter", "severity", "path", "line"],
|
||||
"Exclude": [
|
||||
".*generated.*",
|
||||
"dnsfilter/rule_to_regexp.go"
|
||||
],
|
||||
"EnableGC": true,
|
||||
"Linters": {
|
||||
"nakedret": {
|
||||
"Command": "nakedret",
|
||||
"Pattern": "^(?P<path>.*?\\.go):(?P<line>\\d+)\\s*(?P<message>.*)$"
|
||||
}
|
||||
},
|
||||
"WarnUnmatchedDirective": true,
|
||||
|
||||
"EnableAll": true,
|
||||
"DisableAll": false,
|
||||
"Disable": [
|
||||
"maligned",
|
||||
"goconst",
|
||||
"vetshadow"
|
||||
],
|
||||
|
||||
"Cyclo": 20,
|
||||
"LineLength": 200
|
||||
}
|
||||
69
.travis.yml
69
.travis.yml
@@ -1,15 +1,9 @@
|
||||
language: go
|
||||
sudo: false
|
||||
|
||||
go:
|
||||
- 1.11.x
|
||||
- 1.12.x
|
||||
- 1.x
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.cache/go-build
|
||||
- $HOME/gopath/pkg/mod
|
||||
- $HOME/Library/Caches/go-build
|
||||
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
@@ -21,10 +15,67 @@ before_install:
|
||||
install:
|
||||
- npm --prefix client install
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.cache/go-build
|
||||
- $HOME/gopath/pkg/mod
|
||||
- $HOME/Library/Caches/go-build
|
||||
|
||||
script:
|
||||
- node -v
|
||||
- npm -v
|
||||
- go test ./...
|
||||
# Run tests
|
||||
- go test -race -v -bench=. -coverprofile=coverage.txt -covermode=atomic ./...
|
||||
# Make
|
||||
- make build/static/index.html
|
||||
- make
|
||||
|
||||
after_success:
|
||||
- bash <(curl -s https://codecov.io/bash)
|
||||
|
||||
matrix:
|
||||
include:
|
||||
# Release build configuration
|
||||
- name: release
|
||||
go:
|
||||
- 1.12.x
|
||||
os:
|
||||
- linux
|
||||
|
||||
script:
|
||||
- node -v
|
||||
- npm -v
|
||||
# Run tests just in case
|
||||
- go test -race -v -bench=. ./...
|
||||
# Prepare releases
|
||||
- ./release.sh
|
||||
- ls -l dist
|
||||
|
||||
deploy:
|
||||
provider: releases
|
||||
api_key: $GITHUB_TOKEN
|
||||
file:
|
||||
- dist/AdGuardHome_*
|
||||
on:
|
||||
repo: AdguardTeam/AdGuardHome
|
||||
tags: true
|
||||
draft: true
|
||||
file_glob: true
|
||||
skip_cleanup: true
|
||||
|
||||
- name: docker
|
||||
if: type != pull_request AND (branch = master OR tag IS present)
|
||||
go:
|
||||
- 1.12.x
|
||||
os:
|
||||
- linux
|
||||
services:
|
||||
- docker
|
||||
before_script:
|
||||
- nvm install node
|
||||
- npm install -g npm
|
||||
script:
|
||||
- docker login -u="$DOCKER_USER" -p="$DOCKER_PASSWORD"
|
||||
- ./build_docker.sh
|
||||
after_script:
|
||||
- docker images
|
||||
|
||||
24
Dockerfile
Normal file
24
Dockerfile
Normal file
@@ -0,0 +1,24 @@
|
||||
FROM golang:alpine AS build
|
||||
|
||||
RUN apk add --update git make build-base npm && \
|
||||
rm -rf /var/cache/apk/*
|
||||
|
||||
WORKDIR /src/AdGuardHome
|
||||
COPY . /src/AdGuardHome
|
||||
RUN make
|
||||
|
||||
FROM alpine:latest
|
||||
LABEL maintainer="AdGuard Team <devteam@adguard.com>"
|
||||
|
||||
# Update CA certs
|
||||
RUN apk --no-cache --update add ca-certificates && \
|
||||
rm -rf /var/cache/apk/* && mkdir -p /opt/adguardhome
|
||||
|
||||
COPY --from=build /src/AdGuardHome/AdGuardHome /opt/adguardhome/AdGuardHome
|
||||
|
||||
EXPOSE 53/tcp 53/udp 67/tcp 67/udp 68/tcp 68/udp 80/tcp 443/tcp 853/tcp 853/udp 3000/tcp
|
||||
|
||||
VOLUME ["/opt/adguardhome/conf", "/opt/adguardhome/work"]
|
||||
|
||||
ENTRYPOINT ["/opt/adguardhome/AdGuardHome"]
|
||||
CMD ["-c", "/opt/adguardhome/conf/AdGuardHome.yaml", "-w", "/opt/adguardhome/work"]
|
||||
@@ -1,48 +0,0 @@
|
||||
FROM easypi/alpine-arm:latest
|
||||
LABEL maintainer="Erik Rogers <erik.rogers@live.com>"
|
||||
|
||||
# AdGuard version
|
||||
ARG ADGUARD_VERSION="0.92-hotfix2"
|
||||
ENV ADGUARD_VERSION $ADGUARD_VERSION
|
||||
|
||||
# AdGuard architecture and package info
|
||||
ARG ADGUARD_ARCH="linux_arm"
|
||||
ENV ADGUARD_ARCH ${ADGUARD_ARCH}
|
||||
ENV ADGUARD_PACKAGE "AdGuardHome_v${ADGUARD_VERSION}_${ADGUARD_ARCH}"
|
||||
|
||||
# AdGuard release info
|
||||
ARG ADGUARD_ARCHIVE="${ADGUARD_PACKAGE}.tar.gz"
|
||||
ENV ADGUARD_ARCHIVE ${ADGUARD_ARCHIVE}
|
||||
ARG ADGUARD_RELEASE="https://github.com/AdguardTeam/AdGuardHome/releases/download/v${ADGUARD_VERSION}/${ADGUARD_ARCHIVE}"
|
||||
ENV ADGUARD_RELEASE ${ADGUARD_RELEASE}
|
||||
|
||||
# AdGuard directory
|
||||
ARG ADGUARD_DIR="/data/adguard"
|
||||
ENV ADGUARD_DIR ${ADGUARD_DIR}
|
||||
|
||||
# Update CA certs and download AdGuard binaries
|
||||
RUN apk --no-cache --update add ca-certificates \
|
||||
&& cd /tmp \
|
||||
&& wget ${ADGUARD_RELEASE} \
|
||||
&& tar xvf ${ADGUARD_ARCHIVE} \
|
||||
&& mkdir -p "${ADGUARD_DIR}" \
|
||||
&& cp "AdGuardHome/AdGuardHome" "${ADGUARD_DIR}" \
|
||||
&& chmod +x "${ADGUARD_DIR}/AdGuardHome" \
|
||||
&& rm -rf "AdGuardHome" \
|
||||
&& rm ${ADGUARD_ARCHIVE}
|
||||
|
||||
# Expose DNS port 53
|
||||
EXPOSE 53
|
||||
|
||||
# Expose UI port 3000
|
||||
ARG ADGUARD_UI_HOST="0.0.0.0"
|
||||
ENV ADGUARD_UI_HOST ${ADGUARD_UI_HOST}
|
||||
ARG ADGUARD_UI_PORT="3000"
|
||||
ENV ADGUARD_UI_PORT ${ADGUARD_UI_PORT}
|
||||
|
||||
EXPOSE ${ADGUARD_UI_PORT}
|
||||
|
||||
# Run AdGuardHome
|
||||
WORKDIR ${ADGUARD_DIR}
|
||||
VOLUME ${ADGUARD_DIR}
|
||||
ENTRYPOINT ./AdGuardHome --host ${ADGUARD_UI_HOST} --port ${ADGUARD_UI_PORT}
|
||||
@@ -1,48 +0,0 @@
|
||||
FROM alpine:latest
|
||||
LABEL maintainer="Erik Rogers <erik.rogers@live.com>"
|
||||
|
||||
# AdGuard version
|
||||
ARG ADGUARD_VERSION="0.92-hotfix2"
|
||||
ENV ADGUARD_VERSION $ADGUARD_VERSION
|
||||
|
||||
# AdGuard architecture and package info
|
||||
ARG ADGUARD_ARCH="linux_386"
|
||||
ENV ADGUARD_ARCH ${ADGUARD_ARCH}
|
||||
ENV ADGUARD_PACKAGE "AdGuardHome_v${ADGUARD_VERSION}_${ADGUARD_ARCH}"
|
||||
|
||||
# AdGuard release info
|
||||
ARG ADGUARD_ARCHIVE="${ADGUARD_PACKAGE}.tar.gz"
|
||||
ENV ADGUARD_ARCHIVE ${ADGUARD_ARCHIVE}
|
||||
ARG ADGUARD_RELEASE="https://github.com/AdguardTeam/AdGuardHome/releases/download/v${ADGUARD_VERSION}/${ADGUARD_ARCHIVE}"
|
||||
ENV ADGUARD_RELEASE ${ADGUARD_RELEASE}
|
||||
|
||||
# AdGuard directory
|
||||
ARG ADGUARD_DIR="/data/adguard"
|
||||
ENV ADGUARD_DIR ${ADGUARD_DIR}
|
||||
|
||||
# Update CA certs and download AdGuard binaries
|
||||
RUN apk --no-cache --update add ca-certificates \
|
||||
&& cd /tmp \
|
||||
&& wget ${ADGUARD_RELEASE} \
|
||||
&& tar xvf ${ADGUARD_ARCHIVE} \
|
||||
&& mkdir -p "${ADGUARD_DIR}" \
|
||||
&& cp "AdGuardHome/AdGuardHome" "${ADGUARD_DIR}" \
|
||||
&& chmod +x "${ADGUARD_DIR}/AdGuardHome" \
|
||||
&& rm -rf "AdGuardHome" \
|
||||
&& rm ${ADGUARD_ARCHIVE}
|
||||
|
||||
# Expose DNS port 53
|
||||
EXPOSE 53
|
||||
|
||||
# Expose UI port 3000
|
||||
ARG ADGUARD_UI_HOST="0.0.0.0"
|
||||
ENV ADGUARD_UI_HOST ${ADGUARD_UI_HOST}
|
||||
ARG ADGUARD_UI_PORT="3000"
|
||||
ENV ADGUARD_UI_PORT ${ADGUARD_UI_PORT}
|
||||
|
||||
EXPOSE ${ADGUARD_UI_PORT}
|
||||
|
||||
# Run AdGuardHome
|
||||
WORKDIR ${ADGUARD_DIR}
|
||||
VOLUME ${ADGUARD_DIR}
|
||||
ENTRYPOINT ./AdGuardHome --host ${ADGUARD_UI_HOST} --port ${ADGUARD_UI_PORT}
|
||||
@@ -1,48 +0,0 @@
|
||||
FROM alpine:latest
|
||||
LABEL maintainer="Erik Rogers <erik.rogers@live.com>"
|
||||
|
||||
# AdGuard version
|
||||
ARG ADGUARD_VERSION="0.92-hotfix2"
|
||||
ENV ADGUARD_VERSION $ADGUARD_VERSION
|
||||
|
||||
# AdGuard architecture and package info
|
||||
ARG ADGUARD_ARCH="linux_amd64"
|
||||
ENV ADGUARD_ARCH ${ADGUARD_ARCH}
|
||||
ENV ADGUARD_PACKAGE "AdGuardHome_v${ADGUARD_VERSION}_${ADGUARD_ARCH}"
|
||||
|
||||
# AdGuard release info
|
||||
ARG ADGUARD_ARCHIVE="${ADGUARD_PACKAGE}.tar.gz"
|
||||
ENV ADGUARD_ARCHIVE ${ADGUARD_ARCHIVE}
|
||||
ARG ADGUARD_RELEASE="https://github.com/AdguardTeam/AdGuardHome/releases/download/v${ADGUARD_VERSION}/${ADGUARD_ARCHIVE}"
|
||||
ENV ADGUARD_RELEASE ${ADGUARD_RELEASE}
|
||||
|
||||
# AdGuard directory
|
||||
ARG ADGUARD_DIR="/data/adguard"
|
||||
ENV ADGUARD_DIR ${ADGUARD_DIR}
|
||||
|
||||
# Update CA certs and download AdGuard binaries
|
||||
RUN apk --no-cache --update add ca-certificates \
|
||||
&& cd /tmp \
|
||||
&& wget ${ADGUARD_RELEASE} \
|
||||
&& tar xvf ${ADGUARD_ARCHIVE} \
|
||||
&& mkdir -p "${ADGUARD_DIR}" \
|
||||
&& cp "AdGuardHome/AdGuardHome" "${ADGUARD_DIR}" \
|
||||
&& chmod +x "${ADGUARD_DIR}/AdGuardHome" \
|
||||
&& rm -rf "AdGuardHome" \
|
||||
&& rm ${ADGUARD_ARCHIVE}
|
||||
|
||||
# Expose DNS port 53
|
||||
EXPOSE 53
|
||||
|
||||
# Expose UI port 3000
|
||||
ARG ADGUARD_UI_HOST="0.0.0.0"
|
||||
ENV ADGUARD_UI_HOST ${ADGUARD_UI_HOST}
|
||||
ARG ADGUARD_UI_PORT="3000"
|
||||
ENV ADGUARD_UI_PORT ${ADGUARD_UI_PORT}
|
||||
|
||||
EXPOSE ${ADGUARD_UI_PORT}
|
||||
|
||||
# Run AdGuardHome
|
||||
WORKDIR ${ADGUARD_DIR}
|
||||
VOLUME ${ADGUARD_DIR}
|
||||
ENTRYPOINT ./AdGuardHome --host ${ADGUARD_UI_HOST} --port ${ADGUARD_UI_PORT}
|
||||
16
Dockerfile.travis
Normal file
16
Dockerfile.travis
Normal file
@@ -0,0 +1,16 @@
|
||||
FROM alpine:latest
|
||||
LABEL maintainer="AdGuard Team <devteam@adguard.com>"
|
||||
|
||||
# Update CA certs
|
||||
RUN apk --no-cache --update add ca-certificates && \
|
||||
rm -rf /var/cache/apk/* && mkdir -p /opt/adguardhome
|
||||
|
||||
|
||||
COPY ./AdGuardHome /opt/adguardhome/AdGuardHome
|
||||
|
||||
EXPOSE 53/tcp 53/udp 67/tcp 67/udp 68/tcp 68/udp 80/tcp 443/tcp 853/tcp 853/udp 3000/tcp
|
||||
|
||||
VOLUME ["/opt/adguardhome/conf", "/opt/adguardhome/work"]
|
||||
|
||||
ENTRYPOINT ["/opt/adguardhome/AdGuardHome"]
|
||||
CMD ["-h", "0.0.0.0", "-c", "/opt/adguardhome/conf/AdGuardHome.yaml", "-w", "/opt/adguardhome/work"]
|
||||
1
Makefile
1
Makefile
@@ -20,7 +20,6 @@ $(STATIC): $(JSFILES) client/node_modules
|
||||
npm --prefix client run build-prod
|
||||
|
||||
$(TARGET): $(STATIC) *.go dhcpd/*.go dnsfilter/*.go dnsforward/*.go
|
||||
go get -d .
|
||||
GOOS=$(NATIVE_GOOS) GOARCH=$(NATIVE_GOARCH) GO111MODULE=off go get -v github.com/gobuffalo/packr/...
|
||||
PATH=$(GOPATH)/bin:$(PATH) packr -z
|
||||
CGO_ENABLED=0 go build -ldflags="-s -w -X main.VersionString=$(GIT_VERSION)" -asmflags="-trimpath=$(PWD)" -gcflags="-trimpath=$(PWD)"
|
||||
|
||||
152
README.md
152
README.md
@@ -11,11 +11,21 @@
|
||||
<a href="https://adguard.com/">AdGuard.com</a> |
|
||||
<a href="https://github.com/AdguardTeam/AdGuardHome/wiki">Wiki</a> |
|
||||
<a href="https://reddit.com/r/Adguard">Reddit</a> |
|
||||
<a href="https://twitter.com/AdGuard">Twitter</a>
|
||||
<a href="https://twitter.com/AdGuard">Twitter</a> |
|
||||
<a href="https://t.me/adguard_en">Telegram</a>
|
||||
<br /><br />
|
||||
<a href="https://travis-ci.org/AdguardTeam/AdGuardHome">
|
||||
<img src="https://travis-ci.org/AdguardTeam/AdGuardHome.svg" alt="Build status" />
|
||||
</a>
|
||||
<a href="https://codecov.io/github/AdguardTeam/AdGuardHome?branch=master">
|
||||
<img src="https://img.shields.io/codecov/c/github/AdguardTeam/AdGuardHome/master.svg" alt="Code Coverage" />
|
||||
</a>
|
||||
<a href="https://goreportcard.com/report/AdguardTeam/AdGuardHome">
|
||||
<img src="https://goreportcard.com/badge/github.com/AdguardTeam/AdGuardHome" alt="Go Report Card" />
|
||||
</a>
|
||||
<a href="https://golangci.com/r/github.com/AdguardTeam/AdGuardHome">
|
||||
<img src="https://golangci.com/badges/github.com/AdguardTeam/AdGuardHome.svg" alt="GolangCI" />
|
||||
</a>
|
||||
<a href="https://github.com/AdguardTeam/AdGuardHome/releases">
|
||||
<img src="https://img.shields.io/github/release/AdguardTeam/AdGuardHome/all.svg" alt="Latest release" />
|
||||
</a>
|
||||
@@ -29,138 +39,39 @@
|
||||
|
||||
<hr />
|
||||
|
||||
# AdGuard Home
|
||||
|
||||
AdGuard Home is a network-wide software for blocking ads & tracking. After you set it up, it'll cover ALL your home devices, and you don't need any client-side software for that.
|
||||
|
||||
## How does AdGuard Home work?
|
||||
It operates as a DNS server that re-routes tracking domains to a "black hole," thus preventing your devices from connecting to those servers. It's based on software we use for our public [AdGuard DNS](https://adguard.com/en/adguard-dns/overview.html) servers -- both share a lot of common code.
|
||||
|
||||
AdGuard Home operates as a DNS server that re-routes tracking domains to a "black hole," thus preventing your devices from connecting to those servers. It's based on software we use for our public [AdGuard DNS](https://adguard.com/en/adguard-dns/overview.html) servers -- both share a lot of common code.
|
||||
* [Getting Started](#getting-started)
|
||||
* [How to build from source](#how-to-build)
|
||||
* [Contributing](#contributing)
|
||||
* [Reporting issues](#reporting-issues)
|
||||
* [Acknowledgments](#acknowledgments)
|
||||
|
||||
## How is this different from public AdGuard DNS servers?
|
||||
<a id="getting-started"></a>
|
||||
## Getting Started
|
||||
|
||||
Running your own AdGuard Home server allows you to do much more than using a public DNS server.
|
||||
Please read the [Getting Started](https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started) article on our Wiki to learn how to install AdGuard Home, and how to configure your devices to use it.
|
||||
|
||||
* Choose what exactly will the server block or not block;
|
||||
* Monitor your network activity;
|
||||
* Add your own custom filtering rules;
|
||||
Alternatively, you can use our [official Docker image](https://hub.docker.com/r/adguard/adguardhome).
|
||||
|
||||
In the future, AdGuard Home is supposed to become more than just a DNS server.
|
||||
### Guides
|
||||
|
||||
## Installation
|
||||
|
||||
### Mac
|
||||
|
||||
Download this file: [AdGuardHome_v0.92-hotfix2_MacOS.zip](https://github.com/AdguardTeam/AdGuardHome/releases/download/v0.92-hotfix2/AdGuardHome_v0.92-hotfix2_MacOS.zip), then unpack it and follow ["How to run"](#how-to-run) instructions below.
|
||||
|
||||
### Windows 64-bit
|
||||
|
||||
Download this file: [AdGuardHome_v0.92-hotfix2_Windows.zip](https://github.com/AdguardTeam/AdGuardHome/releases/download/v0.92-hotfix2/AdGuardHome_v0.92-hotfix2_Windows.zip), then unpack it and follow ["How to run"](#how-to-run) instructions below.
|
||||
|
||||
### Linux 64-bit Intel
|
||||
|
||||
Download this file: [AdGuardHome_v0.92-hotfix2_linux_amd64.tar.gz](https://github.com/AdguardTeam/AdGuardHome/releases/download/v0.92-hotfix2/AdGuardHome_v0.92-hotfix2_linux_amd64.tar.gz), then unpack it and follow ["How to run"](#how-to-run) instructions below.
|
||||
|
||||
### Linux 32-bit Intel
|
||||
|
||||
Download this file: [AdGuardHome_v0.92-hotfix2_linux_386.tar.gz](https://github.com/AdguardTeam/AdGuardHome/releases/download/v0.92-hotfix2/AdGuardHome_v0.92-hotfix2_linux_386.tar.gz), then unpack it and follow ["How to run"](#how-to-run) instructions below.
|
||||
|
||||
### Raspberry Pi (32-bit ARM)
|
||||
|
||||
Download this file: [AdGuardHome_v0.92-hotfix2_linux_arm.tar.gz](https://github.com/AdguardTeam/AdGuardHome/releases/download/v0.92-hotfix2/AdGuardHome_v0.92-hotfix2_linux_arm.tar.gz), then unpack it and follow ["How to run"](#how-to-run) instructions below.
|
||||
|
||||
## How to update
|
||||
|
||||
We have not yet implemented an auto-update of AdGuard Home, but it is planned for future versions: #448.
|
||||
|
||||
At the moment, the update procedure is manual:
|
||||
|
||||
1. Download the new AdGuard Home binary.
|
||||
2. Replace the old file with the new one.
|
||||
3. Restart AdGuard Home.
|
||||
|
||||
## How to run
|
||||
|
||||
DNS works on port 53, which requires superuser privileges. Therefore, you need to run it with `sudo` in terminal:
|
||||
|
||||
```bash
|
||||
sudo ./AdGuardHome
|
||||
```
|
||||
|
||||
Now open the browser and navigate to http://localhost:3000/ to control your AdGuard Home service.
|
||||
|
||||
### Running without superuser
|
||||
|
||||
You can run AdGuard Home without superuser privileges, but you need to either grant the binary a capability (on Linux) or instruct it to use a different port (all platforms).
|
||||
|
||||
#### Granting the CAP_NET_BIND_SERVICE capability (on Linux)
|
||||
|
||||
Note: using this method requires the `setcap` utility. You may need to install it using your Linux distribution's package manager.
|
||||
|
||||
To allow AdGuard Home running on Linux to listen on port 53 without superuser privileges, run:
|
||||
|
||||
```bash
|
||||
sudo setcap CAP_NET_BIND_SERVICE=+eip ./AdGuardHome
|
||||
```
|
||||
|
||||
Then run `./AdGuardHome` as a unprivileged user.
|
||||
|
||||
#### Changing the DNS listen port
|
||||
|
||||
To configure AdGuard Home to listen on a port that does not require superuser privileges, edit `AdGuardHome.yaml` and find these two lines:
|
||||
|
||||
```yaml
|
||||
dns:
|
||||
port: 53
|
||||
```
|
||||
|
||||
You can change port 53 to anything above 1024 to avoid requiring superuser privileges.
|
||||
|
||||
If the file does not exist, create it in the same folder, type these two lines down and save.
|
||||
|
||||
### Additional configuration
|
||||
|
||||
Upon the first execution, a file named `AdGuardHome.yaml` will be created, with default values written in it. You can modify the file while your AdGuard Home service is not running. Otherwise, any changes to the file will be lost because the running program will overwrite them.
|
||||
|
||||
Settings are stored in [YAML format](https://en.wikipedia.org/wiki/YAML), possible parameters that you can configure are listed below:
|
||||
|
||||
* `bind_host` — Web interface IP address to listen on.
|
||||
* `bind_port` — Web interface IP port to listen on.
|
||||
* `auth_name` — Web interface optional authorization username.
|
||||
* `auth_pass` — Web interface optional authorization password.
|
||||
* `dns` — DNS configuration section.
|
||||
* `port` — DNS server port to listen on.
|
||||
* `protection_enabled` — Whether any kind of filtering and protection should be done, when off it works as a plain dns forwarder.
|
||||
* `filtering_enabled` — Filtering of DNS requests based on filter lists.
|
||||
* `blocked_response_ttl` — For how many seconds the clients should cache a filtered response. Low values are useful on LAN if you change filters very often, high values are useful to increase performance and save traffic.
|
||||
* `querylog_enabled` — Query logging (also used to calculate top 50 clients, blocked domains and requested domains for statistical purposes).
|
||||
* `ratelimit` — DDoS protection, specifies in how many packets per second a client should receive. Anything above that is silently dropped. To disable set 0, default is 20. Safe to disable if DNS server is not available from internet.
|
||||
* `ratelimit_whitelist` — If you want exclude some IP addresses from ratelimiting but keep ratelimiting on for others, put them here.
|
||||
* `refuse_any` — Another DDoS protection mechanism. Requests of type ANY are rarely needed, so refusing to serve them mitigates against attackers trying to use your DNS as a reflection. Safe to disable if DNS server is not available from internet.
|
||||
* `bootstrap_dns` — DNS server used for initial hostname resolution in case if upstream server name is a hostname.
|
||||
* `parental_sensitivity` — Age group for parental control-based filtering, must be either 3, 10, 13 or 17 if enabled.
|
||||
* `parental_enabled` — Parental control-based DNS requests filtering.
|
||||
* `safesearch_enabled` — Enforcing "Safe search" option for search engines, when possible.
|
||||
* `safebrowsing_enabled` — Filtering of DNS requests based on safebrowsing.
|
||||
* `upstream_dns` — List of upstream DNS servers.
|
||||
* `filters` — List of filters, each filter has the following values:
|
||||
* `enabled` — Current filter's status (enabled/disabled).
|
||||
* `url` — URL pointing to the filter contents (filtering rules).
|
||||
* `name` — Name of the filter. If it's an adguard syntax filter it will get updated automatically, otherwise it stays unchanged.
|
||||
* `last_updated` — Time when the filter was last updated from server.
|
||||
* `ID` - filter ID (must be unique).
|
||||
* `user_rules` — User-specified filtering rules.
|
||||
|
||||
Removing an entry from settings file will reset it to the default value. Deleting the file will reset all settings to the default values.
|
||||
* [Configuration](https://github.com/AdguardTeam/AdGuardHome/wiki/Configuration)
|
||||
* [AdGuard Home as a DNS-over-HTTPS or DNS-over-TLS server](https://github.com/AdguardTeam/AdGuardHome/wiki/Encryption)
|
||||
* [How to install and run AdGuard Home on Raspberry Pi](https://github.com/AdguardTeam/AdGuardHome/wiki/Raspberry-Pi)
|
||||
* [How to install and run AdGuard Home on a Virtual Private Server](https://github.com/AdguardTeam/AdGuardHome/wiki/VPS)
|
||||
|
||||
<a id="how-to-build"></a>
|
||||
## How to build from source
|
||||
|
||||
### Prerequisites
|
||||
|
||||
You will need:
|
||||
|
||||
* [go](https://golang.org/dl/) v1.11 or later.
|
||||
* [node.js](https://nodejs.org/en/download/)
|
||||
* [go](https://golang.org/dl/) v1.12 or later.
|
||||
* [node.js](https://nodejs.org/en/download/) v10 or later.
|
||||
|
||||
You can either install it via the provided links or use [brew.sh](https://brew.sh/) if you're on Mac:
|
||||
|
||||
@@ -178,6 +89,7 @@ cd AdGuardHome
|
||||
make
|
||||
```
|
||||
|
||||
<a id="contributing"></a>
|
||||
## Contributing
|
||||
|
||||
You are welcome to fork this repository, make your changes and submit a pull request — https://github.com/AdguardTeam/AdGuardHome/pulls
|
||||
@@ -216,10 +128,12 @@ node upload.js
|
||||
node download.js
|
||||
```
|
||||
|
||||
<a id="reporting-issues"></a>
|
||||
## Reporting issues
|
||||
|
||||
If you run into any problem or have a suggestion, head to [this page](https://github.com/AdguardTeam/AdGuardHome/issues) and click on the `New issue` button.
|
||||
|
||||
<a id="acknowledgments"></a>
|
||||
## Acknowledgments
|
||||
|
||||
This software wouldn't have been possible without:
|
||||
@@ -229,6 +143,8 @@ This software wouldn't have been possible without:
|
||||
* [gcache](https://github.com/bluele/gcache)
|
||||
* [miekg's dns](https://github.com/miekg/dns)
|
||||
* [go-yaml](https://github.com/go-yaml/yaml)
|
||||
* [service](https://godoc.org/github.com/kardianos/service)
|
||||
* [dnsproxy](https://github.com/AdguardTeam/dnsproxy)
|
||||
* [Node.js](https://nodejs.org/) and it's libraries:
|
||||
* [React.js](https://reactjs.org)
|
||||
* [Tabler](https://github.com/tabler/tabler)
|
||||
|
||||
588
app.go
588
app.go
@@ -1,163 +1,47 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/AdguardTeam/golibs/log"
|
||||
"github.com/gobuffalo/packr"
|
||||
"github.com/hmage/golibs/log"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
)
|
||||
|
||||
// VersionString will be set through ldflags, contains current version
|
||||
var VersionString = "undefined"
|
||||
var httpServer *http.Server
|
||||
var httpsServer struct {
|
||||
server *http.Server
|
||||
cond *sync.Cond // reacts to config.TLS.Enabled, PortHTTPS, CertificateChain and PrivateKey
|
||||
sync.Mutex // protects config.TLS
|
||||
}
|
||||
|
||||
const (
|
||||
// Used in config to indicate that syslog or eventlog (win) should be used for logger output
|
||||
configSyslog = "syslog"
|
||||
)
|
||||
|
||||
// main is the entry point
|
||||
func main() {
|
||||
log.Printf("AdGuard Home web interface backend, version %s\n", VersionString)
|
||||
box := packr.NewBox("build/static")
|
||||
{
|
||||
executable, err := os.Executable()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
executableName := filepath.Base(executable)
|
||||
if executableName == "AdGuardHome" {
|
||||
// Binary build
|
||||
config.ourBinaryDir = filepath.Dir(executable)
|
||||
} else {
|
||||
// Most likely we're debugging -- using current working directory in this case
|
||||
workDir, _ := os.Getwd()
|
||||
config.ourBinaryDir = workDir
|
||||
}
|
||||
log.Printf("Current working directory is %s", config.ourBinaryDir)
|
||||
}
|
||||
|
||||
// config can be specified, which reads options from there, but other command line flags have to override config values
|
||||
// therefore, we must do it manually instead of using a lib
|
||||
{
|
||||
var printHelp func()
|
||||
var configFilename *string
|
||||
var bindHost *string
|
||||
var bindPort *int
|
||||
var opts = []struct {
|
||||
longName string
|
||||
shortName string
|
||||
description string
|
||||
callbackWithValue func(value string)
|
||||
callbackNoValue func()
|
||||
}{
|
||||
{"config", "c", "path to config file", func(value string) { configFilename = &value }, nil},
|
||||
{"host", "h", "host address to bind HTTP server on", func(value string) { bindHost = &value }, nil},
|
||||
{"port", "p", "port to serve HTTP pages on", func(value string) {
|
||||
v, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
panic("Got port that is not a number")
|
||||
}
|
||||
bindPort = &v
|
||||
}, nil},
|
||||
{"verbose", "v", "enable verbose output", nil, func() { log.Verbose = true }},
|
||||
{"help", "h", "print this help", nil, func() { printHelp(); os.Exit(64) }},
|
||||
}
|
||||
printHelp = func() {
|
||||
fmt.Printf("Usage:\n\n")
|
||||
fmt.Printf("%s [options]\n\n", os.Args[0])
|
||||
fmt.Printf("Options:\n")
|
||||
for _, opt := range opts {
|
||||
fmt.Printf(" -%s, %-30s %s\n", opt.shortName, "--"+opt.longName, opt.description)
|
||||
}
|
||||
}
|
||||
for i := 1; i < len(os.Args); i++ {
|
||||
v := os.Args[i]
|
||||
knownParam := false
|
||||
for _, opt := range opts {
|
||||
if v == "--"+opt.longName || v == "-"+opt.shortName {
|
||||
if opt.callbackWithValue != nil {
|
||||
if i+1 > len(os.Args) {
|
||||
log.Printf("ERROR: Got %s without argument\n", v)
|
||||
os.Exit(64)
|
||||
}
|
||||
i++
|
||||
opt.callbackWithValue(os.Args[i])
|
||||
} else if opt.callbackNoValue != nil {
|
||||
opt.callbackNoValue()
|
||||
}
|
||||
knownParam = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !knownParam {
|
||||
log.Printf("ERROR: unknown option %v\n", v)
|
||||
printHelp()
|
||||
os.Exit(64)
|
||||
}
|
||||
}
|
||||
if configFilename != nil {
|
||||
config.ourConfigFilename = *configFilename
|
||||
}
|
||||
args := loadOptions()
|
||||
|
||||
err := askUsernamePasswordIfPossible()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Do the upgrade if necessary
|
||||
err = upgradeConfig()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// parse from config file
|
||||
err = parseConfig()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// override bind host/port from the console
|
||||
if bindHost != nil {
|
||||
config.BindHost = *bindHost
|
||||
}
|
||||
if bindPort != nil {
|
||||
config.BindPort = *bindPort
|
||||
}
|
||||
if args.serviceControlAction != "" {
|
||||
handleServiceControlAction(args.serviceControlAction)
|
||||
return
|
||||
}
|
||||
|
||||
// Load filters from the disk
|
||||
// And if any filter has zero ID, assign a new one
|
||||
for i := range config.Filters {
|
||||
filter := &config.Filters[i] // otherwise we're operating on a copy
|
||||
if filter.ID == 0 {
|
||||
filter.ID = assignUniqueFilterID()
|
||||
}
|
||||
err := filter.load()
|
||||
if err != nil {
|
||||
// This is okay for the first start, the filter will be loaded later
|
||||
log.Printf("Couldn't load filter %d contents due to %s", filter.ID, err)
|
||||
// clear LastUpdated so it gets fetched right away
|
||||
}
|
||||
if len(filter.Rules) == 0 {
|
||||
filter.LastUpdated = time.Time{}
|
||||
}
|
||||
}
|
||||
|
||||
// Update filters we've just loaded right away, don't wait for periodic update timer
|
||||
go func() {
|
||||
refreshFiltersIfNeccessary(false)
|
||||
// Save the updated config
|
||||
err := config.write()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
signalChannel := make(chan os.Signal)
|
||||
signal.Notify(signalChannel, syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP, syscall.SIGQUIT)
|
||||
go func() {
|
||||
@@ -166,124 +50,372 @@ func main() {
|
||||
os.Exit(0)
|
||||
}()
|
||||
|
||||
// Save the updated config
|
||||
err := config.write()
|
||||
// run the protection
|
||||
run(args)
|
||||
}
|
||||
|
||||
// run initializes configuration and runs the AdGuard Home
|
||||
// run is a blocking method and it won't exit until the service is stopped!
|
||||
func run(args options) {
|
||||
// config file path can be overridden by command-line arguments:
|
||||
if args.configFilename != "" {
|
||||
config.ourConfigFilename = args.configFilename
|
||||
}
|
||||
|
||||
// configure working dir and config path
|
||||
initWorkingDir(args)
|
||||
|
||||
// configure log level and output
|
||||
configureLogger(args)
|
||||
|
||||
// enable TLS 1.3
|
||||
enableTLS13()
|
||||
|
||||
// print the first message after logger is configured
|
||||
log.Printf("AdGuard Home, version %s\n", VersionString)
|
||||
log.Debug("Current working directory is %s", config.ourWorkingDir)
|
||||
if args.runningAsService {
|
||||
log.Info("AdGuard Home is running as a service")
|
||||
}
|
||||
|
||||
config.firstRun = detectFirstRun()
|
||||
|
||||
// Do the upgrade if necessary
|
||||
err := upgradeConfig()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
address := net.JoinHostPort(config.BindHost, strconv.Itoa(config.BindPort))
|
||||
// parse from config file
|
||||
err = parseConfig()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// override bind host/port from the console
|
||||
if args.bindHost != "" {
|
||||
config.BindHost = args.bindHost
|
||||
}
|
||||
if args.bindPort != 0 {
|
||||
config.BindPort = args.bindPort
|
||||
}
|
||||
|
||||
loadFilters()
|
||||
|
||||
// Save the updated config
|
||||
err = config.write()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Init the DNS server instance before registering HTTP handlers
|
||||
dnsBaseDir := filepath.Join(config.ourWorkingDir, dataDir)
|
||||
initDNSServer(dnsBaseDir)
|
||||
|
||||
if !config.firstRun {
|
||||
err = startDNSServer()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = startDHCPServer()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Update filters we've just loaded right away, don't wait for periodic update timer
|
||||
go func() {
|
||||
refreshFiltersIfNecessary(false)
|
||||
}()
|
||||
// Schedule automatic filters updates
|
||||
go periodicallyRefreshFilters()
|
||||
|
||||
http.Handle("/", optionalAuthHandler(http.FileServer(box)))
|
||||
// Initialize and run the admin Web interface
|
||||
box := packr.NewBox("build/static")
|
||||
// if not configured, redirect / to /install.html, otherwise redirect /install.html to /
|
||||
http.Handle("/", postInstallHandler(optionalAuthHandler(http.FileServer(box))))
|
||||
registerControlHandlers()
|
||||
|
||||
err = startDNSServer()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
// add handlers for /install paths, we only need them when we're not configured yet
|
||||
if config.firstRun {
|
||||
log.Info("This is the first launch of AdGuard Home, redirecting everything to /install.html ")
|
||||
http.Handle("/install.html", preInstallHandler(http.FileServer(box)))
|
||||
registerInstallHandlers()
|
||||
}
|
||||
|
||||
err = startDHCPServer()
|
||||
httpsServer.cond = sync.NewCond(&httpsServer.Mutex)
|
||||
|
||||
// for https, we have a separate goroutine loop
|
||||
go func() {
|
||||
for { // this is an endless loop
|
||||
httpsServer.cond.L.Lock()
|
||||
// this mechanism doesn't let us through until all conditions are ment
|
||||
for config.TLS.Enabled == false || config.TLS.PortHTTPS == 0 || config.TLS.PrivateKey == "" || config.TLS.CertificateChain == "" { // sleep until necessary data is supplied
|
||||
httpsServer.cond.Wait()
|
||||
}
|
||||
address := net.JoinHostPort(config.BindHost, strconv.Itoa(config.TLS.PortHTTPS))
|
||||
// validate current TLS config and update warnings (it could have been loaded from file)
|
||||
data := validateCertificates(config.TLS.CertificateChain, config.TLS.PrivateKey, config.TLS.ServerName)
|
||||
if !data.ValidPair {
|
||||
log.Fatal(data.WarningValidation)
|
||||
os.Exit(1)
|
||||
}
|
||||
config.Lock()
|
||||
config.TLS.tlsConfigStatus = data // update warnings
|
||||
config.Unlock()
|
||||
|
||||
// prepare certs for HTTPS server
|
||||
// important -- they have to be copies, otherwise changing the contents in config.TLS will break encryption for in-flight requests
|
||||
certchain := make([]byte, len(config.TLS.CertificateChain))
|
||||
copy(certchain, []byte(config.TLS.CertificateChain))
|
||||
privatekey := make([]byte, len(config.TLS.PrivateKey))
|
||||
copy(privatekey, []byte(config.TLS.PrivateKey))
|
||||
cert, err := tls.X509KeyPair(certchain, privatekey)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
httpsServer.cond.L.Unlock()
|
||||
|
||||
// prepare HTTPS server
|
||||
httpsServer.server = &http.Server{
|
||||
Addr: address,
|
||||
TLSConfig: &tls.Config{
|
||||
Certificates: []tls.Certificate{cert},
|
||||
},
|
||||
}
|
||||
|
||||
printHTTPAddresses("https")
|
||||
err = httpsServer.server.ListenAndServeTLS("", "")
|
||||
if err != http.ErrServerClosed {
|
||||
log.Fatal(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// this loop is used as an ability to change listening host and/or port
|
||||
for {
|
||||
printHTTPAddresses("http")
|
||||
|
||||
// we need to have new instance, because after Shutdown() the Server is not usable
|
||||
address := net.JoinHostPort(config.BindHost, strconv.Itoa(config.BindPort))
|
||||
httpServer = &http.Server{
|
||||
Addr: address,
|
||||
}
|
||||
err := httpServer.ListenAndServe()
|
||||
if err != http.ErrServerClosed {
|
||||
log.Fatal(err)
|
||||
os.Exit(1)
|
||||
}
|
||||
// We use ErrServerClosed as a sign that we need to rebind on new address, so go back to the start of the loop
|
||||
}
|
||||
}
|
||||
|
||||
// initWorkingDir initializes the ourWorkingDir
|
||||
// if no command-line arguments specified, we use the directory where our binary file is located
|
||||
func initWorkingDir(args options) {
|
||||
exec, err := os.Executable()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
URL := fmt.Sprintf("http://%s", address)
|
||||
log.Println("Go to " + URL)
|
||||
log.Fatal(http.ListenAndServe(address, nil))
|
||||
if args.workDir != "" {
|
||||
// If there is a custom config file, use it's directory as our working dir
|
||||
config.ourWorkingDir = args.workDir
|
||||
} else {
|
||||
config.ourWorkingDir = filepath.Dir(exec)
|
||||
}
|
||||
}
|
||||
|
||||
// configureLogger configures logger level and output
|
||||
func configureLogger(args options) {
|
||||
ls := getLogSettings()
|
||||
|
||||
// command-line arguments can override config settings
|
||||
if args.verbose {
|
||||
ls.Verbose = true
|
||||
}
|
||||
if args.logFile != "" {
|
||||
ls.LogFile = args.logFile
|
||||
}
|
||||
|
||||
level := log.INFO
|
||||
if ls.Verbose {
|
||||
level = log.DEBUG
|
||||
}
|
||||
log.SetLevel(level)
|
||||
|
||||
if args.runningAsService && ls.LogFile == "" && runtime.GOOS == "windows" {
|
||||
// When running as a Windows service, use eventlog by default if nothing else is configured
|
||||
// Otherwise, we'll simply loose the log output
|
||||
ls.LogFile = configSyslog
|
||||
}
|
||||
|
||||
if ls.LogFile == "" {
|
||||
return
|
||||
}
|
||||
|
||||
if ls.LogFile == configSyslog {
|
||||
// Use syslog where it is possible and eventlog on Windows
|
||||
err := configureSyslog()
|
||||
if err != nil {
|
||||
log.Fatalf("cannot initialize syslog: %s", err)
|
||||
}
|
||||
} else {
|
||||
logFilePath := filepath.Join(config.ourWorkingDir, ls.LogFile)
|
||||
if filepath.IsAbs(ls.LogFile) {
|
||||
logFilePath = ls.LogFile
|
||||
}
|
||||
|
||||
file, err := os.OpenFile(logFilePath, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
|
||||
if err != nil {
|
||||
log.Fatalf("cannot create a log file: %s", err)
|
||||
}
|
||||
log.SetOutput(file)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO after GO 1.13 release TLS 1.3 will be enabled by default. Remove this afterward
|
||||
func enableTLS13() {
|
||||
err := os.Setenv("GODEBUG", os.Getenv("GODEBUG")+",tls13=1")
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to enable TLS 1.3: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func cleanup() {
|
||||
log.Info("Stopping AdGuard Home")
|
||||
|
||||
err := stopDNSServer()
|
||||
if err != nil {
|
||||
log.Printf("Couldn't stop DNS server: %s", err)
|
||||
log.Error("Couldn't stop DNS server: %s", err)
|
||||
}
|
||||
err = stopDHCPServer()
|
||||
if err != nil {
|
||||
log.Error("Couldn't stop DHCP server: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func getInput() (string, error) {
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
scanner.Scan()
|
||||
text := scanner.Text()
|
||||
err := scanner.Err()
|
||||
return text, err
|
||||
// command-line arguments
|
||||
type options struct {
|
||||
verbose bool // is verbose logging enabled
|
||||
configFilename string // path to the config file
|
||||
workDir string // path to the working directory where we will store the filters data and the querylog
|
||||
bindHost string // host address to bind HTTP server on
|
||||
bindPort int // port to serve HTTP pages on
|
||||
logFile string // Path to the log file. If empty, write to stdout. If "syslog", writes to syslog
|
||||
|
||||
// service control action (see service.ControlAction array + "status" command)
|
||||
serviceControlAction string
|
||||
|
||||
// runningAsService flag is set to true when options are passed from the service runner
|
||||
runningAsService bool
|
||||
}
|
||||
|
||||
func promptAndGet(prompt string) (string, error) {
|
||||
for {
|
||||
fmt.Print(prompt)
|
||||
input, err := getInput()
|
||||
// loadOptions reads command line arguments and initializes configuration
|
||||
func loadOptions() options {
|
||||
o := options{}
|
||||
|
||||
var printHelp func()
|
||||
var opts = []struct {
|
||||
longName string
|
||||
shortName string
|
||||
description string
|
||||
callbackWithValue func(value string)
|
||||
callbackNoValue func()
|
||||
}{
|
||||
{"config", "c", "path to the config file", func(value string) { o.configFilename = value }, nil},
|
||||
{"work-dir", "w", "path to the working directory", func(value string) { o.workDir = value }, nil},
|
||||
{"host", "h", "host address to bind HTTP server on", func(value string) { o.bindHost = value }, nil},
|
||||
{"port", "p", "port to serve HTTP pages on", func(value string) {
|
||||
v, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
panic("Got port that is not a number")
|
||||
}
|
||||
o.bindPort = v
|
||||
}, nil},
|
||||
{"service", "s", "service control action: status, install, uninstall, start, stop, restart", func(value string) {
|
||||
o.serviceControlAction = value
|
||||
}, nil},
|
||||
{"logfile", "l", "path to the log file. If empty, writes to stdout, if 'syslog' -- system log", func(value string) {
|
||||
o.logFile = value
|
||||
}, nil},
|
||||
{"verbose", "v", "enable verbose output", nil, func() { o.verbose = true }},
|
||||
{"help", "", "print this help", nil, func() {
|
||||
printHelp()
|
||||
os.Exit(64)
|
||||
}},
|
||||
}
|
||||
printHelp = func() {
|
||||
fmt.Printf("Usage:\n\n")
|
||||
fmt.Printf("%s [options]\n\n", os.Args[0])
|
||||
fmt.Printf("Options:\n")
|
||||
for _, opt := range opts {
|
||||
if opt.shortName != "" {
|
||||
fmt.Printf(" -%s, %-30s %s\n", opt.shortName, "--"+opt.longName, opt.description)
|
||||
} else {
|
||||
fmt.Printf(" %-34s %s\n", "--"+opt.longName, opt.description)
|
||||
}
|
||||
}
|
||||
}
|
||||
for i := 1; i < len(os.Args); i++ {
|
||||
v := os.Args[i]
|
||||
knownParam := false
|
||||
for _, opt := range opts {
|
||||
if v == "--"+opt.longName || (opt.shortName != "" && v == "-"+opt.shortName) {
|
||||
if opt.callbackWithValue != nil {
|
||||
if i+1 >= len(os.Args) {
|
||||
log.Error("Got %s without argument\n", v)
|
||||
os.Exit(64)
|
||||
}
|
||||
i++
|
||||
opt.callbackWithValue(os.Args[i])
|
||||
} else if opt.callbackNoValue != nil {
|
||||
opt.callbackNoValue()
|
||||
}
|
||||
knownParam = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !knownParam {
|
||||
log.Error("unknown option %v\n", v)
|
||||
printHelp()
|
||||
os.Exit(64)
|
||||
}
|
||||
}
|
||||
|
||||
return o
|
||||
}
|
||||
|
||||
// prints IP addresses which user can use to open the admin interface
|
||||
// proto is either "http" or "https"
|
||||
func printHTTPAddresses(proto string) {
|
||||
var address string
|
||||
|
||||
if proto == "https" && config.TLS.ServerName != "" {
|
||||
if config.TLS.PortHTTPS == 443 {
|
||||
log.Printf("Go to https://%s", config.TLS.ServerName)
|
||||
} else {
|
||||
log.Printf("Go to https://%s:%d", config.TLS.ServerName, config.TLS.PortHTTPS)
|
||||
}
|
||||
} else if config.BindHost == "0.0.0.0" {
|
||||
log.Println("AdGuard Home is available on the following addresses:")
|
||||
ifaces, err := getValidNetInterfacesForWeb()
|
||||
if err != nil {
|
||||
log.Printf("Failed to get input, aborting: %s", err)
|
||||
return "", err
|
||||
// That's weird, but we'll ignore it
|
||||
address = net.JoinHostPort(config.BindHost, strconv.Itoa(config.BindPort))
|
||||
log.Printf("Go to %s://%s", proto, address)
|
||||
return
|
||||
}
|
||||
if len(input) != 0 {
|
||||
return input, nil
|
||||
|
||||
for _, iface := range ifaces {
|
||||
address = net.JoinHostPort(iface.Addresses[0], strconv.Itoa(config.BindPort))
|
||||
log.Printf("Go to %s://%s", proto, address)
|
||||
}
|
||||
// try again
|
||||
} else {
|
||||
address = net.JoinHostPort(config.BindHost, strconv.Itoa(config.BindPort))
|
||||
log.Printf("Go to %s://%s", proto, address)
|
||||
}
|
||||
}
|
||||
|
||||
func promptAndGetPassword(prompt string) (string, error) {
|
||||
for {
|
||||
fmt.Print(prompt)
|
||||
password, err := terminal.ReadPassword(int(os.Stdin.Fd()))
|
||||
fmt.Print("\n")
|
||||
if err != nil {
|
||||
log.Printf("Failed to get input, aborting: %s", err)
|
||||
return "", err
|
||||
}
|
||||
if len(password) != 0 {
|
||||
return string(password), nil
|
||||
}
|
||||
// try again
|
||||
}
|
||||
}
|
||||
|
||||
func askUsernamePasswordIfPossible() error {
|
||||
configfile := config.ourConfigFilename
|
||||
if !filepath.IsAbs(configfile) {
|
||||
configfile = filepath.Join(config.ourBinaryDir, config.ourConfigFilename)
|
||||
}
|
||||
_, err := os.Stat(configfile)
|
||||
if !os.IsNotExist(err) {
|
||||
// do nothing, file exists
|
||||
return nil
|
||||
}
|
||||
if !terminal.IsTerminal(int(os.Stdin.Fd())) {
|
||||
return nil // do nothing
|
||||
}
|
||||
if !terminal.IsTerminal(int(os.Stdout.Fd())) {
|
||||
return nil // do nothing
|
||||
}
|
||||
fmt.Printf("Would you like to set user/password for the web interface authentication (yes/no)?\n")
|
||||
yesno, err := promptAndGet("Please type 'yes' or 'no': ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if yesno[0] != 'y' && yesno[0] != 'Y' {
|
||||
return nil
|
||||
}
|
||||
username, err := promptAndGet("Please enter the username: ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
password, err := promptAndGetPassword("Please enter the password: ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
password2, err := promptAndGetPassword("Please enter password again: ")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if password2 != password {
|
||||
fmt.Printf("Passwords do not match! Aborting\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
config.AuthName = username
|
||||
config.AuthPass = password
|
||||
return nil
|
||||
}
|
||||
|
||||
74
build_docker.sh
Executable file
74
build_docker.sh
Executable file
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -eE
|
||||
set -o pipefail
|
||||
set -x
|
||||
|
||||
DOCKERFILE="Dockerfile.travis"
|
||||
IMAGE_NAME="adguard/adguardhome"
|
||||
|
||||
if [[ "${TRAVIS_BRANCH}" == "master" ]]
|
||||
then
|
||||
VERSION="edge"
|
||||
else
|
||||
VERSION=`git describe --abbrev=4 --dirty --always --tags`
|
||||
fi
|
||||
|
||||
build_image() {
|
||||
from="$(awk '$1 == toupper("FROM") { print $2 }' ${DOCKERFILE})"
|
||||
|
||||
# See https://hub.docker.com/r/multiarch/alpine/tags
|
||||
case "${GOARCH}" in
|
||||
arm64)
|
||||
alpineArch='arm64-edge'
|
||||
imageArch='arm64'
|
||||
;;
|
||||
arm)
|
||||
alpineArch='armhf-edge'
|
||||
imageArch='armhf'
|
||||
;;
|
||||
386)
|
||||
alpineArch='i386-edge'
|
||||
imageArch='i386'
|
||||
;;
|
||||
amd64)
|
||||
alpineArch='amd64-edge'
|
||||
;;
|
||||
*)
|
||||
alpineArch='amd64-edge'
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ "${GOOS}" == "linux" ]] && [[ "${GOARCH}" == "amd64" ]]
|
||||
then
|
||||
image="${IMAGE_NAME}:${VERSION}"
|
||||
else
|
||||
image="${IMAGE_NAME}:${imageArch}-${VERSION}"
|
||||
fi
|
||||
|
||||
make cleanfast; CGO_DISABLED=1 make
|
||||
|
||||
docker pull "multiarch/alpine:${alpineArch}"
|
||||
docker tag "multiarch/alpine:${alpineArch}" "$from"
|
||||
docker build -t "${image}" -f ${DOCKERFILE} .
|
||||
docker push ${image}
|
||||
if [[ "${VERSION}" != "edge" ]]
|
||||
then
|
||||
latest=${image/$VERSION/latest}
|
||||
docker tag "${image}" "${latest}"
|
||||
docker push ${latest}
|
||||
docker rmi ${latest}
|
||||
fi
|
||||
docker rmi "$from"
|
||||
}
|
||||
|
||||
# prepare qemu
|
||||
docker run --rm --privileged multiarch/qemu-user-static:register --reset
|
||||
|
||||
make clean
|
||||
|
||||
# Prepare releases
|
||||
GOOS=linux GOARCH=amd64 build_image
|
||||
GOOS=linux GOARCH=386 build_image
|
||||
GOOS=linux GOARCH=arm GOARM=6 build_image
|
||||
GOOS=linux GOARCH=arm64 GOARM=6 build_image
|
||||
4
client/.eslintrc
vendored
4
client/.eslintrc
vendored
@@ -45,9 +45,7 @@
|
||||
}],
|
||||
"class-methods-use-this": "off",
|
||||
"no-shadow": "off",
|
||||
"camelcase": ["error", {
|
||||
"properties": "never"
|
||||
}],
|
||||
"camelcase": "off",
|
||||
"no-console": ["warn", { "allow": ["warn", "error"] }],
|
||||
"import/no-extraneous-dependencies": ["error", { "devDependencies": true }],
|
||||
"import/prefer-default-export": "off"
|
||||
|
||||
7190
client/package-lock.json
generated
vendored
7190
client/package-lock.json
generated
vendored
File diff suppressed because it is too large
Load Diff
12
client/package.json
vendored
12
client/package.json
vendored
@@ -16,7 +16,7 @@
|
||||
"file-saver": "^1.3.8",
|
||||
"i18next": "^12.0.0",
|
||||
"i18next-browser-languagedetector": "^2.2.3",
|
||||
"lodash": "^4.17.10",
|
||||
"lodash": "^4.17.11",
|
||||
"nanoid": "^1.2.3",
|
||||
"prop-types": "^15.6.1",
|
||||
"react": "^16.4.0",
|
||||
@@ -33,14 +33,12 @@
|
||||
"redux-actions": "^2.4.0",
|
||||
"redux-form": "^7.4.2",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"svg-url-loader": "^2.3.2",
|
||||
"whatwg-fetch": "2.0.3"
|
||||
"svg-url-loader": "^2.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "^8.6.3",
|
||||
"babel-core": "6.26.0",
|
||||
"babel-eslint": "^8.2.3",
|
||||
"babel-jest": "20.0.3",
|
||||
"babel-loader": "7.1.2",
|
||||
"babel-plugin-transform-runtime": "^6.23.0",
|
||||
"babel-preset-env": "^1.7.0",
|
||||
@@ -49,6 +47,7 @@
|
||||
"babel-runtime": "6.26.0",
|
||||
"clean-webpack-plugin": "^0.1.19",
|
||||
"compression-webpack-plugin": "^1.1.11",
|
||||
"copy-webpack-plugin": "^4.6.0",
|
||||
"css-loader": "^0.28.11",
|
||||
"eslint": "^4.19.1",
|
||||
"eslint-config-airbnb-base": "^12.1.0",
|
||||
@@ -60,7 +59,6 @@
|
||||
"extract-text-webpack-plugin": "^3.0.2",
|
||||
"file-loader": "1.1.5",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"jest": "20.0.4",
|
||||
"postcss-flexbugs-fixes": "3.2.0",
|
||||
"postcss-import": "^11.1.0",
|
||||
"postcss-loader": "^2.1.5",
|
||||
@@ -68,12 +66,12 @@
|
||||
"postcss-preset-env": "^5.1.0",
|
||||
"postcss-svg": "^2.4.0",
|
||||
"style-loader": "^0.21.0",
|
||||
"stylelint": "9.2.1",
|
||||
"stylelint": "^9.10.1",
|
||||
"stylelint-webpack-plugin": "0.10.4",
|
||||
"uglifyjs-webpack-plugin": "^1.2.7",
|
||||
"url-loader": "^1.0.1",
|
||||
"webpack": "3.8.1",
|
||||
"webpack-dev-server": "2.9.4",
|
||||
"webpack-dev-server": "^3.1.14",
|
||||
"webpack-merge": "^4.1.3"
|
||||
}
|
||||
}
|
||||
|
||||
BIN
client/public/favicon.ico
Normal file
BIN
client/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@@ -1,16 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="theme-color" content="#000000">
|
||||
<link rel="shortcut icon" href="https://adguard.com/img/favicons/favicon.ico">
|
||||
<title>AdGuard Home</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
You need to enable JavaScript to run this app.
|
||||
</noscript>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="theme-color" content="#000000">
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
<title>AdGuard Home</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
You need to enable JavaScript to run this app.
|
||||
</noscript>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
16
client/public/install.html
Normal file
16
client/public/install.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="theme-color" content="#000000">
|
||||
<link rel="shortcut icon" href="favicon.ico">
|
||||
<title>Setup AdGuard Home</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
You need to enable JavaScript to run this app.
|
||||
</noscript>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
250
client/src/__locales/bg.json
Normal file
250
client/src/__locales/bg.json
Normal file
@@ -0,0 +1,250 @@
|
||||
{
|
||||
"url_added_successfully": "\u0423\u0441\u043f\u0435\u0448\u043d\u043e \u0434\u043e\u0431\u0430\u0432\u0435\u043d URL",
|
||||
"check_dhcp_servers": "\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0437\u0430 \u0430\u043a\u0442\u0438\u0432\u0435\u043d DHCP \u0441\u044a\u0440\u0432\u044a\u0440",
|
||||
"save_config": "\u0417\u0430\u043f\u0438\u0448\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u0442\u0435",
|
||||
"enabled_dhcp": "DHCP \u0435 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d",
|
||||
"disabled_dhcp": "DHCP \u0435 \u0437\u0430\u0431\u0440\u0430\u043d\u0435\u043d",
|
||||
"dhcp_title": "DHCP \u0441\u044a\u0440\u0432\u044a\u0440 (\u0442\u0435\u0441\u0442\u043e\u0432\u0438!)",
|
||||
"dhcp_description": "\u0410\u043a\u043e \u0440\u0443\u0442\u0435\u0440\u0430 \u0432\u0438 \u043d\u0435 \u0440\u0430\u0437\u0434\u0430\u0432\u0430 DHCP \u0430\u0434\u0440\u0435\u0441\u0438, \u043c\u043e\u0436\u0435 \u0434\u0430 \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u0442\u0435 \u0432\u0433\u0440\u0430\u0434\u0435\u043d\u0438\u044f \u0432 AdGuard DHCP \u0441\u044a\u0440\u0432\u044a\u0440.",
|
||||
"dhcp_enable": "\u0420\u0437\u0440\u0435\u0448\u0438 DHCP \u0441\u044a\u0440\u0432\u044a\u0440\u0430",
|
||||
"dhcp_disable": "\u0417\u0430\u0431\u0440\u0430\u043d\u0438 DHCP \u0441\u044a\u0440\u0432\u044a\u0440\u0430",
|
||||
"dhcp_not_found": "\u0412\u0430\u0448\u0430\u0442\u0430 \u043c\u0440\u0435\u0436\u0430 \u043d\u044f\u043c\u0430 \u0430\u043a\u0442\u0438\u0432\u0435\u043d DHCP \u0441\u044a\u0440\u0432\u044a\u0440. \u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e \u0435 \u043f\u043e\u043b\u0437\u0432\u0430\u043d\u0435\u0442\u043e \u043d\u0430 \u0432\u0433\u0440\u0430\u0434\u0435\u043d\u0438\u044f DHCP \u0441\u044a\u0440\u0432\u044a\u0440.",
|
||||
"dhcp_found": "\u0412\u0430\u0448\u0430\u0442\u0430 \u043c\u0440\u0435\u0436\u0430 \u0432\u0435\u0447\u0435 \u0438\u043c\u0430 \u0430\u043a\u0442\u0438\u0432\u0435\u043d DHCP \u0441\u044a\u0440\u0432\u044a\u0440. \u041d\u0435 \u0435 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e \u043f\u043e\u043b\u0437\u0432\u0430\u043d\u0435\u0442\u043e \u043d\u0430 \u0432\u0442\u043e\u0440\u0438!",
|
||||
"dhcp_leases": "DHCP \u0440\u0430\u0437\u0434\u0430\u0434\u0435\u043d\u0438 \u0430\u0434\u0440\u0435\u0441\u0438",
|
||||
"dhcp_leases_not_found": "\u041d\u044f\u043c\u0430 \u043d\u0430\u043c\u0435\u0440\u0435\u043d\u0438 \u0430\u043a\u0442\u0438\u0432\u043d\u0438 DHCP \u0430\u0434\u0440\u0435\u0441\u0438",
|
||||
"dhcp_config_saved": "\u0417\u0430\u043f\u0438\u0448\u0438 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f\u0442\u0430 \u043d\u0430 DHCP \u0441\u044a\u0440\u0432\u044a\u0440\u0430",
|
||||
"form_error_required": "\u0417\u0430\u0434\u044a\u043b\u0436\u0438\u0442\u0435\u043b\u043d\u043e \u043f\u043e\u043b\u0435",
|
||||
"form_error_ip_format": "\u041d\u0435\u0432\u0430\u043b\u0438\u0434\u0435\u043d IPv4 \u0430\u0434\u0440\u0435\u0441",
|
||||
"form_error_positive": "\u041f\u0440\u043e\u0432\u0435\u0440\u0435\u0442\u0435 \u0434\u0430\u043b\u0438 \u0435 \u043f\u043e\u043b\u043e\u0436\u0438\u0442\u0435\u043b\u043d\u043e \u0447\u0438\u0441\u043b\u043e",
|
||||
"dhcp_form_gateway_input": "IP \u0448\u043b\u044e\u0437",
|
||||
"dhcp_form_subnet_input": "\u041c\u0440\u0435\u0436\u043e\u0432\u0430 \u043c\u0430\u0441\u043a\u0430",
|
||||
"dhcp_form_range_title": "\u0413\u0440\u0443\u043f\u0430 \u043e\u0442 IP \u0430\u0434\u0440\u0435\u0441\u0438",
|
||||
"dhcp_form_range_start": "\u041f\u044a\u0440\u0432\u0438 \u0430\u0434\u0440\u0435\u0441",
|
||||
"dhcp_form_range_end": "\u041f\u043e\u0441\u043b\u0435\u0434\u0435\u043d \u0430\u0434\u0440\u0435\u0441",
|
||||
"dhcp_form_lease_title": "\u041e\u0442\u0434\u0430\u0434\u0435\u043d\u0438 \u0430\u0434\u0440\u0435\u0441\u0438 (\u0441\u0435\u043a\u0443\u043d\u0434\u0438)",
|
||||
"dhcp_form_lease_input": "\u041e\u0442\u0447\u0435\u0442 \u0437\u0430 \u0440\u0430\u0437\u0434\u0430\u0434\u0435\u043d\u0438 \u0430\u0434\u0440\u0435\u0441\u0438",
|
||||
"dhcp_interface_select": "\u0418\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u043c\u0440\u0435\u0436\u043e\u0432 \u0430\u0434\u0430\u043f\u0442\u0435\u0440 \u0437\u0430 DHCP",
|
||||
"dhcp_hardware_address": "\u0425\u0430\u0440\u0434\u0443\u0435\u0440\u043d\u0438 \u0430\u0434\u0440\u0435\u0441\u0438 (MAC)",
|
||||
"dhcp_ip_addresses": "IP \u0430\u0434\u0440\u0435\u0441\u0438",
|
||||
"dhcp_table_hostname": "\u0418\u043c\u0435 \u043d\u0430 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e",
|
||||
"dhcp_table_expires": "\u0418\u0441\u0442\u043e\u0440\u0438\u044f",
|
||||
"dhcp_warning": "\u0410\u043a\u043e \u0438\u0441\u043a\u0430\u0442\u0435 \u0434\u0430 \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u0442\u0435 \u0432\u0433\u0440\u0430\u0434\u0435\u043d\u0438\u044f DHCP \u0441\u044a\u0440\u0432\u044a\u0440, \u0442\u0440\u044f\u0431\u0432\u0430 \u0434\u0430 \u043d\u044f\u043c\u0430 \u0434\u0440\u0443\u0433 \u0430\u043a\u0442\u0438\u0432\u0435\u043d DHCP \u0432 \u043c\u0440\u0435\u0436\u0430\u0442\u0430 \u0412\u0438!",
|
||||
"back": "\u041d\u0430\u0437\u0430\u0434",
|
||||
"dashboard": "\u0422\u0430\u0431\u043b\u043e",
|
||||
"settings": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438",
|
||||
"filters": "\u0424\u0438\u043b\u0442\u0440\u0438",
|
||||
"query_log": "\u0418\u0441\u0442\u043e\u0440\u0438\u044f \u043d\u0430 \u0437\u0430\u044f\u0432\u043a\u0438\u0442\u0435",
|
||||
"faq": "\u0427\u0417\u0412",
|
||||
"version": "\u0432\u0435\u0440\u0441\u0438\u044f",
|
||||
"address": "\u0430\u0434\u0440\u0435\u0441",
|
||||
"on": "\u0412\u041a\u041b\u042e\u0427\u0415\u041d\u041e",
|
||||
"off": "\u0418\u0417\u041a\u041b\u042e\u0427\u0415\u041d\u041e",
|
||||
"copyright": "\u0410\u0432\u0442\u043e\u0440\u0441\u043a\u043e \u043f\u0440\u0430\u0432\u043e",
|
||||
"homepage": "\u0414\u043e\u043c\u0430\u0448\u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430",
|
||||
"report_an_issue": "\u0421\u044a\u043e\u0431\u0449\u0438 \u0437\u0430 \u043f\u0440\u043e\u0431\u043b\u0435\u043c",
|
||||
"enable_protection": "\u0420\u0430\u0437\u0440\u0435\u0448\u0438 \u0437\u0430\u0449\u0438\u0442\u0430",
|
||||
"enabled_protection": "\u0417\u0430\u0449\u0438\u0442\u0430\u0442\u0430 \u0435 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0430",
|
||||
"disable_protection": "\u0417\u0430\u0431\u0440\u0430\u043d\u0438 \u0437\u0430\u0449\u0438\u0442\u0430",
|
||||
"disabled_protection": "\u0417\u0430\u0449\u0438\u0442\u0430\u0442\u0430 \u0435 \u0437\u0430\u0431\u0440\u0430\u043d\u0435\u043d\u0430",
|
||||
"refresh_statics": "\u041e\u0431\u043d\u043e\u0432\u0438 \u0441\u0442\u0430\u0442\u0438\u0441\u0442\u0438\u043a\u0430\u0442\u0430",
|
||||
"dns_query": "DNS \u0437\u0430\u043f\u0438\u0442\u0432\u0430\u043d\u0438\u044f",
|
||||
"blocked_by": "\u0411\u043b\u043e\u043a\u0438\u0440\u0430\u043d\u0438 \u043e\u0442",
|
||||
"stats_malware_phishing": "\u0432\u0438\u0440\u0443\u0441\u0438\/\u0430\u0442\u0430\u043a\u0438",
|
||||
"stats_adult": "\u0441\u0430\u0439\u0442\u043e\u0432\u0435 \u0437\u0430 \u0432\u044a\u0437\u0440\u0430\u0441\u0442\u043d\u0438",
|
||||
"stats_query_domain": "\u041d\u0430\u0439-\u043e\u0442\u0432\u0430\u0440\u044f\u043d\u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0438",
|
||||
"for_last_24_hours": "\u0437\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0442\u0435 24 \u0447\u0430\u0441\u0430",
|
||||
"no_domains_found": "\u041d\u044f\u043c\u0430 \u043d\u0430\u043c\u0435\u0440\u0435\u043d\u0438 \u0440\u0435\u0437\u0443\u043b\u0442\u0430\u0442\u0438",
|
||||
"requests_count": "\u0421\u0443\u043c\u0430 \u043d\u0430 \u0437\u0430\u044f\u0432\u043a\u0438\u0442\u0435",
|
||||
"top_blocked_domains": "\u041d\u0430\u0439-\u0431\u043b\u043e\u043a\u0438\u0440\u0430\u043d\u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0438",
|
||||
"top_clients": "\u041d\u0430\u0439-\u0430\u043a\u0442\u0438\u0432\u043d\u0438 IP \u0430\u0434\u0440\u0435\u0441\u0438",
|
||||
"no_clients_found": "\u041d\u044f\u043ca \u043d\u0430\u043c\u0435\u0440\u0435\u043d\u0438 \u0430\u0434\u0440\u0435\u0441\u0438",
|
||||
"general_statistics": "\u041e\u0431\u0449\u0430 \u0441\u0442\u0430\u0442\u0438\u0441\u0438\u043a\u0430",
|
||||
"number_of_dns_query_24_hours": "\u0421\u0443\u043c\u0430 \u043d\u0430 DNS \u0437\u0430\u044f\u0432\u043a\u0438 \u0437\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0442\u0435 24 \u0447\u0430\u0441\u0430",
|
||||
"number_of_dns_query_blocked_24_hours": "\u0421\u0443\u043c\u0430 \u043d\u0430 \u0431\u043b\u043e\u043a\u0438\u0440\u0430\u043d\u0438 DNS \u0437\u0430\u044f\u0432\u043a\u0438 \u043e\u0442 \u0444\u0438\u043b\u0442\u0440\u0438\u0442\u0435 \u0437\u0430 \u0440\u0435\u043a\u043b\u0430\u043c\u0430 \u0438 \u043c\u0435\u0441\u0442\u043d\u0438",
|
||||
"number_of_dns_query_blocked_24_hours_by_sec": "\u0421\u0443\u043c\u0430 \u043d\u0430 \u0431\u043b\u043e\u043a\u0438\u0440\u0430\u043d\u0438 DNS \u0437\u0430\u044f\u0432\u043a\u0438 \u043e\u0442 AdGuard \u0441\u0432\u044a\u0440\u0437\u0430\u043d\u0438 \u0441\u044a\u0441 \u0441\u0438\u0433\u0443\u0440\u043d\u043e\u0441\u0442\u0442\u0430",
|
||||
"number_of_dns_query_blocked_24_hours_adult": "\u0421\u0443\u043c\u0430 \u043d\u0430 \u0431\u043b\u043e\u043a\u0438\u0440\u0430\u043d\u0438 \u0441\u0430\u0439\u0442\u043e\u0432\u0435 \u0437\u0430 \u0432\u044a\u0437\u0440\u0430\u0441\u0442\u043d\u0438",
|
||||
"enforced_save_search": "\u0410\u043a\u0442\u0438\u0432\u0438\u0440\u0430\u043d\u043e \u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e \u0422\u044a\u0440\u0441\u0435\u043d\u0435",
|
||||
"number_of_dns_query_to_safe_search": "\u0421\u0443\u043c\u0430 \u043d\u0430 DNS \u0437\u0430\u044f\u0432\u043a\u0438 \u043f\u0440\u0438 \u043a\u043e\u0439\u0442\u043e \u0435 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u043e \u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e \u0422\u044a\u0440\u0441\u0435\u043d\u0435",
|
||||
"average_processing_time": "\u0421\u0440\u0435\u0434\u043d\u043e \u0432\u0440\u0435\u043c\u0435 \u0437\u0430 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430",
|
||||
"average_processing_time_hint": "\u0421\u0440\u0435\u0434\u043d\u043e \u0432\u0440\u0435\u043c\u0435 \u0437\u0430 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u043d\u0430 DNS \u0437\u0430\u044f\u0432\u043a\u0438 \u0432 \u043c\u0438\u043b\u0438\u0441\u0435\u043a\u0443\u043d\u0434\u0438",
|
||||
"block_domain_use_filters_and_hosts": "\u0411\u043b\u043e\u043a\u0438\u0440\u0430\u043d\u0438 \u0434\u043e\u043c\u0435\u0439\u043d\u0438 - \u043e\u0431\u0449\u0438 \u0438 \u043c\u0435\u0441\u0442\u043d\u0438 \u0444\u0438\u043b\u0442\u0440\u0438",
|
||||
"filters_block_toggle_hint": "\u041c\u043e\u0436\u0435 \u0434\u0430 \u0437\u0430\u0434\u0430\u0434\u0435\u0442\u0435 \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0432 <a href='#filters'>\u0424\u0438\u043b\u0442\u0440\u0438<\/a>.",
|
||||
"use_adguard_browsing_sec": "\u0418\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u0439\u0442\u0435 AdGuard \u043c\u043e\u0434\u0443\u043b \u0437\u0430 \u0441\u0438\u0433\u0443\u0440\u043d\u043e\u0441\u0442\u0442\u0430",
|
||||
"use_adguard_browsing_sec_hint": "\u041c\u043e\u0434\u0443\u043b \u0421\u0438\u0433\u0443\u0440\u043d\u043e\u0441\u0442 \u0432 AdGuard Home \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0432\u0430 \u0432\u0441\u044f\u043a\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u043a\u043e\u044f\u0442\u043e \u043e\u0442\u0432\u0430\u0440\u044f\u0442\u0435 \u0434\u0430\u043b\u0438 \u0435 \u0432 \u0447\u0435\u0440\u043d\u0438\u0442\u0435 \u0441\u043f\u0438\u0441\u044a\u0446\u0438 \u0437\u0430\u0441\u0442\u0440\u0430\u0448\u0430\u0432\u0430\u0449\u0438 \u0432\u0430\u0448\u0430\u0442\u0430 \u0441\u0438\u0433\u0443\u0440\u043d\u043e\u0441\u0442. \u0418\u0437\u043f\u043e\u043b\u0437\u0432\u0430 \u0441\u0435 \u043f\u0440\u043e\u0433\u0440\u0430\u043c\u0435\u043d \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 \u043a\u043e\u0439\u0442\u043e \u0437\u0430\u0449\u0438\u0442\u0430\u0432\u0430 \u0432\u0430\u0448\u0430\u0442\u0430 \u0430\u043d\u043e\u043d\u0438\u043c\u043d\u043e\u0441\u0442 \u0438 \u0438\u0437\u043f\u0440\u0430\u0449\u0430 \u0441\u0430\u043c\u043e SHA256 \u0441\u0443\u043c\u0430 \u0431\u0430\u0437\u0438\u0440\u0430\u043d\u0430 \u043d\u0430 \u0447\u0430\u0441\u0442 \u043e\u0442 \u0434\u043e\u043c\u0435\u0439\u043d\u0430 \u043a\u043e\u0439\u0442\u043e \u043f\u043e\u0441\u0435\u0449\u0430\u0432\u0430\u0442\u0435.",
|
||||
"use_adguard_parental": "\u0412\u043a\u043b\u044e\u0447\u0438 AdGuard \u0420\u043e\u0434\u0438\u0442\u0435\u043b\u0441\u043a\u0438 \u041d\u0430\u0434\u0437\u043e\u0440",
|
||||
"use_adguard_parental_hint": "\u041c\u043e\u0434\u0443\u043b XXX \u0432 AdGuard Home \u0449\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u0438 \u0434\u0430\u043b\u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0442\u0430 \u0438\u043c\u0430 \u043c\u0430\u0442\u0435\u0440\u0438\u0430\u043b\u0438 \u0437\u0430 \u0432\u044a\u0437\u0432\u044a\u0441\u0442\u043d\u0438. \u0418\u0437\u043f\u043e\u043b\u0437\u0432\u0430 \u0441\u0435 \u0441\u044a\u0449\u0438\u044f API \u0437\u0430 \u0430\u043d\u043e\u043d\u0438\u043c\u043d\u043e\u0441\u0442 \u043a\u0430\u0442\u043e \u043f\u0440\u0438 \u043c\u043e\u0434\u0443\u043b\u0430 \u0437\u0430 \u0421\u0438\u0433\u0443\u0440\u043d\u043e\u0441\u0442.",
|
||||
"enforce_safe_search": "\u0412\u043a\u043b\u044e\u0447\u0438 \u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e \u0422\u044a\u0440\u0441\u0435\u043d\u0435",
|
||||
"enforce_save_search_hint": "AdGuard Home \u043f\u0440\u0438\u043b\u0430\u0433\u0430 \u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e \u0422\u044a\u0440\u0441\u0435\u043d\u0435 \u0432 \u0441\u043b\u0435\u0434\u043d\u0438\u0442\u0435 \u0442\u044a\u0440\u0441\u0430\u0447\u043a\u0438 \u0438 \u0441\u0430\u0439\u0442\u043e\u0432\u0435: Google, Youtube, Bing, \u0438 Yandex.",
|
||||
"no_servers_specified": "\u041d\u044f\u043c\u0430 \u0438\u0437\u0431\u0440\u0430\u043d\u0438 \u0443\u0441\u043b\u0443\u0433\u0438",
|
||||
"no_settings": "\u041d\u044f\u043c\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438",
|
||||
"general_settings": "\u041e\u0431\u0449\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438",
|
||||
"upstream_dns": "\u0413\u043b\u0430\u0432\u0435\u043d DNS \u0441\u044a\u0440\u0432\u044a\u0440",
|
||||
"upstream_dns_hint": "\u0410\u043a\u043e \u043e\u0441\u0442\u0430\u0432\u0438\u0442\u0435 \u043f\u0440\u0430\u0437\u043d\u043e, AdGuard Home \u0449\u0435 \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430 <a href='https:\/\/1.1.1.1\/' target='_blank'>Cloudflare DNS<\/a> \u0437\u0430 \u0433\u043b\u0430\u0432\u0435\u043d. \u0418\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u0439 tls:\/\/ \u043f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043a\u0430 \u0437\u0430 DNS \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u0449\u0438 TLS \u0432\u0440\u044a\u0437\u043a\u0430.",
|
||||
"test_upstream_btn": "\u0422\u0435\u0441\u0442\u0432\u0430\u0439 \u0433\u043b\u0430\u0432\u043d\u0438\u044f DNS",
|
||||
"apply_btn": "\u041f\u0440\u0438\u043b\u043e\u0436\u0438",
|
||||
"disabled_filtering_toast": "\u0417\u0430\u0431\u0440\u0430\u043d\u0438 \u0444\u0438\u043b\u0442\u0440\u0438\u0440\u0430\u043d\u0435\u0442\u043e",
|
||||
"enabled_filtering_toast": "\u0420\u0430\u0437\u0440\u0435\u0448\u0438 \u0444\u0438\u0442\u0440\u0438\u0440\u0430\u043d\u0435\u0442\u043e",
|
||||
"disabled_safe_browsing_toast": "\u0417\u0430\u0431\u0440\u0430\u043d\u0438 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e-\u0441\u044a\u0440\u0444\u0438\u0440\u0430\u043d\u0435",
|
||||
"enabled_safe_browsing_toast": "\u0420\u0437\u0440\u0435\u0448\u0438 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e-\u0441\u044a\u0440\u0444\u0438\u0440\u0430\u043d\u0435",
|
||||
"disabled_parental_toast": "\u0417\u0430\u0431\u0440\u0430\u043d\u0438 \u0420\u043e\u0434\u0438\u0442\u0435\u043b\u0441\u043a\u0438 \u041d\u0430\u0434\u0437\u043e\u0440",
|
||||
"enabled_parental_toast": "\u0420\u0430\u0437\u0440\u0435\u0448\u0438 \u0420\u043e\u0434\u0438\u0442\u0435\u043b\u0441\u043a\u0438 \u041d\u0430\u0434\u0437\u043e\u0440",
|
||||
"disabled_safe_search_toast": "\u0417\u0430\u0431\u0440\u0430\u043d\u0438 \u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e \u0422\u044a\u0440\u0441\u0435\u043d\u0435",
|
||||
"enabled_save_search_toast": "\u0420\u0430\u0437\u0440\u0435\u0448\u0438 \u0411\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e \u0422\u044a\u0440\u0441\u0435\u043d\u0435",
|
||||
"enabled_table_header": "\u0420\u0430\u0437\u0440\u0435\u0448\u0438",
|
||||
"name_table_header": "\u0418\u043c\u0435",
|
||||
"filter_url_table_header": "URL \u0444\u0438\u043b\u0442\u044a\u0440",
|
||||
"rules_count_table_header": "\u041f\u0440\u0430\u0432\u0438\u043b\u0430 \u043e\u0431\u0449\u043e",
|
||||
"last_time_updated_table_header": "\u041f\u043e\u0441\u043b\u0435\u0434\u043d\u043e \u043e\u0431\u043d\u043e\u0432\u0435\u043d",
|
||||
"actions_table_header": "\u0414\u0435\u0439\u0441\u0442\u0432\u0438\u044f",
|
||||
"delete_table_action": "\u0418\u0437\u0442\u0440\u0438\u0439",
|
||||
"filters_and_hosts": "\u0427\u0435\u0440\u043d\u0438 \u0441\u043f\u0438\u0441\u044a\u0446\u0438 \u0441 \u043e\u0431\u0449\u0438 \u0438 \u043c\u0435\u0441\u0442\u043d\u0438 \u0444\u0438\u043b\u0442\u0440\u0438",
|
||||
"filters_and_hosts_hint": "AdGuard Home \u0440\u0430\u0437\u0431\u0438\u0440\u0430 adblock \u0438 host \u0441\u0438\u043d\u0442\u0430\u043a\u0441\u0438\u0441.",
|
||||
"no_filters_added": "\u041d\u044f\u043c\u0430 \u0434\u043e\u0431\u0430\u0432\u0435\u043d\u0438 \u0444\u0438\u043b\u0442\u0440\u0438",
|
||||
"add_filter_btn": "\u0414\u043e\u0431\u0430\u0432\u0438 \u0444\u0438\u043b\u0442\u044a\u0440",
|
||||
"cancel_btn": "\u041e\u0442\u043a\u0430\u0436\u0438",
|
||||
"enter_name_hint": "\u0412\u044a\u0432\u0435\u0434\u0438 \u0438\u043c\u0435",
|
||||
"enter_url_hint": "\u0412\u044a\u0432\u0435\u0434\u0438 URL",
|
||||
"check_updates_btn": "\u041f\u0440\u043e\u0432\u0435\u0440\u0438 \u0437\u0430 \u0430\u043a\u0442\u0443\u0430\u043b\u0438\u0437\u0430\u0446\u0438\u044f",
|
||||
"new_filter_btn": "\u0412\u044a\u0432\u0435\u0434\u0438 \u043d\u043e\u0432 \u0444\u0438\u043b\u0442\u044a\u0440",
|
||||
"enter_valid_filter_url": "\u041c\u043e\u043b\u044f \u0432\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u0432\u0430\u043b\u0438\u0434\u0435\u043d URL \u0437\u0430 \u0444\u0438\u043b\u0442\u044a\u0440\u0430 \u0438\u043b\u0438 \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u0442\u0435 host \u043f\u0440\u0430\u0432\u043e\u043f\u0438\u0441\u0430.",
|
||||
"custom_filter_rules": "\u041c\u0435\u0441\u0442\u043d\u0438 \u043f\u0440\u0430\u0432\u0438\u043b\u0430 \u0437\u0430 \u0444\u0438\u043b\u0442\u0440\u0438\u0440\u0430\u043d\u0435",
|
||||
"custom_filter_rules_hint": "\u0412\u044a\u0432\u0435\u0436\u0434\u0430\u0439\u0442\u0435 \u0432\u0441\u044f\u043a\u043e \u043f\u0440\u0430\u0432\u0438\u043b\u043e \u043d\u0430 \u043d\u043e\u0432 \u0440\u0435\u0434. \u041c\u043e\u0436\u0435 \u0434\u0430 \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u0442\u0435 adblock \u0438\u043b\u0438 hosts \u0444\u0430\u0439\u043b\u043e\u0432 \u0441\u0438\u043d\u0442\u0430\u043a\u0441\u0438\u0441.",
|
||||
"examples_title": "\u041f\u0440\u0438\u043c\u0435\u0440\u0438",
|
||||
"example_meaning_filter_block": "\u0411\u043b\u043e\u043a\u0438\u0440\u0430\u0439 \u0434\u043e\u0441\u0442\u044a\u043f \u0434\u043e \u0434\u043e\u043c\u0435\u0439\u043d example.org \u0438 \u0432\u0441\u0438\u0447\u043a\u0438 \u043f\u043e\u0434 \u0434\u043e\u043c\u0435\u0439\u043d\u0438.",
|
||||
"example_meaning_filter_whitelist": "\u0420\u0430\u0437\u0440\u0435\u0448\u0438 \u0434\u043e\u0441\u0442\u044a\u043f \u0434\u043e \u0434\u043e\u043c\u0435\u0439\u043d example.org \u0438 \u0432\u0441\u0438\u0447\u043a\u0438\u0442\u0435 \u043c\u0443 \u043f\u043e\u0434 \u0434\u043e\u043c\u0435\u0439\u043d\u0438.",
|
||||
"example_meaning_host_block": "AdGuard Home \u0449\u0435 \u043e\u0442\u0433\u043e\u0432\u043e\u0440\u0438 \u0441 127.0.0.1 = \u043f\u0440\u0430\u0437\u0435\u043d \u0430\u0434\u0440\u0435\u0441 \u0437\u0430 \u0434\u043e\u043c\u0435\u0439\u043d example.org (\u043d\u043e \u043d\u0435 \u0438 \u0437\u0430 \u043f\u043e\u0434 \u0434\u043e\u043c\u0435\u0439\u043d\u0438).",
|
||||
"example_comment": "! \u0422\u043e\u0432\u0430 \u0435 \u043a\u043e\u043c\u0435\u043d\u0442\u0430\u0440",
|
||||
"example_comment_meaning": "\u043f\u0440\u0438\u043c\u0435\u0440 \u0437\u0430 \u043a\u043e\u043c\u0435\u043d\u0442\u0430\u0440",
|
||||
"example_comment_hash": "# \u0422\u043e\u0432\u0430 \u0435 \u0441\u044a\u0449\u043e \u043a\u043e\u043c\u0435\u043d\u0442\u0430\u0440",
|
||||
"example_regex_meaning": "\u0411\u043b\u043e\u043a\u0438\u0440\u0430\u0439 \u0434\u043e\u0441\u0442\u044a\u043f \u0434\u043e \u0434\u043e\u043c\u0435\u0439\u043d\u0438 \u043a\u043e\u0439\u0442\u043e \u0441\u044a\u0432\u043f\u0430\u0434\u0430\u0442 \u0441\u044a\u0441 \u0441\u043b\u0435\u0434\u043d\u043e\u0442\u043e",
|
||||
"example_upstream_regular": "\u043a\u043b\u0430\u0441\u0438\u0447\u0435\u0441\u043a\u0438 DNS (UDP \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b)",
|
||||
"example_upstream_dot": "\u043a\u0440\u0438\u043f\u0442\u0438\u0440\u0430\u043d <0>DNS-\u0432\u044a\u0440\u0445\u0443-TLS<\/0>",
|
||||
"example_upstream_doh": "\u043a\u0440\u0438\u043f\u0442\u0438\u0440\u0430\u043d <0>DNS-\u0432\u044a\u0440\u0445\u0443-HTTPS<\/0>",
|
||||
"example_upstream_sdns": "\u043c\u043e\u0436\u0435 \u0434\u0430 \u043f\u043e\u043b\u0437\u0432\u0430\u0442\u0435 <0>DNS \u041f\u043e\u0434\u043f\u0438\u0441\u0432\u0430\u043d\u0435<\/0> \u0437\u0430 <1>DNSCrypt<\/1> \u0438\u043b\u0438 <2>DNS-\u0432\u044a\u0440\u0445\u0443-HTTPS<\/2> \u0441\u044a\u0440\u0432\u044a\u0440\u0438",
|
||||
"example_upstream_tcp": "\u043a\u043b\u0430\u0441\u0438\u0447\u0435\u0441\u043a\u0438 DNS (TCP \u043f\u0440\u043e\u0442\u043e\u043a\u043e\u043b)",
|
||||
"all_filters_up_to_date_toast": "\u0412\u0441\u0438\u0447\u043a\u0438 \u0444\u0438\u043b\u0442\u0438 \u0441\u0430 \u0430\u043a\u0442\u0443\u0430\u043b\u0438\u0437\u0438\u0440\u0430\u043d\u0438",
|
||||
"updated_upstream_dns_toast": "\u0413\u043b\u043e\u0431\u0430\u043b\u043d\u0438\u0442\u0435 DNS \u0441\u044a\u0440\u0432\u044a\u0440\u0438 \u0441\u0430 \u043e\u0431\u043d\u043e\u0432\u0435\u043d\u0438",
|
||||
"dns_test_ok_toast": "\u0412\u044a\u0432\u0435\u0434\u0435\u043d\u0438\u0442\u0435 DNS \u0441\u044a\u0440\u0432\u044a\u0440\u0438 \u0440\u0430\u0431\u043e\u0442\u044f\u0442 \u043a\u043e\u0440\u0435\u043a\u0442\u043d\u043e",
|
||||
"dns_test_not_ok_toast": "\u0421\u044a\u0440\u0432\u044a\u0440 \"{{key}}\": \u043d\u0435 \u0440\u0430\u0431\u043e\u0442\u0438, \u043c\u043e\u043b\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u0442\u0435 \u0434\u0430\u043b\u0438 \u0435 \u0432\u044a\u0432\u0435\u0434\u0435\u043d \u043a\u043e\u0440\u0435\u043a\u0442\u043d\u043e",
|
||||
"unblock_btn": "\u041e\u0442\u0431\u043b\u043e\u043a\u0438\u0440\u0430\u0439",
|
||||
"block_btn": "\u0411\u043b\u043e\u043a\u0438\u0440\u0430\u0439",
|
||||
"time_table_header": "\u0412\u0440\u0435\u043c\u0435",
|
||||
"domain_name_table_header": "\u0418\u043c\u0435 \u043d\u0430 \u0434\u043e\u043c\u0435\u0439\u043d",
|
||||
"type_table_header": "\u0422\u0438\u043f",
|
||||
"response_table_header": "\u041e\u0442\u0433\u043e\u0432\u043e\u0440",
|
||||
"client_table_header": "\u041a\u043b\u0438\u0435\u043d\u0442",
|
||||
"empty_response_status": "\u041f\u0440\u0430\u0437\u0435\u043d",
|
||||
"show_all_filter_type": "\u041f\u043e\u043a\u0430\u0436\u0438 \u0432\u0441\u0438\u0447\u043a\u0438",
|
||||
"show_filtered_type": "\u041f\u043e\u043a\u0430\u0436\u0438 \u0444\u0438\u043b\u0442\u0440\u0438\u0440\u0430\u043d\u0438",
|
||||
"no_logs_found": "\u041d\u044f\u043c\u0430 \u0438\u0441\u0442\u043e\u0440\u0438\u044f",
|
||||
"disabled_log_btn": "\u0417\u0430\u0431\u0440\u0430\u043d\u0438 \u0438\u0441\u0442\u043e\u0440\u0438\u044f\u0442\u0430",
|
||||
"download_log_file_btn": "\u0421\u043c\u044a\u043a\u043d\u0438 \u0438\u0441\u0442\u043e\u0440\u0438\u044f\u0442\u0430",
|
||||
"refresh_btn": "\u041e\u0431\u043d\u043e\u0432\u0438",
|
||||
"enabled_log_btn": "\u0420\u0430\u0437\u0440\u0435\u0448\u0438 \u0438\u0441\u0442\u043e\u0440\u0438\u044f\u0442\u0430",
|
||||
"last_dns_queries": "\u041f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0442\u0435 5000 DNS \u0437\u0430\u044f\u0432\u043a\u0438",
|
||||
"previous_btn": "\u041f\u0440\u0435\u0434\u0445\u043e\u0434\u0435\u043d",
|
||||
"next_btn": "\u0421\u043b\u0435\u0434\u0432\u0430\u0449",
|
||||
"loading_table_status": "\u0417\u0430\u0440\u0435\u0436\u0434\u0430\u043d\u0435...",
|
||||
"page_table_footer_text": "\u0421\u0442\u0440\u0430\u043d\u0438\u0446\u0430",
|
||||
"of_table_footer_text": "\u043e\u0442",
|
||||
"rows_table_footer_text": "\u0440\u0435\u0434\u043e\u0432\u0435",
|
||||
"updated_custom_filtering_toast": "\u041e\u0431\u043d\u043e\u0432\u0435\u043d\u0438 \u043c\u0435\u0441\u0442\u043d\u0438 \u043f\u0440\u0430\u0432\u0438\u043b\u0430 \u0437\u0430 \u0444\u0438\u043b\u0442\u0440\u0438\u0440\u0430\u043d\u0435",
|
||||
"rule_removed_from_custom_filtering_toast": "\u041f\u0440\u0435\u043c\u0430\u0445\u043d\u0430\u0442\u043e \u043e\u0442 \u043c\u0435\u0441\u0442\u043d\u0438 \u043f\u0440\u0430\u0432\u0438\u043b\u0430 \u0437\u0430 \u0444\u0438\u043b\u0442\u0440\u0438\u0440\u0430\u043d\u0435",
|
||||
"rule_added_to_custom_filtering_toast": "\u0414\u043e\u0431\u0430\u0432\u0435\u043d\u043e \u0434\u043e \u043c\u0435\u0441\u0442\u043d\u0438 \u043f\u0440\u0430\u0432\u0438\u043b\u0430 \u0437\u0430 \u0444\u0438\u043b\u0442\u0440\u0438\u0440\u0430\u043d\u0435",
|
||||
"query_log_disabled_toast": "\u0418\u0441\u0442\u043e\u0440\u0438\u044f\u0442\u0430 \u0435 \u0437\u0430\u0431\u0440\u0430\u043d\u0435\u043d\u0430",
|
||||
"query_log_enabled_toast": "\u0418\u0441\u0442\u043e\u0440\u0438\u044f\u0442\u0430 \u0435 \u0440\u0430\u0437\u0440\u0435\u0448\u0435\u043d\u0430",
|
||||
"source_label": "\u0418\u0437\u0442\u043e\u0447\u043d\u0438\u043a",
|
||||
"found_in_known_domain_db": "\u041d\u0430\u043c\u0435\u0440\u0435\u043d \u0432 \u0441\u043f\u0438\u0441\u044a\u0446\u0438\u0442\u0435 \u0441 \u0434\u043e\u043c\u0435\u0439\u043d\u0438.",
|
||||
"category_label": "\u041a\u0430\u0442\u0435\u0433\u043e\u0440\u0438\u044f",
|
||||
"rule_label": "\u041f\u0440\u0430\u0432\u0438\u043b\u043e",
|
||||
"filter_label": "\u0424\u0438\u043b\u0442\u044a\u0440",
|
||||
"unknown_filter": "\u041d\u0435\u043f\u043e\u0437\u043d\u0430\u0442 \u0444\u0438\u043b\u0442\u044a\u0440 {{filterId}}",
|
||||
"install_welcome_title": "\u0414\u043e\u0431\u0440\u0435 \u0434\u043e\u0448\u043b\u0438 \u0432 AdGuard Home!",
|
||||
"install_welcome_desc": "AdGuard Home e \u043c\u0440\u0435\u0436\u043e\u0432\u043e \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u0437\u0430 \u0431\u043b\u043e\u043a\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u0440\u0435\u043a\u043b\u0430\u043c\u0438 \u0438 \u0442\u0440\u0430\u043a\u0435\u0440\u0438 \u043d\u0430 DNS \u043d\u0438\u0432\u043e. \u0421\u044a\u0437\u0434\u0430\u0434\u0435\u043d\u043e \u0435 \u0437\u0430 \u0434\u0430 \u0432\u0438 \u0434\u0430\u0434\u0435 \u043f\u044a\u043b\u0435\u043d \u043a\u043e\u043d\u0442\u0440\u043e\u043b \u043d\u0430\u0434 \u043c\u0440\u0435\u0436\u0430\u0442\u0430 \u0438 \u0432\u0441\u0438\u0447\u043a\u0438\u0442\u0435 \u0432\u0438 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430, \u0431\u0435\u0437 \u0434\u0430 \u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0434\u043e\u043f\u044a\u043b\u043d\u0438\u0442\u0435\u043b\u043d\u043e \u0438\u043d\u0441\u0442\u0430\u043b\u0438\u0440\u0430\u043d\u0435 \u043d\u0430 \u0434\u0440\u0443\u0433 \u0441\u043e\u0444\u0442\u0443\u0435\u0440.",
|
||||
"install_settings_title": "\u0410\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f",
|
||||
"install_settings_listen": "\u0410\u043a\u0442\u0438\u0432\u043d\u0438 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0438",
|
||||
"install_settings_port": "\u041f\u043e\u0440\u0442",
|
||||
"install_settings_interface_link": "\u0412\u0430\u0448\u0430\u0442\u0430 AdGuard Home \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430 \u0437\u0430 \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u0449\u0435 \u0435 \u0434\u043e\u0441\u0442\u044a\u043f\u043d\u0430 \u043d\u0430 \u0442\u043e\u0437\u0438 \u0430\u0434\u0440\u0435\u0441:",
|
||||
"form_error_port": "\u041c\u043e\u043b\u044f \u0432\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u0432\u0430\u043b\u0438\u0434\u0435\u043d \u043f\u043e\u0440\u0442",
|
||||
"install_settings_dns": "DNS \u0441\u044a\u0440\u0432\u044a\u0440",
|
||||
"install_settings_dns_desc": "\u0417\u0430 \u0434\u0430 \u0440\u0430\u0431\u043e\u0442\u0438, \u0449\u0435 \u0442\u0440\u044f\u0431\u0432\u0430 \u0434\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u0435 \u0432\u0430\u0448\u0438\u044f\u0442 \u0440\u0443\u0442\u0435\u0440 \u0438\u043b\u0438 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u0434\u0430 \u043f\u043e\u043b\u0437\u0432\u0430\u0442 DNS \u0441\u044a\u0440\u0432\u044a\u0440 \u0441 \u0430\u0434\u0440\u0435\u0441:",
|
||||
"install_settings_all_interfaces": "\u0412\u0441\u0438\u0447\u043a\u0438 \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441\u0438",
|
||||
"install_auth_title": "\u0423\u0434\u043e\u0441\u0442\u043e\u0432\u0435\u0440\u044f\u0432\u0430\u043d\u0435",
|
||||
"install_auth_desc": "\u041c\u043d\u043e\u0433\u043e \u0435 \u0432\u0430\u0436\u043d\u043e \u0434\u0430 \u0437\u0430\u0434\u0430\u0434\u0435\u0442\u0435 \u0438\u043c\u0435 \u0438 \u043f\u0430\u0440\u043e\u043b\u0430 \u0437\u0430 \u0434\u043e\u0441\u0442\u044a\u043f \u0434\u043e \u0432\u0430\u0448\u0438\u044f \u043f\u0430\u043d\u0435\u043b \u0437\u0430 \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u043d\u0430 AdGuard Home. \u041f\u0440\u0435\u043f\u043e\u0440\u044a\u0447\u0432\u0430\u043c\u0435 \u0432\u0438 \u0434\u0430 \u0437\u0430\u0434\u0430\u0434\u0435\u0442\u0435 \u0438\u043c\u0435 \u0438 \u043f\u0430\u0440\u043e\u043b\u0430 \u043d\u0435\u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e \u0447\u0435 \u0433\u043e \u043f\u043e\u043b\u0437\u0432\u0430\u0442\u0435 \u0441\u0430\u043c\u043e \u0432 \u043a\u044a\u0449\u0438.",
|
||||
"install_auth_username": "\u041f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b",
|
||||
"install_auth_password": "\u041f\u0430\u0440\u043e\u043b\u0430",
|
||||
"install_auth_confirm": "\u041f\u043e\u0442\u0432\u044a\u0440\u0434\u0435\u0442\u0435 \u043f\u0430\u0440\u043e\u043b\u0430\u0442\u0430",
|
||||
"install_auth_username_enter": "\u0412\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u043f\u043e\u0442\u0440\u0435\u0431\u0438\u0442\u0435\u043b",
|
||||
"install_auth_password_enter": "\u0412\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u043f\u0430\u0440\u043e\u043b\u0430",
|
||||
"install_step": "\u0421\u0442\u044a\u043f\u043a\u0430",
|
||||
"install_devices_title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u0442\u0435 \u0432\u0430\u0448\u0435\u0442\u043e \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u043e",
|
||||
"install_devices_desc": "\u0414\u0430 \u0437\u0430\u043f\u043e\u0447\u043d\u0435\u0442\u0435 \u0434\u0430 \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u0442\u0435 AdGuard Home, \u0435 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e \u0434\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u0435 \u0432\u0430\u0448\u0438\u0442\u0435 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430.",
|
||||
"install_submit_title": "\u041f\u043e\u0437\u0434\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f!",
|
||||
"install_submit_desc": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430\u0442\u0430 \u0435 \u0437\u0430\u0432\u044a\u0440\u0448\u0435\u043d\u0430, \u043c\u043e\u0436\u0435 \u0434\u0430 \u0437\u0430\u043f\u043e\u0447\u043d\u0435\u0442\u0435 \u0434\u0430 \u043f\u043e\u043b\u0437\u0432\u0430\u0442\u0435 AdGuard Home.",
|
||||
"install_devices_router": "\u0420\u0443\u0442\u0435\u0440",
|
||||
"install_devices_router_desc": "\u0410\u043a\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u0435 \u0432\u0430\u0448\u0438\u044f\u0442 \u0440\u0443\u0442\u0435\u0440 \u043d\u044f\u043c\u0430 \u043d\u0443\u0436\u0434\u0430 \u0440\u044a\u0447\u043d\u043e \u0434\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u0432\u0430\u0442\u0435 \u0432\u0441\u044f\u043a\u043e \u0435\u0434\u043d\u043e \u043e\u0442 \u0443\u0441\u0442\u0440\u0439\u0441\u0442\u0432\u0430\u0442\u0430 \u0432 \u043c\u0440\u0435\u0436\u0430\u0442\u0430.",
|
||||
"install_devices_address": "AdGuard Home DNS \u0441\u044a\u0440\u0432\u044a\u0440\u044a\u0442 \u0435 \u043d\u0430 \u0441\u043b\u0435\u0434\u043d\u0438\u044f \u0430\u0434\u0440\u0435\u0441",
|
||||
"install_devices_router_list_1": "\u041e\u0442\u0432\u043e\u0440\u0435\u0442\u0435 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0442\u0430 \u0437\u0430 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043d\u0430 \u0432\u0430\u0448\u0438\u044f \u0440\u0443\u0442\u0435\u0440. \u041e\u0431\u0438\u043a\u043d\u043e\u0432\u0435\u043d\u043e \u0442\u044f \u0441\u0435 \u043d\u0430\u043c\u0438\u0440\u0430 \u043d\u0430 URL (\u0442\u0443\u043a http:\/\/192.168.0.1\/ \u0438\u043b\u0438 \u0442\u0443\u043a http:\/\/192.168.1.1\/). \u0417\u0430 \u0434\u043e\u0441\u0442\u044a\u043f \u043c\u043e\u0436\u0435 \u0434\u0430 \u0432\u0438 \u0442\u0440\u044f\u0431\u0432\u0430 \u043f\u0430\u0440\u043e\u043b\u0430. \u0410\u043a\u043e \u0441\u0442\u0435 \u0437\u0430\u0431\u0440\u0430\u0432\u0438\u043b\u0438 \u043f\u0430\u0440\u043e\u043b\u0430\u0442\u0430 \u043c\u043e\u0436\u0435 \u0434\u0430 \u044f \u0440\u0435\u0441\u0435\u0442\u043d\u0435\u0442\u0435 \u043a\u0430\u0442\u043e \u043d\u0430\u0442\u0438\u0441\u043d\u0435\u0442\u0430 \u0441\u043a\u0440\u0438\u0442\u0438\u044f \u0440\u0435\u0441\u0435\u0442 \u0431\u0443\u0442\u043e\u043d - \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u0442\u043e\u0432\u0430 \u0449\u0435 \u0440\u0435\u0441\u0435\u0442\u043d\u0435 \u0432\u0441\u0438\u0447\u043a\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043d\u0430 \u0440\u0443\u0442\u0435\u0440\u0430 \u0434\u043e \u0444\u0430\u0431\u0440\u0438\u0447\u043d\u0438! \u041d\u044f\u043a\u043e\u0439 \u0440\u0443\u0442\u0435\u0440\u0438 \u043c\u043e\u0433\u0430\u0442 \u0434\u0430 \u0431\u044a\u0434\u0430\u0442\u0435 \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0438\u0440\u0430\u043d\u0438 \u043e\u0442 \u0441\u043e\u0444\u0442\u0443\u0435\u0440 \u0438\u043b\u0438 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0435, \u043a\u043e\u0439\u0442\u043e \u0431\u0438 \u0442\u0440\u044f\u0431\u0432\u0430\u043b\u043e \u0434\u0430 \u0435 \u0432\u0435\u0447\u0435 \u0438\u043d\u0441\u0442\u0430\u043b\u0438\u0440\u0430\u043d \u043d\u0430 \u043a\u043e\u043c\u043f\u044e\u0442\u044a\u0440\u0430\/\u0442\u0435\u043b\u0435\u0444\u043e\u043d\u0430 \u0432\u0438.",
|
||||
"install_devices_router_list_2": "\u041d\u0430\u043c\u0435\u0440\u0435\u0442\u0430 DHCP\/DNS \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438. \u0412 \u043f\u043e\u0434 \u0440\u0430\u0437\u0434\u0435\u043b DHCP \u0440\u0437\u0433\u043b\u0435\u0434\u0430\u0439\u0442\u0435 \u0438 \u043d\u0430\u043c\u0435\u0440\u0435\u0442\u0435 \u043a\u044a\u0434\u0435 \u0435 \u043f\u043e\u043b\u0435\u0442\u043e \u0437\u0430 DNS \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u0432 \u043a\u043e\u0435\u0442\u043e \u043c\u043e\u0436\u0435 \u0434\u0430 \u0432\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u043f\u0435\u0440\u0441\u043e\u043d\u0430\u043b\u0438\u0437\u0438\u0440\u0430\u043d\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0437\u0430 DNS \u0441\u044a\u0440\u0432\u044a\u0440\u0438.",
|
||||
"install_devices_router_list_3": "\u0412\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u0430\u0434\u0440\u0435\u0441\u044a\u0442 \u043d\u0430 AdGuard Home \u0441\u044a\u0440\u0432\u044a\u0440\u0430.",
|
||||
"install_devices_windows_list_1": "\u041e\u0442\u0432\u043e\u0440\u0435\u0442\u0435 \u041a\u043e\u043d\u0442\u0440\u043e\u043b\u043d\u0438\u044f \u041f\u0430\u043d\u0435\u043b \u043f\u0440\u0435\u0437 \u0421\u0442\u0430\u0440\u0442 \u043c\u0435\u043d\u044e \u0438\u043b\u0438 \u0447\u0440\u0435\u0437 \u0444\u0443\u043d\u043a\u0446\u0438\u044f \u0442\u044a\u0440\u0441\u0435\u043d\u0435 \u043d\u0430 Windows.",
|
||||
"install_devices_windows_list_2": "\u0412\u044a\u0440\u0432\u0435\u0442\u0435 \u0434\u043e \u041d\u0430\u0441\u0442\u0440\u0439\u043a\u0438 \u043d\u0430 \u041c\u0440\u0435\u0436\u0438 \u0438 \u0418\u043d\u0442\u0435\u0440\u043d\u0435\u0442 \u0438 \u043e\u0442 \u0442\u0430\u043c \u0438\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u041c\u0440\u0435\u0436\u0438 \u0438 \u0426\u0435\u043d\u0442\u044a\u0440 \u0437\u0430 \u0421\u043f\u043e\u0434\u0435\u043b\u044f\u043d\u0435.",
|
||||
"install_devices_windows_list_3": "\u041e\u0442 \u043b\u044f\u0432\u043e \u043d\u0430 \u0435\u043a\u0440\u0430\u043d\u0430 \u043d\u0430\u043c\u0435\u0440\u0435\u0442\u0435 \u0421\u043c\u0435\u043d\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u043a\u0438 \u043d\u0430 \u043c\u0440\u0435\u0436\u043e\u0432\u0438\u044f \u0430\u0434\u0430\u043f\u0442\u0435\u0440 \u0438 \u043a\u043b\u0438\u043a\u043d\u0435\u0442\u0435 \u043d\u0430 \u043d\u0435\u0433\u043e.",
|
||||
"install_devices_windows_list_4": "\u0418\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u0442\u043e\u0437\u0438 \u043a\u043e\u0439\u0442\u043e \u0435 \u0430\u043a\u0442\u0438\u0432\u0435\u043d, \u0434\u044f\u0441\u043d\u043e-\u043a\u043b\u0438\u043a\u0432\u0430\u043d\u0435 \u0438 \u0438\u0437\u0431\u0435\u0440\u0435\u0442\u0430 \u0421\u0432\u043e\u0439\u0441\u0442\u0432\u0430.",
|
||||
"install_devices_windows_list_5": "\u041d\u0430\u043c\u0435\u0440\u0435\u0442\u0435 \u0418\u043d\u0442\u0435\u0440\u043d\u0435\u0442 \u041f\u0440\u043e\u0442\u043e\u043a\u043e\u043b \u0412\u0435\u0440\u0441\u0438\u044f 4 (TCP\/IP) \u0432 \u0441\u043f\u0438\u0441\u044a\u043a\u0430, \u0438\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u0438 \u043a\u043b\u0438\u043a\u043d\u0435\u0442\u0435 \u043e\u0442\u043d\u043e\u0432\u043e \u043d\u0430 \u0421\u0432\u043e\u0439\u0441\u0442\u0432\u0430.",
|
||||
"install_devices_windows_list_6": "\u0418\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u0418\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u0439 \u0441\u043b\u0435\u0434\u043d\u0438\u0442\u0435 \u0430\u0434\u0440\u0435\u0441\u0438 \u0437\u0430 DNS \u0441\u044a\u0440\u0441\u044a\u0440\u0438 \u0438 \u0432\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u0430\u0434\u0440\u0435\u0441\u0430 \u043d\u0430 AdGuard Home \u0441\u044a\u0440\u0432\u044a\u0440\u0430 \u0432\u0438.",
|
||||
"install_devices_macos_list_1": "\u0426\u044a\u043a\u043d\u0435\u0442\u0435 \u043d\u0430 Apple \u0438\u043a\u043e\u043d\u043a\u0430\u0442\u0430 \u0438 \u0438\u0437\u0431\u0435\u0440\u0435\u0442\u0435 System Preferences...",
|
||||
"install_devices_macos_list_2": "\u0426\u044a\u043a\u043d\u0435\u0442\u0435 \u043d\u0430 Network.",
|
||||
"install_devices_macos_list_3": "\u0418\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u0437\u0435\u043b\u0435\u043d\u0430\u0442\u0430-\u0430\u043a\u0442\u0438\u0432\u043d\u0430 \u0432\u0440\u044a\u0437\u043a\u0430 \u0432 \u0441\u043f\u0438\u0441\u044a\u043a\u0430 \u0438 \u043a\u043b\u0438\u043a\u043d\u0435\u0442\u0435 \u043d\u0430 Advanced.",
|
||||
"install_devices_macos_list_4": "\u0418\u0437\u0431\u0435\u0440\u0435\u0442\u0435 DNS \u0442\u0430\u0431 \u0438 \u043a\u043b\u0438\u043a\u043d\u0435\u0442\u0435 \u043d\u0430 + \u0437\u0430 \u0434\u0430 \u0432\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u0430\u0434\u0440\u0435\u0441\u0430 \u043d\u0430 AdGuard Home \u0441\u044a\u0440\u0432\u044a\u0440\u0430.",
|
||||
"install_devices_android_list_1": "\u0418\u0437\u0431\u0435\u0440\u0435\u0442\u0435 Android \u041c\u0435\u043d\u044e \u043e\u0442 \u0434\u043e\u043c\u0430\u0448\u043d\u0438\u044f \u0435\u043a\u0440\u0430\u043d, \u0438 \u0446\u044a\u043a\u043d\u0435\u0442\u0435 \u043d\u0430 \u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438.",
|
||||
"install_devices_android_list_2": "\u0426\u044a\u043a\u043d\u0435\u0442\u0435 \u043d\u0430 Wi-Fi \u043c\u0435\u043d\u044e. \u041d\u0430 \u0435\u043a\u0440\u0430\u043d\u0430 \u0449\u0435 \u0441\u0435 \u043f\u043e\u044f\u0432\u0430\u0442 \u0432\u0441\u0438\u0447\u043a\u0438 \u0431\u0435\u0437\u0436\u0438\u0447\u043d\u0438 \u043f\u0440\u0435\u0436\u0438 (\u0442\u0430\u043c \u043d\u044f\u043c\u0430 \u0432\u044a\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442 \u0437\u0430 \u0432\u044a\u0432\u0435\u0436\u0434\u0430\u043d\u0435 \u043d\u0430 DNS \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438).",
|
||||
"install_devices_android_list_3": "\u0426\u044a\u043a\u043d\u0435\u0442\u0435 \u0438 \u0437\u0430\u0434\u0440\u044a\u0436\u0434\u0435 \u0432\u044a\u0440\u0445\u0443 \u0412\u0438\u0435 \u0441\u0442\u0435 \u0441\u0432\u044a\u0440\u0437\u0430\u043d\u0438 \u0441.., \u0438 \u043a\u043b\u0438\u043a\u043d\u0435\u0442\u0435 \u043d\u0430 \u041c\u043e\u0434\u0438\u0444\u0438\u0446\u0438\u0440\u0430\u0439 \u043c\u0440\u0435\u0436\u0430.",
|
||||
"install_devices_android_list_4": "\u041d\u0430 \u043d\u044f\u043a\u043e\u0439 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 \u043c\u043e\u0436\u0435 \u0434\u0430 \u0435 \u043d\u0435\u043e\u0445\u043e\u0434\u0438\u043c\u043e \u0434\u0430 \u043c\u0430\u0440\u043a\u0438\u0440\u0430\u0442\u0435 \u043f\u043e\u043a\u0430\u0436\u0438 \u0420\u0430\u0437\u0448\u0438\u0440\u0435\u043d\u0438, \u0437\u0430 \u0434\u0430 \u0432\u0438\u0434\u0438\u0442\u0435 \u0432\u0441\u0438\u0447\u043a\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438. \u0417\u0430 \u0434\u0430 \u043f\u0440\u043e\u043c\u0435\u043d\u0438\u0442\u0435 Android DNS \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u0442\u0435, \u043c\u043e\u0436\u0435 \u0434\u0430 \u0441\u0435 \u043d\u0430\u043b\u043e\u0436\u0438 \u0434\u0430 \u043f\u0440\u043e\u043c\u0435\u043d\u0438\u0442\u0435 IP \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u0442\u0435 \u043e\u0442 DHCP \u043d\u0430 \u0421\u0442\u0430\u0442\u0438\u0447\u043d\u0438.",
|
||||
"install_devices_android_list_5": "\u041f\u0440\u043e\u043c\u0435\u043d\u0435\u0442\u0435 \u0441\u0442\u043e\u0439\u043d\u043e\u0441\u0442\u0438\u0442\u0435 \u043d\u0430 DNS 1 \u0438 DNS 2 \u0434\u0430 \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u0442 AdGuard Home \u0441\u044a\u0440\u0432\u044a\u0440\u0430.",
|
||||
"install_devices_ios_list_1": "\u041e\u0442 \u043d\u0430\u0447\u0430\u043b\u0435\u043d \u0435\u043a\u0440\u0430\u043d, \u0446\u044a\u043a\u043d\u0435\u0442\u0435 \u043d\u0430 Settings.",
|
||||
"install_devices_ios_list_2": "\u0418\u0437\u0431\u0435\u0440\u0435\u0442\u0435 Wi-Fi \u043e\u0442 \u043b\u044f\u0432\u043e\u0442\u043e \u043c\u0435\u043d\u044e (\u0442\u0430\u043c \u043d\u044f\u043c\u0430 \u0432\u044a\u0437\u043c\u043e\u0436\u043d\u043e\u0441\u0442 \u0437\u0430 \u0432\u044a\u0432\u0435\u0436\u0434\u0430\u043d\u0435 \u043d\u0430 DNS \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438).",
|
||||
"install_devices_ios_list_3": "\u041a\u043b\u0438\u043d\u0435\u0442\u0435 \u043d\u0430 \u0438\u043c\u0435\u0442\u043e \u043d\u0430 \u0430\u043a\u0442\u0438\u0432\u043d\u0430\u0442\u0430 \u043c\u0440\u0435\u0436\u0430 \u043a\u044a\u043c \u043a\u043e\u044f\u0442\u043e \u0441\u0442\u0435 \u0441\u0432\u044a\u0440\u0437\u0430\u043d\u0438.",
|
||||
"install_devices_ios_list_4": "\u0412 \u043f\u043e\u043b\u0435\u0442\u043e \u0437\u0430 DNS \u0438\u0437\u0431\u0435\u0440\u0435\u0442\u0435 \u0440\u044a\u0447\u043d\u043e \u0438 \u0432\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u0430\u0434\u0440\u0435\u0441\u0430 \u043d\u0430 AdGuard Home \u0441\u044a\u0440\u0432\u044a\u0440\u0430.",
|
||||
"get_started": "\u0414\u0430 \u0437\u0430\u043f\u043e\u0447\u0432\u0430\u043c\u0435",
|
||||
"next": "\u0421\u043b\u0435\u0434\u0432\u0430\u0449",
|
||||
"open_dashboard": "\u041e\u0442\u0432\u043e\u0440\u0438 \u0442\u0430\u0431\u043b\u043e",
|
||||
"install_saved": "\u0423\u0441\u043f\u0435\u0448\u043d\u043e \u0437\u0430\u043f\u0438\u0441\u0430\u043d\u043e",
|
||||
"encryption_title": "\u041a\u0440\u0438\u043f\u0442\u0438\u0440\u0430\u043d\u0435",
|
||||
"encryption_desc": "\u041f\u043e\u0434\u044a\u0440\u0436\u0430 \u0441\u0435 \u0441\u0438\u0433\u0443\u0440\u043d\u0430 \u0432\u0440\u044a\u0437\u043a\u0430 (HTTPS\/TLS) \u0432\u043a\u043b\u044e\u0447\u0438\u0442\u0435\u043b\u043d\u043e \u0437\u0430 DNS \u0438 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0442\u0430 \u0437\u0430 \u0430\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f",
|
||||
"encryption_config_saved": "\u041a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f\u0442\u0430 \u0435 \u0443\u0441\u043f\u0435\u0448\u043d\u043e \u0437\u0430\u043f\u0438\u0441\u0430\u043d\u0430",
|
||||
"encryption_server": "\u0418\u043c\u0435 \u043d\u0430 \u0441\u044a\u0440\u0432\u044a\u0440\u0430",
|
||||
"encryption_server_enter": "\u0412\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u0438\u043c\u0435 \u043d\u0430 \u0434\u043e\u043c\u0435\u0439\u043d\u0430",
|
||||
"encryption_server_desc": "\u0417\u0430 \u0434\u0430 \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u0442\u0435 HTTPS, \u0442\u0440\u044f\u0431\u0432\u0430 \u0438\u043c\u0435\u0442\u043e \u043d\u0430 \u0441\u044a\u0440\u0432\u044a\u0440\u0430 \u0434\u0430 \u0441\u044a\u0432\u043f\u0430\u0434\u0430 \u0441 \u0442\u043e\u0432\u0430 \u043d\u0430 SSL \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u0430.",
|
||||
"encryption_redirect": "\u0410\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u043e \u043f\u0440\u0435\u043d\u0430\u0441\u043e\u0447\u0432\u0430\u043d\u0435 \u043a\u044a\u043c HTTPS",
|
||||
"encryption_redirect_desc": "\u0421\u043b\u0443\u0436\u0438 \u0437\u0430 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u043d\u043e \u043f\u0440\u0435\u043d\u0430\u0441\u043e\u0447\u0432\u0430\u043d\u0435 \u043e\u0442 HTTP \u043a\u044a\u043c HTTPS \u043d\u0430 \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0442\u0430 \u0437\u0430 \u0410\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u0432 AdGuard Home.",
|
||||
"encryption_https": "HTTPS \u043f\u043e\u0440\u0442",
|
||||
"encryption_https_desc": "\u0410\u043a\u043e \u0437\u0430\u0434\u0430\u0434\u0435\u0442\u0435 HTTPS \u043f\u043e\u0440\u0442, \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0442\u0430 \u0437\u0430 \u0410\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u043d\u0430 AdGuard Home \u0449\u0435 \u0431\u044a\u0434\u0435 \u0434\u043e\u0441\u0442\u044a\u043f\u043d\u0430 \u043d\u0430 HTTPS, \u0438 \u0441\u044a\u0449\u043e \u0449\u0435 \u043e\u0442\u0433\u043e\u0432\u0430\u0440\u044f \u043d\u0430 DNS-\u0432\u044a\u0440\u0445\u0443-HTTPS '\/dns-\u0437\u0430\u043f\u0438\u0442\u0432\u0430\u043d\u0438\u044f'.",
|
||||
"encryption_dot": "DNS-\u0432\u044a\u0440\u0445\u0443-TLS \u043f\u043e\u0440\u0442",
|
||||
"encryption_dot_desc": "\u0410\u043a\u043e \u043f\u043e\u0440\u0442\u0430 \u0435 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0438\u0440\u0430\u043d, AdGuard Home \u0449\u0435 \u0441\u0442\u0430\u0440\u0442\u0438\u0440\u0430 \u0438 \u0441\u044a\u0440\u0432\u044a\u0440 \u0437\u0430 DNS-\u0432\u044a\u0440\u0445\u0443-TLS.",
|
||||
"encryption_certificates": "\u0421\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u0438",
|
||||
"encryption_certificates_desc": "\u0417\u0430 \u0434\u0430 \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u0442\u0435 \u0441\u0438\u0433\u0443\u0440\u043d\u0430 \u0432\u0440\u044a\u0437\u043a\u0430, \u0449\u0435 \u0442\u0440\u044f\u0431\u0432\u0430 \u0434\u0430 \u043e\u0441\u0438\u0433\u0443\u0440\u0438\u0442\u0435 SSL \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u0438 \u0437\u0430 \u0432\u0430\u0448\u0438\u044f \u0434\u043e\u043c\u0435\u0439\u043d. \u041c\u043e\u0436\u0435 \u0434\u0430 \u0437\u0430\u044f\u0432\u0438\u0442\u0435 \u0431\u0435\u0437\u043f\u043b\u0430\u0442\u0435\u043d \u043e\u0442 <0>{{link}}<\/0> \u0438\u043b\u0438 \u0434\u0430 \u0437\u0430\u043a\u0443\u043f\u0438\u0442\u0435 \u043e\u0442 Certificate Authorities.",
|
||||
"encryption_certificates_input": "\u041a\u043e\u043f\u0438\u0440\u0430\u0439\/\u043f\u043e\u0441\u0442\u0430\u0432\u0438 \u0432\u0430\u0448\u0438\u044f PEM-\u043a\u043e\u0434\u0438\u0440\u0430\u043d \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 \u0442\u0443\u043a.",
|
||||
"encryption_status": "\u0421\u044a\u0441\u0442\u043e\u044f\u043d\u0438\u0435",
|
||||
"encryption_expire": "\u0413\u043e\u0434\u0435\u043d \u0434\u043e",
|
||||
"encryption_key": "\u0427\u0430\u0441\u0442\u0435\u043d \u043a\u043b\u044e\u0447",
|
||||
"encryption_key_input": "\u041a\u043e\u043f\u0438\u0440\u0430\u0439\/\u043f\u043e\u0441\u0442\u0430\u0432\u0438 \u0432\u0430\u0448\u0438\u044f PEM-\u043a\u043e\u0434\u0438\u0440\u0430\u043d \u0447p\u0430\u0441\u0442\u0435\u043d \u043a\u043b\u044e\u0447 \u0437\u0430 \u0432\u0430\u0448\u0438\u044f \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 \u0442\u0443\u043a.",
|
||||
"encryption_enable": "\u0420\u0430\u0437p\u0435\u0448\u0438 \u043a\u0440\u0438\u043f\u0442\u0438\u0440\u0430\u043d\u0435 (HTTPS, DNS-\u0432\u044a\u0440\u0445\u0443-HTTPS, \u0438 DNS-\u0432\u044a\u0440\u0445\u0443-TLS)",
|
||||
"encryption_enable_desc": "\u0410\u043a\u043e \u0441\u0442\u0435 \u0440\u0430\u0437\u0440\u0435\u0448\u0438\u043b\u0438 \u043a\u0440\u0438\u043f\u0442\u0438\u0440\u0430\u043d\u0435, \u0441\u0442\u0440\u0430\u043d\u0438\u0446\u0430\u0442\u0430 \u0437\u0430 \u0410\u0434\u043c\u0438\u043d\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u043d\u0430 AdGuard Home \u0449\u0435 \u0431\u044a\u0434\u0435 \u0434\u043e\u0441\u0442\u044a\u043f\u043d\u0430 \u043f\u0440\u0435\u0437 HTTPS, \u0438 DNS \u0441\u044a\u0440\u0432\u044a\u0440\u0430 \u0449\u0435 \u043e\u0442\u0433\u043e\u0432\u0430\u0440\u044f \u0441\u044a\u0449\u043e \u043d\u0430 \u0437\u0430\u043f\u0438\u0442\u0432\u0430\u043d\u0438\u044f DNS-\u0432\u044a\u0440\u0445\u0443-HTTPS \u0438 DNS-\u0432\u044a\u0440\u0445\u0443-TLS.",
|
||||
"encryption_chain_valid": "\u0419\u0435\u0440\u0430\u0440\u0445\u0438\u044f\u0442\u0430 \u043e\u0442 \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u0438 \u0435 \u0432\u0430\u043b\u0438\u0434\u043d\u0430",
|
||||
"encryption_chain_invalid": "\u0419\u0435\u0440\u0430\u0440\u0445\u0438\u044f\u0442\u0430 \u043e\u0442 \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u0438 \u0435 \u043d\u0435\u0432\u0430\u043b\u0438\u0434\u043d\u0430",
|
||||
"encryption_key_valid": "\u0422\u043e\u0432\u0430 \u0435 \u0432\u0430\u043b\u0438\u0434\u0435\u043d {{type}} \u0447\u0430\u0441\u0442\u0435\u043d \u043a\u043b\u044e\u0447",
|
||||
"encryption_key_invalid": "\u0422\u043e\u0432\u0430 \u0435 \u043d\u0435\u0432\u0430\u043b\u0438\u0434\u0435\u043d {{type}} \u0447\u0430\u0441\u0442\u0435\u043d \u043a\u043b\u044e\u0447",
|
||||
"encryption_subject": "\u0422\u0435\u043c\u0430",
|
||||
"encryption_issuer": "\u0418\u0437\u043f\u044a\u043b\u043d\u0438\u0442\u0435\u043b",
|
||||
"encryption_hostnames": "\u0418\u043c\u0435\u043d\u0430 \u043d\u0430 \u0445\u043e\u0441\u0442\u0430",
|
||||
"encryption_reset": "\u0421\u0438\u0433\u0443\u0440\u043d\u0438 \u043b\u0438 \u0441\u0442\u0435 \u0447\u0435 \u0438\u0441\u043a\u0430\u0442\u0435 \u0434\u0430 \u0438\u0437\u0442\u0440\u0438\u0435\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438\u0442\u0435 \u0437\u0430 \u043a\u0440\u0438\u043f\u0442\u0438\u0440\u0430\u043d\u0435?",
|
||||
"topline_expiring_certificate": "\u0412\u0430\u0448\u0438\u044f\u0442 SSL \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 \u0438\u0437\u0442\u0438\u0447\u0430. \u041e\u0431\u043d\u043e\u0432\u0438 <0>\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0437\u0430 \u043a\u0440\u0438\u043f\u0442\u0438\u0440\u0430\u043d\u0435<\/0>.",
|
||||
"topline_expired_certificate": "\u0412\u0430\u0448\u0438\u044f\u0442 SSL \u0441\u0435\u0440\u0442\u0438\u0444\u0438\u043a\u0430\u0442 \u0435 \u0438\u0437\u0442\u0435\u043a\u044a\u043b. \u041e\u0431\u043d\u043e\u0432\u0438 <0>\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0437\u0430 \u043a\u0440\u0438\u043f\u0442\u0438\u0440\u0430\u043d\u0435<\/0>.",
|
||||
"form_error_port_range": "\u0412\u044a\u0432\u0435\u0434\u0435\u0442\u0435 \u043f\u043e\u0440\u0442 \u0432 \u0434\u0438\u0430\u043f\u0430\u0437\u043e\u043d\u0430 80-65535",
|
||||
"form_error_port_unsafe": "\u041d\u0435 \u0435 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e \u0434\u0430 \u0438\u0437\u043f\u043e\u043b\u0437\u0432\u0430\u0442\u0435 \u0442\u043e\u0437\u0438 \u043f\u043e\u0440\u0442",
|
||||
"form_error_equal": "\u041d\u0435 \u0442\u0440\u044f\u0431\u0432\u0430 \u0434\u0430 \u0441\u044a\u0432\u043f\u0430\u0434\u0430",
|
||||
"form_error_password": "\u041f\u0430\u0440\u043e\u043b\u0430\u0442\u0430 \u043d\u0435 \u0441\u044a\u0432\u043f\u0430\u0434\u0430",
|
||||
"reset_settings": "\u0418\u0437\u0442\u0440\u0438\u0439 \u0432\u0441\u0438\u0447\u043a\u0438 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438",
|
||||
"update_announcement": "\u0418\u043c\u0430 \u043d\u043e\u0432\u0430 AdGuard Home {{version}}! <0>\u0426\u044a\u043a\u043d\u0438 \u0442\u0443\u043a<\/0> \u0437\u0430 \u043f\u043e\u0432\u0435\u0447\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044f."
|
||||
}
|
||||
@@ -1,4 +1,8 @@
|
||||
{
|
||||
"upstream_parallel": "Use parallel queries to speed up resolving by simultaneously querying all upstream servers",
|
||||
"bootstrap_dns": "Bootstrap DNS servers",
|
||||
"bootstrap_dns_desc": "Bootstrap DNS servers are used to resolve IP addresses of the DoH\/DoT resolvers you specify as upstreams.",
|
||||
"url_added_successfully": "URL added successfully",
|
||||
"check_dhcp_servers": "Check for DHCP servers",
|
||||
"save_config": "Save config",
|
||||
"enabled_dhcp": "DHCP server enabled",
|
||||
@@ -8,7 +12,7 @@
|
||||
"dhcp_enable": "Enable DHCP server",
|
||||
"dhcp_disable": "Disable DHCP server",
|
||||
"dhcp_not_found": "No active DHCP servers found on the network. It is safe to enable the built-in DHCP server.",
|
||||
"dhcp_found": "Found active DHCP servers found on the network. It is not safe to enable the built-in DHCP server.",
|
||||
"dhcp_found": "Some active DHCP servers found on the network. It is not safe to enable the built-in DHCP server.",
|
||||
"dhcp_leases": "DHCP leases",
|
||||
"dhcp_leases_not_found": "No DHCP leases found",
|
||||
"dhcp_config_saved": "Saved DHCP server config",
|
||||
@@ -25,6 +29,9 @@
|
||||
"dhcp_interface_select": "Select DHCP interface",
|
||||
"dhcp_hardware_address": "Hardware address",
|
||||
"dhcp_ip_addresses": "IP addresses",
|
||||
"dhcp_table_hostname": "Hostname",
|
||||
"dhcp_table_expires": "Expires",
|
||||
"dhcp_warning": "If you want to enable the built-in DHCP server, make sure that there is no other active DHCP server. Otherwise, it can break the internet for connected devices!",
|
||||
"back": "Back",
|
||||
"dashboard": "Dashboard",
|
||||
"settings": "Settings",
|
||||
@@ -75,7 +82,7 @@
|
||||
"no_settings": "No settings",
|
||||
"general_settings": "General settings",
|
||||
"upstream_dns": "Upstream DNS servers",
|
||||
"upstream_dns_hint": "If you keep this field empty, AdGuard Home will use <a href='https:\/\/1.1.1.1\/' target='_blank'>Cloudflare DNS<\/a> as an upstream. Use tls:\/\/ prefix for DNS over TLS servers.",
|
||||
"upstream_dns_hint": "If you keep this field empty, AdGuard Home will use <a href='https:\/\/1.1.1.1\/' target='_blank'>Cloudflare DNS<\/a> as an upstream.",
|
||||
"test_upstream_btn": "Test upstreams",
|
||||
"apply_btn": "Apply",
|
||||
"disabled_filtering_toast": "Disabled filtering",
|
||||
@@ -112,11 +119,13 @@
|
||||
"example_comment": "! Here goes a comment",
|
||||
"example_comment_meaning": "just a comment",
|
||||
"example_comment_hash": "# Also a comment",
|
||||
"example_regex_meaning": "block access to the domains matching the specified regular expression",
|
||||
"example_upstream_regular": "regular DNS (over UDP)",
|
||||
"example_upstream_dot": "encrypted <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_TLS' target='_blank'>DNS-over-TLS<\/a>",
|
||||
"example_upstream_doh": "encrypted <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>DNS-over-HTTPS<\/a>",
|
||||
"example_upstream_sdns": "you can use <a href='https:\/\/dnscrypt.info\/stamps\/' target='_blank'>DNS Stamps<\/a> for <a href='https:\/\/dnscrypt.info\/' target='_blank'>DNSCrypt<\/a> or <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>DNS-over-HTTPS<\/a> resolvers",
|
||||
"example_upstream_dot": "encrypted <0>DNS-over-TLS</0>",
|
||||
"example_upstream_doh": "encrypted <0>DNS-over-HTTPS</0>",
|
||||
"example_upstream_sdns": "you can use <0>DNS Stamps</0> for <1>DNSCrypt</1> or <2>DNS-over-HTTPS</2> resolvers",
|
||||
"example_upstream_tcp": "regular DNS (over TCP)",
|
||||
"example_upstream_reserved": "you can specify DNS upstream <0>for a specific domain(s)</0>",
|
||||
"all_filters_up_to_date_toast": "All filters are already up-to-date",
|
||||
"updated_upstream_dns_toast": "Updated the upstream DNS servers",
|
||||
"dns_test_ok_toast": "Specified DNS servers are working correctly",
|
||||
@@ -153,5 +162,95 @@
|
||||
"category_label": "Category",
|
||||
"rule_label": "Rule",
|
||||
"filter_label": "Filter",
|
||||
"unknown_filter": "Unknown filter {{filterId}}"
|
||||
"unknown_filter": "Unknown filter {{filterId}}",
|
||||
"install_welcome_title": "Welcome to AdGuard Home!",
|
||||
"install_welcome_desc": "AdGuard Home is a network-wide ad-and-tracker blocking DNS server. Its purpose is to let you control your entire network and all your devices, and it does not require using a client-side program.",
|
||||
"install_settings_title": "Admin Web Interface",
|
||||
"install_settings_listen": "Listen interface",
|
||||
"install_settings_port": "Port",
|
||||
"install_settings_interface_link": "Your AdGuard Home admin web interface will be available on the following addresses:",
|
||||
"form_error_port": "Enter valid port value",
|
||||
"install_settings_dns": "DNS server",
|
||||
"install_settings_dns_desc": "You will need to configure your devices or router to use the DNS server on the following addresses:",
|
||||
"install_settings_all_interfaces": "All interfaces",
|
||||
"install_auth_title": "Authentication",
|
||||
"install_auth_desc": "It is highly recommended to configure password authentication to your AdGuard Home admin web interface. Even if it is accessible only in your local network, it is still important to have it protected from unrestricted access.",
|
||||
"install_auth_username": "Username",
|
||||
"install_auth_password": "Password",
|
||||
"install_auth_confirm": "Confirm password",
|
||||
"install_auth_username_enter": "Enter username",
|
||||
"install_auth_password_enter": "Enter password",
|
||||
"install_step": "Step",
|
||||
"install_devices_title": "Configure your devices",
|
||||
"install_devices_desc": "In order for AdGuard Home to start working, you need to configure your devices to use it.",
|
||||
"install_submit_title": "Congratulations!",
|
||||
"install_submit_desc": "The setup procedure is finished and you are ready to start using AdGuard Home.",
|
||||
"install_devices_router": "Router",
|
||||
"install_devices_router_desc": "This setup will automatically cover all the devices connected to your home router and you will not need to configure each of them manually.",
|
||||
"install_devices_address": "AdGuard Home DNS server is listening to the following addresses",
|
||||
"install_devices_router_list_1": "Open the preferences for your router. Usually, you can access it from your browser via a URL (like http:\/\/192.168.0.1\/ or http:\/\/192.168.1.1\/). You may be asked to enter the password. If you don't remember it, you can often reset the password by pressing a button on the router itself. Some routers require a specific application, which in that case should be already installed on your computer\/phone.",
|
||||
"install_devices_router_list_2": "Find the DHCP\/DNS settings. Look for the DNS letters next to a field which allows two or three sets of numbers, each broken into four groups of one to three digits.",
|
||||
"install_devices_router_list_3": "Enter your AdGuard Home server addresses there.",
|
||||
"install_devices_windows_list_1": "Open Control Panel through Start menu or Windows search.",
|
||||
"install_devices_windows_list_2": "Go to Network and Internet category and then to Network and Sharing Center.",
|
||||
"install_devices_windows_list_3": "On the left side of the screen find Change adapter settings and click on it.",
|
||||
"install_devices_windows_list_4": "Select your active connection, right-click on it and choose Properties.",
|
||||
"install_devices_windows_list_5": "Find Internet Protocol Version 4 (TCP\/IP) in the list, select it and then click on Properties again.",
|
||||
"install_devices_windows_list_6": "Choose Use the following DNS server addresses and enter your AdGuard Home server addresses.",
|
||||
"install_devices_macos_list_1": "Click on Apple icon and go to System Preferences.",
|
||||
"install_devices_macos_list_2": "Click on Network.",
|
||||
"install_devices_macos_list_3": "Select the first connection in your list and click Advanced.",
|
||||
"install_devices_macos_list_4": "Select the DNS tab and enter your AdGuard Home server addresses.",
|
||||
"install_devices_android_list_1": "From the Android Menu home screen, tap Settings.",
|
||||
"install_devices_android_list_2": "Tap Wi-Fi on the menu. The screen listing all of the available networks will be shown (it is impossible to set custom DNS for mobile connection).",
|
||||
"install_devices_android_list_3": "Long press the network you're connected to, and tap Modify Network.",
|
||||
"install_devices_android_list_4": "On some devices, you may need to check the box for Advanced to see further settings. To adjust your Android DNS settings, you will need to switch the IP settings from DHCP to Static.",
|
||||
"install_devices_android_list_5": "Change set DNS 1 and DNS 2 values to your AdGuard Home server addresses.",
|
||||
"install_devices_ios_list_1": "From the home screen, tap Settings.",
|
||||
"install_devices_ios_list_2": "Choose Wi-Fi in the left menu (it is impossible to configure DNS for mobile networks).",
|
||||
"install_devices_ios_list_3": "Tap on the name of the currently active network.",
|
||||
"install_devices_ios_list_4": "In the DNS field enter your AdGuard Home server addresses.",
|
||||
"get_started": "Get Started",
|
||||
"next": "Next",
|
||||
"open_dashboard": "Open Dashboard",
|
||||
"install_saved": "Saved successfully",
|
||||
"encryption_title": "Encryption",
|
||||
"encryption_desc": "Encryption (HTTPS\/TLS) support for both DNS and admin web interface",
|
||||
"encryption_config_saved": "Encryption config saved",
|
||||
"encryption_server": "Server name",
|
||||
"encryption_server_enter": "Enter your domain name",
|
||||
"encryption_server_desc": "In order to use HTTPS, you need to enter the server name that matches your SSL certificate.",
|
||||
"encryption_redirect": "Redirect to HTTPS automatically",
|
||||
"encryption_redirect_desc": "If checked, AdGuard Home will automatically redirect you from HTTP to HTTPS addresses.",
|
||||
"encryption_https": "HTTPS port",
|
||||
"encryption_https_desc": "If HTTPS port is configured, AdGuard Home admin interface will be accessible via HTTPS, and it will also provide DNS-over-HTTPS on '\/dns-query' location.",
|
||||
"encryption_dot": "DNS-over-TLS port",
|
||||
"encryption_dot_desc": "If this port is configured, AdGuard Home will run a DNS-over-TLS server on this port.",
|
||||
"encryption_certificates": "Certificates",
|
||||
"encryption_certificates_desc": "In order to use encryption, you need to provide a valid SSL certificates chain for your domain. You can get a free certificate on <0>{{link}}<\/0> or you can buy it from one of the trusted Certificate Authorities.",
|
||||
"encryption_certificates_input": "Copy\/paste your PEM-encoded certificates here.",
|
||||
"encryption_status": "Status",
|
||||
"encryption_expire": "Expires",
|
||||
"encryption_key": "Private key",
|
||||
"encryption_key_input": "Copy\/paste your PEM-encoded private key for your certificate here.",
|
||||
"encryption_enable": "Enable Encryption (HTTPS, DNS-over-HTTPS, and DNS-over-TLS)",
|
||||
"encryption_enable_desc": "If encryption is enabled, AdGuard Home admin interface will work over HTTPS, and the DNS server will listen for requests over DNS-over-HTTPS and DNS-over-TLS.",
|
||||
"encryption_chain_valid": "Certificate chain is valid",
|
||||
"encryption_chain_invalid": "Certificate chain is invalid",
|
||||
"encryption_key_valid": "This is a valid {{type}} private key",
|
||||
"encryption_key_invalid": "This is an invalid {{type}} private key",
|
||||
"encryption_subject": "Subject",
|
||||
"encryption_issuer": "Issuer",
|
||||
"encryption_hostnames": "Hostnames",
|
||||
"encryption_reset": "Are you sure you want to reset encryption settings?",
|
||||
"topline_expiring_certificate": "Your SSL certificate is about to expire. Update <0>Encryption settings<\/0>.",
|
||||
"topline_expired_certificate": "Your SSL certificate is expired. Update <0>Encryption settings<\/0>.",
|
||||
"form_error_port_range": "Enter port value in the range of 80-65535",
|
||||
"form_error_port_unsafe": "This is an unsafe port",
|
||||
"form_error_equal": "Shouldn't be equal",
|
||||
"form_error_password": "Password mismatched",
|
||||
"reset_settings": "Reset settings",
|
||||
"update_announcement": "AdGuard Home {{version}} is now available! <0>Click here<\/0> for more info.",
|
||||
"setup_guide": "Setup guide",
|
||||
"dns_addresses": "DNS addresses"
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
"save_config": "Guardar config",
|
||||
"enabled_dhcp": "Servidor DHCP habilitado",
|
||||
"disabled_dhcp": "Servidor DHCP deshabilitado",
|
||||
"dhcp_title": "Servidor DHCP",
|
||||
"dhcp_title": "Servidor DHCP (experimental)",
|
||||
"dhcp_description": "Si su enrutador no proporciona la configuraci\u00f3n DHCP, puede utilizar el propio servidor DHCP incorporado de AdGuard.",
|
||||
"dhcp_enable": "Habilitar servidor DHCP",
|
||||
"dhcp_disable": "Deshabilitar el servidor DHCP",
|
||||
@@ -25,6 +25,8 @@
|
||||
"dhcp_interface_select": "Seleccione la interfaz DHCP",
|
||||
"dhcp_hardware_address": "Direcci\u00f3n de hardware",
|
||||
"dhcp_ip_addresses": "Direcciones IP",
|
||||
"dhcp_table_hostname": "Hostname",
|
||||
"dhcp_table_expires": "Expira",
|
||||
"back": "Atr\u00e1s",
|
||||
"dashboard": "Tablero de rendimiento",
|
||||
"settings": "Ajustes",
|
||||
@@ -44,7 +46,7 @@
|
||||
"disabled_protection": "Protecci\u00f3n desactivada",
|
||||
"refresh_statics": "Restablecer estad\u00edsticas",
|
||||
"dns_query": "Consultas DNS",
|
||||
"blocked_by": "Bloqueado por Filtros",
|
||||
"blocked_by": "Bloqueado por filtros",
|
||||
"stats_malware_phishing": "Malware\/phishing bloqueado",
|
||||
"stats_adult": "Contenido para adultos bloqueado",
|
||||
"stats_query_domain": "Dominios m\u00e1s solicitados",
|
||||
@@ -113,9 +115,9 @@
|
||||
"example_comment_meaning": "solo un comentario",
|
||||
"example_comment_hash": "# Tambi\u00e9n un comentario",
|
||||
"example_upstream_regular": "DNS regular (a trav\u00e9s de UDP)",
|
||||
"example_upstream_dot": "encriptado <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_TLS' target='_blank'>DNS-a-trav\u00e9s-de-TLS<\/a>",
|
||||
"example_upstream_doh": "encriptado <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>DNS-a-trav\u00e9s-de-TLS<\/a>",
|
||||
"example_upstream_sdns": "puedes usar <a href='https:\/\/dnscrypt.info\/stamps\/' target='_blank'>DNS Stamps<\/a> para <a href='https:\/\/dnscrypt.info\/' target='_blank'>DNSCrypt<\/a> o <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>DNS-over-HTTPS<\/a> resolutores",
|
||||
"example_upstream_dot": "encriptado <0>DNS-a-trav\u00e9s-de-TLS<\/0>",
|
||||
"example_upstream_doh": "encriptado <0>DNS-a-trav\u00e9s-de-TLS<\/0>",
|
||||
"example_upstream_sdns": "puedes usar <0>DNS Stamps<\/0> para <1>DNSCrypt<\/1> o <2>DNS-over-HTTPS<\/2> resolutores",
|
||||
"example_upstream_tcp": "DNS regular (a trav\u00e9s de TCP)",
|
||||
"all_filters_up_to_date_toast": "Todos los filtros son actualizados",
|
||||
"updated_upstream_dns_toast": "Servidores DNS upstream actualizados",
|
||||
|
||||
@@ -1,4 +1,33 @@
|
||||
{
|
||||
"url_added_successfully": "Url ajout\u00e9e",
|
||||
"check_dhcp_servers": "Rechercher les serveurs DHCP",
|
||||
"save_config": "Sauvegarder la configuration",
|
||||
"enabled_dhcp": "Serveur DHCP activ\u00e9",
|
||||
"disabled_dhcp": "Serveur DHCP d\u00e9sactiv\u00e9",
|
||||
"dhcp_title": "Serveur DHCP (experimental !)",
|
||||
"dhcp_description": "Si votre routeur ne fonctionne pas avec les r\u00e9glages DHCP, vous pouvez utiliser le serveur DHCP par d\u00e9faut d'AdGuard.",
|
||||
"dhcp_enable": "Activer le serveur DHCP",
|
||||
"dhcp_disable": "D\u00e9sactiver le serveur DHCP",
|
||||
"dhcp_not_found": "Aucun serveur DHCP actif trouv\u00e9 sur le r\u00e9seau. Vous pouvez activer le serveur DHCP int\u00e9gr\u00e9.",
|
||||
"dhcp_found": "Il y a plusieurs serveurs DHCP actifs sur le r\u00e9seau. Ce n'est pas prudent d'activer le serveur DHCP int\u00e9gr\u00e9 en ce moment.",
|
||||
"dhcp_leases": "Locations des serveurs DHCP",
|
||||
"dhcp_leases_not_found": "Aucune location des serveurs DHCP trouv\u00e9e",
|
||||
"dhcp_config_saved": "La configuration du serveur DHCP est sauvegard\u00e9e",
|
||||
"form_error_required": "Champ requis",
|
||||
"form_error_ip_format": "Format IPv4 invalide",
|
||||
"form_error_positive": "Doit \u00eatre sup\u00e9rieur \u00e0 0\u001c",
|
||||
"dhcp_form_gateway_input": "IP de la passerelle",
|
||||
"dhcp_form_subnet_input": "Masque de sous-r\u00e9seau",
|
||||
"dhcp_form_range_title": "Rang\u00e9e des adresses IP",
|
||||
"dhcp_form_range_start": "D\u00e9but de la rang\u00e9e",
|
||||
"dhcp_form_range_end": "Fin de la rang\u00e9e",
|
||||
"dhcp_form_lease_title": "P\u00e9riode de location du serveur DHCP (secondes)",
|
||||
"dhcp_form_lease_input": "Dur\u00e9e de la location",
|
||||
"dhcp_interface_select": "S\u00e9lectionner l'interface du serveur DHCP",
|
||||
"dhcp_hardware_address": "Adresse de la machine",
|
||||
"dhcp_ip_addresses": "Adresses IP",
|
||||
"dhcp_table_hostname": "Nom de machine",
|
||||
"dhcp_table_expires": "Expire le",
|
||||
"back": "Retour",
|
||||
"dashboard": "Tableau de bord",
|
||||
"settings": "Param\u00e8tres",
|
||||
@@ -87,8 +116,9 @@
|
||||
"example_comment_meaning": "commentaire",
|
||||
"example_comment_hash": "# Et comme \u00e7a aussi on peut laisser des commentaires",
|
||||
"example_upstream_regular": "DNS classique (au-dessus de UDP)",
|
||||
"example_upstream_dot": "<a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_TLS' target='_blank'>DNS-au-dessus-de-TLS<\/a> chiffr\u00e9",
|
||||
"example_upstream_doh": "<a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>DNS-au-dessus-de-HTTPS<\/a> chiffr\u00e9",
|
||||
"example_upstream_dot": "<0>DNS-au-dessus-de-TLS<\/0> chiffr\u00e9",
|
||||
"example_upstream_doh": "<0>DNS-au-dessus-de-HTTPS<\/0> chiffr\u00e9",
|
||||
"example_upstream_sdns": "vous pouvez utiliser <0>DNS Stamps<\/0> pour <1>DNSCrypt<\/1> ou les resolveurs <2>DNS-au-dessus-de-HTTPS<\/2>",
|
||||
"example_upstream_tcp": "DNS classique (au-dessus de TCP)",
|
||||
"all_filters_up_to_date_toast": "Tous les filtres sont mis \u00e0 jour",
|
||||
"updated_upstream_dns_toast": "Les serveurs DNS upstream sont mis \u00e0 jour",
|
||||
@@ -125,5 +155,6 @@
|
||||
"found_in_known_domain_db": "Trouv\u00e9 dans la base de donn\u00e9es des domaines connus",
|
||||
"category_label": "Cat\u00e9gorie",
|
||||
"rule_label": "R\u00e8gle",
|
||||
"filter_label": "Filtre"
|
||||
"filter_label": "Filtre",
|
||||
"unknown_filter": "Filtre inconnu {{filterId}}"
|
||||
}
|
||||
@@ -3,12 +3,12 @@
|
||||
"save_config": "\u8a2d\u5b9a\u3092\u4fdd\u5b58\u3059\u308b",
|
||||
"enabled_dhcp": "DHCP\u30b5\u30fc\u30d0\u3092\u6709\u52b9\u306b\u3057\u307e\u3057\u305f",
|
||||
"disabled_dhcp": "DHCP\u30b5\u30fc\u30d0\u3092\u7121\u52b9\u306b\u3057\u307e\u3057\u305f",
|
||||
"dhcp_title": "DHCP\u30b5\u30fc\u30d0",
|
||||
"dhcp_description": "\u3042\u306a\u305f\u306e\u30eb\u30fc\u30bf\u304cDHCP\u306e\u8a2d\u5b9a\u3092\u63d0\u4f9b\u3057\u3066\u3044\u306a\u3044\u306e\u306a\u3089\u3001AdGuard\u306b\u5185\u8535\u3055\u308c\u305fDHCP\u30b5\u30fc\u30d0\u3092\u5229\u7528\u3067\u304d\u307e\u3059\u3002",
|
||||
"dhcp_title": "DHCP\u30b5\u30fc\u30d0\uff08\u5b9f\u9a13\u7684\uff01\uff09",
|
||||
"dhcp_description": "\u3042\u306a\u305f\u306e\u30eb\u30fc\u30bf\u304cDHCP\u306e\u8a2d\u5b9a\u3092\u63d0\u4f9b\u3057\u3066\u3044\u306a\u3044\u306e\u306a\u3089\u3001AdGuard\u306b\u5185\u8535\u3055\u308c\u3066\u3044\u308bDHCP\u30b5\u30fc\u30d0\u3092\u5229\u7528\u3067\u304d\u307e\u3059\u3002",
|
||||
"dhcp_enable": "DHCP\u30b5\u30fc\u30d0\u3092\u6709\u52b9\u306b\u3059\u308b",
|
||||
"dhcp_disable": "DHCP\u30b5\u30fc\u30d0\u3092\u7121\u52b9\u306b\u3059\u308b",
|
||||
"dhcp_not_found": "\u30cd\u30c3\u30c8\u30ef\u30fc\u30af\u5185\u306b\u52d5\u4f5c\u3057\u3066\u3044\u308bDHCP\u30b5\u30fc\u30d0\u306f\u3042\u308a\u307e\u305b\u3093\u3002\u5185\u8535\u3055\u308c\u305fDHCP\u30b5\u30fc\u30d0\u3092\u6709\u52b9\u306b\u3057\u3066\u3082\u5b89\u5168\u3067\u3059\u3002",
|
||||
"dhcp_found": "\u30cd\u30c3\u30c8\u30ef\u30fc\u30af\u5185\u306b\u6d3b\u52d5\u4e2d\u306eDHCP\u30b5\u30fc\u30d0\u3092\u898b\u3064\u3051\u307e\u3057\u305f\u3002\u5185\u81d3\u3055\u308c\u305fDHCP\u30b5\u30fc\u30d0\u3092\u6709\u52b9\u306b\u3059\u308b\u306b\u306f\u5b89\u5168\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002",
|
||||
"dhcp_not_found": "\u30cd\u30c3\u30c8\u30ef\u30fc\u30af\u5185\u306b\u52d5\u4f5c\u3057\u3066\u3044\u308bDHCP\u30b5\u30fc\u30d0\u306f\u3042\u308a\u307e\u305b\u3093\u3002\u5185\u8535\u3055\u308c\u3066\u3044\u308bDHCP\u30b5\u30fc\u30d0\u3092\u6709\u52b9\u306b\u3057\u3066\u3082\u5b89\u5168\u3067\u3059\u3002",
|
||||
"dhcp_found": "\u30cd\u30c3\u30c8\u30ef\u30fc\u30af\u5185\u306b\u52d5\u4f5c\u3057\u3066\u3044\u308bDHCP\u30b5\u30fc\u30d0\u3092\u898b\u3064\u3051\u307e\u3057\u305f\u3002\u5185\u81d3\u3055\u308c\u3066\u3044\u308bDHCP\u30b5\u30fc\u30d0\u3092\u6709\u52b9\u306b\u3059\u308b\u306b\u306f\u5b89\u5168\u3067\u306f\u3042\u308a\u307e\u305b\u3093\u3002",
|
||||
"dhcp_leases": "DHCP\u5272\u5f53",
|
||||
"dhcp_leases_not_found": "DHCP\u5272\u5f53\u306f\u3042\u308a\u307e\u305b\u3093",
|
||||
"dhcp_config_saved": "DHCP\u30b5\u30fc\u30d0\u306e\u8a2d\u5b9a\u3092\u4fdd\u5b58\u3057\u307e\u3057\u305f",
|
||||
@@ -25,6 +25,8 @@
|
||||
"dhcp_interface_select": "DHCP\u30a4\u30f3\u30bf\u30d5\u30a7\u30fc\u30b9\u306e\u9078\u629e",
|
||||
"dhcp_hardware_address": "MAC\u30a2\u30c9\u30ec\u30b9",
|
||||
"dhcp_ip_addresses": "IP\u30a2\u30c9\u30ec\u30b9",
|
||||
"dhcp_table_hostname": "\u30db\u30b9\u30c8\u540d",
|
||||
"dhcp_table_expires": "\u6709\u52b9\u671f\u9650",
|
||||
"back": "\u623b\u308b",
|
||||
"dashboard": "\u30c0\u30c3\u30b7\u30e5\u30dc\u30fc\u30c9",
|
||||
"settings": "\u8a2d\u5b9a",
|
||||
@@ -113,9 +115,9 @@
|
||||
"example_comment_meaning": "\u305f\u3060\u306e\u30b3\u30e1\u30f3\u30c8\u3067\u3059",
|
||||
"example_comment_hash": "# \u3053\u3053\u3082\u30b3\u30e1\u30f3\u30c8\u3067\u3059",
|
||||
"example_upstream_regular": "\u901a\u5e38\u306eDNS\uff08UDP\u3067\u306e\u554f\u3044\u5408\u308f\u305b\uff09",
|
||||
"example_upstream_dot": "\u6697\u53f7\u5316\u3055\u308c\u3066\u3044\u308b <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_TLS' target='_blank'>DNS-over-TLS<\/a>",
|
||||
"example_upstream_doh": "\u6697\u53f7\u5316\u3055\u308c\u3066\u3044\u308b <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>DNS-over-HTTPS<\/a>",
|
||||
"example_upstream_sdns": "<a href='https:\/\/dnscrypt.info\/' target='_blank'>DNSCrypt<\/a> \u307e\u305f\u306f <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>DNS-over-HTTPS<\/a> \u30ea\u30be\u30eb\u30d0\u306e\u305f\u3081\u306b <a href='https:\/\/dnscrypt.info\/stamps\/' target='_blank'>DNS Stamps<\/a> \u3092\u4f7f\u3048\u307e\u3059",
|
||||
"example_upstream_dot": "\u6697\u53f7\u5316\u3055\u308c\u3066\u3044\u308b <0>DNS-over-TLS<\/0>",
|
||||
"example_upstream_doh": "\u6697\u53f7\u5316\u3055\u308c\u3066\u3044\u308b <0>DNS-over-HTTPS<\/0>",
|
||||
"example_upstream_sdns": "<0>DNSCrypt<\/0> \u307e\u305f\u306f <1>DNS-over-HTTPS<\/1> \u30ea\u30be\u30eb\u30d0\u306e\u305f\u3081\u306b <2>DNS Stamps<\/2> \u3092\u4f7f\u3048\u307e\u3059",
|
||||
"example_upstream_tcp": "\u901a\u5e38\u306eDNS\uff08TCP\u3067\u306e\u554f\u3044\u5408\u308f\u305b\uff09",
|
||||
"all_filters_up_to_date_toast": "\u3059\u3079\u3066\u306e\u30d5\u30a3\u30eb\u30bf\u306f\u65e2\u306b\u6700\u65b0\u3067\u3059",
|
||||
"updated_upstream_dns_toast": "\u4e0a\u6d41DNS\u30b5\u30fc\u30d0\u3092\u66f4\u65b0\u3057\u307e\u3057\u305f",
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
{
|
||||
"check_dhcp_servers": "Verifique se h\u00e1 servidores DHCP",
|
||||
"url_added_successfully": "URL adicionada com sucesso",
|
||||
"check_dhcp_servers": "Verificar por servidores DHCP",
|
||||
"save_config": "Salvar configura\u00e7\u00e3o",
|
||||
"enabled_dhcp": "Servidor DHCP ativado",
|
||||
"disabled_dhcp": "Servidor DHCP desativado",
|
||||
"dhcp_title": "Servidor DHCP",
|
||||
"dhcp_title": "Servidor DHCP (experimental)",
|
||||
"dhcp_description": "Se o seu roteador n\u00e3o fornecer configura\u00e7\u00f5es de DHCP, voc\u00ea poder\u00e1 usar o servidor DHCP integrado do AdGuard.",
|
||||
"dhcp_enable": "Ativar servidor DHCP",
|
||||
"dhcp_disable": "Desativar servidor DHCP",
|
||||
"dhcp_not_found": "Nenhum servidor DHCP ativo foi encontrado na sua rede. \u00c9 seguro ativar o servidor DHCP integrado.",
|
||||
"dhcp_found": "Nenhum servidor DHCP ativo foi encontrado na sua rede. N\u00e3o \u00e9 seguro ativar o servidor DHCP integrado.",
|
||||
"dhcp_found": "Foram encontrados servidores DHCP ativos na rede. N\u00e3o \u00e9 seguro ativar o servidor DHCP integrado.",
|
||||
"dhcp_leases": "Concess\u00f5es DHCP",
|
||||
"dhcp_leases_not_found": "Nenhuma concess\u00e3o DHCP encontrada",
|
||||
"dhcp_config_saved": "Salvar configura\u00e7\u00f5es do servidor DHCP",
|
||||
@@ -25,6 +26,9 @@
|
||||
"dhcp_interface_select": "Selecione a interface DHCP",
|
||||
"dhcp_hardware_address": "Endere\u00e7o de hardware",
|
||||
"dhcp_ip_addresses": "Endere\u00e7o de IP",
|
||||
"dhcp_table_hostname": "Hostname",
|
||||
"dhcp_table_expires": "Expira",
|
||||
"dhcp_warning": "Se voc\u00ea quiser ativar o servidor DHCP interno, certifique-se que n\u00e3o h\u00e1 outro servidor DHCP ativo. Caso contr\u00e1rio, isso pode impedir que outros dispositivos conectem \u00e1 internet!",
|
||||
"back": "Voltar",
|
||||
"dashboard": "Painel",
|
||||
"settings": "Configura\u00e7\u00f5es",
|
||||
@@ -112,10 +116,11 @@
|
||||
"example_comment": "! Aqui vai um coment\u00e1rio",
|
||||
"example_comment_meaning": "apenas um coment\u00e1rio",
|
||||
"example_comment_hash": "# Tamb\u00e9m um coment\u00e1rio",
|
||||
"example_regex_meaning": "bloqueia o acesso aos dom\u00ednios correspondentes \u00e0 express\u00e3o regular especificada",
|
||||
"example_upstream_regular": "DNS regular (atrav\u00e9s do UDP)",
|
||||
"example_upstream_dot": "DNS criptografado <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_TLS' target='_blank'>atrav\u00e9s do TLS<\/a>",
|
||||
"example_upstream_doh": "DNS criptografado <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>atrav\u00e9s do HTTPS<\/a>",
|
||||
"example_upstream_sdns": "Voc\u00ea pode usar <a href='https:\/\/dnscrypt.info\/stamps\/' target='_blank'>DNS Stamps<\/a>para o<a href='https:\/\/dnscrypt.info\/' target='_blank'>DNSCrypt<\/a>ou usar resolvedores<a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>DNS-sobre-HTTPS<\/a>",
|
||||
"example_upstream_dot": "DNS criptografado <0>atrav\u00e9s do TLS<\/0>",
|
||||
"example_upstream_doh": "DNS criptografado <0>atrav\u00e9s do HTTPS<\/0>",
|
||||
"example_upstream_sdns": "Voc\u00ea pode usar <0>DNS Stamps<\/0> para o <1>DNSCrypt<\/1> ou usar resolvedores <2>DNS-sobre-HTTPS<\/2>",
|
||||
"example_upstream_tcp": "DNS regular (atrav\u00e9s do TCP)",
|
||||
"all_filters_up_to_date_toast": "Todos os filtros j\u00e1 est\u00e3o atualizados",
|
||||
"updated_upstream_dns_toast": "Atualizado os servidores DNS upstream",
|
||||
@@ -153,5 +158,93 @@
|
||||
"category_label": "Categoria",
|
||||
"rule_label": "Regra",
|
||||
"filter_label": "Filtro",
|
||||
"unknown_filter": "Filtro desconhecido {{filterId}}"
|
||||
"unknown_filter": "Filtro desconhecido {{filterId}}",
|
||||
"install_welcome_title": "Bem-vindo(a) ao AdGuard Home!",
|
||||
"install_welcome_desc": "O AdGuard Home \u00e9 um servidor de DNS para bloqueio de an\u00fancios e rastreamento em toda a rede. Sua finalidade \u00e9 permitir que voc\u00ea controle toda a sua rede e seus dispositivos sem precisar ter um programa instalado.",
|
||||
"install_settings_title": "Interface web de administrador",
|
||||
"install_settings_listen": "Interface de escuta",
|
||||
"install_settings_port": "Porta",
|
||||
"install_settings_interface_link": "A interface web de administrador do AdGuard estar\u00e1 dispon\u00edvel nos seguintes endere\u00e7os:",
|
||||
"form_error_port": "Digite uma porta v\u00e1lida",
|
||||
"install_settings_dns": "Servidor DNS",
|
||||
"install_settings_dns_desc": "Voc\u00ea precisa configurar seu dispositivo ou roteador para usar o servidor DNS nos seguintes endere\u00e7os:",
|
||||
"install_settings_all_interfaces": "Todas interfaces",
|
||||
"install_auth_title": "Autentica\u00e7\u00e3o",
|
||||
"install_auth_desc": "\u00c9 altamente recomend\u00e1vel configurar uma senha de autentica\u00e7\u00e3o na interface web de administrador do AdGuard Home. Mesmo que seja acess\u00edvel apenas em sua rede local, ainda \u00e9 importante proteg\u00ea-lo contra o acesso irrestrito.",
|
||||
"install_auth_username": "Nome de usu\u00e1rio",
|
||||
"install_auth_password": "Senha",
|
||||
"install_auth_confirm": "Confirmar senha",
|
||||
"install_auth_username_enter": "Digite o nome de usu\u00e1rio",
|
||||
"install_auth_password_enter": "Digite a senha",
|
||||
"install_step": "Passo",
|
||||
"install_devices_title": "Configure seus dispositivos",
|
||||
"install_devices_desc": "Para que o AdGuard Home comece a funcionar, voc\u00ea precisa configurar seus dispositivos para us\u00e1-lo.",
|
||||
"install_submit_title": "Parab\u00e9ns!",
|
||||
"install_submit_desc": "O procedimento de configura\u00e7\u00e3o est\u00e1 conclu\u00eddo e voc\u00ea est\u00e1 pronto para come\u00e7ar a usar o AdGuard Home.",
|
||||
"install_devices_router": "Roteador",
|
||||
"install_devices_router_desc": "Esta configura\u00e7\u00e3o cobrir\u00e1 automaticamente todos os dispositivos conectados ao seu roteador dom\u00e9stico e voc\u00ea n\u00e3o ir\u00e1 precisar configurar cada um deles manualmente.",
|
||||
"install_devices_address": "O servidor de DNS do AdGuard Home est\u00e1 capturando os seguintes endere\u00e7os",
|
||||
"install_devices_router_list_1": "Abra as configura\u00e7\u00f5es do seu roteador\nNo navegador digite o IP do roteador, o padr\u00e3o \u00e9 (http:\/\/192.168.0.1\/ ou http:\/\/192.168.1.1\/), e o login e senha \u00e9 admin\/admin; Se voc\u00ea n\u00e3o se lembra da senha, voc\u00ea pode redefinir a senha rapidamente pressionando um bot\u00e3o no pr\u00f3prio roteador. Alguns roteadores t\u00eam um aplicativo espec\u00edfico que j\u00e1 deve estar instalado em seu computador\/telefone.",
|
||||
"install_devices_router_list_2": "Encontre as Configura\u00e7\u00f5es de DNS. Procure as letras DNS ao lado de um campo que permite dois ou tr\u00eas conjuntos de n\u00fameros, cada um dividido em quatro grupos de um a tr\u00eas n\u00fameros.",
|
||||
"install_devices_router_list_3": "Digite aqui seu servidor do AdGuard Home.",
|
||||
"install_devices_windows_list_1": "Abra o Painel de Controle pelo Menu Iniciar ou pela Pesquisa do Windows.",
|
||||
"install_devices_windows_list_2": "Entre na categoria Rede e Internet e depois clique em Central de Rede e Compartilhamento.",
|
||||
"install_devices_windows_list_3": "No lado esquerdo da janela clique em Alterar as configura\u00e7\u00f5es do adaptador.",
|
||||
"install_devices_windows_list_4": "Selecione sua atual conex\u00e3o, clique nela com o bot\u00e3o direito do mouse e depois clique em Propriedades.",
|
||||
"install_devices_windows_list_5": "Procure na lista por Internet Protocol Version 4 (TCP\/IP), selecione e clique em Propriedades novamente.",
|
||||
"install_devices_windows_list_6": "Marque usar os seguintes endere\u00e7os de servidor DNS e digite os endere\u00e7os do servidores do AdGuard Home.",
|
||||
"install_devices_macos_list_1": "Clique na \u00edcone da Apple e depois em Prefer\u00eancias do Sistema.",
|
||||
"install_devices_macos_list_2": "Clique em Rede.",
|
||||
"install_devices_macos_list_3": "Selecione a primeira conex\u00e3o da lista e clique em Avan\u00e7ado.",
|
||||
"install_devices_macos_list_4": "Selecione a guia DNS e digite os endere\u00e7os dos servidores do AdGuard Home.",
|
||||
"install_devices_android_list_1": "Na tela inicial do menu Android, toque em Configura\u00e7\u00f5es.",
|
||||
"install_devices_android_list_2": "Toque em Wi-Fi. A tela listando todas as redes ser\u00e1 exibida (n\u00e3o \u00e9 poss\u00edvel configurar DNS personalizado para uma conex\u00e3o de dados m\u00f3veis)",
|
||||
"install_devices_android_list_3": "Pressione prolongadamente a rede para a qual voc\u00ea est\u00e1 conectado e toque em Modificar rede",
|
||||
"install_devices_android_list_4": "Em alguns dispositivos, talvez seja necess\u00e1rio marcar a caixa Avan\u00e7ado para ver as outras configura\u00e7\u00f5es. Para ajustar suas configura\u00e7\u00f5es de DNS do Android, voc\u00ea precisar\u00e1 alternar as configura\u00e7\u00f5es de IP de DHCP para Est\u00e1tico.",
|
||||
"install_devices_android_list_5": "Altere o conjunto dos valores DNS 1 e DNS 2 para os endere\u00e7os de servidores do AdGuard Home.",
|
||||
"install_devices_ios_list_1": "Na tela incial, toque em Ajustes.",
|
||||
"install_devices_ios_list_2": "Selecione Wi-Fi no menu esquerdo (n\u00e3o \u00e9 poss\u00edvel configurar o DNS em conex\u00f5es de dados m\u00f3veis).",
|
||||
"install_devices_ios_list_3": "Toque no nome da rede atualmente ativa.",
|
||||
"install_devices_ios_list_4": "No campo DNS, digite os endere\u00e7os dos servidores do AdGuard Home.",
|
||||
"get_started": "Come\u00e7ar",
|
||||
"next": "Pr\u00f3ximo",
|
||||
"open_dashboard": "Abrir painel",
|
||||
"install_saved": "Salvo com sucesso",
|
||||
"encryption_title": "Criptografia",
|
||||
"encryption_desc": "Encryption (HTTPS\/TLS) support for both DNS and admin web interface",
|
||||
"encryption_config_saved": "Configura\u00e7\u00e3o de criptografia salva",
|
||||
"encryption_server": "Nome do servidor",
|
||||
"encryption_server_enter": "Digite seu nome de dom\u00ednio",
|
||||
"encryption_server_desc": "In order to use HTTPS, you need to enter the server name that matches your SSL certificate.",
|
||||
"encryption_redirect": "Redirecionar automaticamente para HTTPS",
|
||||
"encryption_redirect_desc": "If checked, AdGuard Home will automatically redirect you from HTTP to HTTPS addresses.",
|
||||
"encryption_https": "Porta HTTPS",
|
||||
"encryption_https_desc": "If HTTPS port is configured, AdGuard Home admin interface will be accessible via HTTPS, and it will also provide DNS-over-HTTPS on '\/dns-query' location.",
|
||||
"encryption_dot": "Porta DNS-sobre-TLS",
|
||||
"encryption_dot_desc": "Se essa porta estiver configurada, o AdGuard Home ir\u00e1 executar o servidor DNS-sobre- TSL nesta porta.",
|
||||
"encryption_certificates": "Certificados",
|
||||
"encryption_certificates_desc": "In order to use encryption, you need to provide a valid SSL certificates chain for your domain. You can get a free certificate on <0>{{link}}<\/0> or you can buy it from one of the trusted Certificate Authorities.",
|
||||
"encryption_certificates_input": "Copie\/cole aqui seu certificado codificado em PEM.",
|
||||
"encryption_status": "Status",
|
||||
"encryption_expire": "Expira",
|
||||
"encryption_key": "Chave privada",
|
||||
"encryption_key_input": "Copie\/cole aqui a chave privada codificada em PEM para seu certificado.",
|
||||
"encryption_enable": "Ativar criptografia (HTTPS, DNS-sobre-HTTPS e DNS-sobre-TLS)",
|
||||
"encryption_enable_desc": "If encryption is enabled, AdGuard Home admin interface will work over HTTPS, and the DNS server will listen for requests over DNS-over-HTTPS and DNS-over-TLS.",
|
||||
"encryption_chain_valid": "Cadeia de chave v\u00e1lida.",
|
||||
"encryption_chain_invalid": "A cadeia de certificado \u00e9 inv\u00e1lida",
|
||||
"encryption_key_valid": "Esta \u00e9 uma chave privada {{type}} v\u00e1lida",
|
||||
"encryption_key_invalid": "Esta \u00e9 uma chave privada {{type}} inv\u00e1lida",
|
||||
"encryption_subject": "Assunto",
|
||||
"encryption_issuer": "Emissor",
|
||||
"encryption_hostnames": "Hostnames",
|
||||
"encryption_reset": "Voc\u00ea tem certeza de que deseja redefinir a configura\u00e7\u00e3o de criptografia?",
|
||||
"topline_expiring_certificate": "Seu certificado SSL est\u00e1 prestes a expirar. Atualize suas <0>configura\u00e7\u00f5es de criptografia<\/]0>",
|
||||
"topline_expired_certificate": "Seu certificado SSL est\u00e1 expirado. Atualize suas <0>configura\u00e7\u00f5es de criptografia<\/0>",
|
||||
"form_error_port_range": "Digite um porta entre 80 e 65535",
|
||||
"form_error_port_unsafe": "Esta porta n\u00e3o \u00e9 segura",
|
||||
"form_error_equal": "N\u00e3o deve ser igual",
|
||||
"form_error_password": "Senhas n\u00e3o coincidem",
|
||||
"reset_settings": "Redefinir configura\u00e7\u00f5es",
|
||||
"update_announcement": "AdGuard Home {{version}} est\u00e1 dispon\u00edvel!<0>Clique aqui<\/0> para mais informa\u00e7\u00f5es."
|
||||
}
|
||||
@@ -1,4 +1,32 @@
|
||||
{
|
||||
"check_dhcp_servers": "\u041f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c DHCP-\u0441\u0435\u0440\u0432\u0435\u0440\u044b",
|
||||
"save_config": "\u0421\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044e",
|
||||
"enabled_dhcp": "DHCP-\u0441\u0435\u0440\u0432\u0435\u0440 \u0432\u043a\u043b\u044e\u0447\u0435\u043d",
|
||||
"disabled_dhcp": "DHCP-\u0441\u0435\u0440\u0432\u0435\u0440 \u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d",
|
||||
"dhcp_title": "DHCP-\u0441\u0435\u0440\u0432\u0435\u0440 (\u044d\u043a\u0441\u043f\u0435\u0440\u0438\u043c\u0435\u043d\u0442\u0430\u043b\u044c\u043d\u044b\u0439!)",
|
||||
"dhcp_description": "\u0415\u0441\u043b\u0438 \u0432\u0430\u0448 \u0440\u043e\u0443\u0442\u0435\u0440 \u043d\u0435 \u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u044f\u0435\u0442 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 DHCP, \u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0441\u043e\u0431\u0441\u0442\u0432\u0435\u043d\u043d\u044b\u0439 \u0432\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0439 DHCP-\u0441\u0435\u0440\u0432\u0435\u0440 AdGuard.",
|
||||
"dhcp_enable": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c DHCP-\u0441\u0435\u0440\u0432\u0435\u0440",
|
||||
"dhcp_disable": "\u041e\u0442\u043a\u043b\u044e\u0447\u0438\u0442\u044c DHCP-\u0441\u0435\u0440\u0432\u0435\u0440",
|
||||
"dhcp_not_found": "\u0410\u043a\u0442\u0438\u0432\u043d\u044b\u0435 DHCP-\u0441\u0435\u0440\u0432\u0435\u0440\u044b \u0432 \u0441\u0435\u0442\u0438 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b. \u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e \u0432\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u0432\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u044b\u0439 \u0441\u0435\u0440\u0432\u0435\u0440 DHCP.",
|
||||
"dhcp_found": "\u041d\u0435\u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0430\u043a\u0442\u0438\u0432\u043d\u044b\u0435 DHCP-\u0441\u0435\u0440\u0432\u0435\u0440\u044b \u043d\u0430\u0439\u0434\u0435\u043d\u044b \u0432 \u0441\u0435\u0442\u0438. \u0412\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u0432\u0441\u0442\u0440\u043e\u0435\u043d\u043d\u043e\u0433\u043e DHCP-\u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u043d\u0435\u0431\u0435\u0437\u043e\u043f\u0430\u0441\u043d\u043e.",
|
||||
"dhcp_leases": "\u0410\u0440\u0435\u043d\u0434\u0430 DHCP",
|
||||
"dhcp_leases_not_found": "\u0410\u0440\u0435\u043d\u0434\u0430 DHCP \u043d\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0430",
|
||||
"dhcp_config_saved": "\u0421\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u043d\u0430\u044f \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f DHCP-\u0441\u0435\u0440\u0432\u0435\u0440\u0430",
|
||||
"form_error_required": "\u041e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u043f\u043e\u043b\u0435",
|
||||
"form_error_ip_format": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u0444\u043e\u0440\u043c\u0430\u0442 IPv4",
|
||||
"form_error_positive": "\u0414\u043e\u043b\u0436\u043d\u043e \u0431\u044b\u0442\u044c \u0431\u043e\u043b\u044c\u0448\u0435 0",
|
||||
"dhcp_form_gateway_input": "IP-\u0430\u0434\u0440\u0435\u0441 \u0448\u043b\u044e\u0437\u0430",
|
||||
"dhcp_form_subnet_input": "\u041c\u0430\u0441\u043a\u0430 \u043f\u043e\u0434\u0441\u0435\u0442\u0438",
|
||||
"dhcp_form_range_title": "\u0414\u0438\u0430\u043f\u0430\u0437\u043e\u043d IP-\u0430\u0434\u0440\u0435\u0441\u043e\u0432",
|
||||
"dhcp_form_range_start": "\u041d\u0430\u0447\u0430\u043b\u043e \u0434\u0438\u0430\u043f\u0430\u0437\u043e\u043d\u0430",
|
||||
"dhcp_form_range_end": "\u041a\u043e\u043d\u0435\u0446 \u0434\u0438\u0430\u043f\u0430\u0437\u043e\u043d\u0430",
|
||||
"dhcp_form_lease_title": "\u0412\u0440\u0435\u043c\u044f \u0430\u0440\u0435\u043d\u0434\u044b DHCP (\u0432 \u0441\u0435\u043a\u0443\u043d\u0434\u0430\u0445)",
|
||||
"dhcp_form_lease_input": "\u0421\u0440\u043e\u043a \u0430\u0440\u0435\u043d\u0434\u044b",
|
||||
"dhcp_interface_select": "\u0412\u044b\u0431\u0440\u0430\u0442\u044c \u0438\u043d\u0442\u0435\u0440\u0444\u0435\u0439\u0441 DHCP",
|
||||
"dhcp_hardware_address": "\u0410\u043f\u043f\u0430\u0440\u0430\u0442\u043d\u044b\u0439 \u0430\u0434\u0440\u0435\u0441",
|
||||
"dhcp_ip_addresses": "IP-\u0430\u0434\u0440\u0435\u0441\u0430",
|
||||
"dhcp_table_hostname": "\u0418\u043c\u044f \u0445\u043e\u0441\u0442\u0430",
|
||||
"dhcp_table_expires": "\u0418\u0441\u0442\u0435\u043a\u0430\u0435\u0442",
|
||||
"back": "\u041d\u0430\u0437\u0430\u0434",
|
||||
"dashboard": "\u041f\u0430\u043d\u0435\u043b\u044c \u0443\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u044f",
|
||||
"settings": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438",
|
||||
@@ -18,7 +46,7 @@
|
||||
"disabled_protection": "\u0417\u0430\u0449\u0438\u0442\u0430 \u0432\u044b\u043a\u043b.",
|
||||
"refresh_statics": "\u041e\u0431\u043d\u043e\u0432\u0438\u0442\u044c \u0441\u0442\u0430\u0442\u0438\u0441\u0442\u0438\u043a\u0443",
|
||||
"dns_query": "DNS-\u0437\u0430\u043f\u0440\u043e\u0441\u044b",
|
||||
"blocked_by": "\u0417\u0430\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u043d\u043e \u0424\u0438\u043b\u044c\u0442\u0440\u044b",
|
||||
"blocked_by": "\u0417\u0430\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u043d\u043e \u0444\u0438\u043b\u044c\u0442\u0440\u0430\u043c\u0438",
|
||||
"stats_malware_phishing": "\u0417\u0430\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \u0432\u0440\u0435\u0434\u043e\u043d\u043e\u0441\u043d\u044b\u0435 \u0438 \u0444\u0438\u0448\u0438\u043d\u0433\u043e\u0432\u044b\u0435 \u0441\u0430\u0439\u0442\u044b",
|
||||
"stats_adult": "\u0417\u0430\u0431\u043b\u043e\u043a\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0435 \"\u0432\u0437\u0440\u043e\u0441\u043b\u044b\u0435\" \u0441\u0430\u0439\u0442\u044b",
|
||||
"stats_query_domain": "\u0427\u0430\u0441\u0442\u043e \u0437\u0430\u043f\u0440\u0430\u0448\u0438\u0432\u0430\u0435\u043c\u044b\u0435 \u0434\u043e\u043c\u0435\u043d\u044b",
|
||||
@@ -87,8 +115,9 @@
|
||||
"example_comment_meaning": "\u043a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0439",
|
||||
"example_comment_hash": "# \u0418 \u0432\u043e\u0442 \u0442\u0430\u043a \u0442\u043e\u0436\u0435",
|
||||
"example_upstream_regular": "\u043e\u0431\u044b\u0447\u043d\u044b\u0439 DNS (\u043f\u043e\u0432\u0435\u0440\u0445 UDP)",
|
||||
"example_upstream_dot": "\u0437\u0430\u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_TLS' target='_blank'>DNS-\u043f\u043e\u0432\u0435\u0440\u0445-TLS<\/a>",
|
||||
"example_upstream_doh": "\u0437\u0430\u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>DNS-\u043f\u043e\u0432\u0435\u0440\u0445-HTTPS<\/a>",
|
||||
"example_upstream_dot": "\u0437\u0430\u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 <0>DNS-\u043f\u043e\u0432\u0435\u0440\u0445-TLS<\/0>",
|
||||
"example_upstream_doh": "\u0437\u0430\u0448\u0438\u0444\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 <0>DNS-\u043f\u043e\u0432\u0435\u0440\u0445-HTTPS<\/0>",
|
||||
"example_upstream_sdns": "\u0432\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c <0>DNS Stamps<\/0> \u0434\u043b\u044f <1>DNSCrypt<\/1> \u0438\u043b\u0438 <2>DNS-over-HTTPS<\/2> \u0440\u0435\u0437\u043e\u043b\u0432\u0435\u0440\u043e\u0432",
|
||||
"example_upstream_tcp": "\u043e\u0431\u044b\u0447\u043d\u044b\u0439 DNS (\u043f\u043e\u0432\u0435\u0440\u0445 TCP)",
|
||||
"all_filters_up_to_date_toast": "\u0412\u0441\u0435 \u0444\u0438\u043b\u044c\u0442\u0440\u044b \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u044b",
|
||||
"updated_upstream_dns_toast": "Upstream DNS-\u0441\u0435\u0440\u0432\u0435\u0440\u044b \u043e\u0431\u043d\u043e\u0432\u043b\u0435\u043d\u044b",
|
||||
@@ -125,5 +154,6 @@
|
||||
"found_in_known_domain_db": "\u041d\u0430\u0439\u0434\u0435\u043d \u0432 \u0431\u0430\u0437\u0435 \u0438\u0437\u0432\u0435\u0441\u0442\u043d\u044b\u0445 \u0434\u043e\u043c\u0435\u043d\u043e\u0432.",
|
||||
"category_label": "\u041a\u0430\u0442\u0435\u0433\u043e\u0440\u0438\u044f",
|
||||
"rule_label": "\u041f\u0440\u0430\u0432\u0438\u043b\u043e",
|
||||
"filter_label": "\u0424\u0438\u043b\u044c\u0442\u0440"
|
||||
"filter_label": "\u0424\u0438\u043b\u044c\u0442\u0440",
|
||||
"unknown_filter": "\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u044b\u0439 \u0444\u0438\u043b\u044c\u0442\u0440 {{filterId}}"
|
||||
}
|
||||
@@ -1,13 +1,15 @@
|
||||
{
|
||||
"refresh_status": "Uppdatera status",
|
||||
"url_added_successfully": "URL tillagd utan fel",
|
||||
"check_dhcp_servers": "Letar efter DHCP-servrar",
|
||||
"save_config": "Spara inst\u00e4llningar",
|
||||
"enabled_dhcp": "DHCP-server aktiverad",
|
||||
"disabled_dhcp": "Dhcp-server avaktiverad",
|
||||
"dhcp_title": "DHCP-server",
|
||||
"dhcp_title": "DHCP-server (experimentell)",
|
||||
"dhcp_description": "Om din router inte har inst\u00e4llningar f\u00f6r DHCP kan du anv\u00e4nda AdGuards inbyggda server.",
|
||||
"dhcp_enable": "Aktivera DHCP.-server",
|
||||
"dhcp_disable": "Avaktivera DHCP-server",
|
||||
"dhcp_not_found": "Ingen aktiv DHCP-server hittades i n\u00e4tverkat.",
|
||||
"dhcp_found": "N\u00e5gra aktiva DHCP-servar uppt\u00e4cktes. Det \u00e4r inte s\u00e4kert att aktivera inbyggda DHCP-servrar.",
|
||||
"dhcp_leases": "DHCP-lease",
|
||||
"dhcp_leases_not_found": "Ingen DHCP-lease hittad",
|
||||
"dhcp_config_saved": "Sparade inst\u00e4llningar f\u00f6r DHCP-servern",
|
||||
@@ -21,6 +23,12 @@
|
||||
"dhcp_form_range_end": "Gr\u00e4nsslut",
|
||||
"dhcp_form_lease_title": "DHCP-leasetid (i sekunder)",
|
||||
"dhcp_form_lease_input": "Leasetid",
|
||||
"dhcp_interface_select": "V\u00e4lj DHCP-gr\u00e4nssnitt",
|
||||
"dhcp_hardware_address": "H\u00e5rdvaruadress",
|
||||
"dhcp_ip_addresses": "IP-adresser",
|
||||
"dhcp_table_hostname": "V\u00e4rdnamn",
|
||||
"dhcp_table_expires": "Utg\u00e5r",
|
||||
"dhcp_warning": "Om du vill koppla in den inbyggda DHCP-servern m\u00e5ste du f\u00f6rs\u00e4kra dig om att det finns n\u00e5gra andra DHCP-servar som i s\u00e5 fall riskerar att orsaka avbrott p\u00e5 internet!",
|
||||
"back": "Tiilbaka",
|
||||
"dashboard": "Kontrollpanel",
|
||||
"settings": "Inst\u00e4llningar",
|
||||
@@ -108,10 +116,11 @@
|
||||
"example_comment": "! H\u00e4r kommer en kommentar",
|
||||
"example_comment_meaning": "Endast en kommentar",
|
||||
"example_comment_hash": "# Ocks\u00e5 en kommentar",
|
||||
"example_regex_meaning": "blockera \u00e5tkomst till en dom\u00e4n som matchar det angivna uttrycket",
|
||||
"example_upstream_regular": "vanlig DNS (\u00f6ver UDP)",
|
||||
"example_upstream_dot": "krypterat <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_TLS' target='_blank'>DNS-over-TLS<\/a>",
|
||||
"example_upstream_doh": "krypterat <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>DNS-over-HTTPS<\/a>",
|
||||
"example_upstream_sdns": "Du kan anv\u00e4nda <a href='https:\/\/dnscrypt.info\/stamps\/' target='_blank'>DNS-stamps<\/a> f\u00f6r <a href='https:\/\/dnscrypt.info\/' target='_blank'>DNSCrypt<\/a> eller <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>DNS-\u00f6ver-HTTPS<\/a>\n-resolvers",
|
||||
"example_upstream_dot": "krypterat <0>DNS-over-TLS<\/0>",
|
||||
"example_upstream_doh": "krypterat <0>DNS-over-HTTPS<\/0>",
|
||||
"example_upstream_sdns": "Du kan anv\u00e4nda <0>DNS-stamps<\/0> f\u00f6r <1>DNSCrypt<\/1> eller <2>DNS-\u00f6ver-HTTPS<\/2>\n-resolvers",
|
||||
"example_upstream_tcp": "vanlig DNS (\u00f6ver UDP)",
|
||||
"all_filters_up_to_date_toast": "Alla filter \u00e4r redan aktuella",
|
||||
"updated_upstream_dns_toast": "Uppdaterade uppstr\u00f6ms-dns-servrar",
|
||||
@@ -149,5 +158,93 @@
|
||||
"category_label": "Kategori",
|
||||
"rule_label": "Regel",
|
||||
"filter_label": "Filter",
|
||||
"unknown_filter": "Ok\u00e4nt filter {{filterId}}"
|
||||
"unknown_filter": "Ok\u00e4nt filter {{filterId}}",
|
||||
"install_welcome_title": "V\u00e4lkommen till AdGuard Home!",
|
||||
"install_welcome_desc": "AdGuard Home \u00e4r en DNS-server f\u00f6r n\u00e4tverkst\u00e4ckande annons- och sp\u00e5rningsblockering. Dess syfte \u00e4r att de dig kontroll \u00f6ver hela n\u00e4tverket och alla dina enheter, utan behov av att anv\u00e4nda klientbaserade program.",
|
||||
"install_settings_title": "Administrat\u00f6rens webbgr\u00e4nssnitt",
|
||||
"install_settings_listen": "\u00d6vervakningsgr\u00e4nssnitt",
|
||||
"install_settings_port": "Port",
|
||||
"install_settings_interface_link": "Din administrat\u00f6rssida f\u00f6r AdGuard Home finns p\u00e5 f\u00f6ljande adresser:",
|
||||
"form_error_port": "Skriv in ett giltigt portnummer",
|
||||
"install_settings_dns": "DNS-server",
|
||||
"install_settings_dns_desc": "Du beh\u00f6ver st\u00e4lla in dina enheter eller din router f\u00f6r att anv\u00e4nda DNS-server p\u00e5 f\u00f6ljande adresser.",
|
||||
"install_settings_all_interfaces": "Alla gr\u00e4nssnitt",
|
||||
"install_auth_title": "Autentisering",
|
||||
"install_auth_desc": "Det rekommenderas starkt att st\u00e4lla in l\u00f6senordsskydd till webbgr\u00e4nssnittets administrativa del i ditt AdGuard Home. \u00c4ven om den endast \u00e4r \u00e5tkomlig p\u00e5 ditt lokala n\u00e4tverk rekommenderas det \u00e4nd\u00e5 att skydda det mot o\u00f6nskad \u00e5tkomst.",
|
||||
"install_auth_username": "Anv\u00e4ndarnamn",
|
||||
"install_auth_password": "L\u00f6senord",
|
||||
"install_auth_confirm": "Bekr\u00e4fta l\u00f6senord",
|
||||
"install_auth_username_enter": "Skriv in anv\u00e4ndarnamn",
|
||||
"install_auth_password_enter": "Skriv in l\u00f6senord",
|
||||
"install_step": "Steg",
|
||||
"install_devices_title": "St\u00e4ll in dina enheter",
|
||||
"install_devices_desc": "F\u00f6r att kunna anv\u00e4nda AdGuard Home m\u00e5ste du s\u00e4lla in dina enheter f\u00f6r att utnyttja den.",
|
||||
"install_submit_title": "Grattis!",
|
||||
"install_submit_desc": "Inst\u00e4llningsproceduren \u00e4r klar och du kan b\u00f6rja anv\u00e4nda AdGuard Home.",
|
||||
"install_devices_router": "Router",
|
||||
"install_devices_router_desc": "Den h\u00e4r anpassningen kommer att automatiskt t\u00e4cka in alla de enheter som \u00e4r anslutna till din hemmarouter och du beh\u00f6ver d\u00e4rf\u00f6r inte konfigurera var och en individuellt.",
|
||||
"install_devices_address": "AdGuard Home DNS-server t\u00e4cker f\u00f6ljande adresser",
|
||||
"install_devices_router_list_1": "\u00d6ppna routern Inst\u00e4llningar. Vanligtvis f\u00e5r man \u00e5tkomst via en URL (http:\/\/192.168.0.1 eller 192.168.1.1)- Du kommer att bli ombes att ange ett l\u00f6senord. L\u00f6senordet kan st\u00e5 angivet p\u00e5 routerns bak- eller undersida. Om l\u00f6senordet \u00e4ndrats och du inte k\u00e4nner till det kan du \u00e5terst\u00e4lla med Reset-knappen. En del routrar kr\u00e4ver en s\u00e4rskild applikation som skall finnas p\u00e5 antingen din dator eller i din mobil.",
|
||||
"install_devices_router_list_2": "Leta upp DHCP\/DNS-inst\u00e4llningarna. Titta efter DNS-tecken intill ett f\u00e4lt med tv\u00e5 eller tre upps\u00e4ttningar siffror, var och en uppdelade i grupper om fyra med en eller tre siffror.",
|
||||
"install_devices_router_list_3": "Ange serveradressen till ditt AdGuard Home.",
|
||||
"install_devices_windows_list_1": "\u00d6ppna Kontrollpanelen via Start eller Windows S\u00f6k.",
|
||||
"install_devices_windows_list_2": "V\u00e4lj N\u00e4tverks och delningscenter, N\u00e4tverk och Internet.",
|
||||
"install_devices_windows_list_3": "Leta upp \u00c4ndra n\u00e4tverkskortsalternativ",
|
||||
"install_devices_windows_list_4": "Markera din aktiva anslutning. H\u00f6gerklicka p\u00e5 den och v\u00e4lj Egenskaper.",
|
||||
"install_devices_windows_list_5": "Markera Internet Protocol Version 4 (TCP\/IP) och klicka p\u00e5 knappen Egenskaper.",
|
||||
"install_devices_windows_list_6": "Markera Anv\u00e4nd f\u00f6ljande DNS-serveradresser och skriv in adresserna till ditt AdGuard Home.",
|
||||
"install_devices_macos_list_1": "Klicka p\u00e5 Apple-ikonen och v\u00e4lj Systemalternativ.",
|
||||
"install_devices_macos_list_2": "Klicka p\u00e5 N\u00e4tverk.",
|
||||
"install_devices_macos_list_3": "V\u00e4lj den f\u00f6rsta anslutningen i listan och klicka p\u00e5 Avancerat.",
|
||||
"install_devices_macos_list_4": "Klicka p\u00e5 DNS-fliken och skriv in AdGuard Homes serveradresser",
|
||||
"install_devices_android_list_1": "V\u00e4lj Inst\u00e4llningar fr\u00e5n Androids hemknapp",
|
||||
"install_devices_android_list_2": "Tryck p\u00e5 N\u00e4tverk och Internet, Wi-Fi. Alla tillg\u00e4ngliga n\u00e4tverk visas i en lista (det g\u00e5r inte all v\u00e4lja egen DNS p\u00e5 mobiln\u00e4tverk.",
|
||||
"install_devices_android_list_3": "H\u00e5ll ner p\u00e5 n\u00e4tverksnamnet som du \u00e4r ansluten till och v\u00e4lj \u00c4ndra n\u00e4tverk.",
|
||||
"install_devices_android_list_4": "P\u00e5 en del enheter kan du beh\u00f6va v\u00e4lja Avancerat f\u00f6r att komma \u00e5t ytterligare inst\u00e4llningar. F\u00f6r att \u00e4ndra p\u00e5 DNS-inst\u00e4llningar m\u00e5ste du byta IP-inst\u00e4llning fr\u00e5n DHCP till Statisk. P\u00e5 Android Pie v\u00e4ljs Privat DNS p\u00e5 N\u00e4tverk och internet.",
|
||||
"install_devices_android_list_5": "\u00c4ndra DNS 1 och DNS 2 till serveradresserna f\u00f6r AdGuard Home.",
|
||||
"install_devices_ios_list_1": "Tryck Inst\u00e4llningar fr\u00e5n hemsk\u00e4rmen.",
|
||||
"install_devices_ios_list_2": "V\u00e4lj Wi_Fi p\u00e5 den v\u00e4nstra menyn (det g\u00e5r inte att st\u00e4lla in egen DNS f\u00f6r mobila n\u00e4tverk).",
|
||||
"install_devices_ios_list_3": "Tryck p\u00e5 namnet p\u00e5 den aktiva anslutningen.",
|
||||
"install_devices_ios_list_4": "Skriv in AdGuard Homes serveradresser i DNS-f\u00e4lten.",
|
||||
"get_started": "Kom ig\u00e5ng",
|
||||
"next": "N\u00e4sta",
|
||||
"open_dashboard": "\u00d6ppna Kontrollbordet",
|
||||
"install_saved": "Sparat utan fel",
|
||||
"encryption_title": "Encryption",
|
||||
"encryption_desc": "Encryption (HTTPS\/TLS) support for both DNS and admin web interface",
|
||||
"encryption_config_saved": "Encryption config saved",
|
||||
"encryption_server": "Server name",
|
||||
"encryption_server_enter": "Enter your domain name",
|
||||
"encryption_server_desc": "In order to use HTTPS, you need to enter the server name that matches your SSL certificate.",
|
||||
"encryption_redirect": "Redirect to HTTPS automatically",
|
||||
"encryption_redirect_desc": "If checked, AdGuard Home will automatically redirect you from HTTP to HTTPS addresses.",
|
||||
"encryption_https": "HTTPS port",
|
||||
"encryption_https_desc": "If HTTPS port is configured, AdGuard Home admin interface will be accessible via HTTPS, and it will also provide DNS-over-HTTPS on '\/dns-query' location.",
|
||||
"encryption_dot": "DNS-over-TLS port",
|
||||
"encryption_dot_desc": "If this port is configured, AdGuard Home will run a DNS-over-TLS server on this port.",
|
||||
"encryption_certificates": "Certificates",
|
||||
"encryption_certificates_desc": "In order to use encryption, you need to provide a valid SSL certificates chain for your domain. You can get a free certificate on <0>{{link}}<\/0> or you can buy it from one of the trusted Certificate Authorities.",
|
||||
"encryption_certificates_input": "Copy\/paste your PEM-encoded certificates here.",
|
||||
"encryption_status": "Status",
|
||||
"encryption_expire": "Expires",
|
||||
"encryption_key": "Private key",
|
||||
"encryption_key_input": "Copy\/paste your PEM-encoded private key for your certificate here.",
|
||||
"encryption_enable": "Enable Encryption (HTTPS, DNS-over-HTTPS, and DNS-over-TLS)",
|
||||
"encryption_enable_desc": "If encryption is enabled, AdGuard Home admin interface will work over HTTPS, and the DNS server will listen for requests over DNS-over-HTTPS and DNS-over-TLS.",
|
||||
"encryption_chain_valid": "Certificate chain is valid",
|
||||
"encryption_chain_invalid": "Certificate chain is invalid",
|
||||
"encryption_key_valid": "This is a valid {{type}} private key",
|
||||
"encryption_key_invalid": "This is an invalid {{type}} private key",
|
||||
"encryption_subject": "Subject",
|
||||
"encryption_issuer": "Issuer",
|
||||
"encryption_hostnames": "Hostnames",
|
||||
"encryption_reset": "Are you sure you want to reset encryption settings?",
|
||||
"topline_expiring_certificate": "Your SSL certificate is about to expire. Update <0>Encryption settings<\/0>.",
|
||||
"topline_expired_certificate": "Your SSL certificate is expired. Update <0>Encryption settings<\/0>.",
|
||||
"form_error_port_range": "Enter port value in the range of 80-65535",
|
||||
"form_error_port_unsafe": "This is an unsafe port",
|
||||
"form_error_equal": "Shouldn't be equal",
|
||||
"form_error_password": "L\u00f6senorden \u00f6verensst\u00e4mmer inte",
|
||||
"reset_settings": "Reset settings",
|
||||
"update_announcement": "AdGuard Home {{version}} is now available! <0>Click here<\/0> for more info."
|
||||
}
|
||||
@@ -1,4 +1,30 @@
|
||||
{
|
||||
"check_dhcp_servers": "Ki\u1ec3m tra m\u00e1y ch\u1ee7 DHCP",
|
||||
"save_config": "L\u01b0u thi\u1ebft l\u1eadp",
|
||||
"enabled_dhcp": "M\u00e1y ch\u1ee7 DHCP \u0111\u00e3 k\u00edch ho\u1ea1t",
|
||||
"disabled_dhcp": "M\u00e1y ch\u1ee7 DHCP \u0111\u00e3 t\u1eaft",
|
||||
"dhcp_title": "M\u00e1y ch\u1ee7 DHCP (th\u1eed nghi\u1ec7m!)",
|
||||
"dhcp_description": "N\u1ebfu b\u1ed9 \u0111\u1ecbnh tuy\u1ebfn kh\u00f4ng tr\u1ee3 c\u00e0i \u0111\u1eb7t DHCP, b\u1ea1n c\u00f3 th\u1ec3 d\u00f9ng m\u00e1y ch\u1ee7 DHCP d\u1ef1ng s\u1eb5n c\u1ee7a AdGuard",
|
||||
"dhcp_enable": "B\u1eadt m\u00e1y ch\u1ee7 DHCP",
|
||||
"dhcp_disable": "T\u1eaft m\u00e1y ch\u1ee7 DHCP",
|
||||
"dhcp_not_found": "Kh\u00f4ng c\u00f3 m\u00e1y ch\u1ee7 DHCP n\u00e0o \u0111\u01b0\u1ee3c t\u00ecm th\u1ea5y trong m\u1ea1ng. C\u00f3 th\u1ec3 b\u1eadt m\u00e1y ch\u1ee7 DHCP m\u1ed9t c\u00e1ch an to\u00e0n",
|
||||
"dhcp_found": "\u0110\u00e3 t\u00ecm th\u1ea5y m\u00e1y ch\u1ee7 DHCP trong m\u1ea1ng. C\u00f3 th\u1ec3 c\u00f3 r\u1ee7i ro n\u1ebfu k\u00edch ho\u1ea1t m\u00e1y ch\u1ee7 DHCP d\u1ef1ng s\u1eb5n",
|
||||
"dhcp_leases": "DHCP leases",
|
||||
"dhcp_leases_not_found": "No DHCP leases found",
|
||||
"dhcp_config_saved": "Saved DHCP server config",
|
||||
"form_error_required": "Required field",
|
||||
"form_error_ip_format": "Invalid IPv4 format",
|
||||
"form_error_positive": "Ph\u1ea3i l\u1edbn h\u01a1n 0",
|
||||
"dhcp_form_gateway_input": "Gateway IP",
|
||||
"dhcp_form_subnet_input": "Subnet mask",
|
||||
"dhcp_form_range_title": "Range of IP addresses",
|
||||
"dhcp_form_range_start": "Range start",
|
||||
"dhcp_form_range_end": "IP k\u1ebft th\u00fac",
|
||||
"dhcp_form_lease_title": "DHCP lease time (in seconds)",
|
||||
"dhcp_form_lease_input": "Lease duration",
|
||||
"dhcp_interface_select": "Ch\u1ecdn m\u1ed9t card m\u1ea1ng",
|
||||
"dhcp_hardware_address": "Hardware address",
|
||||
"dhcp_ip_addresses": "IP addresses",
|
||||
"back": "Quay l\u1ea1i",
|
||||
"dashboard": "T\u1ed5ng quan",
|
||||
"settings": "C\u00e0i \u0111\u1eb7t",
|
||||
@@ -18,7 +44,7 @@
|
||||
"disabled_protection": "\u0110\u00e3 t\u1eaft b\u1ea3o v\u1ec7",
|
||||
"refresh_statics": "L\u00e0m m\u1edbi th\u1ed1ng k\u00ea",
|
||||
"dns_query": "Truy v\u1ea5n DNS",
|
||||
"blocked_by": "Ch\u1eb7n b\u1edfi B\u1ed9 l\u1ecdc",
|
||||
"blocked_by": "Ch\u1eb7n b\u1edfi b\u1ed9 l\u1ecdc",
|
||||
"stats_malware_phishing": "M\u00e3 \u0111\u1ed9c\/l\u1eeba \u0111\u1ea3o \u0111\u00e3 ch\u1eb7n",
|
||||
"stats_adult": "Website ng\u01b0\u1eddi l\u1edbn \u0111\u00e3 ch\u1eb7n",
|
||||
"stats_query_domain": "T\u00ean mi\u1ec1n truy v\u1ea5n nhi\u1ec1u",
|
||||
@@ -87,8 +113,9 @@
|
||||
"example_comment_meaning": "Ch\u1ec9 l\u00e0 m\u1ed9t ch\u00fa th\u00edch",
|
||||
"example_comment_hash": "# C\u0169ng l\u00e0 m\u1ed9t ch\u00fa th\u00edch",
|
||||
"example_upstream_regular": "DNS th\u00f4ng th\u01b0\u1eddng (d\u00f9ng UDP)",
|
||||
"example_upstream_dot": "\u0111\u01b0\u1ee3c m\u00e3 ho\u00e1 <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_TLS' target='_blank'>DNS-d\u1ef1a te-TLS<\/a>",
|
||||
"example_upstream_doh": "\u0111\u01b0\u1ee3c m\u00e3 ho\u00e1 <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>DNS-over-HTTPS<\/a>",
|
||||
"example_upstream_dot": "\u0111\u01b0\u1ee3c m\u00e3 ho\u00e1 <0>DNS-over-TLS<\/0>",
|
||||
"example_upstream_doh": "\u0111\u01b0\u1ee3c m\u00e3 ho\u00e1 <0>DNS-over-HTTPS<\/0>",
|
||||
"example_upstream_sdns": "b\u1ea1n c\u00f3 th\u1ec3 s\u1eed d\u1ee5ng <0>DNS Stamps<\/0> for <1>DNSCrypt<\/1> ho\u1eb7c <2>DNS-over-HTTPS<\/2> ",
|
||||
"example_upstream_tcp": "DNS th\u00f4ng th\u01b0\u1eddng(d\u00f9ng TCP)",
|
||||
"all_filters_up_to_date_toast": "T\u1ea5t c\u1ea3 b\u1ed9 l\u1ecdc \u0111\u00e3 \u0111\u01b0\u1ee3c c\u1eadp nh\u1eadt",
|
||||
"updated_upstream_dns_toast": "\u0110\u00e3 c\u1eadp nh\u1eadt m\u00e1y ch\u1ee7 DNS t\u00ecm ki\u1ebfm",
|
||||
@@ -103,7 +130,7 @@
|
||||
"client_table_header": "Ng\u01b0\u1eddi d\u00f9ng cu\u1ed1i",
|
||||
"empty_response_status": "R\u1ed7ng",
|
||||
"show_all_filter_type": "Hi\u1ec7n t\u1ea5t c\u1ea3",
|
||||
"show_filtered_type": "Ch\u1ec9 hi\u1ec7n \u0111\u00e3 ch\u1eb7n",
|
||||
"show_filtered_type": "Ch\u1ec9 hi\u1ec7n \u0111\u00e3 l\u1ecdc",
|
||||
"no_logs_found": "Kh\u00f4ng c\u00f3 l\u1ecbch s\u1eed truy v\u1ea5n",
|
||||
"disabled_log_btn": "T\u1eaft l\u1ecbch s\u1eed truy v\u1ea5n",
|
||||
"download_log_file_btn": "T\u1ea3i t\u1eadp tin l\u1ecbch s\u1eed truy v\u1ea5n",
|
||||
@@ -125,5 +152,7 @@
|
||||
"found_in_known_domain_db": "T\u00ecm th\u1ea5y trong c\u01a1 s\u1edf d\u1eef li\u1ec7u t\u00ean mi\u1ec1n",
|
||||
"category_label": "Th\u1ec3 lo\u1ea1i",
|
||||
"rule_label": "Quy t\u1eafc",
|
||||
"filter_label": "B\u1ed9 l\u1ecdc"
|
||||
"filter_label": "B\u1ed9 l\u1ecdc",
|
||||
"url_added_successfully": "Th\u00eam b\u1ed9 l\u1ecdc th\u00e0nh c\u00f4ng",
|
||||
"unknown_filter": "B\u1ed9 l\u1ecdc kh\u00f4ng r\u00f5 {{filterId}}"
|
||||
}
|
||||
253
client/src/__locales/zh-cn.json
Normal file
253
client/src/__locales/zh-cn.json
Normal file
@@ -0,0 +1,253 @@
|
||||
{
|
||||
"upstream_parallel": "\u901a\u8fc7\u540c\u65f6\u67e5\u8be2\u6240\u6709\u4e0a\u6d41\u670d\u52a1\u5668\u4ee5\u4f7f\u7528\u5e76\u884c\u67e5\u8be2\u52a0\u901f\u89e3\u6790",
|
||||
"bootstrap_dns": "Bootstrap DNS \u670d\u52a1\u5668",
|
||||
"bootstrap_dns_desc": "Bootstrap DNS servers are used to resolve IP addresses of the DoH\/DoT resolvers you specify as upstreams.",
|
||||
"url_added_successfully": "\u7f51\u5740\u6dfb\u52a0\u6210\u529f",
|
||||
"check_dhcp_servers": "\u68c0\u67e5 DHCP \u670d\u52a1\u5668",
|
||||
"save_config": "\u4fdd\u5b58\u914d\u7f6e",
|
||||
"enabled_dhcp": "DHCP \u670d\u52a1\u5668\u5df2\u542f\u7528",
|
||||
"disabled_dhcp": "DHCP \u670d\u52a1\u5668\u5df2\u7981\u7528",
|
||||
"dhcp_title": "DHCP \u670d\u52a1\u5668\uff08\u5b9e\u9a8c\u6027\uff09",
|
||||
"dhcp_description": "\u5982\u679c\u4f60\u7684\u8def\u7531\u5668\u6ca1\u6709\u63d0\u4f9b\u52a8\u6001\u4e3b\u673a\u8bbe\u7f6e\u534f\u8bae\uff08DHCP\uff09\u8bbe\u7f6e\uff0c\u4f60\u53ef\u4ee5\u4f7f\u7528 AdGuard \u5185\u7f6e\u7684 DHCP \u670d\u52a1\u5668\u3002",
|
||||
"dhcp_enable": "\u542f\u7528 DHCP \u670d\u52a1\u5668",
|
||||
"dhcp_disable": "\u7981\u7528 DHCP \u670d\u52a1\u5668",
|
||||
"dhcp_not_found": "\u5728\u5f53\u524d\u7f51\u7edc\u4e2d\u672a\u68c0\u6d4b\u5230 DHCP \u670d\u52a1\u5668\u3002\u60a8\u53ef\u4ee5\u5b89\u5168\u5730\u542f\u7528\u5185\u7f6e DHCP \u670d\u52a1\u5668\u3002",
|
||||
"dhcp_found": "\u5728\u5f53\u524d\u7f51\u7edc\u4e2d\u68c0\u6d4b\u5230 DHCP \u670d\u52a1\u5668\u3002\u5982\u679c\u542f\u7528\u5185\u7f6e\u7684 DHCP \u670d\u52a1\u5668\u53ef\u80fd\u4e0d\u5b89\u5168\u3002",
|
||||
"dhcp_leases": "DHCP \u79df\u7ea6",
|
||||
"dhcp_leases_not_found": "\u672a\u68c0\u6d4b\u5230 DHCP \u79df\u7ea6",
|
||||
"dhcp_config_saved": "\u4fdd\u5b58 DHCP \u670d\u52a1\u5668\u914d\u7f6e",
|
||||
"form_error_required": "\u5fc5\u586b\u5b57\u6bb5",
|
||||
"form_error_ip_format": "\u65e0\u6548\u7684 IPv4 \u683c\u5f0f",
|
||||
"form_error_positive": "\u5fc5\u987b\u5927\u4e8e 0",
|
||||
"dhcp_form_gateway_input": "\u7f51\u5173 IP",
|
||||
"dhcp_form_subnet_input": "\u5b50\u7f51\u63a9\u7801",
|
||||
"dhcp_form_range_title": "IP \u5730\u5740\u8303\u56f4",
|
||||
"dhcp_form_range_start": "\u8d77\u59cb IP \u5730\u5740",
|
||||
"dhcp_form_range_end": "\u672b\u5c3e IP \u5730\u5740",
|
||||
"dhcp_form_lease_title": "DHCP \u79df\u7ea6\u65f6\u95f4\uff08\u79d2\uff09",
|
||||
"dhcp_form_lease_input": "\u79df\u671f",
|
||||
"dhcp_interface_select": "\u9009\u62e9 DHCP \u63a5\u53e3",
|
||||
"dhcp_hardware_address": "\u786c\u4ef6\u5730\u5740",
|
||||
"dhcp_ip_addresses": "IP \u5730\u5740",
|
||||
"dhcp_table_hostname": "\u4e3b\u673a\u540d",
|
||||
"dhcp_table_expires": "\u5230\u671f",
|
||||
"dhcp_warning": "\u5982\u679c\u4f60\u60f3\u8981\u542f\u7528\u5185\u7f6e\u7684 DHCP \u670d\u52a1\u5668\uff0c\u8bf7\u786e\u4fdd\u5728\u5f53\u524d\u7f51\u7edc\u4e2d\u6ca1\u6709\u5176\u5b83\u6d3b\u52a8\u7684 DHCP \u670d\u52a1\u5668\u3002\u5426\u5219\uff0c\u6b64\u64cd\u4f5c\u53ef\u80fd\u4f1a\u7834\u574f\u5df2\u8fde\u63a5\u8bbe\u5907\u7684\u7f51\u7edc\u8fde\u63a5\uff01",
|
||||
"back": "\u8fd4\u56de",
|
||||
"dashboard": "\u4eea\u8868\u76d8",
|
||||
"settings": "\u8bbe\u7f6e",
|
||||
"filters": "\u8fc7\u6ee4\u5668",
|
||||
"query_log": "\u67e5\u8be2\u65e5\u5fd7",
|
||||
"faq": "\u5e38\u89c1\u95ee\u9898",
|
||||
"version": "\u7248\u672c",
|
||||
"address": "\u5730\u5740",
|
||||
"on": "\u542f\u7528\u4e2d",
|
||||
"off": "\u7981\u7528\u4e2d",
|
||||
"copyright": "\u7248\u6743",
|
||||
"homepage": "\u4e3b\u9875",
|
||||
"report_an_issue": "\u95ee\u9898\u53cd\u9988",
|
||||
"enable_protection": "\u542f\u7528\u4fdd\u62a4",
|
||||
"enabled_protection": "\u4fdd\u62a4\u5df2\u542f\u7528",
|
||||
"disable_protection": "\u7981\u7528\u4fdd\u62a4",
|
||||
"disabled_protection": "\u4fdd\u62a4\u5df2\u7981\u7528",
|
||||
"refresh_statics": "\u5237\u65b0\u72b6\u6001",
|
||||
"dns_query": "DNS\u67e5\u8be2",
|
||||
"blocked_by": "\u5df2\u88ab\u8fc7\u6ee4\u5668\u62e6\u622a",
|
||||
"stats_malware_phishing": "\u88ab\u62e6\u622a\u7684\u6076\u610f\/\u9493\u9c7c\u7f51\u7ad9",
|
||||
"stats_adult": "\u88ab\u62e6\u622a\u7684\u6210\u4eba\u7f51\u7ad9",
|
||||
"stats_query_domain": "\u8bf7\u6c42\u57df\u540d\u6392\u884c",
|
||||
"for_last_24_hours": "\u5728\u8fc7\u53bb 24 \u5c0f\u65f6",
|
||||
"no_domains_found": "\u672a\u627e\u5230\u57df\u540d",
|
||||
"requests_count": "\u8bf7\u6c42\u6570",
|
||||
"top_blocked_domains": "\u88ab\u62e6\u622a\u57df\u540d\u6392\u884c",
|
||||
"top_clients": "\u5ba2\u6237\u7aef\u6392\u884c",
|
||||
"no_clients_found": "\u672a\u627e\u5230\u5ba2\u6237\u7aef",
|
||||
"general_statistics": "\u6982\u51b5\u7edf\u8ba1",
|
||||
"number_of_dns_query_24_hours": "\u8fc7\u53bb 24 \u5c0f\u65f6\u5185\u5904\u7406\u7684 DNS \u8bf7\u6c42\u603b\u6570",
|
||||
"number_of_dns_query_blocked_24_hours": "\u88ab\u5e7f\u544a\u8fc7\u6ee4\u5668\u548c Hosts \u62e6\u622a\u6e05\u5355\u62e6\u622a\u7684 DNS \u8bf7\u6c42\u603b\u6570",
|
||||
"number_of_dns_query_blocked_24_hours_by_sec": "\u88ab AdGuard \u5b89\u5168\u6d4f\u89c8\u6a21\u5757\u62e6\u622a\u7684 DNS \u8bf7\u6c42\u603b\u6570",
|
||||
"number_of_dns_query_blocked_24_hours_adult": "\u88ab\u62e6\u622a\u7684\u6210\u4eba\u7f51\u7ad9\u603b\u6570",
|
||||
"enforced_save_search": "\u5f3a\u5236\u5b89\u5168\u641c\u7d22",
|
||||
"number_of_dns_query_to_safe_search": "\u542f\u7528\u5f3a\u5236\u5b89\u5168\u641c\u7d22\u540e\u5bf9\u641c\u7d22\u5f15\u64ce\u7684 DNS \u8bf7\u6c42\u603b\u6570",
|
||||
"average_processing_time": "\u5e73\u5747\u5904\u7406\u65f6\u95f4",
|
||||
"average_processing_time_hint": "\u5904\u7406 DNS \u8bf7\u6c42\u7684\u5e73\u5747\u65f6\u95f4\uff08\u6beb\u79d2\uff09",
|
||||
"block_domain_use_filters_and_hosts": "\u4f7f\u7528\u8fc7\u6ee4\u5668\u548c Hosts \u6587\u4ef6\u4ee5\u62e6\u622a\u6307\u5b9a\u57df\u540d",
|
||||
"filters_block_toggle_hint": "\u4f60\u53ef\u4ee5\u5728 <a href='#filters'>\u8fc7\u6ee4\u5668<\/a> \u8bbe\u7f6e\u4e2d\u6dfb\u52a0\u8fc7\u6ee4\u89c4\u5219\u3002",
|
||||
"use_adguard_browsing_sec": "\u4f7f\u7528 AdGuard\u3010\u6d4f\u89c8\u5b89\u5168\u3011\u7f51\u9875\u670d\u52a1",
|
||||
"use_adguard_browsing_sec_hint": "AdGuard Home \u5c06\u68c0\u67e5\u57df\u540d\u662f\u5426\u88ab\u6d4f\u89c8\u5b89\u5168\u670d\u52a1\u5217\u5165\u9ed1\u540d\u5355\u3002\u5b83\u5c06\u4f7f\u7528\u9690\u79c1\u6027\u5f3a\u7684\u68c0\u7d22 API \u6765\u6267\u884c\u68c0\u67e5\uff0c\u53ea\u6709\u57df\u540d\u7684 SHA256 \u7684\u77ed\u524d\u7f00\u4f1a\u88ab\u53d1\u9001\u5230\u670d\u52a1\u5668\u3002",
|
||||
"use_adguard_parental": "\u4f7f\u7528 AdGuard \u3010\u5bb6\u957f\u63a7\u5236\u3011\u670d\u52a1",
|
||||
"use_adguard_parental_hint": "AdGuard Home \u5c06\u4f7f\u7528\u4e0e\u6d4f\u89c8\u5b89\u5168\u670d\u52a1\u76f8\u540c\u7684\u9690\u79c1\u6027\u5f3a\u7684 API \u6765\u68c0\u67e5\u57df\u540d\u6307\u5411\u7684\u7f51\u7ad9\u662f\u5426\u5305\u542b\u6210\u4eba\u5185\u5bb9\u3002",
|
||||
"enforce_safe_search": "\u5f3a\u5236\u5b89\u5168\u641c\u7d22",
|
||||
"enforce_save_search_hint": "AdGuard Home \u5c06\u5bf9\u4ee5\u4e0b\u641c\u7d22\u5f15\u64ce\u5f3a\u5236\u542f\u7528\u5b89\u5168\u641c\u7d22\uff1aGoogle\u3001YouTube\u3001Bing \u548c Yandex\u3002",
|
||||
"no_servers_specified": "\u672a\u627e\u5230\u6307\u5b9a\u7684\u670d\u52a1\u5668",
|
||||
"no_settings": "\u672a\u627e\u5230\u8bbe\u7f6e",
|
||||
"general_settings": "\u5e38\u89c4\u8bbe\u7f6e",
|
||||
"upstream_dns": "\u4e0a\u6e38 DNS \u670d\u52a1\u5668",
|
||||
"upstream_dns_hint": "\u5982\u679c\u6b64\u5904\u7559\u7a7a\uff0cAdGuard Home \u5c06\u4f1a\u4f7f\u7528 <a href='https:\/\/1.1.1.1\/' target='_blank'>Cloudflare DNS<\/a> \u4f5c\u4e3a\u4e0a\u6e38 DNS\u3002\u5982\u679c\u60f3\u8981\u4f7f\u7528\u4f7f\u7528 DNS over TLS\uff0c\u8bf7\u4ee5 tls:\/\/ \u4e3a\u5f00\u5934\u3002",
|
||||
"test_upstream_btn": "\u6d4b\u8bd5\u4e0a\u6e38 DNS",
|
||||
"apply_btn": "\u5e94\u7528",
|
||||
"disabled_filtering_toast": "\u8fc7\u6ee4\u5668\u5df2\u7981\u7528",
|
||||
"enabled_filtering_toast": "\u8fc7\u6ee4\u5668\u5df2\u542f\u7528",
|
||||
"disabled_safe_browsing_toast": "\u5b89\u5168\u6d4f\u89c8\u5df2\u7981\u7528",
|
||||
"enabled_safe_browsing_toast": "\u5b89\u5168\u6d4f\u89c8\u5df2\u542f\u7528",
|
||||
"disabled_parental_toast": "\u5bb6\u957f\u63a7\u5236\u5df2\u7981\u7528",
|
||||
"enabled_parental_toast": "\u5bb6\u957f\u63a7\u5236\u5df2\u542f\u7528",
|
||||
"disabled_safe_search_toast": "\u5b89\u5168\u641c\u7d22\u5df2\u7981\u7528",
|
||||
"enabled_save_search_toast": "\u5b89\u5168\u641c\u7d22\u5df2\u542f\u7528",
|
||||
"enabled_table_header": "\u5df2\u542f\u7528",
|
||||
"name_table_header": "\u540d\u79f0",
|
||||
"filter_url_table_header": "\u8fc7\u6ee4\u5668\u5730\u5740",
|
||||
"rules_count_table_header": "\u89c4\u5219\u6570",
|
||||
"last_time_updated_table_header": "\u4e0a\u6b21\u66f4\u65b0\u65f6\u95f4",
|
||||
"actions_table_header": "\u6d3b\u8dc3\u72b6\u6001",
|
||||
"delete_table_action": "\u5220\u9664",
|
||||
"filters_and_hosts": "\u8fc7\u6ee4\u5668\u548c Hosts \u62e6\u622a\u6e05\u5355",
|
||||
"filters_and_hosts_hint": "AdGuard Home \u53ef\u4ee5\u89e3\u6790\u57fa\u7840\u7684 adblock \u89c4\u5219\u548c Hosts \u8bed\u6cd5\u3002",
|
||||
"no_filters_added": "\u672a\u6dfb\u52a0\u4efb\u4f55\u8fc7\u6ee4\u5668",
|
||||
"add_filter_btn": "\u6dfb\u52a0\u8fc7\u6ee4\u5668",
|
||||
"cancel_btn": "\u53d6\u6d88",
|
||||
"enter_name_hint": "\u8f93\u5165\u540d\u79f0",
|
||||
"enter_url_hint": "\u8f93\u5165 URL",
|
||||
"check_updates_btn": "\u68c0\u67e5\u66f4\u65b0",
|
||||
"new_filter_btn": "\u8ba2\u9605\u65b0\u7684\u8fc7\u6ee4\u5668",
|
||||
"enter_valid_filter_url": "\u8f93\u5165\u4e00\u4e2a\u8fc7\u6ee4\u5668\u8ba2\u9605\u6216 Hosts \u6587\u4ef6\u7684\u6709\u6548 URL",
|
||||
"custom_filter_rules": "\u81ea\u5b9a\u4e49\u8fc7\u6ee4\u5668\u89c4\u5219",
|
||||
"custom_filter_rules_hint": "\u8bf7\u786e\u4fdd\u6bcf\u884c\u53ea\u8f93\u5165\u4e00\u6761\u89c4\u5219\u3002\u4f60\u53ef\u4ee5\u8f93\u5165\u7b26\u5408 adblock \u8bed\u6cd5\u6216 Hosts \u8bed\u6cd5\u7684\u89c4\u5219\u3002",
|
||||
"examples_title": "\u8303\u4f8b",
|
||||
"example_meaning_filter_block": "\u62e6\u622a example.org \u57df\u540d\u53ca\u5176\u6240\u6709\u5b50\u57df\u540d",
|
||||
"example_meaning_filter_whitelist": "\u653e\u884c example.org \u53ca\u5176\u6240\u6709\u5b50\u57df\u540d",
|
||||
"example_meaning_host_block": "AdGuard Home \u73b0\u5728\u5c06\u4f1a\u628a example.org\uff08\u4f46\u4e0d\u5305\u62ec\u5b83\u7684\u5b50\u57df\u540d\uff09\u89e3\u6790\u5230 127.0.0.1\u3002",
|
||||
"example_comment": "! \u8fd9\u662f\u4e00\u884c\u6ce8\u91ca",
|
||||
"example_comment_meaning": "\u53ea\u662f\u4e00\u6761\u6ce8\u91ca",
|
||||
"example_comment_hash": "# \u8fd9\u4e5f\u662f\u4e00\u884c\u6ce8\u91ca",
|
||||
"example_regex_meaning": "\u963b\u6b62\u8bbf\u95ee\u4e0e\u6307\u5b9a\u7684\u6b63\u5219\u8868\u8fbe\u5f0f\u5339\u914d\u7684\u57df\u540d",
|
||||
"example_upstream_regular": "\u5e38\u89c4 DNS\uff08\u57fa\u4e8e UDP\uff09",
|
||||
"example_upstream_dot": "\u52a0\u5bc6 <0>DNS-over-TLS<\/0>",
|
||||
"example_upstream_doh": "\u52a0\u5bc6 <0>DNS-over-HTTPS<\/0>",
|
||||
"example_upstream_sdns": "\u4f60\u53ef\u4ee5\u4f7f\u7528 <0>DNSCrypt<\/0> \u7684 <1>DNS Stamps<\/1> \u6216\u8005 <2>DNS-over-HTTPS<\/2> \u89e3\u6790\u5668",
|
||||
"example_upstream_tcp": "\u5e38\u89c4 DNS\uff08\u57fa\u4e8e TCP \uff09",
|
||||
"all_filters_up_to_date_toast": "\u6240\u6709\u8fc7\u6ee4\u5668\u5df2\u66f4\u65b0\u81f3\u6700\u65b0",
|
||||
"updated_upstream_dns_toast": "\u4e0a\u6e38 DNS \u5df2\u66f4\u65b0",
|
||||
"dns_test_ok_toast": "\u6307\u5b9a\u7684 DNS \u670d\u52a1\u5668\u73b0\u5df2\u6b63\u5e38\u8fd0\u884c",
|
||||
"dns_test_not_ok_toast": "\u670d\u52a1\u5668 \"{{key}}\"\uff1a\u65e0\u6cd5\u4f7f\u7528\uff0c\u8bf7\u68c0\u67e5\u4f60\u8f93\u5165\u7684\u662f\u5426\u6b63\u786e",
|
||||
"unblock_btn": "\u653e\u884c",
|
||||
"block_btn": "\u62e6\u622a",
|
||||
"time_table_header": "\u65f6\u95f4",
|
||||
"domain_name_table_header": "\u57df\u540d",
|
||||
"type_table_header": "\u7c7b\u578b",
|
||||
"response_table_header": "\u54cd\u5e94",
|
||||
"client_table_header": "\u5ba2\u6237\u7aef",
|
||||
"empty_response_status": "\u7a7a",
|
||||
"show_all_filter_type": "\u663e\u793a\u6240\u6709",
|
||||
"show_filtered_type": "\u663e\u793a\u88ab\u62e6\u622a\u7684",
|
||||
"no_logs_found": "\u672a\u627e\u5230\u65e5\u5fd7",
|
||||
"disabled_log_btn": "\u7981\u7528\u65e5\u5fd7",
|
||||
"download_log_file_btn": "\u4e0b\u8f7d\u65e5\u5fd7\u6587\u4ef6",
|
||||
"refresh_btn": "\u5237\u65b0",
|
||||
"enabled_log_btn": "\u542f\u7528\u65e5\u5fd7",
|
||||
"last_dns_queries": "\u6700\u8fd1\u7684 5000 \u4e2a DNS \u8bf7\u6c42",
|
||||
"previous_btn": "\u4e0a\u4e00\u9875",
|
||||
"next_btn": "\u4e0b\u4e00\u9875",
|
||||
"loading_table_status": "\u52a0\u8f7d\u4e2d\u2026\u2026",
|
||||
"page_table_footer_text": "\u9875",
|
||||
"of_table_footer_text": "\u5728",
|
||||
"rows_table_footer_text": "\u884c",
|
||||
"updated_custom_filtering_toast": "\u81ea\u5b9a\u4e49\u8fc7\u6ee4\u89c4\u5219\u5df2\u66f4\u65b0",
|
||||
"rule_removed_from_custom_filtering_toast": "\u89c4\u5219\u5df2\u4ece\u81ea\u5b9a\u4e49\u8fc7\u6ee4\u89c4\u5219\u5217\u8868\u4e2d\u79fb\u9664",
|
||||
"rule_added_to_custom_filtering_toast": "\u89c4\u5219\u5df2\u6dfb\u52a0\u5230\u81ea\u5b9a\u4e49\u8fc7\u6ee4\u89c4\u5219\u5217\u8868\u4e2d",
|
||||
"query_log_disabled_toast": "\u67e5\u8be2\u65e5\u5fd7\u5df2\u7981\u7528",
|
||||
"query_log_enabled_toast": "\u67e5\u8be2\u65e5\u5fd7\u5df2\u542f\u7528",
|
||||
"source_label": "\u6e90",
|
||||
"found_in_known_domain_db": "\u6210\u529f\u5728\u5df2\u77e5\u57df\u540d\u6570\u636e\u5e93\u4e2d\u67e5\u8be2\u5230",
|
||||
"category_label": "\u7c7b\u522b",
|
||||
"rule_label": "\u89c4\u5219",
|
||||
"filter_label": "\u8fc7\u6ee4\u5668",
|
||||
"unknown_filter": "\u672a\u77e5\u8fc7\u6ee4\u5668 {{filterId}}",
|
||||
"install_welcome_title": "\u6b22\u8fce\u4f7f\u7528 AdGuard Home\uff01",
|
||||
"install_welcome_desc": "AdGuard Home \u662f\u4e00\u4e2a\u53ef\u5728\u7279\u5b9a\u7f51\u7edc\u8303\u56f4\u5185\u62e6\u622a\u6240\u6709\u5e7f\u544a\u548c\u8ddf\u8e2a\u5668\u7684 DNS \u670d\u52a1\u5668\u3002\u5b83\u7684\u76ee\u7684\u662f\u8ba9\u60a8\u63a7\u5236\u6574\u4e2a\u7f51\u7edc\u548c\u60a8\u7684\u6240\u6709\u8bbe\u5907\uff0c\u4e14\u4e0d\u9700\u8981\u4f7f\u7528\u4efb\u4f55\u5ba2\u6237\u7aef\u7a0b\u5e8f\u3002",
|
||||
"install_settings_title": "\u7f51\u9875\u7ba1\u7406\u754c\u9762",
|
||||
"install_settings_listen": "\u76d1\u542c\u63a5\u53e3",
|
||||
"install_settings_port": "\u7aef\u53e3",
|
||||
"install_settings_interface_link": "\u60a8\u53ef\u4ee5\u901a\u8fc7\u4ee5\u4e0b\u5730\u5740\u8bbf\u95ee\u60a8\u7684 AdGuard Home \u7f51\u9875\u7ba1\u7406\u754c\u9762\uff1a",
|
||||
"form_error_port": "\u8f93\u5165\u6709\u6548\u7684\u7aef\u53e3\u503c",
|
||||
"install_settings_dns": "DNS \u670d\u52a1\u5668",
|
||||
"install_settings_dns_desc": "\u60a8\u5c06\u9700\u8981\u4f7f\u7528\u4ee5\u4e0b\u5730\u5740\u6765\u8bbe\u7f6e\u60a8\u7684\u8bbe\u5907\u6216\u8def\u7531\u5668\u7684 DNS \u670d\u52a1\u5668\uff1a",
|
||||
"install_settings_all_interfaces": "\u6240\u6709\u63a5\u53e3",
|
||||
"install_auth_title": "\u8eab\u4efd\u8ba4\u8bc1",
|
||||
"install_auth_desc": "\u6211\u4eec\u5f3a\u70c8\u5efa\u8bae\u60a8\u4e3a AdGuard Home \u7684\u7f51\u9875\u7ba1\u7406\u754c\u9762\u914d\u7f6e\u5bc6\u7801\u3002\u5c3d\u7ba1\u8be5\u9875\u9762\u53ea\u80fd\u901a\u8fc7\u60a8\u7684\u672c\u5730\u7f51\u7edc\u8bbf\u95ee\uff0c\u4f46\u907f\u514d\u5b83\u88ab\u4e0d\u52a0\u9650\u5236\u5730\u8bbf\u95ee\u4ecd\u5341\u5206\u91cd\u8981\u3002",
|
||||
"install_auth_username": "\u7528\u6237\u540d",
|
||||
"install_auth_password": "\u5bc6\u7801",
|
||||
"install_auth_confirm": "\u786e\u8ba4\u5bc6\u7801",
|
||||
"install_auth_username_enter": "\u8f93\u5165\u7528\u6237\u540d",
|
||||
"install_auth_password_enter": "\u8f93\u5165\u5bc6\u7801",
|
||||
"install_step": "\u6b65\u9aa4",
|
||||
"install_devices_title": "\u914d\u7f6e\u60a8\u7684\u8bbe\u5907",
|
||||
"install_devices_desc": "\u4e3a\u4fdd\u8bc1 AdGuard Home \u53ef\u4ee5\u5f00\u59cb\u6b63\u5e38\u5de5\u4f5c\uff0c\u60a8\u9700\u8981\u5728\u8bbe\u5907\u4e0a\u5bf9\u5176\u8fdb\u884c\u914d\u7f6e\u3002",
|
||||
"install_submit_title": "\u606d\u559c\u60a8\uff01",
|
||||
"install_submit_desc": "\u5b89\u88c5\u8fc7\u7a0b\u5df2\u7ecf\u5b8c\u6210\uff0c\u60a8\u53ef\u4ee5\u5f00\u59cb\u4f7f\u7528 AdGuard Home \u4e86\u3002",
|
||||
"install_devices_router": "\u8def\u7531\u5668",
|
||||
"install_devices_router_desc": "\u6b64\u8bbe\u7f6e\u5c06\u81ea\u52a8\u8986\u76d6\u8fde\u63a5\u5230\u60a8\u7684\u5bb6\u5ead\u8def\u7531\u5668\u7684\u6240\u6709\u8bbe\u5907\uff0c\u60a8\u4e0d\u9700\u8981\u624b\u52a8\u914d\u7f6e\u5b83\u4eec\u3002",
|
||||
"install_devices_address": "AdGuard Home DNS \u670d\u52a1\u5668\u6b63\u5728\u76d1\u542c\u4ee5\u4e0b\u5730\u5740",
|
||||
"install_devices_router_list_1": "\u6253\u5f00\u60a8\u7684\u8def\u7531\u5668\u914d\u7f6e\u754c\u9762\u3002\u901a\u5e38\u60c5\u51b5\u4e0b\uff0c\u60a8\u53ef\u4ee5\u901a\u8fc7\u6d4f\u89c8\u5668\u8bbf\u95ee\u5730\u5740\uff08\u5982 http:\/\/192.168.0.1\/ \u6216 http:\/\/192.168.1.1 \uff09\u3002\u6253\u5f00\u540e\u60a8\u53ef\u80fd\u9700\u8981\u8f93\u5165\u5bc6\u7801\u4ee5\u8fdb\u5165\u914d\u7f6e\u754c\u9762\u3002\u5982\u679c\u60a8\u4e0d\u8bb0\u5f97\u5bc6\u7801\uff0c\u901a\u5e38\u53ef\u4ee5\u901a\u8fc7\u6309\u4e0b\u8def\u7531\u5668\u4e0a\u7684\u91cd\u7f6e\u6309\u94ae\u6765\u91cd\u8bbe\u5bc6\u7801\u3002\u4e00\u4e9b\u8def\u7531\u5668\u53ef\u80fd\u9700\u8981\u901a\u8fc7\u7279\u5b9a\u7684\u5e94\u7528\u6765\u8fdb\u884c\u8fd9\u4e00\u64cd\u4f5c\uff0c\u8bf7\u786e\u4fdd\u60a8\u5df2\u7ecf\u5728\u8ba1\u7b97\u673a\u6216\u624b\u673a\u4e0a\u5b89\u88c5\u4e86\u76f8\u5173\u5e94\u7528\u3002",
|
||||
"install_devices_router_list_2": "\u627e\u5230\u8def\u7531\u5668\u7684 DHCP\/DNS \u8bbe\u7f6e\u9875\u9762\u3002\u60a8\u4f1a\u5728 DNS \u8fd9\u4e00\u5355\u8bcd\u65c1\u8fb9\u627e\u5230\u4e24\u5230\u4e09\u884c\u5141\u8bb8\u8f93\u5165\u7684\u8f93\u5165\u6846\uff0c\u6bcf\u4e00\u884c\u8f93\u5165\u6846\u5206\u4e3a\u56db\u7ec4\uff0c\u6bcf\u7ec4\u5141\u8bb8\u8f93\u5165\u4e00\u5230\u4e09\u4e2a\u6570\u5b57\u3002",
|
||||
"install_devices_router_list_3": "\u8bf7\u5728\u6b64\u5904\u8f93\u5165\u60a8\u7684 AdGuard Home \u670d\u52a1\u5668\u5730\u5740\u3002",
|
||||
"install_devices_windows_list_1": "\u901a\u8fc7\u5f00\u59cb\u83dc\u5355\u6216 Windows \u641c\u7d22\u529f\u80fd\u6253\u5f00\u63a7\u5236\u9762\u677f\u3002",
|
||||
"install_devices_windows_list_2": "\u70b9\u51fb\u8fdb\u5165 \u201d\u7f51\u7edc\u548c Internet\u201c \u540e\uff0c\u518d\u6b21\u70b9\u51fb\u8fdb\u5165 \u201c\u7f51\u7edc\u548c\u5171\u4eab\u4e2d\u5fc3\u201d",
|
||||
"install_devices_windows_list_3": "\u5728\u7a97\u53e3\u7684\u5de6\u4fa7\u627e\u5230 \u201d\u66f4\u6539\u9002\u914d\u5668\u8bbe\u7f6e\u201c \u5e76\u70b9\u51fb\u8fdb\u5165\u3002",
|
||||
"install_devices_windows_list_4": "\u9009\u62e9\u60a8\u6b63\u5728\u8fde\u63a5\u7684\u7f51\u7edc\u8bbe\u5907\uff0c\u53f3\u51fb\u5b83\u5e76\u9009\u62e9 \u201d\u5c5e\u6027\u201c \u3002",
|
||||
"install_devices_windows_list_5": "\u5728\u5217\u8868\u4e2d\u627e\u5230 \u201dInternet \u534f\u8bae\u7248\u672c 4 (TCP\/IPv4)\u201c \uff0c\u9009\u62e9\u5e76\u518d\u6b21\u70b9\u51fb \u201d\u5c5e\u6027\u201c \u3002",
|
||||
"install_devices_windows_list_6": "\u9009\u62e9 \u201d\u4f7f\u7528\u4e0b\u9762\u7684 DNS \u670d\u52a1\u5668\u5730\u5740\u201c \uff0c\u5e76\u8f93\u5165\u60a8\u7684 AdGuard Home \u670d\u52a1\u5668\u5730\u5740\u3002",
|
||||
"install_devices_macos_list_1": "\u70b9\u51fb\u82f9\u679c\u56fe\u6807\uff0c\u8fdb\u5165 \u201d\u7cfb\u7edf\u9996\u9009\u9879\u201c\u3002",
|
||||
"install_devices_macos_list_2": "\u70b9\u51fb \u201d\u7f51\u7edc\u201c \u3002",
|
||||
"install_devices_macos_list_3": "\u9009\u62e9\u5728\u5217\u8868\u4e2d\u7684\u7b2c\u4e00\u4e2a\u8fde\u63a5\uff0c\u5e76\u70b9\u51fb \u201d\u9ad8\u7ea7\u201c \u3002",
|
||||
"install_devices_macos_list_4": "\u9009\u62e9 \u201dDNS\u201c \u9009\u9879\u5361\uff0c\u5e76\u8f93\u5165\u60a8\u7684 AdGuard Home \u670d\u52a1\u5668\u5730\u5740\u3002",
|
||||
"install_devices_android_list_1": "\u5728\u5b89\u5353\u4e3b\u5c4f\u5e55\u83dc\u5355\u4e2d\u70b9\u51fb\u8bbe\u7f6e\u3002",
|
||||
"install_devices_android_list_2": "\u70b9\u51fb\u83dc\u5355\u4e0a\u7684 \u201d\u65e0\u7ebf\u5c40\u57df\u7f51\u201c \u9009\u9879\u3002\u5728\u5c4f\u5e55\u4e0a\u5c06\u5217\u51fa\u6240\u6709\u53ef\u7528\u7684\u7f51\u7edc\uff08\u8702\u7a9d\u79fb\u52a8\u7f51\u7edc\u4e0d\u652f\u6301\u4fee\u6539 DNS \uff09\u3002",
|
||||
"install_devices_android_list_3": "\u957f\u6309\u5f53\u524d\u5df2\u8fde\u63a5\u7684\u7f51\u7edc\uff0c\u7136\u540e\u70b9\u51fb \u201d\u4fee\u6539\u7f51\u7edc\u8bbe\u7f6e\u201c \u3002",
|
||||
"install_devices_android_list_4": "\u5728\u67d0\u4e9b\u8bbe\u5907\u4e0a\uff0c\u60a8\u53ef\u80fd\u9700\u8981\u9009\u4e2d \u201d\u9ad8\u7ea7\u201c \u590d\u9009\u6846\u4ee5\u67e5\u770b\u8fdb\u4e00\u6b65\u7684\u8bbe\u7f6e\u3002\u60a8\u53ef\u80fd\u9700\u8981\u8c03\u6574\u60a8\u5b89\u5353\u8bbe\u5907\u7684 DNS \u8bbe\u7f6e\uff0c\u6216\u662f\u9700\u8981\u5c06 IP \u8bbe\u7f6e\u4ece DHCP \u5207\u6362\u5230\u9759\u6001\u3002",
|
||||
"install_devices_android_list_5": "\u5c06 \"DNS 1 \/ \u4e3b DNS\" \u548c \u201dDNS 2 \/ \u526f DNS\u201c \u7684\u503c\u6539\u4e3a\u60a8\u7684 AdGuard Home \u670d\u52a1\u5668\u5730\u5740\u3002",
|
||||
"install_devices_ios_list_1": "\u4ece\u4e3b\u5c4f\u5e55\u4e2d\u70b9\u51fb \u201d\u8bbe\u7f6e\u201c \u3002",
|
||||
"install_devices_ios_list_2": "\u4ece\u5de6\u4fa7\u76ee\u5f55\u4e2d\u9009\u62e9 \u201d\u65e0\u7ebf\u5c40\u57df\u7f51\u201c\uff08\u79fb\u52a8\u6570\u636e\u7f51\u7edc\u73af\u5883\u4e0b\u4e0d\u652f\u6301\u4fee\u6539 DNS \uff09\u3002",
|
||||
"install_devices_ios_list_3": "\u70b9\u51fb\u5f53\u524d\u5df2\u8fde\u63a5\u7f51\u7edc\u7684\u540d\u79f0\u3002",
|
||||
"install_devices_ios_list_4": "\u5728 DNS \u5b57\u6bb5\u4e2d\u8f93\u5165\u60a8\u7684 AdGuard Home \u670d\u52a1\u5668\u5730\u5740\u3002",
|
||||
"get_started": "\u5f00\u59cb\u914d\u7f6e",
|
||||
"next": "\u4e0b\u4e00\u6b65",
|
||||
"open_dashboard": "\u6253\u5f00\u4eea\u8868\u76d8",
|
||||
"install_saved": "\u4fdd\u5b58\u6210\u529f",
|
||||
"encryption_title": "\u52a0\u5bc6",
|
||||
"encryption_desc": "\u4e3a DNS \u4e0e\u7f51\u9875\u7ba1\u7406\u754c\u9762\u542f\u7528\u52a0\u5bc6\uff08HTTPS\/TLS\uff09",
|
||||
"encryption_config_saved": "\u52a0\u5bc6\u914d\u7f6e\u5df2\u4fdd\u5b58",
|
||||
"encryption_server": "\u670d\u52a1\u5668\u540d\u79f0",
|
||||
"encryption_server_enter": "\u8f93\u5165\u60a8\u7684\u57df\u540d",
|
||||
"encryption_server_desc": "\u82e5\u8981\u4f7f\u7528 HTTPS \uff0c\u60a8\u9700\u8981\u8f93\u5165\u4e0e SSL \u8bc1\u4e66\u76f8\u5339\u914d\u7684\u670d\u52a1\u5668\u540d\u79f0\u3002",
|
||||
"encryption_redirect": "HTTPS \u81ea\u52a8\u91cd\u5b9a\u5411",
|
||||
"encryption_redirect_desc": "\u5982\u679c\u52fe\u9009\u6b64\u9009\u9879\uff0cAdGuard Home \u5c06\u81ea\u52a8\u5c06\u60a8\u4ece HTTP \u91cd\u5b9a\u5411\u5230 HTTPS \u5730\u5740\u3002",
|
||||
"encryption_https": "HTTPS \u7aef\u53e3",
|
||||
"encryption_https_desc": "\u5982\u679c\u914d\u7f6e\u4e86 HTTPS \u7aef\u53e3\uff0cAdGuard Home \u7ba1\u7406\u754c\u9762\u5c06\u53ef\u4ee5\u901a\u8fc7 HTTPS \u8bbf\u95ee\uff0c\u5b83\u8fd8\u5c06\u5728\u5728 '\/dns-query' \u4f4d\u7f6e\u63d0\u4f9b DNS-over-HTTPS \u3002",
|
||||
"encryption_dot": "DNS-over-TLS \u7aef\u53e3",
|
||||
"encryption_dot_desc": "\u5982\u679c\u914d\u7f6e\u4e86\u6b64\u7aef\u53e3\uff0cAdGuard Home \u5c06\u5728\u6b64\u7aef\u53e3\u4e0a\u8fd0\u884c\u4e00\u4e2a DNS-over-TLS \u670d\u52a1\u5668\u3002",
|
||||
"encryption_certificates": "\u8bc1\u4e66",
|
||||
"encryption_certificates_desc": "\u4e3a\u4e86\u4f7f\u7528\u52a0\u5bc6\uff0c\u60a8\u9700\u8981\u4e3a\u57df\u63d0\u4f9b\u6709\u6548\u7684 SSL \u8bc1\u4e66\u94fe\u3002\u60a8\u53ef\u4ee5\u5728 <0>{{link}}<\/0> \u4e0a\u83b7\u5f97\u514d\u8d39\u8bc1\u4e66\uff0c\u4e5f\u53ef\u4ee5\u4ece\u53d7\u4fe1\u4efb\u7684\u8bc1\u4e66\u9881\u53d1\u673a\u6784\u8d2d\u4e70\u8bc1\u4e66\u3002",
|
||||
"encryption_certificates_input": "\u5c06\u60a8\u4ee5 PEM \u683c\u5f0f\u7f16\u7801\u7684\u8bc1\u4e66\u590d\u5236\u7c98\u8d34\u5230\u6b64\u5904\u3002",
|
||||
"encryption_status": "\u72b6\u6001",
|
||||
"encryption_expire": "\u6709\u6548\u671f",
|
||||
"encryption_key": "\u79c1\u94a5",
|
||||
"encryption_key_input": "\u5c06\u60a8\u4ee5 PEM \u683c\u5f0f\u7f16\u7801\u7684\u8bc1\u4e66\u79c1\u94a5\u590d\u5236\u7c98\u8d34\u5230\u6b64\u5904\u3002",
|
||||
"encryption_enable": "\u542f\u7528\u52a0\u5bc6\uff08HTTPS\u3001DNS-over-HTTPS\u3001DNS-over-TLS\uff09",
|
||||
"encryption_enable_desc": "\u5982\u679c\u542f\u7528\u52a0\u5bc6\u9009\u9879\uff0cAdGuard Home \u7684\u7f51\u9875\u7ba1\u7406\u754c\u9762\u5c06\u901a\u8fc7 HTTPS \u8fde\u63a5\u8bbf\u95ee\uff0c\u540c\u65f6 DNS \u670d\u52a1\u5668\u5c06\u76d1\u542c\u901a\u8fc7 DNS-over-HTTPS \u4e0e DNS-over-TLS \u53d1\u9001\u7684\u8bf7\u6c42\u3002",
|
||||
"encryption_chain_valid": "\u8bc1\u4e66\u94fe\u9a8c\u8bc1\u6709\u6548",
|
||||
"encryption_chain_invalid": "\u8bc1\u4e66\u94fe\u9a8c\u8bc1\u65e0\u6548",
|
||||
"encryption_key_valid": "\u8be5 {{type}} \u79c1\u94a5\u9a8c\u8bc1\u6709\u6548",
|
||||
"encryption_key_invalid": "\u8be5 {{type}} \u79c1\u94a5\u9a8c\u8bc1\u65e0\u6548",
|
||||
"encryption_subject": "\u4f7f\u7528\u8005",
|
||||
"encryption_issuer": "\u9881\u53d1\u8005",
|
||||
"encryption_hostnames": "\u4e3b\u673a\u540d",
|
||||
"encryption_reset": "\u60a8\u786e\u5b9a\u60f3\u8981\u91cd\u7f6e\u52a0\u5bc6\u8bbe\u7f6e\uff1f",
|
||||
"topline_expiring_certificate": "\u60a8\u7684 SSL \u8bc1\u4e66\u5373\u5c06\u8fc7\u671f\u3002\u8bf7\u66f4\u65b0 <0>\u52a0\u5bc6\u8bbe\u7f6e<\/0> \u3002",
|
||||
"topline_expired_certificate": "\u60a8\u7684 SSL \u8bc1\u4e66\u5df2\u8fc7\u671f\u3002\u8bf7\u66f4\u65b0 <0>\u52a0\u5bc6\u8bbe\u7f6e<\/0> \u3002",
|
||||
"form_error_port_range": "\u8f93\u5165 80 - 65535 \u8303\u56f4\u5185\u7684\u7aef\u53e3\u503c",
|
||||
"form_error_port_unsafe": "\u8fd9\u662f\u4e00\u4e2a\u4e0d\u5b89\u5168\u7684\u7aef\u53e3",
|
||||
"form_error_equal": "\u4e0d\u5e94\u8be5\u76f8\u540c",
|
||||
"form_error_password": "\u5bc6\u7801\u4e0d\u5339\u914d",
|
||||
"reset_settings": "\u91cd\u7f6e\u8bbe\u7f6e",
|
||||
"update_announcement": "AdGuard Home {{version}} \u73b0\u5df2\u53d1\u5e03\uff01 <0>\u70b9\u51fb\u6b64\u5904<\/0> \u4ee5\u83b7\u53d6\u8be6\u7ec6\u4fe1\u606f\u3002"
|
||||
}
|
||||
@@ -1,14 +1,18 @@
|
||||
{
|
||||
"upstream_parallel": "\u900f\u904e\u540c\u6642\u5730\u67e5\u8a62\u6240\u6709\u4e0a\u6e38\u7684\u4f3a\u670d\u5668\uff0c\u4f7f\u7528\u4e26\u884c\u7684\u67e5\u8a62\u4ee5\u52a0\u901f\u89e3\u6790",
|
||||
"bootstrap_dns": "\u81ea\u6211\u555f\u52d5\uff08Bootstrap\uff09DNS \u4f3a\u670d\u5668",
|
||||
"bootstrap_dns_desc": "\u81ea\u6211\u555f\u52d5\uff08Bootstrap\uff09DNS\u4f3a\u670d\u5668\u88ab\u7528\u65bc\u89e3\u6790\u60a8\u660e\u78ba\u6307\u5b9a\u4f5c\u70ba\u4e0a\u6e38\u7684DoH\/DoT\u89e3\u6790\u5668\u4e4bIP\u4f4d\u5740\u3002",
|
||||
"url_added_successfully": "\u7db2\u5740\u88ab\u6210\u529f\u5730\u52a0\u5165",
|
||||
"check_dhcp_servers": "\u6aa2\u67e5\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u4f3a\u670d\u5668",
|
||||
"save_config": "\u5132\u5b58\u914d\u7f6e",
|
||||
"enabled_dhcp": "\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u4f3a\u670d\u5668\u5df2\u88ab\u555f\u7528",
|
||||
"disabled_dhcp": "\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u4f3a\u670d\u5668\u5df2\u88ab\u7981\u7528",
|
||||
"dhcp_title": "\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u4f3a\u670d\u5668",
|
||||
"enabled_dhcp": "\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u4f3a\u670d\u5668\u88ab\u555f\u7528",
|
||||
"disabled_dhcp": "\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u4f3a\u670d\u5668\u88ab\u7981\u7528",
|
||||
"dhcp_title": "\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u4f3a\u670d\u5668\uff08\u5be6\u9a57\u6027\u7684\uff01\uff09",
|
||||
"dhcp_description": "\u5982\u679c\u60a8\u7684\u8def\u7531\u5668\u672a\u63d0\u4f9b\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u8a2d\u5b9a\uff0c\u60a8\u53ef\u4f7f\u7528AdGuard\u81ea\u8eab\u5167\u5efa\u7684DHCP\u4f3a\u670d\u5668\u3002",
|
||||
"dhcp_enable": "\u555f\u7528\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u4f3a\u670d\u5668",
|
||||
"dhcp_disable": "\u7981\u7528\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u4f3a\u670d\u5668",
|
||||
"dhcp_not_found": "\u65bc\u7db2\u8def\u4e0a\u7121\u5df2\u767c\u73fe\u4e4b\u6709\u6548\u7684\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u4f3a\u670d\u5668\u3002\u555f\u7528\u5167\u5efa\u7684DHCP\u4f3a\u670d\u5668\u70ba\u5b89\u5168\u7684\u3002",
|
||||
"dhcp_found": "\u65bc\u7db2\u8def\u4e0a\u5df2\u767c\u73fe\u4e4b\u6709\u6548\u7684\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u4f3a\u670d\u5668\u3002\u555f\u7528\u5167\u5efa\u7684DHCP\u4f3a\u670d\u5668\u70ba\u4e0d\u5b89\u5168\u7684\u3002",
|
||||
"dhcp_not_found": "\u65bc\u8a72\u7db2\u8def\u4e0a\u7121\u73fe\u884c\u7684\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u4f3a\u670d\u5668\u88ab\u767c\u73fe\u3002\u555f\u7528\u5167\u5efa\u7684DHCP\u4f3a\u670d\u5668\u70ba\u5b89\u5168\u7684\u3002",
|
||||
"dhcp_found": "\u65bc\u8a72\u7db2\u8def\u4e0a\u67d0\u4e9b\u73fe\u884c\u7684\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u4f3a\u670d\u5668\u88ab\u767c\u73fe\u3002\u555f\u7528\u5167\u5efa\u7684DHCP\u4f3a\u670d\u5668\u70ba\u4e0d\u5b89\u5168\u7684\u3002",
|
||||
"dhcp_leases": "\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u79df\u8cc3",
|
||||
"dhcp_leases_not_found": "\u7121\u5df2\u767c\u73fe\u4e4b\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u79df\u8cc3",
|
||||
"dhcp_config_saved": "\u5df2\u5132\u5b58\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u4f3a\u670d\u5668\u914d\u7f6e",
|
||||
@@ -17,7 +21,7 @@
|
||||
"form_error_positive": "\u5fc5\u9808\u5927\u65bc0",
|
||||
"dhcp_form_gateway_input": "\u9598\u9053 IP",
|
||||
"dhcp_form_subnet_input": "\u5b50\u7db2\u8def\u906e\u7f69",
|
||||
"dhcp_form_range_title": "IP\u4f4d\u5740\u7bc4\u570d",
|
||||
"dhcp_form_range_title": "IP \u4f4d\u5740\u7bc4\u570d",
|
||||
"dhcp_form_range_start": "\u7bc4\u570d\u958b\u59cb",
|
||||
"dhcp_form_range_end": "\u7bc4\u570d\u7d50\u675f",
|
||||
"dhcp_form_lease_title": "\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u79df\u8cc3\u6642\u9593\uff08\u4ee5\u79d2\u6578\uff09",
|
||||
@@ -25,6 +29,9 @@
|
||||
"dhcp_interface_select": "\u9078\u64c7\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u4ecb\u9762",
|
||||
"dhcp_hardware_address": "\u786c\u9ad4\u4f4d\u5740",
|
||||
"dhcp_ip_addresses": "IP \u4f4d\u5740",
|
||||
"dhcp_table_hostname": "\u4e3b\u6a5f\u540d\u7a31",
|
||||
"dhcp_table_expires": "\u5230\u671f",
|
||||
"dhcp_warning": "\u5982\u679c\u60a8\u60f3\u8981\u555f\u7528\u5167\u5efa\u7684\u52d5\u614b\u4e3b\u6a5f\u8a2d\u5b9a\u5354\u5b9a\uff08DHCP\uff09\u4f3a\u670d\u5668\uff0c\u78ba\u4fdd\u7121\u5176\u5b83\u73fe\u884c\u7684DHCP\u4f3a\u670d\u5668\u3002\u5426\u5247\uff0c\u5b83\u53ef\u80fd\u6703\u7834\u58de\u4f9b\u5df2\u9023\u7dda\u7684\u88dd\u7f6e\u4e4b\u7db2\u969b\u7db2\u8def\uff01",
|
||||
"back": "\u8fd4\u56de",
|
||||
"dashboard": "\u5100\u8868\u677f",
|
||||
"settings": "\u8a2d\u5b9a",
|
||||
@@ -33,8 +40,8 @@
|
||||
"faq": "\u5e38\u898b\u554f\u7b54\u96c6",
|
||||
"version": "\u7248\u672c",
|
||||
"address": "\u4f4d\u5740",
|
||||
"on": "\u958b\u555f",
|
||||
"off": "\u95dc\u9589",
|
||||
"on": "\u958b\u8457",
|
||||
"off": "\u95dc\u8457",
|
||||
"copyright": "\u7248\u6b0a",
|
||||
"homepage": "\u9996\u9801",
|
||||
"report_an_issue": "\u5831\u544a\u554f\u984c",
|
||||
@@ -55,7 +62,7 @@
|
||||
"top_clients": "\u71b1\u9580\u7528\u6236\u7aef",
|
||||
"no_clients_found": "\u7121\u5df2\u767c\u73fe\u4e4b\u7528\u6236\u7aef",
|
||||
"general_statistics": "\u4e00\u822c\u7684\u7d71\u8a08\u8cc7\u6599",
|
||||
"number_of_dns_query_24_hours": "\u5728\u6700\u8fd1\u768424 \u5c0f\u6642\u5167\u5df2\u8655\u7406\u7684DNS\u67e5\u8a62\u4e4b\u6578\u91cf",
|
||||
"number_of_dns_query_24_hours": "\u5728\u6700\u8fd1\u768424\u5c0f\u6642\u5167\u5df2\u8655\u7406\u7684DNS\u67e5\u8a62\u4e4b\u6578\u91cf",
|
||||
"number_of_dns_query_blocked_24_hours": "\u5df2\u88ab\u5ee3\u544a\u5c01\u9396\u904e\u6ffe\u5668\u548c\u4e3b\u6a5f\u5c01\u9396\u6e05\u55ae\u5c01\u9396\u7684DNS\u8acb\u6c42\u4e4b\u6578\u91cf",
|
||||
"number_of_dns_query_blocked_24_hours_by_sec": "\u5df2\u88abAdGuard\u700f\u89bd\u5b89\u5168\u6a21\u7d44\u5c01\u9396\u7684DNS\u8acb\u6c42\u4e4b\u6578\u91cf",
|
||||
"number_of_dns_query_blocked_24_hours_adult": "\u5df2\u5c01\u9396\u7684\u6210\u4eba\u7db2\u7ad9\u4e4b\u6578\u91cf",
|
||||
@@ -70,12 +77,12 @@
|
||||
"use_adguard_parental": "\u4f7f\u7528AdGuard\u5bb6\u9577\u76e3\u63a7\u4e4b\u7db2\u8def\u670d\u52d9",
|
||||
"use_adguard_parental_hint": "AdGuard Home\u5c07\u6aa2\u67e5\u7db2\u57df\u662f\u5426\u5305\u542b\u6210\u4eba\u8cc7\u6599\u3002\u5b83\u4f7f\u7528\u5982\u540c\u700f\u89bd\u5b89\u5168\u7db2\u8def\u670d\u52d9\u4e00\u6a23\u4e4b\u53cb\u597d\u7684\u96b1\u79c1\u61c9\u7528\u7a0b\u5f0f\u4ecb\u9762\uff08API\uff09\u3002",
|
||||
"enforce_safe_search": "\u5f37\u5236\u57f7\u884c\u5b89\u5168\u641c\u5c0b",
|
||||
"enforce_save_search_hint": "AdGuard Home\u53ef\u5728\u4ee5\u4e0b\u7684\u641c\u5c0b\u5f15\u64ce\uff1aGoogle\u3001YouTube\u3001Bing\u548cYandex\u4e2d\u5f37\u5236\u57f7\u884c\u5b89\u5168\u641c\u5c0b\u3002",
|
||||
"enforce_save_search_hint": "AdGuard Home\u53ef\u5728\u4e0b\u5217\u7684\u641c\u5c0b\u5f15\u64ce\uff1aGoogle\u3001YouTube\u3001Bing\u548cYandex\u4e2d\u5f37\u5236\u57f7\u884c\u5b89\u5168\u641c\u5c0b\u3002",
|
||||
"no_servers_specified": "\u7121\u5df2\u660e\u78ba\u6307\u5b9a\u7684\u4f3a\u670d\u5668",
|
||||
"no_settings": "\u7121\u8a2d\u5b9a",
|
||||
"general_settings": "\u4e00\u822c\u7684\u8a2d\u5b9a",
|
||||
"upstream_dns": "\u4e0a\u6e38\u7684DNS\u4f3a\u670d\u5668",
|
||||
"upstream_dns_hint": "\u5982\u679c\u60a8\u4fdd\u7559\u8a72\u6b04\u4f4d\u7a7a\u767d\u7684\uff0cAdGuard Home\u5c07\u4f7f\u7528<a href='https:\/\/1.1.1.1\/' target='_blank'>Cloudflare DNS<\/a>\u4f5c\u70ba\u4e0a\u6e38\u3002\u5c0d\u65bcDNS over TLS\u4f3a\u670d\u5668\u4f7f\u7528 tls:\/\/ \u524d\u7db4\u3002",
|
||||
"upstream_dns_hint": "\u5982\u679c\u60a8\u5c07\u8a72\u6b04\u4f4d\u7559\u7a7a\uff0cAdGuard Home\u5c07\u4f7f\u7528<a href='https:\/\/1.1.1.1\/' target='_blank'>Cloudflare DNS<\/a>\u4f5c\u70ba\u4e0a\u6e38\u3002",
|
||||
"test_upstream_btn": "\u6e2c\u8a66\u4e0a\u884c\u8cc7\u6599\u6d41",
|
||||
"apply_btn": "\u5957\u7528",
|
||||
"disabled_filtering_toast": "\u5df2\u7981\u7528\u904e\u6ffe",
|
||||
@@ -112,24 +119,25 @@
|
||||
"example_comment": "! \u770b\uff0c\u4e00\u500b\u8a3b\u89e3",
|
||||
"example_comment_meaning": "\u53ea\u662f\u4e00\u500b\u8a3b\u89e3",
|
||||
"example_comment_hash": "# \u4e5f\u662f\u4e00\u500b\u8a3b\u89e3",
|
||||
"example_regex_meaning": "\u5c01\u9396\u81f3\u8207\u5df2\u660e\u78ba\u6307\u5b9a\u7684\u898f\u5247\u904b\u7b97\u5f0f\uff08Regular Expression\uff09\u76f8\u7b26\u7684\u7db2\u57df\u4e4b\u5b58\u53d6",
|
||||
"example_upstream_regular": "\u4e00\u822c\u7684 DNS\uff08\u900f\u904eUDP\uff09",
|
||||
"example_upstream_dot": "\u52a0\u5bc6\u7684 <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_TLS' target='_blank'>DNS-over-TLS<\/a>",
|
||||
"example_upstream_doh": "\u52a0\u5bc6\u7684 <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>DNS-over-HTTPS <\/a>",
|
||||
"example_upstream_sdns": "\u60a8\u53ef\u4f7f\u7528\u5c0d\u65bc <a href='https:\/\/dnscrypt.info\/' target='_blank'>DNSCrypt<\/a> \u6216 <a href='https:\/\/en.wikipedia.org\/wiki\/DNS_over_HTTPS' target='_blank'>DNS-over-HTTPS<\/a> \u89e3\u6790\u5668\u4e4b <a href='https:\/\/dnscrypt.info\/stamps\/' target='_blank'>DNS \u6233\u8a18<\/a>",
|
||||
"example_upstream_dot": "\u52a0\u5bc6\u7684 <0>DNS-over-TLS<\/0>",
|
||||
"example_upstream_doh": "\u52a0\u5bc6\u7684 <0>DNS-over-HTTPS <\/0>",
|
||||
"example_upstream_sdns": "\u60a8\u53ef\u4f7f\u7528\u95dc\u65bc <0>DNSCrypt<\/0> \u6216 <1>DNS-over-HTTPS<\/1> \u89e3\u6790\u5668\u4e4b <2>DNS \u6233\u8a18<\/2>",
|
||||
"example_upstream_tcp": "\u4e00\u822c\u7684 DNS\uff08\u900f\u904eTCP\uff09",
|
||||
"all_filters_up_to_date_toast": "\u6240\u6709\u7684\u904e\u6ffe\u5668\u5df2\u662f\u6700\u65b0\u7684",
|
||||
"updated_upstream_dns_toast": "\u5df2\u66f4\u65b0\u4e0a\u6e38\u7684DNS\u4f3a\u670d\u5668",
|
||||
"dns_test_ok_toast": "\u660e\u78ba\u6307\u5b9a\u7684DNS\u4f3a\u670d\u5668\u6b63\u78ba\u5730\u904b\u4f5c\u4e2d",
|
||||
"dns_test_ok_toast": "\u5df2\u660e\u78ba\u6307\u5b9a\u7684DNS\u4f3a\u670d\u5668\u6b63\u5728\u6b63\u78ba\u5730\u904b\u4f5c",
|
||||
"dns_test_not_ok_toast": "\u4f3a\u670d\u5668 \"{{key}}\"\uff1a\u7121\u6cd5\u88ab\u4f7f\u7528\uff0c\u8acb\u6aa2\u67e5\u60a8\u5df2\u6b63\u78ba\u5730\u586b\u5beb\u5b83",
|
||||
"unblock_btn": "\u89e3\u9664\u5c01\u9396",
|
||||
"block_btn": "\u5c01\u9396",
|
||||
"time_table_header": "\u6642\u9593",
|
||||
"domain_name_table_header": "\u57df\u540d",
|
||||
"type_table_header": "\u985e\u578b",
|
||||
"response_table_header": "\u53cd\u61c9",
|
||||
"response_table_header": "\u56de\u61c9",
|
||||
"client_table_header": "\u7528\u6236\u7aef",
|
||||
"empty_response_status": "\u7a7a\u767d\u7684",
|
||||
"show_all_filter_type": "\u986f\u793a\u6240\u6709",
|
||||
"show_all_filter_type": "\u986f\u793a\u5168\u90e8",
|
||||
"show_filtered_type": "\u986f\u793a\u5df2\u904e\u6ffe\u7684",
|
||||
"no_logs_found": "\u7121\u5df2\u767c\u73fe\u4e4b\u8a18\u9304",
|
||||
"disabled_log_btn": "\u7981\u7528\u8a18\u9304",
|
||||
@@ -144,14 +152,104 @@
|
||||
"of_table_footer_text": "\u4e4b",
|
||||
"rows_table_footer_text": "\u5217",
|
||||
"updated_custom_filtering_toast": "\u5df2\u66f4\u65b0\u81ea\u8a02\u7684\u904e\u6ffe\u898f\u5247",
|
||||
"rule_removed_from_custom_filtering_toast": "\u898f\u5247\u5df2\u5f9e\u81ea\u8a02\u7684\u904e\u6ffe\u898f\u5247\u4e2d\u88ab\u79fb\u9664",
|
||||
"rule_added_to_custom_filtering_toast": "\u898f\u5247\u5df2\u5f9e\u81ea\u8a02\u7684\u904e\u6ffe\u898f\u5247\u4e2d\u88ab\u52a0\u5165",
|
||||
"query_log_disabled_toast": "\u67e5\u8a62\u8a18\u9304\u5df2\u88ab\u7981\u7528",
|
||||
"query_log_enabled_toast": "\u67e5\u8a62\u8a18\u9304\u5df2\u88ab\u555f\u7528",
|
||||
"rule_removed_from_custom_filtering_toast": "\u898f\u5247\u5f9e\u81ea\u8a02\u7684\u904e\u6ffe\u898f\u5247\u4e2d\u88ab\u79fb\u9664",
|
||||
"rule_added_to_custom_filtering_toast": "\u898f\u5247\u88ab\u52a0\u81f3\u81ea\u8a02\u7684\u904e\u6ffe\u898f\u5247\u4e2d",
|
||||
"query_log_disabled_toast": "\u67e5\u8a62\u8a18\u9304\u88ab\u7981\u7528",
|
||||
"query_log_enabled_toast": "\u67e5\u8a62\u8a18\u9304\u88ab\u555f\u7528",
|
||||
"source_label": "\u4f86\u6e90",
|
||||
"found_in_known_domain_db": "\u5728\u5df2\u77e5\u7684\u57df\u540d\u8cc7\u6599\u5eab\u4e2d\u88ab\u767c\u73fe\u3002",
|
||||
"category_label": "\u985e\u5225",
|
||||
"rule_label": "\u898f\u5247",
|
||||
"filter_label": "\u904e\u6ffe\u5668",
|
||||
"unknown_filter": "\u672a\u77e5\u7684\u904e\u6ffe\u5668 {{filterId}}"
|
||||
"unknown_filter": "\u672a\u77e5\u7684\u904e\u6ffe\u5668 {{filterId}}",
|
||||
"install_welcome_title": "\u6b61\u8fce\u81f3AdGuard Home\uff01",
|
||||
"install_welcome_desc": "AdGuard Home\u662f\u5168\u7db2\u8def\u7bc4\u570d\u5ee3\u544a\u548c\u8ffd\u8e64\u5668\u5c01\u9396\u7684DNS\u4f3a\u670d\u5668\u3002\u5b83\u7684\u76ee\u7684\u70ba\u8b93\u60a8\u63a7\u5236\u60a8\u6574\u500b\u7684\u7db2\u8def\u548c\u6240\u6709\u60a8\u7684\u88dd\u7f6e\uff0c\u4e14\u4e0d\u9700\u8981\u4f7f\u7528\u7528\u6236\u7aef\u7a0b\u5f0f\u3002",
|
||||
"install_settings_title": "\u7ba1\u7406\u54e1\u7db2\u9801\u4ecb\u9762",
|
||||
"install_settings_listen": "\u76e3\u807d\u4ecb\u9762",
|
||||
"install_settings_port": "\u9023\u63a5\u57e0",
|
||||
"install_settings_interface_link": "\u60a8\u7684AdGuard Home\u7ba1\u7406\u54e1\u7db2\u9801\u4ecb\u9762\u5c07\u65bc\u4e0b\u5217\u7684\u4f4d\u5740\u4e0a\u70ba\u53ef\u7528\u7684\uff1a",
|
||||
"form_error_port": "\u8f38\u5165\u6709\u6548\u7684\u9023\u63a5\u57e0\u503c",
|
||||
"install_settings_dns": "DNS \u4f3a\u670d\u5668",
|
||||
"install_settings_dns_desc": "\u60a8\u5c07\u9700\u8981\u914d\u7f6e\u60a8\u7684\u88dd\u7f6e\u6216\u8def\u7531\u5668\u4ee5\u4f7f\u7528\u65bc\u4e0b\u5217\u7684\u4f4d\u5740\u4e0a\u4e4bDNS\u4f3a\u670d\u5668\uff1a",
|
||||
"install_settings_all_interfaces": "\u6240\u6709\u7684\u4ecb\u9762",
|
||||
"install_auth_title": "\u9a57\u8b49",
|
||||
"install_auth_desc": "\u88ab\u975e\u5e38\u5efa\u8b70\u914d\u7f6e\u5c6c\u65bc\u60a8\u7684AdGuard Home\u7ba1\u7406\u54e1\u7db2\u9801\u4ecb\u9762\u4e4b\u5bc6\u78bc\u9a57\u8b49\u3002\u5373\u4f7f\u5b83\u50c5\u5728\u60a8\u7684\u5340\u57df\u7db2\u8def\u4e2d\u70ba\u53ef\u5b58\u53d6\u7684\uff0c\u8b93\u5b83\u53d7\u4fdd\u8b77\u514d\u65bc\u4e0d\u53d7\u9650\u5236\u7684\u5b58\u53d6\u70ba\u4ecd\u7136\u91cd\u8981\u7684\u3002",
|
||||
"install_auth_username": "\u7528\u6236\u540d",
|
||||
"install_auth_password": "\u5bc6\u78bc",
|
||||
"install_auth_confirm": "\u78ba\u8a8d\u5bc6\u78bc",
|
||||
"install_auth_username_enter": "\u8f38\u5165\u7528\u6236\u540d",
|
||||
"install_auth_password_enter": "\u8f38\u5165\u5bc6\u78bc",
|
||||
"install_step": "\u6b65\u9a5f",
|
||||
"install_devices_title": "\u914d\u7f6e\u60a8\u7684\u88dd\u7f6e",
|
||||
"install_devices_desc": "\u70ba\u4f7fAdGuard Home\u958b\u59cb\u904b\u4f5c\uff0c\u60a8\u9700\u8981\u914d\u7f6e\u60a8\u7684\u88dd\u7f6e\u4ee5\u4f7f\u7528\u5b83\u3002",
|
||||
"install_submit_title": "\u606d\u559c\uff01",
|
||||
"install_submit_desc": "\u8a72\u8a2d\u7f6e\u7a0b\u5e8f\u88ab\u5b8c\u6210\uff0c\u4e14\u60a8\u6e96\u5099\u597d\u958b\u59cb\u4f7f\u7528AdGuard Home\u3002",
|
||||
"install_devices_router": "\u8def\u7531\u5668",
|
||||
"install_devices_router_desc": "\u8a72\u8a2d\u7f6e\u5c07\u81ea\u52d5\u5730\u6db5\u84cb\u88ab\u9023\u7dda\u81f3\u60a8\u7684\u5bb6\u5ead\u8def\u7531\u5668\u4e4b\u6240\u6709\u7684\u88dd\u7f6e\uff0c\u4e14\u60a8\u5c07\u7121\u9700\u624b\u52d5\u5730\u914d\u7f6e\u5b83\u5011\u6bcf\u500b\u3002",
|
||||
"install_devices_address": "AdGuard Home DNS\u4f3a\u670d\u5668\u6b63\u5728\u76e3\u807d\u4e0b\u5217\u7684\u4f4d\u5740",
|
||||
"install_devices_router_list_1": "\u958b\u555f\u95dc\u65bc\u60a8\u7684\u8def\u7531\u5668\u4e4b\u504f\u597d\u8a2d\u5b9a\u3002\u901a\u5e38\u5730\uff0c\u60a8\u53ef\u900f\u904e\u7db2\u5740\uff08\u5982 http:\/\/192.168.0.1\/ \u6216 http:\/\/192.168.1.1\/\uff09\u5f9e\u60a8\u7684\u700f\u89bd\u5668\u4e2d\u5b58\u53d6\u5b83\u3002\u60a8\u53ef\u80fd\u88ab\u8981\u6c42\u8f38\u5165\u8a72\u5bc6\u78bc\u3002\u5982\u679c\u60a8\u4e0d\u8a18\u5f97\u5b83\uff0c\u60a8\u7d93\u5e38\u53ef\u900f\u904e\u6309\u58d3\u65bc\u8a72\u8def\u7531\u5668\u672c\u8eab\u4e0a\u7684\u6309\u9215\u4f86\u91cd\u7f6e\u5bc6\u78bc\u3002\u67d0\u4e9b\u8def\u7531\u5668\u9700\u8981\u7279\u5b9a\u7684\u61c9\u7528\u7a0b\u5f0f\uff0c\u65e2\u7136\u5982\u6b64\u5176\u61c9\u5df2\u88ab\u5b89\u88dd\u65bc\u60a8\u7684\u96fb\u8166\/\u624b\u6a5f\u4e0a\u3002",
|
||||
"install_devices_router_list_2": "\u627e\u5230DHCP\/DNS\u8a2d\u5b9a\u3002\u5c0b\u627e\u7dca\u9130\u8457\u5141\u8a31\u5169\u7d44\u6216\u4e09\u7d44\u6578\u5b57\u96c6\u7684\u6b04\u4f4d\u4e4bDNS\u5b57\u6bcd\uff0c\u6bcf\u7d44\u88ab\u62c6\u6210\u56db\u500b\u542b\u6709\u4e00\u81f3\u4e09\u500b\u6578\u5b57\u7684\u7fa4\u96c6\u3002",
|
||||
"install_devices_router_list_3": "\u5728\u90a3\u88e1\u8f38\u5165\u60a8\u7684AdGuard Home\u4f3a\u670d\u5668\u4f4d\u5740\u3002",
|
||||
"install_devices_windows_list_1": "\u901a\u904e\u958b\u59cb\u529f\u80fd\u8868\u6216Windows \u641c\u5c0b\uff0c\u958b\u555f\u63a7\u5236\u53f0\u3002",
|
||||
"install_devices_windows_list_2": "\u53bb\u7db2\u8def\u548c\u7db2\u969b\u7db2\u8def\u985e\u5225\uff0c\u7136\u5f8c\u53bb\u7db2\u8def\u548c\u5171\u7528\u4e2d\u5fc3\u3002",
|
||||
"install_devices_windows_list_3": "\u65bc\u756b\u9762\u4e4b\u5de6\u5074\u4e0a\u627e\u5230\u8b8a\u66f4\u4ecb\u9762\u5361\u8a2d\u5b9a\u4e26\u65bc\u5b83\u4e0a\u9ede\u64ca\u3002",
|
||||
"install_devices_windows_list_4": "\u9078\u64c7\u60a8\u73fe\u884c\u7684\u9023\u7dda\uff0c\u65bc\u5b83\u4e0a\u9ede\u64ca\u6ed1\u9f20\u53f3\u9375\uff0c\u7136\u5f8c\u9078\u64c7\u5167\u5bb9\u3002",
|
||||
"install_devices_windows_list_5": "\u5728\u6e05\u55ae\u4e2d\u627e\u5230\u7db2\u969b\u7db2\u8def\u901a\u8a0a\u5354\u5b9a\u7b2c 4 \u7248\uff08TCP\/IPv4\uff09\uff0c\u9078\u64c7\u5b83\uff0c\u7136\u5f8c\u518d\u6b21\u65bc\u5167\u5bb9\u4e0a\u9ede\u64ca\u3002",
|
||||
"install_devices_windows_list_6": "\u9078\u64c7\u4f7f\u7528\u4e0b\u5217\u7684DNS\u4f3a\u670d\u5668\u4f4d\u5740\uff0c\u7136\u5f8c\u8f38\u5165\u60a8\u7684AdGuard Home\u4f3a\u670d\u5668\u4f4d\u5740\u3002",
|
||||
"install_devices_macos_list_1": "\u65bcApple\u5716\u50cf\u4e0a\u9ede\u64ca\uff0c\u7136\u5f8c\u53bb\u7cfb\u7d71\u504f\u597d\u8a2d\u5b9a\u3002",
|
||||
"install_devices_macos_list_2": "\u65bc\u7db2\u8def\u4e0a\u9ede\u64ca\u3002",
|
||||
"install_devices_macos_list_3": "\u9078\u64c7\u5728\u60a8\u7684\u6e05\u55ae\u4e2d\u4e4b\u9996\u8981\u7684\u9023\u7dda\uff0c\u7136\u5f8c\u9ede\u64ca\u9032\u968e\u7684\u3002",
|
||||
"install_devices_macos_list_4": "\u9078\u64c7\u8a72DNS\u5206\u9801\uff0c\u7136\u5f8c\u8f38\u5165\u60a8\u7684AdGuard Home\u4f3a\u670d\u5668\u4f4d\u5740\u3002",
|
||||
"install_devices_android_list_1": "\u5f9eAndroid\u9078\u55ae\u4e3b\u756b\u9762\u4e2d\uff0c\u8f15\u89f8\u8a2d\u5b9a\u3002",
|
||||
"install_devices_android_list_2": "\u65bc\u8a72\u9078\u55ae\u4e0a\u8f15\u89f8Wi-Fi\u3002\u6b63\u5728\u5217\u51fa\u6240\u6709\u53ef\u7528\u7684\u7db2\u8def\u4e4b\u756b\u9762\u5c07\u88ab\u986f\u793a\uff08\u4e0d\u53ef\u80fd\u70ba\u884c\u52d5\u9023\u7dda\u8a2d\u5b9a\u81ea\u8a02\u7684DNS\uff09\u3002",
|
||||
"install_devices_android_list_3": "\u9577\u6309\u60a8\u6240\u9023\u7dda\u81f3\u7684\u7db2\u8def\uff0c\u7136\u5f8c\u8f15\u89f8\u4fee\u6539\u7db2\u8def\u3002",
|
||||
"install_devices_android_list_4": "\u65bc\u67d0\u4e9b\u88dd\u7f6e\u4e0a\uff0c\u60a8\u53ef\u80fd\u9700\u8981\u6aa2\u67e5\u95dc\u65bc\u9032\u968e\u7684\u65b9\u6846\u4ee5\u67e5\u770b\u9032\u4e00\u6b65\u7684\u8a2d\u5b9a\u3002\u70ba\u4e86\u8abf\u6574\u60a8\u7684Android DNS\u8a2d\u5b9a\uff0c\u60a8\u5c07\u9700\u8981\u628aIP \u8a2d\u5b9a\u5f9eDHCP\u8f49\u63db\u6210\u975c\u614b\u3002",
|
||||
"install_devices_android_list_5": "\u4f7f\u8a2d\u5b9aDNS 1\u548cDNS 2\u503c\u66f4\u6539\u6210\u60a8\u7684AdGuard Home\u4f3a\u670d\u5668\u4f4d\u5740\u3002",
|
||||
"install_devices_ios_list_1": "\u5f9e\u4e3b\u756b\u9762\u4e2d\uff0c\u8f15\u89f8\u8a2d\u5b9a\u3002",
|
||||
"install_devices_ios_list_2": "\u5728\u5de6\u5074\u7684\u9078\u55ae\u4e2d\u9078\u64c7Wi-Fi\uff08\u4e0d\u53ef\u80fd\u70ba\u884c\u52d5\u7db2\u8def\u914d\u7f6eDNS\uff09\u3002",
|
||||
"install_devices_ios_list_3": "\u65bc\u76ee\u524d\u73fe\u884c\u7684\u7db2\u8def\u4e4b\u540d\u7a31\u4e0a\u8f15\u89f8\u3002",
|
||||
"install_devices_ios_list_4": "\u5728\u8a72DNS\u6b04\u4f4d\u4e2d\uff0c\u8f38\u5165\u60a8\u7684AdGuard Home\u4f3a\u670d\u5668\u4f4d\u5740\u3002",
|
||||
"get_started": "\u958b\u59cb\u5427",
|
||||
"next": "\u4e0b\u4e00\u6b65",
|
||||
"open_dashboard": "\u958b\u555f\u5100\u8868\u677f",
|
||||
"install_saved": "\u5df2\u6210\u529f\u5730\u5132\u5b58",
|
||||
"encryption_title": "\u52a0\u5bc6",
|
||||
"encryption_desc": "\u52a0\u5bc6\uff08HTTPS\/TLS\uff09\u652f\u63f4\u4f9bDNS\u548c\u7ba1\u7406\u54e1\u7db2\u9801\u4ecb\u9762\u5169\u8005",
|
||||
"encryption_config_saved": "\u52a0\u5bc6\u914d\u7f6e\u5df2\u88ab\u5132\u5b58",
|
||||
"encryption_server": "\u4f3a\u670d\u5668\u540d\u7a31",
|
||||
"encryption_server_enter": "\u8f38\u5165\u60a8\u7684\u57df\u540d",
|
||||
"encryption_server_desc": "\u70ba\u4e86\u4f7f\u7528HTTPS\uff0c\u60a8\u9700\u8981\u8f38\u5165\u8207\u60a8\u7684\u5b89\u5168\u901a\u8a0a\u7aef\u5c64\uff08SSL\uff09\u6191\u8b49\u76f8\u7b26\u7684\u4f3a\u670d\u5668\u540d\u7a31\u3002",
|
||||
"encryption_redirect": "\u81ea\u52d5\u5730\u91cd\u5b9a\u5411\u5230HTTPS",
|
||||
"encryption_redirect_desc": "\u5982\u679c\u88ab\u52fe\u9078\uff0cAdGuard Home\u5c07\u81ea\u52d5\u5730\u91cd\u5b9a\u5411\u60a8\u5f9eHTTP\u5230HTTPS\u4f4d\u5740\u3002",
|
||||
"encryption_https": "HTTPS \u9023\u63a5\u57e0",
|
||||
"encryption_https_desc": "\u5982\u679cHTTPS\u9023\u63a5\u57e0\u88ab\u914d\u7f6e\uff0cAdGuard Home\u7ba1\u7406\u54e1\u4ecb\u9762\u900f\u904eHTTPS\u5c07\u70ba\u53ef\u5b58\u53d6\u7684\uff0c\u4e14\u5b83\u4e5f\u5c07\u65bc '\/dns-query' \u4f4d\u7f6e\u4e0a\u63d0\u4f9bDNS-over-HTTPS\u3002",
|
||||
"encryption_dot": "DNS-over-TLS \u9023\u63a5\u57e0",
|
||||
"encryption_dot_desc": "\u5982\u679c\u8a72\u9023\u63a5\u57e0\u88ab\u914d\u7f6e\uff0cAdGuard Home\u5c07\u65bc\u6b64\u9023\u63a5\u57e0\u4e0a\u904b\u884cDNS-over-TLS\u4f3a\u670d\u5668\u3002",
|
||||
"encryption_certificates": "\u6191\u8b49",
|
||||
"encryption_certificates_desc": "\u70ba\u4e86\u4f7f\u7528\u52a0\u5bc6\uff0c\u60a8\u9700\u8981\u63d0\u4f9b\u6709\u6548\u7684\u5b89\u5168\u901a\u8a0a\u7aef\u5c64\uff08SSL\uff09\u6191\u8b49\u93c8\u7d50\u4f9b\u60a8\u7684\u7db2\u57df\u3002\u65bc <0>{{link}}<\/0> \u4e0a\u60a8\u53ef\u53d6\u5f97\u514d\u8cbb\u7684\u6191\u8b49\u6216\u60a8\u53ef\u5f9e\u53d7\u4fe1\u4efb\u7684\u6191\u8b49\u6388\u6b0a\u55ae\u4f4d\u4e4b\u4e00\u8cfc\u8cb7\u5b83\u3002",
|
||||
"encryption_certificates_input": "\u65bc\u6b64\u8907\u88fd\/\u8cbc\u4e0a\u60a8\u7684\u96b1\u79c1\u589e\u5f37\u90f5\u4ef6\u7de8\u78bc\u4e4b\uff08PEM-encoded\uff09\u6191\u8b49\u3002",
|
||||
"encryption_status": "\u72c0\u614b",
|
||||
"encryption_expire": "\u5230\u671f",
|
||||
"encryption_key": "\u79c1\u5bc6\u91d1\u9470",
|
||||
"encryption_key_input": "\u65bc\u6b64\u8907\u88fd\/\u8cbc\u4e0a\u60a8\u7684\u96b1\u79c1\u589e\u5f37\u90f5\u4ef6\u7de8\u78bc\u4e4b\uff08PEM-encoded\uff09\u79c1\u5bc6\u91d1\u9470\u4f9b\u60a8\u7684\u6191\u8b49\u3002",
|
||||
"encryption_enable": "\u555f\u7528\u52a0\u5bc6\uff08HTTPS\u3001DNS-over-HTTPS\u548cDNS-over-TLS\uff09",
|
||||
"encryption_enable_desc": "\u5982\u679c\u52a0\u5bc6\u88ab\u555f\u7528\uff0cAdGuard Home\u7ba1\u7406\u54e1\u4ecb\u9762\u900f\u904eHTTPS\u5c07\u904b\u4f5c\uff0c\u4e14\u8a72DNS\u4f3a\u670d\u5668\u5c07\u7559\u5fc3\u76e3\u807d\u900f\u904eDNS-over-HTTPS\u548cDNS-over-TLS\u4e4b\u8acb\u6c42\u3002",
|
||||
"encryption_chain_valid": "\u6191\u8b49\u93c8\u7d50\u70ba\u6709\u6548\u7684",
|
||||
"encryption_chain_invalid": "\u6191\u8b49\u93c8\u7d50\u70ba\u7121\u6548\u7684",
|
||||
"encryption_key_valid": "\u6b64\u70ba\u6709\u6548\u7684 {{type}} \u79c1\u5bc6\u91d1\u9470",
|
||||
"encryption_key_invalid": "\u6b64\u70ba\u7121\u6548\u7684 {{type}} \u79c1\u5bc6\u91d1\u9470",
|
||||
"encryption_subject": "\u7269\u4ef6",
|
||||
"encryption_issuer": "\u7c3d\u767c\u8005",
|
||||
"encryption_hostnames": "\u4e3b\u6a5f\u540d\u7a31",
|
||||
"encryption_reset": "\u60a8\u78ba\u5b9a\u60a8\u60f3\u8981\u91cd\u7f6e\u52a0\u5bc6\u8a2d\u5b9a\u55ce\uff1f",
|
||||
"topline_expiring_certificate": "\u60a8\u7684\u5b89\u5168\u901a\u8a0a\u7aef\u5c64\uff08SSL\uff09\u6191\u8b49\u5373\u5c07\u5230\u671f\u3002\u66f4\u65b0<0>\u52a0\u5bc6\u8a2d\u5b9a<\/0>\u3002",
|
||||
"topline_expired_certificate": "\u60a8\u7684\u5b89\u5168\u901a\u8a0a\u7aef\u5c64\uff08SSL\uff09\u6191\u8b49\u70ba\u5df2\u5230\u671f\u7684\u3002\u66f4\u65b0<0>\u52a0\u5bc6\u8a2d\u5b9a<\/0>\u3002",
|
||||
"form_error_port_range": "\u572880-65535\u4e4b\u7bc4\u570d\u5167\u8f38\u5165\u9023\u63a5\u57e0\u503c",
|
||||
"form_error_port_unsafe": "\u6b64\u70ba\u4e0d\u5b89\u5168\u7684\u9023\u63a5\u57e0",
|
||||
"form_error_equal": "\u4e0d\u61c9\u70ba\u76f8\u7b49\u7684",
|
||||
"form_error_password": "\u4e0d\u76f8\u7b26\u7684\u5bc6\u78bc",
|
||||
"reset_settings": "\u91cd\u7f6e\u8a2d\u5b9a",
|
||||
"update_announcement": "AdGuard Home {{version}} \u73fe\u70ba\u53ef\u7528\u7684\uff01\u95dc\u65bc\u66f4\u591a\u7684\u8cc7\u8a0a\uff0c<0>\u9ede\u64ca\u9019\u88e1<\/0>\u3002",
|
||||
"setup_guide": "\u5b89\u88dd\u6307\u5357",
|
||||
"dns_addresses": "DNS \u4f4d\u5740"
|
||||
}
|
||||
73
client/src/actions/encryption.js
Normal file
73
client/src/actions/encryption.js
Normal file
@@ -0,0 +1,73 @@
|
||||
import { createAction } from 'redux-actions';
|
||||
import Api from '../api/Api';
|
||||
import { addErrorToast, addSuccessToast } from './index';
|
||||
import { redirectToCurrentProtocol } from '../helpers/helpers';
|
||||
|
||||
const apiClient = new Api();
|
||||
|
||||
export const getTlsStatusRequest = createAction('GET_TLS_STATUS_REQUEST');
|
||||
export const getTlsStatusFailure = createAction('GET_TLS_STATUS_FAILURE');
|
||||
export const getTlsStatusSuccess = createAction('GET_TLS_STATUS_SUCCESS');
|
||||
|
||||
export const getTlsStatus = () => async (dispatch) => {
|
||||
dispatch(getTlsStatusRequest());
|
||||
try {
|
||||
const status = await apiClient.getTlsStatus();
|
||||
status.certificate_chain = atob(status.certificate_chain);
|
||||
status.private_key = atob(status.private_key);
|
||||
|
||||
dispatch(getTlsStatusSuccess(status));
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(getTlsStatusFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const setTlsConfigRequest = createAction('SET_TLS_CONFIG_REQUEST');
|
||||
export const setTlsConfigFailure = createAction('SET_TLS_CONFIG_FAILURE');
|
||||
export const setTlsConfigSuccess = createAction('SET_TLS_CONFIG_SUCCESS');
|
||||
|
||||
export const setTlsConfig = config => async (dispatch, getState) => {
|
||||
dispatch(setTlsConfigRequest());
|
||||
try {
|
||||
const { httpPort } = getState().dashboard;
|
||||
const values = { ...config };
|
||||
values.certificate_chain = btoa(values.certificate_chain);
|
||||
values.private_key = btoa(values.private_key);
|
||||
values.port_https = values.port_https || 0;
|
||||
values.port_dns_over_tls = values.port_dns_over_tls || 0;
|
||||
|
||||
const response = await apiClient.setTlsConfig(values);
|
||||
response.certificate_chain = atob(response.certificate_chain);
|
||||
response.private_key = atob(response.private_key);
|
||||
dispatch(setTlsConfigSuccess(response));
|
||||
dispatch(addSuccessToast('encryption_config_saved'));
|
||||
redirectToCurrentProtocol(response, httpPort);
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(setTlsConfigFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const validateTlsConfigRequest = createAction('VALIDATE_TLS_CONFIG_REQUEST');
|
||||
export const validateTlsConfigFailure = createAction('VALIDATE_TLS_CONFIG_FAILURE');
|
||||
export const validateTlsConfigSuccess = createAction('VALIDATE_TLS_CONFIG_SUCCESS');
|
||||
|
||||
export const validateTlsConfig = config => async (dispatch) => {
|
||||
dispatch(validateTlsConfigRequest());
|
||||
try {
|
||||
const values = { ...config };
|
||||
values.certificate_chain = btoa(values.certificate_chain);
|
||||
values.private_key = btoa(values.private_key);
|
||||
values.port_https = values.port_https || 0;
|
||||
values.port_dns_over_tls = values.port_dns_over_tls || 0;
|
||||
|
||||
const response = await apiClient.validateTlsConfig(values);
|
||||
response.certificate_chain = atob(response.certificate_chain);
|
||||
response.private_key = atob(response.private_key);
|
||||
dispatch(validateTlsConfigSuccess(response));
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(validateTlsConfigFailure());
|
||||
}
|
||||
};
|
||||
@@ -3,7 +3,8 @@ import round from 'lodash/round';
|
||||
import { t } from 'i18next';
|
||||
import { showLoading, hideLoading } from 'react-redux-loading-bar';
|
||||
|
||||
import { normalizeHistory, normalizeFilteringStatus, normalizeLogs } from '../helpers/helpers';
|
||||
import { normalizeHistory, normalizeFilteringStatus, normalizeLogs, normalizeTextarea } from '../helpers/helpers';
|
||||
import { SETTINGS_NAMES } from '../helpers/constants';
|
||||
import Api from '../api/Api';
|
||||
|
||||
const apiClient = new Api();
|
||||
@@ -18,9 +19,8 @@ export const showSettingsFailure = createAction('SETTINGS_FAILURE_SHOW');
|
||||
export const toggleSetting = (settingKey, status) => async (dispatch) => {
|
||||
let successMessage = '';
|
||||
try {
|
||||
// TODO move setting keys to constants
|
||||
switch (settingKey) {
|
||||
case 'filtering':
|
||||
case SETTINGS_NAMES.filtering:
|
||||
if (status) {
|
||||
successMessage = 'disabled_filtering_toast';
|
||||
await apiClient.disableFiltering();
|
||||
@@ -30,7 +30,7 @@ export const toggleSetting = (settingKey, status) => async (dispatch) => {
|
||||
}
|
||||
dispatch(toggleSettingStatus({ settingKey }));
|
||||
break;
|
||||
case 'safebrowsing':
|
||||
case SETTINGS_NAMES.safebrowsing:
|
||||
if (status) {
|
||||
successMessage = 'disabled_safe_browsing_toast';
|
||||
await apiClient.disableSafebrowsing();
|
||||
@@ -40,7 +40,7 @@ export const toggleSetting = (settingKey, status) => async (dispatch) => {
|
||||
}
|
||||
dispatch(toggleSettingStatus({ settingKey }));
|
||||
break;
|
||||
case 'parental':
|
||||
case SETTINGS_NAMES.parental:
|
||||
if (status) {
|
||||
successMessage = 'disabled_parental_toast';
|
||||
await apiClient.disableParentalControl();
|
||||
@@ -50,7 +50,7 @@ export const toggleSetting = (settingKey, status) => async (dispatch) => {
|
||||
}
|
||||
dispatch(toggleSettingStatus({ settingKey }));
|
||||
break;
|
||||
case 'safesearch':
|
||||
case SETTINGS_NAMES.safesearch:
|
||||
if (status) {
|
||||
successMessage = 'disabled_safe_search_toast';
|
||||
await apiClient.disableSafesearch();
|
||||
@@ -352,11 +352,11 @@ export const refreshFiltersFailure = createAction('FILTERING_REFRESH_FAILURE');
|
||||
export const refreshFiltersSuccess = createAction('FILTERING_REFRESH_SUCCESS');
|
||||
|
||||
export const refreshFilters = () => async (dispatch) => {
|
||||
dispatch(refreshFiltersRequest);
|
||||
dispatch(refreshFiltersRequest());
|
||||
dispatch(showLoading());
|
||||
try {
|
||||
const refreshText = await apiClient.refreshFilters();
|
||||
dispatch(refreshFiltersSuccess);
|
||||
dispatch(refreshFiltersSuccess());
|
||||
|
||||
if (refreshText.includes('OK')) {
|
||||
if (refreshText.includes('OK 0')) {
|
||||
@@ -434,7 +434,6 @@ export const downloadQueryLogRequest = createAction('DOWNLOAD_QUERY_LOG_REQUEST'
|
||||
export const downloadQueryLogFailure = createAction('DOWNLOAD_QUERY_LOG_FAILURE');
|
||||
export const downloadQueryLogSuccess = createAction('DOWNLOAD_QUERY_LOG_SUCCESS');
|
||||
|
||||
// TODO create some common flasher with all server errors
|
||||
export const downloadQueryLog = () => async (dispatch) => {
|
||||
let data;
|
||||
dispatch(downloadQueryLogRequest());
|
||||
@@ -453,10 +452,18 @@ export const setUpstreamRequest = createAction('SET_UPSTREAM_REQUEST');
|
||||
export const setUpstreamFailure = createAction('SET_UPSTREAM_FAILURE');
|
||||
export const setUpstreamSuccess = createAction('SET_UPSTREAM_SUCCESS');
|
||||
|
||||
export const setUpstream = url => async (dispatch) => {
|
||||
export const setUpstream = config => async (dispatch) => {
|
||||
dispatch(setUpstreamRequest());
|
||||
try {
|
||||
await apiClient.setUpstream(url);
|
||||
const values = { ...config };
|
||||
values.bootstrap_dns = (
|
||||
values.bootstrap_dns && normalizeTextarea(values.bootstrap_dns)
|
||||
) || [];
|
||||
values.upstream_dns = (
|
||||
values.upstream_dns && normalizeTextarea(values.upstream_dns)
|
||||
) || [];
|
||||
|
||||
await apiClient.setUpstream(values);
|
||||
dispatch(addSuccessToast('updated_upstream_dns_toast'));
|
||||
dispatch(setUpstreamSuccess());
|
||||
} catch (error) {
|
||||
@@ -469,11 +476,18 @@ export const testUpstreamRequest = createAction('TEST_UPSTREAM_REQUEST');
|
||||
export const testUpstreamFailure = createAction('TEST_UPSTREAM_FAILURE');
|
||||
export const testUpstreamSuccess = createAction('TEST_UPSTREAM_SUCCESS');
|
||||
|
||||
export const testUpstream = servers => async (dispatch) => {
|
||||
export const testUpstream = config => async (dispatch) => {
|
||||
dispatch(testUpstreamRequest());
|
||||
try {
|
||||
const upstreamResponse = await apiClient.testUpstream(servers);
|
||||
const values = { ...config };
|
||||
values.bootstrap_dns = (
|
||||
values.bootstrap_dns && normalizeTextarea(values.bootstrap_dns)
|
||||
) || [];
|
||||
values.upstream_dns = (
|
||||
values.upstream_dns && normalizeTextarea(values.upstream_dns)
|
||||
) || [];
|
||||
|
||||
const upstreamResponse = await apiClient.testUpstream(values);
|
||||
const testMessages = Object.keys(upstreamResponse).map((key) => {
|
||||
const message = upstreamResponse[key];
|
||||
if (message !== 'OK') {
|
||||
@@ -573,36 +587,40 @@ export const setDhcpConfigSuccess = createAction('SET_DHCP_CONFIG_SUCCESS');
|
||||
export const setDhcpConfigFailure = createAction('SET_DHCP_CONFIG_FAILURE');
|
||||
|
||||
// TODO rewrite findActiveDhcp part
|
||||
export const setDhcpConfig = config => async (dispatch) => {
|
||||
export const setDhcpConfig = values => async (dispatch, getState) => {
|
||||
const { config } = getState().dhcp;
|
||||
const updatedConfig = { ...config, ...values };
|
||||
dispatch(setDhcpConfigRequest());
|
||||
try {
|
||||
if (config.interface_name) {
|
||||
dispatch(findActiveDhcpRequest());
|
||||
try {
|
||||
const activeDhcp = await apiClient.findActiveDhcp(config.interface_name);
|
||||
dispatch(findActiveDhcpSuccess(activeDhcp));
|
||||
|
||||
if (!activeDhcp.found) {
|
||||
await apiClient.setDhcpConfig(config);
|
||||
if (values.interface_name) {
|
||||
dispatch(findActiveDhcpRequest());
|
||||
try {
|
||||
const activeDhcp = await apiClient.findActiveDhcp(values.interface_name);
|
||||
dispatch(findActiveDhcpSuccess(activeDhcp));
|
||||
if (!activeDhcp.found) {
|
||||
try {
|
||||
await apiClient.setDhcpConfig(updatedConfig);
|
||||
dispatch(setDhcpConfigSuccess(updatedConfig));
|
||||
dispatch(addSuccessToast('dhcp_config_saved'));
|
||||
dispatch(setDhcpConfigSuccess());
|
||||
dispatch(getDhcpStatus());
|
||||
} else {
|
||||
dispatch(addErrorToast({ error: 'dhcp_found' }));
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(setDhcpConfigFailure());
|
||||
}
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(findActiveDhcpFailure());
|
||||
} else {
|
||||
dispatch(addErrorToast({ error: 'dhcp_found' }));
|
||||
}
|
||||
} else {
|
||||
await apiClient.setDhcpConfig(config);
|
||||
dispatch(addSuccessToast('dhcp_config_saved'));
|
||||
dispatch(setDhcpConfigSuccess());
|
||||
dispatch(getDhcpStatus());
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(findActiveDhcpFailure());
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
await apiClient.setDhcpConfig(updatedConfig);
|
||||
dispatch(setDhcpConfigSuccess(updatedConfig));
|
||||
dispatch(addSuccessToast('dhcp_config_saved'));
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(setDhcpConfigFailure());
|
||||
}
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(setDhcpConfigFailure());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -615,11 +633,10 @@ export const toggleDhcp = config => async (dispatch) => {
|
||||
dispatch(toggleDhcpRequest());
|
||||
|
||||
if (config.enabled) {
|
||||
dispatch(addSuccessToast('disabled_dhcp'));
|
||||
try {
|
||||
await apiClient.setDhcpConfig({ ...config, enabled: false });
|
||||
dispatch(toggleDhcpSuccess());
|
||||
dispatch(getDhcpStatus());
|
||||
dispatch(addSuccessToast('disabled_dhcp'));
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(toggleDhcpFailure());
|
||||
@@ -634,12 +651,11 @@ export const toggleDhcp = config => async (dispatch) => {
|
||||
try {
|
||||
await apiClient.setDhcpConfig({ ...config, enabled: true });
|
||||
dispatch(toggleDhcpSuccess());
|
||||
dispatch(getDhcpStatus());
|
||||
dispatch(addSuccessToast('enabled_dhcp'));
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(toggleDhcpFailure());
|
||||
}
|
||||
dispatch(addSuccessToast('enabled_dhcp'));
|
||||
} else {
|
||||
dispatch(addErrorToast({ error: 'dhcp_found' }));
|
||||
}
|
||||
@@ -649,3 +665,18 @@ export const toggleDhcp = config => async (dispatch) => {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const getClientsRequest = createAction('GET_CLIENTS_REQUEST');
|
||||
export const getClientsFailure = createAction('GET_CLIENTS_FAILURE');
|
||||
export const getClientsSuccess = createAction('GET_CLIENTS_SUCCESS');
|
||||
|
||||
export const getClients = () => async (dispatch) => {
|
||||
dispatch(getClientsRequest());
|
||||
try {
|
||||
const clients = await apiClient.getGlobalClients();
|
||||
dispatch(getClientsSuccess(clients));
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(getClientsFailure());
|
||||
}
|
||||
};
|
||||
|
||||
46
client/src/actions/install.js
Normal file
46
client/src/actions/install.js
Normal file
@@ -0,0 +1,46 @@
|
||||
import { createAction } from 'redux-actions';
|
||||
import Api from '../api/Api';
|
||||
import { addErrorToast, addSuccessToast } from './index';
|
||||
|
||||
const apiClient = new Api();
|
||||
|
||||
export const nextStep = createAction('NEXT_STEP');
|
||||
export const prevStep = createAction('PREV_STEP');
|
||||
|
||||
export const getDefaultAddressesRequest = createAction('GET_DEFAULT_ADDRESSES_REQUEST');
|
||||
export const getDefaultAddressesFailure = createAction('GET_DEFAULT_ADDRESSES_FAILURE');
|
||||
export const getDefaultAddressesSuccess = createAction('GET_DEFAULT_ADDRESSES_SUCCESS');
|
||||
|
||||
export const getDefaultAddresses = () => async (dispatch) => {
|
||||
dispatch(getDefaultAddressesRequest());
|
||||
try {
|
||||
const addresses = await apiClient.getDefaultAddresses();
|
||||
dispatch(getDefaultAddressesSuccess(addresses));
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(getDefaultAddressesFailure());
|
||||
}
|
||||
};
|
||||
|
||||
export const setAllSettingsRequest = createAction('SET_ALL_SETTINGS_REQUEST');
|
||||
export const setAllSettingsFailure = createAction('SET_ALL_SETTINGS_FAILURE');
|
||||
export const setAllSettingsSuccess = createAction('SET_ALL_SETTINGS_SUCCESS');
|
||||
|
||||
export const setAllSettings = values => async (dispatch) => {
|
||||
dispatch(setAllSettingsRequest());
|
||||
try {
|
||||
const {
|
||||
confirm_password,
|
||||
...config
|
||||
} = values;
|
||||
|
||||
await apiClient.setAllSettings(config);
|
||||
dispatch(setAllSettingsSuccess());
|
||||
dispatch(addSuccessToast('install_saved'));
|
||||
dispatch(nextStep());
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(setAllSettingsFailure());
|
||||
dispatch(prevStep());
|
||||
}
|
||||
};
|
||||
@@ -15,7 +15,11 @@ export default class Api {
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
throw new Error(`${this.baseUrl}/${path} | ${error.response.data} | ${error.response.status}`);
|
||||
const errorPath = `${this.baseUrl}/${path}`;
|
||||
if (error.response) {
|
||||
throw new Error(`${errorPath} | ${error.response.data} | ${error.response.status}`);
|
||||
}
|
||||
throw new Error(`${errorPath} | ${error.message ? error.message : error}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,11 +34,12 @@ export default class Api {
|
||||
GLOBAL_QUERY_LOG = { path: 'querylog', method: 'GET' };
|
||||
GLOBAL_QUERY_LOG_ENABLE = { path: 'querylog_enable', method: 'POST' };
|
||||
GLOBAL_QUERY_LOG_DISABLE = { path: 'querylog_disable', method: 'POST' };
|
||||
GLOBAL_SET_UPSTREAM_DNS = { path: 'set_upstream_dns', method: 'POST' };
|
||||
GLOBAL_SET_UPSTREAM_DNS = { path: 'set_upstreams_config', method: 'POST' };
|
||||
GLOBAL_TEST_UPSTREAM_DNS = { path: 'test_upstream_dns', method: 'POST' };
|
||||
GLOBAL_VERSION = { path: 'version.json', method: 'GET' };
|
||||
GLOBAL_ENABLE_PROTECTION = { path: 'enable_protection', method: 'POST' };
|
||||
GLOBAL_DISABLE_PROTECTION = { path: 'disable_protection', method: 'POST' };
|
||||
GLOBAL_CLIENTS = { path: 'clients', method: 'GET' }
|
||||
|
||||
restartGlobalFiltering() {
|
||||
const { path, method } = this.GLOBAL_RESTART;
|
||||
@@ -106,7 +111,7 @@ export default class Api {
|
||||
const { path, method } = this.GLOBAL_SET_UPSTREAM_DNS;
|
||||
const config = {
|
||||
data: url,
|
||||
header: { 'Content-Type': 'text/plain' },
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, config);
|
||||
}
|
||||
@@ -115,7 +120,7 @@ export default class Api {
|
||||
const { path, method } = this.GLOBAL_TEST_UPSTREAM_DNS;
|
||||
const config = {
|
||||
data: servers,
|
||||
header: { 'Content-Type': 'text/plain' },
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, config);
|
||||
}
|
||||
@@ -135,6 +140,11 @@ export default class Api {
|
||||
return this.makeRequest(path, method);
|
||||
}
|
||||
|
||||
getGlobalClients() {
|
||||
const { path, method } = this.GLOBAL_CLIENTS;
|
||||
return this.makeRequest(path, method);
|
||||
}
|
||||
|
||||
// Filtering
|
||||
FILTERING_STATUS = { path: 'filtering/status', method: 'GET' };
|
||||
FILTERING_ENABLE = { path: 'filtering/enable', method: 'POST' };
|
||||
@@ -336,4 +346,50 @@ export default class Api {
|
||||
};
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
|
||||
// Installation
|
||||
INSTALL_GET_ADDRESSES = { path: 'install/get_addresses', method: 'GET' };
|
||||
INSTALL_CONFIGURE = { path: 'install/configure', method: 'POST' };
|
||||
|
||||
getDefaultAddresses() {
|
||||
const { path, method } = this.INSTALL_GET_ADDRESSES;
|
||||
return this.makeRequest(path, method);
|
||||
}
|
||||
|
||||
setAllSettings(config) {
|
||||
const { path, method } = this.INSTALL_CONFIGURE;
|
||||
const parameters = {
|
||||
data: config,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
|
||||
// DNS-over-HTTPS and DNS-over-TLS
|
||||
TLS_STATUS = { path: 'tls/status', method: 'GET' };
|
||||
TLS_CONFIG = { path: 'tls/configure', method: 'POST' };
|
||||
TLS_VALIDATE = { path: 'tls/validate', method: 'POST' };
|
||||
|
||||
getTlsStatus() {
|
||||
const { path, method } = this.TLS_STATUS;
|
||||
return this.makeRequest(path, method);
|
||||
}
|
||||
|
||||
setTlsConfig(config) {
|
||||
const { path, method } = this.TLS_CONFIG;
|
||||
const parameters = {
|
||||
data: config,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
|
||||
validateTlsConfig(config) {
|
||||
const { path, method } = this.TLS_VALIDATE;
|
||||
const parameters = {
|
||||
data: config,
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
};
|
||||
return this.makeRequest(path, method, parameters);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: sans-serif;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", Arial, sans-serif;
|
||||
}
|
||||
|
||||
.status {
|
||||
@@ -19,8 +19,14 @@ body {
|
||||
}
|
||||
|
||||
.loading-bar {
|
||||
position: absolute;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 103;
|
||||
height: 3px;
|
||||
background: linear-gradient(45deg, rgba(99, 125, 120, 1) 0%, rgba(88, 177, 101, 1) 100%);
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { HashRouter, Route } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withNamespaces } from 'react-i18next';
|
||||
import LoadingBar from 'react-redux-loading-bar';
|
||||
|
||||
import 'react-table/react-table.css';
|
||||
@@ -13,16 +14,19 @@ import Dashboard from '../../containers/Dashboard';
|
||||
import Settings from '../../containers/Settings';
|
||||
import Filters from '../../containers/Filters';
|
||||
import Logs from '../../containers/Logs';
|
||||
import Footer from '../ui/Footer';
|
||||
import SetupGuide from '../../containers/SetupGuide';
|
||||
import Toasts from '../Toasts';
|
||||
import Footer from '../ui/Footer';
|
||||
import Status from '../ui/Status';
|
||||
import Update from '../ui/Update';
|
||||
import UpdateTopline from '../ui/UpdateTopline';
|
||||
import EncryptionTopline from '../ui/EncryptionTopline';
|
||||
import i18n from '../../i18n';
|
||||
|
||||
class App extends Component {
|
||||
componentDidMount() {
|
||||
this.props.getDnsStatus();
|
||||
this.props.getVersion();
|
||||
this.props.getClients();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
@@ -50,7 +54,7 @@ class App extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { dashboard } = this.props;
|
||||
const { dashboard, encryption } = this.props;
|
||||
const updateAvailable =
|
||||
!dashboard.processingVersions &&
|
||||
dashboard.isCoreRunning &&
|
||||
@@ -60,11 +64,14 @@ class App extends Component {
|
||||
<HashRouter hashType='noslash'>
|
||||
<Fragment>
|
||||
{updateAvailable &&
|
||||
<Update
|
||||
announcement={dashboard.announcement}
|
||||
announcementUrl={dashboard.announcementUrl}
|
||||
<UpdateTopline
|
||||
url={dashboard.announcementUrl}
|
||||
version={dashboard.version}
|
||||
/>
|
||||
}
|
||||
{!encryption.processing &&
|
||||
<EncryptionTopline notAfter={encryption.not_after} />
|
||||
}
|
||||
<LoadingBar className="loading-bar" updateTime={1000} />
|
||||
<Route component={Header} />
|
||||
<div className="container container--wrap">
|
||||
@@ -81,6 +88,7 @@ class App extends Component {
|
||||
<Route path="/settings" component={Settings} />
|
||||
<Route path="/filters" component={Filters} />
|
||||
<Route path="/logs" component={Logs} />
|
||||
<Route path="/guide" component={SetupGuide} />
|
||||
</Fragment>
|
||||
}
|
||||
</div>
|
||||
@@ -100,6 +108,8 @@ App.propTypes = {
|
||||
error: PropTypes.string,
|
||||
getVersion: PropTypes.func,
|
||||
changeLanguage: PropTypes.func,
|
||||
encryption: PropTypes.object,
|
||||
getClients: PropTypes.func,
|
||||
};
|
||||
|
||||
export default App;
|
||||
export default withNamespaces()(App);
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Trans, withNamespaces } from 'react-i18next';
|
||||
import Card from '../ui/Card';
|
||||
import Cell from '../ui/Cell';
|
||||
|
||||
import { getPercent } from '../../helpers/helpers';
|
||||
import { getPercent, getClientName } from '../../helpers/helpers';
|
||||
import { STATUS_COLORS } from '../../helpers/constants';
|
||||
|
||||
class Clients extends Component {
|
||||
@@ -23,7 +23,24 @@ class Clients extends Component {
|
||||
columns = [{
|
||||
Header: 'IP',
|
||||
accessor: 'ip',
|
||||
Cell: ({ value }) => (<div className="logs__row logs__row--overflow"><span className="logs__text" title={value}>{value}</span></div>),
|
||||
Cell: ({ value }) => {
|
||||
const clientName = getClientName(this.props.clients, value);
|
||||
let client;
|
||||
|
||||
if (clientName) {
|
||||
client = <span>{clientName} <small>({value})</small></span>;
|
||||
} else {
|
||||
client = value;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="logs__row logs__row--overflow">
|
||||
<span className="logs__text" title={value}>
|
||||
{client}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
sortMethod: (a, b) => parseInt(a.replace(/\./g, ''), 10) - parseInt(b.replace(/\./g, ''), 10),
|
||||
}, {
|
||||
Header: <Trans>requests_count</Trans>,
|
||||
@@ -61,6 +78,7 @@ Clients.propTypes = {
|
||||
topClients: PropTypes.object.isRequired,
|
||||
dnsQueries: PropTypes.number.isRequired,
|
||||
refreshButton: PropTypes.node.isRequired,
|
||||
clients: PropTypes.array.isRequired,
|
||||
t: PropTypes.func,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import 'whatwg-fetch';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
|
||||
import Statistics from './Statistics';
|
||||
@@ -25,12 +24,17 @@ class Dashboard extends Component {
|
||||
}
|
||||
|
||||
getToggleFilteringButton = () => {
|
||||
const { protectionEnabled } = this.props.dashboard;
|
||||
const { protectionEnabled, processingProtection } = this.props.dashboard;
|
||||
const buttonText = protectionEnabled ? 'disable_protection' : 'enable_protection';
|
||||
const buttonClass = protectionEnabled ? 'btn-gray' : 'btn-success';
|
||||
|
||||
return (
|
||||
<button type="button" className={`btn btn-sm mr-2 ${buttonClass}`} onClick={() => this.props.toggleProtection(protectionEnabled)}>
|
||||
<button
|
||||
type="button"
|
||||
className={`btn btn-sm mr-2 ${buttonClass}`}
|
||||
onClick={() => this.props.toggleProtection(protectionEnabled)}
|
||||
disabled={processingProtection}
|
||||
>
|
||||
<Trans>{buttonText}</Trans>
|
||||
</button>
|
||||
);
|
||||
@@ -42,6 +46,7 @@ class Dashboard extends Component {
|
||||
dashboard.processing ||
|
||||
dashboard.processingStats ||
|
||||
dashboard.processingStatsHistory ||
|
||||
dashboard.processingClients ||
|
||||
dashboard.processingTopStats;
|
||||
|
||||
const refreshFullButton = <button type="button" className="btn btn-outline-primary btn-sm" onClick={() => this.getAllStats()}><Trans>refresh_statics</Trans></button>;
|
||||
@@ -90,6 +95,7 @@ class Dashboard extends Component {
|
||||
dnsQueries={dashboard.stats.dns_queries}
|
||||
refreshButton={refreshButton}
|
||||
topClients={dashboard.topStats.top_clients}
|
||||
clients={dashboard.clients}
|
||||
/>
|
||||
</div>
|
||||
<div className="col-lg-6">
|
||||
@@ -125,6 +131,7 @@ Dashboard.propTypes = {
|
||||
isCoreRunning: PropTypes.bool,
|
||||
getFiltering: PropTypes.func,
|
||||
toggleProtection: PropTypes.func,
|
||||
processingProtection: PropTypes.bool,
|
||||
t: PropTypes.func,
|
||||
};
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ class UserRules extends Component {
|
||||
<textarea className="form-control form-control--textarea-large" value={this.props.userRules} onChange={this.handleChange} />
|
||||
<div className="card-actions">
|
||||
<button
|
||||
className="btn btn-success btn-standart"
|
||||
className="btn btn-success btn-standard"
|
||||
type="submit"
|
||||
onClick={this.handleSubmit}
|
||||
>
|
||||
@@ -52,6 +52,9 @@ class UserRules extends Component {
|
||||
<li>
|
||||
<code>{ t('example_comment_hash') }</code> - { t('example_comment_meaning') }
|
||||
</li>
|
||||
<li>
|
||||
<code>/REGEX/</code> - { t('example_regex_meaning') }
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
@@ -68,7 +68,7 @@ class Filters extends Component {
|
||||
|
||||
render() {
|
||||
const { t } = this.props;
|
||||
const { filters, userRules } = this.props.filtering;
|
||||
const { filters, userRules, processingRefreshFilters } = this.props.filtering;
|
||||
return (
|
||||
<div>
|
||||
<PageTitle title={ t('filters') } />
|
||||
@@ -84,12 +84,32 @@ class Filters extends Component {
|
||||
columns={this.columns}
|
||||
showPagination={true}
|
||||
defaultPageSize={10}
|
||||
minRows={4}
|
||||
// Text
|
||||
previousText={ t('previous_btn') }
|
||||
nextText={ t('next_btn') }
|
||||
loadingText={ t('loading_table_status') }
|
||||
pageText={ t('page_table_footer_text') }
|
||||
ofText={ t('of_table_footer_text') }
|
||||
rowsText={ t('rows_table_footer_text') }
|
||||
noDataText={ t('no_filters_added') }
|
||||
minRows={4} // TODO find out what to show if rules.length is 0
|
||||
/>
|
||||
<div className="card-actions">
|
||||
<button className="btn btn-success btn-standart mr-2" type="submit" onClick={this.props.toggleFilteringModal}><Trans>add_filter_btn</Trans></button>
|
||||
<button className="btn btn-primary btn-standart" type="submit" onClick={this.props.refreshFilters}><Trans>check_updates_btn</Trans></button>
|
||||
<button
|
||||
className="btn btn-success btn-standard mr-2"
|
||||
type="submit"
|
||||
onClick={this.props.toggleFilteringModal}
|
||||
>
|
||||
<Trans>add_filter_btn</Trans>
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-primary btn-standard"
|
||||
type="submit"
|
||||
onClick={this.props.refreshFilters}
|
||||
disabled={processingRefreshFilters}
|
||||
>
|
||||
<Trans>check_updates_btn</Trans>
|
||||
</button>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
@@ -107,6 +127,7 @@ class Filters extends Component {
|
||||
toggleModal={this.props.toggleFilteringModal}
|
||||
addFilter={this.props.addFilter}
|
||||
isFilterAdded={this.props.filtering.isFilterAdded}
|
||||
processingAddFilter={this.props.filtering.processingAddFilter}
|
||||
title={ t('new_filter_btn') }
|
||||
inputDescription={ t('enter_valid_filter_url') }
|
||||
/>
|
||||
@@ -123,6 +144,8 @@ Filters.propTypes = {
|
||||
filters: PropTypes.array,
|
||||
isFilteringModalOpen: PropTypes.bool.isRequired,
|
||||
isFilterAdded: PropTypes.bool,
|
||||
processingAddFilter: PropTypes.bool,
|
||||
processingRefreshFilters: PropTypes.bool,
|
||||
}),
|
||||
removeFilter: PropTypes.func.isRequired,
|
||||
toggleFilterStatus: PropTypes.func.isRequired,
|
||||
|
||||
@@ -76,6 +76,13 @@
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.nav-version__link {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
border-bottom: 1px dashed #495057;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.header-brand-img {
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import PropTypes from 'prop-types';
|
||||
import enhanceWithClickOutside from 'react-click-outside';
|
||||
import classnames from 'classnames';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
import { REPOSITORY } from '../../helpers/constants';
|
||||
|
||||
class Menu extends Component {
|
||||
handleClickOutside = () => {
|
||||
@@ -56,10 +55,10 @@ class Menu extends Component {
|
||||
</NavLink>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<a href={`${REPOSITORY.URL}/wiki`} className="nav-link" target="_blank" rel="noopener noreferrer">
|
||||
<NavLink to="/guide" href="/guide" className="nav-link">
|
||||
<svg className="nav-icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#66b574" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><circle cx="12" cy="12" r="10"></circle><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path><line x1="12" y1="17" x2="12" y2="17"></line></svg>
|
||||
<Trans>faq</Trans>
|
||||
</a>
|
||||
<Trans>setup_guide</Trans>
|
||||
</NavLink>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -2,24 +2,35 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
|
||||
import { getDnsAddress } from '../../helpers/helpers';
|
||||
|
||||
function Version(props) {
|
||||
const { dnsVersion, dnsAddress, dnsPort } = props;
|
||||
const { dnsVersion, dnsAddresses, dnsPort } = props;
|
||||
return (
|
||||
<div className="nav-version">
|
||||
<div className="nav-version__text">
|
||||
<Trans>version</Trans>: <span className="nav-version__value">{dnsVersion}</span>
|
||||
</div>
|
||||
<div className="nav-version__text">
|
||||
<Trans>address</Trans>: <span className="nav-version__value">{dnsAddress}:{dnsPort}</span>
|
||||
<div className="nav-version__link">
|
||||
<div className="popover__trigger popover__trigger--address">
|
||||
<Trans>dns_addresses</Trans>
|
||||
</div>
|
||||
<div className="popover__body popover__body--address">
|
||||
<div className="popover__list">
|
||||
{dnsAddresses
|
||||
.map(ip => <li key={ip}>{getDnsAddress(ip, dnsPort)}</li>)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Version.propTypes = {
|
||||
dnsVersion: PropTypes.string,
|
||||
dnsAddress: PropTypes.string,
|
||||
dnsPort: PropTypes.number,
|
||||
dnsVersion: PropTypes.string.isRequired,
|
||||
dnsAddresses: PropTypes.array.isRequired,
|
||||
dnsPort: PropTypes.number.isRequired,
|
||||
};
|
||||
|
||||
export default withNamespaces()(Version);
|
||||
|
||||
@@ -6,13 +6,12 @@ import { Trans, withNamespaces } from 'react-i18next';
|
||||
|
||||
import Menu from './Menu';
|
||||
import Version from './Version';
|
||||
import logo from './logo.svg';
|
||||
import logo from '../ui/svg/logo.svg';
|
||||
import './Header.css';
|
||||
|
||||
class Header extends Component {
|
||||
state = {
|
||||
isMenuOpen: false,
|
||||
isDropdownOpen: false,
|
||||
};
|
||||
|
||||
toggleMenuOpen = () => {
|
||||
@@ -25,6 +24,7 @@ class Header extends Component {
|
||||
|
||||
render() {
|
||||
const { dashboard } = this.props;
|
||||
const { isMenuOpen } = this.state;
|
||||
const badgeClass = classnames({
|
||||
'badge dns-status': true,
|
||||
'badge-success': dashboard.protectionEnabled,
|
||||
@@ -52,15 +52,17 @@ class Header extends Component {
|
||||
</div>
|
||||
<Menu
|
||||
location={this.props.location}
|
||||
isMenuOpen={this.state.isMenuOpen}
|
||||
isMenuOpen={isMenuOpen}
|
||||
toggleMenuOpen={this.toggleMenuOpen}
|
||||
closeMenu={this.closeMenu}
|
||||
/>
|
||||
<div className="col col-sm-6 col-lg-3">
|
||||
<Version
|
||||
{ ...this.props.dashboard }
|
||||
/>
|
||||
</div>
|
||||
{!dashboard.processing &&
|
||||
<div className="col col-sm-6 col-lg-3">
|
||||
<Version
|
||||
{ ...this.props.dashboard }
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,7 @@ import escapeRegExp from 'lodash/escapeRegExp';
|
||||
import endsWith from 'lodash/endsWith';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
|
||||
import { formatTime } from '../../helpers/helpers';
|
||||
import { formatTime, getClientName } from '../../helpers/helpers';
|
||||
import { getTrackerData } from '../../helpers/trackers/trackers';
|
||||
import PageTitle from '../ui/PageTitle';
|
||||
import Card from '../ui/Card';
|
||||
@@ -77,6 +77,7 @@ class Logs extends Component {
|
||||
type="button"
|
||||
className={`btn btn-sm ${buttonClass}`}
|
||||
onClick={() => this.toggleBlocking(buttonType, domain)}
|
||||
disabled={this.props.filtering.processingRules}
|
||||
>
|
||||
<Trans>{buttonText}</Trans>
|
||||
</button>
|
||||
@@ -85,7 +86,7 @@ class Logs extends Component {
|
||||
}
|
||||
|
||||
renderLogs(logs) {
|
||||
const { t } = this.props;
|
||||
const { t, dashboard } = this.props;
|
||||
const columns = [{
|
||||
Header: t('time_table_header'),
|
||||
accessor: 'time',
|
||||
@@ -195,11 +196,19 @@ class Logs extends Component {
|
||||
Cell: (row) => {
|
||||
const { reason } = row.original;
|
||||
const isFiltered = row ? reason.indexOf('Filtered') === 0 : false;
|
||||
const clientName = getClientName(dashboard.clients, row.value);
|
||||
let client;
|
||||
|
||||
if (clientName) {
|
||||
client = <span>{clientName} <small>({row.value})</small></span>;
|
||||
} else {
|
||||
client = row.value;
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div className="logs__row">
|
||||
{row.value}
|
||||
{client}
|
||||
</div>
|
||||
{this.renderBlockingButton(isFiltered, row.original.domain)}
|
||||
</Fragment>
|
||||
@@ -269,7 +278,7 @@ class Logs extends Component {
|
||||
saveAs(dataBlob, DOWNLOAD_LOG_FILENAME);
|
||||
};
|
||||
|
||||
renderButtons(queryLogEnabled) {
|
||||
renderButtons(queryLogEnabled, logStatusProcessing) {
|
||||
if (queryLogEnabled) {
|
||||
return (
|
||||
<Fragment>
|
||||
@@ -277,6 +286,7 @@ class Logs extends Component {
|
||||
className="btn btn-gray btn-sm mr-2"
|
||||
type="submit"
|
||||
onClick={() => this.props.toggleLogStatus(queryLogEnabled)}
|
||||
disabled={logStatusProcessing}
|
||||
><Trans>disabled_log_btn</Trans></button>
|
||||
<button
|
||||
className="btn btn-primary btn-sm mr-2"
|
||||
@@ -297,6 +307,7 @@ class Logs extends Component {
|
||||
className="btn btn-success btn-sm mr-2"
|
||||
type="submit"
|
||||
onClick={() => this.props.toggleLogStatus(queryLogEnabled)}
|
||||
disabled={logStatusProcessing}
|
||||
><Trans>enabled_log_btn</Trans></button>
|
||||
);
|
||||
}
|
||||
@@ -308,13 +319,22 @@ class Logs extends Component {
|
||||
<Fragment>
|
||||
<PageTitle title={ t('query_log') } subtitle={ t('last_dns_queries') }>
|
||||
<div className="page-title__actions">
|
||||
{this.renderButtons(queryLogEnabled)}
|
||||
{this.renderButtons(queryLogEnabled, dashboard.logStatusProcessing)}
|
||||
</div>
|
||||
</PageTitle>
|
||||
<Card>
|
||||
{queryLogEnabled && queryLogs.getLogsProcessing && <Loading />}
|
||||
{queryLogEnabled && !queryLogs.getLogsProcessing &&
|
||||
this.renderLogs(queryLogs.logs)}
|
||||
{
|
||||
queryLogEnabled
|
||||
&& queryLogs.getLogsProcessing
|
||||
&& dashboard.processingClients
|
||||
&& <Loading />
|
||||
}
|
||||
{
|
||||
queryLogEnabled
|
||||
&& !queryLogs.getLogsProcessing
|
||||
&& !dashboard.processingClients
|
||||
&& this.renderLogs(queryLogs.logs)
|
||||
}
|
||||
</Card>
|
||||
</Fragment>
|
||||
);
|
||||
@@ -332,6 +352,8 @@ Logs.propTypes = {
|
||||
userRules: PropTypes.string,
|
||||
setRules: PropTypes.func,
|
||||
addSuccessToast: PropTypes.func,
|
||||
processingRules: PropTypes.bool,
|
||||
logStatusProcessing: PropTypes.bool,
|
||||
t: PropTypes.func,
|
||||
};
|
||||
|
||||
|
||||
@@ -1,62 +1,25 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Field, reduxForm } from 'redux-form';
|
||||
import { withNamespaces, Trans } from 'react-i18next';
|
||||
import { withNamespaces } from 'react-i18next';
|
||||
import flow from 'lodash/flow';
|
||||
|
||||
import { R_IPV4 } from '../../../helpers/constants';
|
||||
|
||||
const required = (value) => {
|
||||
if (value || value === 0) {
|
||||
return false;
|
||||
}
|
||||
return <Trans>form_error_required</Trans>;
|
||||
};
|
||||
|
||||
const ipv4 = (value) => {
|
||||
if (value && !new RegExp(R_IPV4).test(value)) {
|
||||
return <Trans>form_error_ip_format</Trans>;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const isPositive = (value) => {
|
||||
if ((value || value === 0) && (value <= 0)) {
|
||||
return <Trans>form_error_positive</Trans>;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const toNumber = value => value && parseInt(value, 10);
|
||||
|
||||
const renderField = ({
|
||||
input, className, placeholder, type, disabled, meta: { touched, error },
|
||||
}) => (
|
||||
<Fragment>
|
||||
<input
|
||||
{...input}
|
||||
placeholder={placeholder}
|
||||
type={type}
|
||||
className={className}
|
||||
disabled={disabled}
|
||||
/>
|
||||
{!disabled && touched && (error && <span className="form__message form__message--error">{error}</span>)}
|
||||
</Fragment>
|
||||
);
|
||||
import { renderField, required, ipv4, isPositive, toNumber } from '../../../helpers/form';
|
||||
|
||||
const Form = (props) => {
|
||||
const {
|
||||
t,
|
||||
handleSubmit,
|
||||
pristine,
|
||||
submitting,
|
||||
invalid,
|
||||
processingConfig,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="row">
|
||||
<div className="col-lg-6">
|
||||
<div className="form__group form__group--dhcp">
|
||||
<div className="form__group form__group--settings">
|
||||
<label>{t('dhcp_form_gateway_input')}</label>
|
||||
<Field
|
||||
name="gateway_ip"
|
||||
@@ -67,7 +30,7 @@ const Form = (props) => {
|
||||
validate={[ipv4, required]}
|
||||
/>
|
||||
</div>
|
||||
<div className="form__group form__group--dhcp">
|
||||
<div className="form__group form__group--settings">
|
||||
<label>{t('dhcp_form_subnet_input')}</label>
|
||||
<Field
|
||||
name="subnet_mask"
|
||||
@@ -80,7 +43,7 @@ const Form = (props) => {
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-6">
|
||||
<div className="form__group form__group--dhcp">
|
||||
<div className="form__group form__group--settings">
|
||||
<div className="row">
|
||||
<div className="col-12">
|
||||
<label>{t('dhcp_form_range_title')}</label>
|
||||
@@ -107,7 +70,7 @@ const Form = (props) => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form__group form__group--dhcp">
|
||||
<div className="form__group form__group--settings">
|
||||
<label>{t('dhcp_form_lease_title')}</label>
|
||||
<Field
|
||||
name="lease_duration"
|
||||
@@ -124,8 +87,8 @@ const Form = (props) => {
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-success btn-standart"
|
||||
disabled={pristine || submitting}
|
||||
className="btn btn-success btn-standard"
|
||||
disabled={submitting || invalid || processingConfig}
|
||||
>
|
||||
{t('save_config')}
|
||||
</button>
|
||||
@@ -135,11 +98,11 @@ const Form = (props) => {
|
||||
|
||||
Form.propTypes = {
|
||||
handleSubmit: PropTypes.func,
|
||||
pristine: PropTypes.bool,
|
||||
submitting: PropTypes.bool,
|
||||
invalid: PropTypes.bool,
|
||||
interfaces: PropTypes.object,
|
||||
processing: PropTypes.bool,
|
||||
initialValues: PropTypes.object,
|
||||
processingConfig: PropTypes.bool,
|
||||
t: PropTypes.func,
|
||||
};
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ let Interface = (props) => {
|
||||
{!processing && interfaces &&
|
||||
<div className="row">
|
||||
<div className="col-sm-12 col-md-6">
|
||||
<div className="form__group form__group--dhcp">
|
||||
<div className="form__group form__group--settings">
|
||||
<label>{t('dhcp_interface_select')}</label>
|
||||
<Field
|
||||
name="interface_name"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ReactTable from 'react-table';
|
||||
import { withNamespaces } from 'react-i18next';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
|
||||
const columns = [{
|
||||
Header: 'MAC',
|
||||
@@ -10,10 +10,10 @@ const columns = [{
|
||||
Header: 'IP',
|
||||
accessor: 'ip',
|
||||
}, {
|
||||
Header: 'Hostname',
|
||||
Header: <Trans>dhcp_table_hostname</Trans>,
|
||||
accessor: 'hostname',
|
||||
}, {
|
||||
Header: 'Expires',
|
||||
Header: <Trans>dhcp_table_expires</Trans>,
|
||||
accessor: 'expires',
|
||||
}];
|
||||
|
||||
|
||||
@@ -13,17 +13,14 @@ class Dhcp extends Component {
|
||||
this.props.setDhcpConfig(values);
|
||||
};
|
||||
|
||||
handleFormChange = (value) => {
|
||||
this.props.setDhcpConfig(value);
|
||||
}
|
||||
|
||||
handleToggle = (config) => {
|
||||
this.props.toggleDhcp(config);
|
||||
this.props.findActiveDhcp(config.interface_name);
|
||||
}
|
||||
|
||||
getToggleDhcpButton = () => {
|
||||
const { config, active } = this.props.dhcp;
|
||||
const {
|
||||
config, active, processingDhcp, processingConfig,
|
||||
} = this.props.dhcp;
|
||||
const activeDhcpFound = active && active.found;
|
||||
const filledConfig = Object.keys(config).every((key) => {
|
||||
if (key === 'enabled') {
|
||||
@@ -37,8 +34,9 @@ class Dhcp extends Component {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-standart mr-2 btn-gray"
|
||||
className="btn btn-standard mr-2 btn-gray"
|
||||
onClick={() => this.props.toggleDhcp(config)}
|
||||
disabled={processingDhcp || processingConfig}
|
||||
>
|
||||
<Trans>dhcp_disable</Trans>
|
||||
</button>
|
||||
@@ -48,9 +46,14 @@ class Dhcp extends Component {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-standart mr-2 btn-success"
|
||||
className="btn btn-standard mr-2 btn-success"
|
||||
onClick={() => this.handleToggle(config)}
|
||||
disabled={!filledConfig || activeDhcpFound}
|
||||
disabled={
|
||||
!filledConfig
|
||||
|| activeDhcpFound
|
||||
|| processingDhcp
|
||||
|| processingConfig
|
||||
}
|
||||
>
|
||||
<Trans>dhcp_enable</Trans>
|
||||
</button>
|
||||
@@ -63,14 +66,14 @@ class Dhcp extends Component {
|
||||
if (active) {
|
||||
if (active.error) {
|
||||
return (
|
||||
<div className="text-danger">
|
||||
<div className="text-danger mb-2">
|
||||
{active.error}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<div className="mb-2">
|
||||
{active.found ? (
|
||||
<div className="text-danger">
|
||||
<Trans>dhcp_found</Trans>
|
||||
@@ -80,7 +83,7 @@ class Dhcp extends Component {
|
||||
<Trans>dhcp_not_found</Trans>
|
||||
</div>
|
||||
)}
|
||||
</Fragment>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -90,9 +93,14 @@ class Dhcp extends Component {
|
||||
render() {
|
||||
const { t, dhcp } = this.props;
|
||||
const statusButtonClass = classnames({
|
||||
'btn btn-primary btn-standart': true,
|
||||
'btn btn-primary btn-standart btn-loading': dhcp.processingStatus,
|
||||
'btn btn-primary btn-standard': true,
|
||||
'btn btn-primary btn-standard btn-loading': dhcp.processingStatus,
|
||||
});
|
||||
const {
|
||||
enabled,
|
||||
interface_name,
|
||||
...values
|
||||
} = dhcp.config;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
@@ -101,17 +109,17 @@ class Dhcp extends Component {
|
||||
{!dhcp.processing &&
|
||||
<Fragment>
|
||||
<Interface
|
||||
onChange={this.handleFormChange}
|
||||
initialValues={dhcp.config}
|
||||
onChange={this.handleFormSubmit}
|
||||
initialValues={{ interface_name }}
|
||||
interfaces={dhcp.interfaces}
|
||||
processing={dhcp.processingInterfaces}
|
||||
enabled={dhcp.config.enabled}
|
||||
/>
|
||||
<Form
|
||||
onSubmit={this.handleFormSubmit}
|
||||
initialValues={dhcp.config}
|
||||
initialValues={{ ...values }}
|
||||
interfaces={dhcp.interfaces}
|
||||
processing={dhcp.processingInterfaces}
|
||||
processingConfig={dhcp.processingConfig}
|
||||
/>
|
||||
<hr/>
|
||||
<div className="card-actions mb-3">
|
||||
@@ -122,12 +130,18 @@ class Dhcp extends Component {
|
||||
onClick={() =>
|
||||
this.props.findActiveDhcp(dhcp.config.interface_name)
|
||||
}
|
||||
disabled={!dhcp.config.interface_name}
|
||||
disabled={
|
||||
!dhcp.config.interface_name
|
||||
|| dhcp.processingConfig
|
||||
}
|
||||
>
|
||||
<Trans>check_dhcp_servers</Trans>
|
||||
</button>
|
||||
</div>
|
||||
{this.getActiveDhcpMessage()}
|
||||
<div className="text-danger">
|
||||
<Trans>dhcp_warning</Trans>
|
||||
</div>
|
||||
</Fragment>
|
||||
}
|
||||
</div>
|
||||
|
||||
367
client/src/components/Settings/Encryption/Form.js
Normal file
367
client/src/components/Settings/Encryption/Form.js
Normal file
@@ -0,0 +1,367 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Field, reduxForm, formValueSelector } from 'redux-form';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
import flow from 'lodash/flow';
|
||||
import format from 'date-fns/format';
|
||||
|
||||
import { renderField, renderSelectField, toNumber, port, isSafePort } from '../../../helpers/form';
|
||||
import { EMPTY_DATE } from '../../../helpers/constants';
|
||||
import i18n from '../../../i18n';
|
||||
|
||||
const validate = (values) => {
|
||||
const errors = {};
|
||||
|
||||
if (values.port_dns_over_tls && values.port_https) {
|
||||
if (values.port_dns_over_tls === values.port_https) {
|
||||
errors.port_dns_over_tls = i18n.t('form_error_equal');
|
||||
errors.port_https = i18n.t('form_error_equal');
|
||||
}
|
||||
}
|
||||
|
||||
return errors;
|
||||
};
|
||||
|
||||
const clearFields = (change, setTlsConfig, t) => {
|
||||
const fields = {
|
||||
private_key: '',
|
||||
certificate_chain: '',
|
||||
port_https: 443,
|
||||
port_dns_over_tls: 853,
|
||||
server_name: '',
|
||||
force_https: false,
|
||||
enabled: false,
|
||||
};
|
||||
// eslint-disable-next-line no-alert
|
||||
if (window.confirm(t('encryption_reset'))) {
|
||||
Object.keys(fields).forEach(field => change(field, fields[field]));
|
||||
setTlsConfig(fields);
|
||||
}
|
||||
};
|
||||
|
||||
let Form = (props) => {
|
||||
const {
|
||||
t,
|
||||
handleSubmit,
|
||||
handleChange,
|
||||
isEnabled,
|
||||
certificateChain,
|
||||
privateKey,
|
||||
change,
|
||||
invalid,
|
||||
submitting,
|
||||
processingConfig,
|
||||
processingValidate,
|
||||
not_after,
|
||||
valid_chain,
|
||||
valid_key,
|
||||
valid_cert,
|
||||
valid_pair,
|
||||
dns_names,
|
||||
key_type,
|
||||
issuer,
|
||||
subject,
|
||||
warning_validation,
|
||||
setTlsConfig,
|
||||
} = props;
|
||||
|
||||
const isSavingDisabled = invalid
|
||||
|| submitting
|
||||
|| processingConfig
|
||||
|| processingValidate
|
||||
|| (isEnabled && (!privateKey || !certificateChain))
|
||||
|| (privateKey && !valid_key)
|
||||
|| (certificateChain && !valid_cert)
|
||||
|| (privateKey && certificateChain && !valid_pair);
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="row">
|
||||
<div className="col-12">
|
||||
<div className="form__group form__group--settings">
|
||||
<Field
|
||||
name="enabled"
|
||||
type="checkbox"
|
||||
component={renderSelectField}
|
||||
placeholder={t('encryption_enable')}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
</div>
|
||||
<div className="form__desc">
|
||||
<Trans>encryption_enable_desc</Trans>
|
||||
</div>
|
||||
<hr/>
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<label className="form__label" htmlFor="server_name">
|
||||
<Trans>encryption_server</Trans>
|
||||
</label>
|
||||
</div>
|
||||
<div className="col-lg-6">
|
||||
<div className="form__group form__group--settings">
|
||||
<Field
|
||||
id="server_name"
|
||||
name="server_name"
|
||||
component={renderField}
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder={t('encryption_server_enter')}
|
||||
onChange={handleChange}
|
||||
disabled={!isEnabled}
|
||||
/>
|
||||
<div className="form__desc">
|
||||
<Trans>encryption_server_desc</Trans>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-6">
|
||||
<div className="form__group form__group--settings">
|
||||
<Field
|
||||
name="force_https"
|
||||
type="checkbox"
|
||||
component={renderSelectField}
|
||||
placeholder={t('encryption_redirect')}
|
||||
onChange={handleChange}
|
||||
disabled={!isEnabled}
|
||||
/>
|
||||
<div className="form__desc">
|
||||
<Trans>encryption_redirect_desc</Trans>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col-lg-6">
|
||||
<div className="form__group form__group--settings">
|
||||
<label className="form__label" htmlFor="port_https">
|
||||
<Trans>encryption_https</Trans>
|
||||
</label>
|
||||
<Field
|
||||
id="port_https"
|
||||
name="port_https"
|
||||
component={renderField}
|
||||
type="number"
|
||||
className="form-control"
|
||||
placeholder={t('encryption_https')}
|
||||
validate={[port, isSafePort]}
|
||||
normalize={toNumber}
|
||||
onChange={handleChange}
|
||||
disabled={!isEnabled}
|
||||
/>
|
||||
<div className="form__desc">
|
||||
<Trans>encryption_https_desc</Trans>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-6">
|
||||
<div className="form__group form__group--settings">
|
||||
<label className="form__label" htmlFor="port_dns_over_tls">
|
||||
<Trans>encryption_dot</Trans>
|
||||
</label>
|
||||
<Field
|
||||
id="port_dns_over_tls"
|
||||
name="port_dns_over_tls"
|
||||
component={renderField}
|
||||
type="number"
|
||||
className="form-control"
|
||||
placeholder={t('encryption_dot')}
|
||||
validate={[port]}
|
||||
normalize={toNumber}
|
||||
onChange={handleChange}
|
||||
disabled={!isEnabled}
|
||||
/>
|
||||
<div className="form__desc">
|
||||
<Trans>encryption_dot_desc</Trans>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col-12">
|
||||
<div className="form__group form__group--settings">
|
||||
<label className="form__label form__label--bold" htmlFor="certificate_chain">
|
||||
<Trans>encryption_certificates</Trans>
|
||||
</label>
|
||||
<div className="form__desc form__desc--top">
|
||||
<Trans
|
||||
values={{ link: 'letsencrypt.org' }}
|
||||
components={[<a href="https://letsencrypt.org/" key="0">link</a>]}
|
||||
>
|
||||
encryption_certificates_desc
|
||||
</Trans>
|
||||
</div>
|
||||
<Field
|
||||
id="certificate_chain"
|
||||
name="certificate_chain"
|
||||
component="textarea"
|
||||
type="text"
|
||||
className="form-control form-control--textarea"
|
||||
placeholder={t('encryption_certificates_input')}
|
||||
onChange={handleChange}
|
||||
disabled={!isEnabled}
|
||||
/>
|
||||
<div className="form__status">
|
||||
{certificateChain &&
|
||||
<Fragment>
|
||||
<div className="form__label form__label--bold">
|
||||
<Trans>encryption_status</Trans>:
|
||||
</div>
|
||||
<ul className="encryption__list">
|
||||
<li className={valid_chain ? 'text-success' : 'text-danger'}>
|
||||
{valid_chain ?
|
||||
<Trans>encryption_chain_valid</Trans>
|
||||
: <Trans>encryption_chain_invalid</Trans>
|
||||
}
|
||||
</li>
|
||||
{valid_cert &&
|
||||
<Fragment>
|
||||
{subject &&
|
||||
<li>
|
||||
<Trans>encryption_subject</Trans>:
|
||||
{subject}
|
||||
</li>
|
||||
}
|
||||
{issuer &&
|
||||
<li>
|
||||
<Trans>encryption_issuer</Trans>:
|
||||
{issuer}
|
||||
</li>
|
||||
}
|
||||
{not_after && not_after !== EMPTY_DATE &&
|
||||
<li>
|
||||
<Trans>encryption_expire</Trans>:
|
||||
{format(not_after, 'YYYY-MM-DD HH:mm:ss')}
|
||||
</li>
|
||||
}
|
||||
{dns_names &&
|
||||
<li>
|
||||
<Trans>encryption_hostnames</Trans>:
|
||||
{dns_names}
|
||||
</li>
|
||||
}
|
||||
</Fragment>
|
||||
}
|
||||
</ul>
|
||||
</Fragment>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col-12">
|
||||
<div className="form__group form__group--settings">
|
||||
<label className="form__label form__label--bold" htmlFor="private_key">
|
||||
<Trans>encryption_key</Trans>
|
||||
</label>
|
||||
<Field
|
||||
id="private_key"
|
||||
name="private_key"
|
||||
component="textarea"
|
||||
type="text"
|
||||
className="form-control form-control--textarea"
|
||||
placeholder="Copy/paste your PEM-encoded private key for your cerficate here."
|
||||
onChange={handleChange}
|
||||
disabled={!isEnabled}
|
||||
/>
|
||||
<div className="form__status">
|
||||
{privateKey &&
|
||||
<Fragment>
|
||||
<div className="form__label form__label--bold">
|
||||
<Trans>encryption_status</Trans>:
|
||||
</div>
|
||||
<ul className="encryption__list">
|
||||
<li className={valid_key ? 'text-success' : 'text-danger'}>
|
||||
{valid_key ?
|
||||
<Trans values={{ type: key_type }}>
|
||||
encryption_key_valid
|
||||
</Trans>
|
||||
: <Trans values={{ type: key_type }}>
|
||||
encryption_key_invalid
|
||||
</Trans>
|
||||
}
|
||||
</li>
|
||||
</ul>
|
||||
</Fragment>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{warning_validation &&
|
||||
<div className="col-12">
|
||||
<p className="text-danger">
|
||||
{warning_validation}
|
||||
</p>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div className="btn-list mt-2">
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-success btn-standart"
|
||||
disabled={isSavingDisabled}
|
||||
>
|
||||
<Trans>save_config</Trans>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary btn-standart"
|
||||
disabled={submitting || processingConfig}
|
||||
onClick={() => clearFields(change, setTlsConfig, t)}
|
||||
>
|
||||
<Trans>reset_settings</Trans>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
Form.propTypes = {
|
||||
handleSubmit: PropTypes.func.isRequired,
|
||||
handleChange: PropTypes.func,
|
||||
isEnabled: PropTypes.bool.isRequired,
|
||||
certificateChain: PropTypes.string.isRequired,
|
||||
privateKey: PropTypes.string.isRequired,
|
||||
change: PropTypes.func.isRequired,
|
||||
submitting: PropTypes.bool.isRequired,
|
||||
invalid: PropTypes.bool.isRequired,
|
||||
initialValues: PropTypes.object.isRequired,
|
||||
processingConfig: PropTypes.bool.isRequired,
|
||||
processingValidate: PropTypes.bool.isRequired,
|
||||
status_key: PropTypes.string,
|
||||
not_after: PropTypes.string,
|
||||
warning_validation: PropTypes.string,
|
||||
valid_chain: PropTypes.bool,
|
||||
valid_key: PropTypes.bool,
|
||||
valid_cert: PropTypes.bool,
|
||||
valid_pair: PropTypes.bool,
|
||||
dns_names: PropTypes.string,
|
||||
key_type: PropTypes.string,
|
||||
issuer: PropTypes.string,
|
||||
subject: PropTypes.string,
|
||||
t: PropTypes.func.isRequired,
|
||||
setTlsConfig: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
const selector = formValueSelector('encryptionForm');
|
||||
|
||||
Form = connect((state) => {
|
||||
const isEnabled = selector(state, 'enabled');
|
||||
const certificateChain = selector(state, 'certificate_chain');
|
||||
const privateKey = selector(state, 'private_key');
|
||||
return {
|
||||
isEnabled,
|
||||
certificateChain,
|
||||
privateKey,
|
||||
};
|
||||
})(Form);
|
||||
|
||||
export default flow([
|
||||
withNamespaces(),
|
||||
reduxForm({
|
||||
form: 'encryptionForm',
|
||||
validate,
|
||||
}),
|
||||
])(Form);
|
||||
72
client/src/components/Settings/Encryption/index.js
Normal file
72
client/src/components/Settings/Encryption/index.js
Normal file
@@ -0,0 +1,72 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withNamespaces } from 'react-i18next';
|
||||
import debounce from 'lodash/debounce';
|
||||
|
||||
import { DEBOUNCE_TIMEOUT } from '../../../helpers/constants';
|
||||
import Form from './Form';
|
||||
import Card from '../../ui/Card';
|
||||
|
||||
class Encryption extends Component {
|
||||
componentDidMount() {
|
||||
this.props.validateTlsConfig(this.props.encryption);
|
||||
}
|
||||
|
||||
handleFormSubmit = (values) => {
|
||||
this.props.setTlsConfig(values);
|
||||
};
|
||||
|
||||
handleFormChange = debounce((values) => {
|
||||
this.props.validateTlsConfig(values);
|
||||
}, DEBOUNCE_TIMEOUT);
|
||||
|
||||
render() {
|
||||
const { encryption, t } = this.props;
|
||||
const {
|
||||
enabled,
|
||||
server_name,
|
||||
force_https,
|
||||
port_https,
|
||||
port_dns_over_tls,
|
||||
certificate_chain,
|
||||
private_key,
|
||||
} = encryption;
|
||||
|
||||
return (
|
||||
<div className="encryption">
|
||||
{encryption &&
|
||||
<Card
|
||||
title={t('encryption_title')}
|
||||
subtitle={t('encryption_desc')}
|
||||
bodyType="card-body box-body--settings"
|
||||
>
|
||||
<Form
|
||||
initialValues={{
|
||||
enabled,
|
||||
server_name,
|
||||
force_https,
|
||||
port_https,
|
||||
port_dns_over_tls,
|
||||
certificate_chain,
|
||||
private_key,
|
||||
}}
|
||||
onSubmit={this.handleFormSubmit}
|
||||
onChange={this.handleFormChange}
|
||||
setTlsConfig={this.props.setTlsConfig}
|
||||
{...this.props.encryption}
|
||||
/>
|
||||
</Card>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Encryption.propTypes = {
|
||||
setTlsConfig: PropTypes.func.isRequired,
|
||||
validateTlsConfig: PropTypes.func.isRequired,
|
||||
encryption: PropTypes.object.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default withNamespaces()(Encryption);
|
||||
@@ -7,11 +7,11 @@
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.form__group--dhcp:last-child {
|
||||
margin-bottom: 15px;
|
||||
.form__group--settings:last-child {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.btn-standart {
|
||||
.btn-standard {
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
@@ -48,3 +48,31 @@
|
||||
.dhcp {
|
||||
min-height: 450px;
|
||||
}
|
||||
|
||||
.form__desc {
|
||||
margin-top: 10px;
|
||||
font-size: 13px;
|
||||
color: rgba(74, 74, 74, 0.7);
|
||||
}
|
||||
|
||||
.form__desc--top {
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
|
||||
.form__label--bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.form__status {
|
||||
margin-top: 10px;
|
||||
font-size: 14px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.encryption__list {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.encryption__list li {
|
||||
list-style: inside;
|
||||
}
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
import Card from '../ui/Card';
|
||||
|
||||
class Upstream extends Component {
|
||||
handleChange = (e) => {
|
||||
const { value } = e.currentTarget;
|
||||
this.props.handleUpstreamChange(value);
|
||||
};
|
||||
|
||||
handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
this.props.handleUpstreamSubmit();
|
||||
};
|
||||
|
||||
handleTest = () => {
|
||||
this.props.handleUpstreamTest();
|
||||
}
|
||||
|
||||
render() {
|
||||
const testButtonClass = classnames({
|
||||
'btn btn-primary btn-standart mr-2': true,
|
||||
'btn btn-primary btn-standart mr-2 btn-loading': this.props.processingTestUpstream,
|
||||
});
|
||||
const { t } = this.props;
|
||||
|
||||
return (
|
||||
<Card
|
||||
title={ t('upstream_dns') }
|
||||
subtitle={ t('upstream_dns_hint') }
|
||||
bodyType="card-body box-body--settings"
|
||||
>
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
<form>
|
||||
<textarea
|
||||
className="form-control form-control--textarea"
|
||||
value={this.props.upstreamDns}
|
||||
onChange={this.handleChange}
|
||||
/>
|
||||
<div className="card-actions">
|
||||
<button
|
||||
className={testButtonClass}
|
||||
type="button"
|
||||
onClick={this.handleTest}
|
||||
>
|
||||
<Trans>test_upstream_btn</Trans>
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-success btn-standart"
|
||||
type="submit"
|
||||
onClick={this.handleSubmit}
|
||||
>
|
||||
<Trans>apply_btn</Trans>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<hr/>
|
||||
<div className="list leading-loose">
|
||||
<Trans>examples_title</Trans>:
|
||||
<ol className="leading-loose">
|
||||
<li>
|
||||
<code>1.1.1.1</code> - { t('example_upstream_regular') }
|
||||
</li>
|
||||
<li>
|
||||
<code>tls://1dot1dot1dot1.cloudflare-dns.com</code> - <span dangerouslySetInnerHTML={{ __html: t('example_upstream_dot') }} />
|
||||
</li>
|
||||
<li>
|
||||
<code>https://cloudflare-dns.com/dns-query</code> - <span dangerouslySetInnerHTML={{ __html: t('example_upstream_doh') }} />
|
||||
</li>
|
||||
<li>
|
||||
<code>tcp://1.1.1.1</code> - { t('example_upstream_tcp') }
|
||||
</li>
|
||||
<li>
|
||||
<code>sdns://...</code> - <span dangerouslySetInnerHTML={{ __html: t('example_upstream_sdns') }} />
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Upstream.propTypes = {
|
||||
upstreamDns: PropTypes.string,
|
||||
processingTestUpstream: PropTypes.bool,
|
||||
handleUpstreamChange: PropTypes.func,
|
||||
handleUpstreamSubmit: PropTypes.func,
|
||||
handleUpstreamTest: PropTypes.func,
|
||||
t: PropTypes.func,
|
||||
};
|
||||
|
||||
export default withNamespaces()(Upstream);
|
||||
85
client/src/components/Settings/Upstream/Examples.js
Normal file
85
client/src/components/Settings/Upstream/Examples.js
Normal file
@@ -0,0 +1,85 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
|
||||
const Examples = props => (
|
||||
<div className="list leading-loose">
|
||||
<Trans>examples_title</Trans>:
|
||||
<ol className="leading-loose">
|
||||
<li>
|
||||
<code>1.1.1.1</code> - { props.t('example_upstream_regular') }
|
||||
</li>
|
||||
<li>
|
||||
<code>tls://1dot1dot1dot1.cloudflare-dns.com</code> –
|
||||
<span>
|
||||
<Trans
|
||||
components={[
|
||||
<a href="https://en.wikipedia.org/wiki/DNS_over_TLS" target="_blank" rel="noopener noreferrer" key="0">
|
||||
DNS-over-TLS
|
||||
</a>,
|
||||
]}
|
||||
>
|
||||
example_upstream_dot
|
||||
</Trans>
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<code>https://cloudflare-dns.com/dns-query</code> –
|
||||
<span>
|
||||
<Trans
|
||||
components={[
|
||||
<a href="https://en.wikipedia.org/wiki/DNS_over_HTTPS" target="_blank" rel="noopener noreferrer" key="0">
|
||||
DNS-over-HTTPS
|
||||
</a>,
|
||||
]}
|
||||
>
|
||||
example_upstream_doh
|
||||
</Trans>
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<code>tcp://1.1.1.1</code> – <Trans>example_upstream_tcp</Trans>
|
||||
</li>
|
||||
<li>
|
||||
<code>sdns://...</code> –
|
||||
<span>
|
||||
<Trans
|
||||
components={[
|
||||
<a href="https://dnscrypt.info/stamps/" target="_blank" rel="noopener noreferrer" key="0">
|
||||
DNS Stamps
|
||||
</a>,
|
||||
<a href="https://dnscrypt.info/" target="_blank" rel="noopener noreferrer" key="1">
|
||||
DNSCrypt
|
||||
</a>,
|
||||
<a href="https://en.wikipedia.org/wiki/DNS_over_HTTPS" target="_blank" rel="noopener noreferrer" key="2">
|
||||
DNS-over-HTTPS
|
||||
</a>,
|
||||
]}
|
||||
>
|
||||
example_upstream_sdns
|
||||
</Trans>
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<code>[/example.local/]1.1.1.1</code> –
|
||||
<span>
|
||||
<Trans
|
||||
components={[
|
||||
<a href="https://github.com/AdguardTeam/AdGuardHome/wiki/Configuration#upstreams-for-domains" target="_blank" rel="noopener noreferrer" key="0">
|
||||
Link
|
||||
</a>,
|
||||
]}
|
||||
>
|
||||
example_upstream_reserved
|
||||
</Trans>
|
||||
</span>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
);
|
||||
|
||||
Examples.propTypes = {
|
||||
t: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default withNamespaces()(Examples);
|
||||
144
client/src/components/Settings/Upstream/Form.js
Normal file
144
client/src/components/Settings/Upstream/Form.js
Normal file
@@ -0,0 +1,144 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Field, reduxForm, formValueSelector } from 'redux-form';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
import flow from 'lodash/flow';
|
||||
import classnames from 'classnames';
|
||||
|
||||
import { renderSelectField } from '../../../helpers/form';
|
||||
import Examples from './Examples';
|
||||
|
||||
let Form = (props) => {
|
||||
const {
|
||||
t,
|
||||
handleSubmit,
|
||||
testUpstream,
|
||||
upstreamDns,
|
||||
bootstrapDns,
|
||||
allServers,
|
||||
submitting,
|
||||
invalid,
|
||||
processingSetUpstream,
|
||||
processingTestUpstream,
|
||||
} = props;
|
||||
|
||||
const testButtonClass = classnames({
|
||||
'btn btn-primary btn-standard mr-2': true,
|
||||
'btn btn-primary btn-standard mr-2 btn-loading': processingTestUpstream,
|
||||
});
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="row">
|
||||
<div className="col-12">
|
||||
<div className="form__group form__group--settings">
|
||||
<label className="form__label" htmlFor="upstream_dns">
|
||||
<Trans>upstream_dns</Trans>
|
||||
</label>
|
||||
<Field
|
||||
id="upstream_dns"
|
||||
name="upstream_dns"
|
||||
component="textarea"
|
||||
type="text"
|
||||
className="form-control form-control--textarea"
|
||||
placeholder={t('upstream_dns')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<div className="form__group form__group--settings">
|
||||
<Field
|
||||
name="all_servers"
|
||||
type="checkbox"
|
||||
component={renderSelectField}
|
||||
placeholder={t('upstream_parallel')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<Examples />
|
||||
<hr/>
|
||||
</div>
|
||||
<div className="col-12">
|
||||
<div className="form__group">
|
||||
<label className="form__label" htmlFor="bootstrap_dns">
|
||||
<Trans>bootstrap_dns</Trans>
|
||||
</label>
|
||||
<div className="form__desc form__desc--top">
|
||||
<Trans>bootstrap_dns_desc</Trans>
|
||||
</div>
|
||||
<Field
|
||||
id="bootstrap_dns"
|
||||
name="bootstrap_dns"
|
||||
component="textarea"
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder={t('bootstrap_dns')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card-actions">
|
||||
<div className="btn-list">
|
||||
<button
|
||||
type="button"
|
||||
className={testButtonClass}
|
||||
onClick={() => testUpstream({
|
||||
upstream_dns: upstreamDns,
|
||||
bootstrap_dns: bootstrapDns,
|
||||
all_servers: allServers,
|
||||
})}
|
||||
disabled={!upstreamDns || processingTestUpstream}
|
||||
>
|
||||
<Trans>test_upstream_btn</Trans>
|
||||
</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-success btn-standard"
|
||||
disabled={
|
||||
submitting
|
||||
|| invalid
|
||||
|| processingSetUpstream
|
||||
|| processingTestUpstream
|
||||
}
|
||||
>
|
||||
<Trans>apply_btn</Trans>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
Form.propTypes = {
|
||||
handleSubmit: PropTypes.func,
|
||||
testUpstream: PropTypes.func,
|
||||
submitting: PropTypes.bool,
|
||||
invalid: PropTypes.bool,
|
||||
initialValues: PropTypes.object,
|
||||
upstreamDns: PropTypes.string,
|
||||
bootstrapDns: PropTypes.string,
|
||||
allServers: PropTypes.bool,
|
||||
processingTestUpstream: PropTypes.bool,
|
||||
processingSetUpstream: PropTypes.bool,
|
||||
t: PropTypes.func,
|
||||
};
|
||||
|
||||
const selector = formValueSelector('upstreamForm');
|
||||
|
||||
Form = connect((state) => {
|
||||
const upstreamDns = selector(state, 'upstream_dns');
|
||||
const bootstrapDns = selector(state, 'bootstrap_dns');
|
||||
const allServers = selector(state, 'all_servers');
|
||||
return {
|
||||
upstreamDns,
|
||||
bootstrapDns,
|
||||
allServers,
|
||||
};
|
||||
})(Form);
|
||||
|
||||
export default flow([
|
||||
withNamespaces(),
|
||||
reduxForm({ form: 'upstreamForm' }),
|
||||
])(Form);
|
||||
64
client/src/components/Settings/Upstream/index.js
Normal file
64
client/src/components/Settings/Upstream/index.js
Normal file
@@ -0,0 +1,64 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withNamespaces } from 'react-i18next';
|
||||
|
||||
import Form from './Form';
|
||||
import Card from '../../ui/Card';
|
||||
|
||||
class Upstream extends Component {
|
||||
handleSubmit = (values) => {
|
||||
this.props.setUpstream(values);
|
||||
};
|
||||
|
||||
handleTest = (values) => {
|
||||
this.props.testUpstream(values);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
t,
|
||||
upstreamDns: upstream_dns,
|
||||
bootstrapDns: bootstrap_dns,
|
||||
allServers: all_servers,
|
||||
processingSetUpstream,
|
||||
processingTestUpstream,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Card
|
||||
title={ t('upstream_dns') }
|
||||
subtitle={ t('upstream_dns_hint') }
|
||||
bodyType="card-body box-body--settings"
|
||||
>
|
||||
<div className="row">
|
||||
<div className="col">
|
||||
<Form
|
||||
initialValues={{
|
||||
upstream_dns,
|
||||
bootstrap_dns,
|
||||
all_servers,
|
||||
}}
|
||||
testUpstream={this.handleTest}
|
||||
onSubmit={this.handleSubmit}
|
||||
processingTestUpstream={processingTestUpstream}
|
||||
processingSetUpstream={processingSetUpstream}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Upstream.propTypes = {
|
||||
upstreamDns: PropTypes.string,
|
||||
bootstrapDns: PropTypes.string,
|
||||
allServers: PropTypes.bool,
|
||||
setUpstream: PropTypes.func.isRequired,
|
||||
testUpstream: PropTypes.func.isRequired,
|
||||
processingSetUpstream: PropTypes.bool.isRequired,
|
||||
processingTestUpstream: PropTypes.bool.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default withNamespaces()(Upstream);
|
||||
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
|
||||
import { withNamespaces, Trans } from 'react-i18next';
|
||||
import Upstream from './Upstream';
|
||||
import Dhcp from './Dhcp';
|
||||
import Encryption from './Encryption';
|
||||
import Checkbox from '../ui/Checkbox';
|
||||
import Loading from '../ui/Loading';
|
||||
import PageTitle from '../ui/PageTitle';
|
||||
@@ -37,24 +38,9 @@ class Settings extends Component {
|
||||
this.props.initSettings(this.settings);
|
||||
this.props.getDhcpStatus();
|
||||
this.props.getDhcpInterfaces();
|
||||
this.props.getTlsStatus();
|
||||
}
|
||||
|
||||
handleUpstreamChange = (value) => {
|
||||
this.props.handleUpstreamChange({ upstreamDns: value });
|
||||
};
|
||||
|
||||
handleUpstreamSubmit = () => {
|
||||
this.props.setUpstream(this.props.dashboard.upstreamDns);
|
||||
};
|
||||
|
||||
handleUpstreamTest = () => {
|
||||
if (this.props.dashboard.upstreamDns.length > 0) {
|
||||
this.props.testUpstream(this.props.dashboard.upstreamDns);
|
||||
} else {
|
||||
this.props.addErrorToast({ error: this.props.t('no_servers_specified') });
|
||||
}
|
||||
};
|
||||
|
||||
renderSettings = (settings) => {
|
||||
if (Object.keys(settings).length > 0) {
|
||||
return Object.keys(settings).map((key) => {
|
||||
@@ -73,8 +59,7 @@ class Settings extends Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const { settings, t } = this.props;
|
||||
const { upstreamDns } = this.props.dashboard;
|
||||
const { settings, dashboard, t } = this.props;
|
||||
return (
|
||||
<Fragment>
|
||||
<PageTitle title={ t('settings') } />
|
||||
@@ -89,11 +74,18 @@ class Settings extends Component {
|
||||
</div>
|
||||
</Card>
|
||||
<Upstream
|
||||
upstreamDns={upstreamDns}
|
||||
upstreamDns={dashboard.upstreamDns}
|
||||
bootstrapDns={dashboard.bootstrapDns}
|
||||
allServers={dashboard.allServers}
|
||||
setUpstream={this.props.setUpstream}
|
||||
testUpstream={this.props.testUpstream}
|
||||
processingTestUpstream={settings.processingTestUpstream}
|
||||
handleUpstreamChange={this.handleUpstreamChange}
|
||||
handleUpstreamSubmit={this.handleUpstreamSubmit}
|
||||
handleUpstreamTest={this.handleUpstreamTest}
|
||||
processingSetUpstream={settings.processingSetUpstream}
|
||||
/>
|
||||
<Encryption
|
||||
encryption={this.props.encryption}
|
||||
setTlsConfig={this.props.setTlsConfig}
|
||||
validateTlsConfig={this.props.validateTlsConfig}
|
||||
/>
|
||||
<Dhcp
|
||||
dhcp={this.props.dhcp}
|
||||
@@ -118,7 +110,6 @@ Settings.propTypes = {
|
||||
toggleSetting: PropTypes.func,
|
||||
handleUpstreamChange: PropTypes.func,
|
||||
setUpstream: PropTypes.func,
|
||||
upstream: PropTypes.string,
|
||||
t: PropTypes.func,
|
||||
};
|
||||
|
||||
|
||||
15
client/src/components/SetupGuide/Guide.css
Normal file
15
client/src/components/SetupGuide/Guide.css
Normal file
@@ -0,0 +1,15 @@
|
||||
.guide {
|
||||
max-width: 768px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.guide__title {
|
||||
margin-bottom: 10px;
|
||||
font-size: 17px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.guide__desc {
|
||||
margin-bottom: 20px;
|
||||
font-size: 15px;
|
||||
}
|
||||
46
client/src/components/SetupGuide/index.js
Normal file
46
client/src/components/SetupGuide/index.js
Normal file
@@ -0,0 +1,46 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
|
||||
import { getDnsAddress } from '../../helpers/helpers';
|
||||
|
||||
import Guide from '../ui/Guide';
|
||||
import Card from '../ui/Card';
|
||||
import PageTitle from '../ui/PageTitle';
|
||||
import './Guide.css';
|
||||
|
||||
const SetupGuide = ({
|
||||
t,
|
||||
dashboard: {
|
||||
dnsAddresses,
|
||||
dnsPort,
|
||||
},
|
||||
}) => (
|
||||
<div className="guide">
|
||||
<PageTitle title={t('setup_guide')} />
|
||||
<Card>
|
||||
<div className="guide__title">
|
||||
<Trans>install_devices_title</Trans>
|
||||
</div>
|
||||
<div className="guide__desc">
|
||||
<Trans>install_devices_desc</Trans>
|
||||
<div className="mt-1">
|
||||
<Trans>install_devices_address</Trans>:
|
||||
</div>
|
||||
<div className="mt-2 font-weight-bold">
|
||||
{dnsAddresses
|
||||
.map(ip => <li key={ip}>{getDnsAddress(ip, dnsPort)}</li>)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<Guide />
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
|
||||
SetupGuide.propTypes = {
|
||||
dashboard: PropTypes.object.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default withNamespaces()(SetupGuide);
|
||||
@@ -22,6 +22,11 @@
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.checkbox--form .checkbox__label:before {
|
||||
top: 2px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.checkbox__label {
|
||||
position: relative;
|
||||
display: flex;
|
||||
@@ -68,19 +73,28 @@
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.checkbox__input:checked+.checkbox__label:before {
|
||||
.checkbox__input:checked + .checkbox__label:before {
|
||||
background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMi4zIDkuMiIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiPjxwYXRoIGQ9Ik0xMS44IDAuNUw1LjMgOC41IDAuNSA0LjIiLz48L3N2Zz4=);
|
||||
}
|
||||
|
||||
.checkbox__input:focus+.checkbox__label:before {
|
||||
.checkbox__input:focus + .checkbox__label:before {
|
||||
box-shadow: 0 0 1px 1px rgba(74, 74, 74, 0.32);
|
||||
}
|
||||
|
||||
.checkbox__input:disabled + .checkbox__label {
|
||||
opacity: 0.6;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.checkbox__label-text {
|
||||
max-width: 515px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.checkbox__label-text--long {
|
||||
max-width: initial;
|
||||
}
|
||||
|
||||
.checkbox__label-title {
|
||||
display: block;
|
||||
line-height: 1.5;
|
||||
|
||||
43
client/src/components/ui/EncryptionTopline.js
Normal file
43
client/src/components/ui/EncryptionTopline.js
Normal file
@@ -0,0 +1,43 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
import isAfter from 'date-fns/is_after';
|
||||
import addDays from 'date-fns/add_days';
|
||||
|
||||
import Topline from './Topline';
|
||||
import { EMPTY_DATE } from '../../helpers/constants';
|
||||
|
||||
const EncryptionTopline = (props) => {
|
||||
if (props.notAfter === EMPTY_DATE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const isAboutExpire = isAfter(addDays(Date.now(), 30), props.notAfter);
|
||||
const isExpired = isAfter(Date.now(), props.notAfter);
|
||||
|
||||
if (isExpired) {
|
||||
return (
|
||||
<Topline type="danger">
|
||||
<Trans components={[<a href="#settings" key="0">link</a>]}>
|
||||
topline_expired_certificate
|
||||
</Trans>
|
||||
</Topline>
|
||||
);
|
||||
} else if (isAboutExpire) {
|
||||
return (
|
||||
<Topline type="warning">
|
||||
<Trans components={[<a href="#settings" key="0">link</a>]}>
|
||||
topline_expiring_certificate
|
||||
</Trans>
|
||||
</Topline>
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
EncryptionTopline.propTypes = {
|
||||
notAfter: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default withNamespaces()(EncryptionTopline);
|
||||
@@ -23,7 +23,7 @@ class Footer extends Component {
|
||||
<div className="footer__row">
|
||||
<div className="footer__column">
|
||||
<div className="footer__copyright">
|
||||
<Trans>copyright</Trans> © {this.getYear()} <a href="https://adguard.com/">AdGuard</a>
|
||||
<Trans>copyright</Trans> © {this.getYear()} <a href="https://adguard.com/">AdGuard</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="footer__column">
|
||||
|
||||
83
client/src/components/ui/Guide.js
Normal file
83
client/src/components/ui/Guide.js
Normal file
@@ -0,0 +1,83 @@
|
||||
import React from 'react';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
|
||||
import Tabs from '../ui/Tabs';
|
||||
import Icons from '../ui/Icons';
|
||||
|
||||
const Guide = () => (
|
||||
<div>
|
||||
<Icons />
|
||||
<Tabs>
|
||||
<div label="Router">
|
||||
<div className="tab__title">
|
||||
<Trans>install_devices_router</Trans>
|
||||
</div>
|
||||
<div className="tab__text">
|
||||
<p><Trans>install_devices_router_desc</Trans></p>
|
||||
<ol>
|
||||
<li><Trans>install_devices_router_list_1</Trans></li>
|
||||
<li><Trans>install_devices_router_list_2</Trans></li>
|
||||
<li><Trans>install_devices_router_list_3</Trans></li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
<div label="Windows">
|
||||
<div className="tab__title">
|
||||
Windows
|
||||
</div>
|
||||
<div className="tab__text">
|
||||
<ol>
|
||||
<li><Trans>install_devices_windows_list_1</Trans></li>
|
||||
<li><Trans>install_devices_windows_list_2</Trans></li>
|
||||
<li><Trans>install_devices_windows_list_3</Trans></li>
|
||||
<li><Trans>install_devices_windows_list_4</Trans></li>
|
||||
<li><Trans>install_devices_windows_list_5</Trans></li>
|
||||
<li><Trans>install_devices_windows_list_6</Trans></li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
<div label="macOS">
|
||||
<div className="tab__title">
|
||||
macOS
|
||||
</div>
|
||||
<div className="tab__text">
|
||||
<ol>
|
||||
<li><Trans>install_devices_macos_list_1</Trans></li>
|
||||
<li><Trans>install_devices_macos_list_2</Trans></li>
|
||||
<li><Trans>install_devices_macos_list_3</Trans></li>
|
||||
<li><Trans>install_devices_macos_list_4</Trans></li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
<div label="Android">
|
||||
<div className="tab__title">
|
||||
Android
|
||||
</div>
|
||||
<div className="tab__text">
|
||||
<ol>
|
||||
<li><Trans>install_devices_android_list_1</Trans></li>
|
||||
<li><Trans>install_devices_android_list_2</Trans></li>
|
||||
<li><Trans>install_devices_android_list_3</Trans></li>
|
||||
<li><Trans>install_devices_android_list_4</Trans></li>
|
||||
<li><Trans>install_devices_android_list_5</Trans></li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
<div label="iOS">
|
||||
<div className="tab__title">
|
||||
iOS
|
||||
</div>
|
||||
<div className="tab__text">
|
||||
<ol>
|
||||
<li><Trans>install_devices_ios_list_1</Trans></li>
|
||||
<li><Trans>install_devices_ios_list_2</Trans></li>
|
||||
<li><Trans>install_devices_ios_list_3</Trans></li>
|
||||
<li><Trans>install_devices_ios_list_4</Trans></li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default withNamespaces()(Guide);
|
||||
27
client/src/components/ui/Icons.js
Normal file
27
client/src/components/ui/Icons.js
Normal file
File diff suppressed because one or more lines are too long
@@ -55,6 +55,7 @@ class Modal extends Component {
|
||||
isOpen,
|
||||
title,
|
||||
inputDescription,
|
||||
processingAddFilter,
|
||||
} = this.props;
|
||||
const { isUrlValid, url, name } = this.state;
|
||||
const inputUrlClass = classnames({
|
||||
@@ -71,8 +72,8 @@ class Modal extends Component {
|
||||
if (!this.props.isFilterAdded) {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<input type="text" className={inputNameClass} placeholder={ this.props.t('enter_name_hint') } onChange={this.handleNameChange} />
|
||||
<input type="text" className={inputUrlClass} placeholder={ this.props.t('enter_url_hint') } onChange={this.handleUrlChange} />
|
||||
<input type="text" className={inputNameClass} placeholder={this.props.t('enter_name_hint')} onChange={this.handleNameChange} />
|
||||
<input type="text" className={inputUrlClass} placeholder={this.props.t('enter_url_hint')} onChange={this.handleUrlChange} />
|
||||
{inputDescription &&
|
||||
<div className="description">
|
||||
{inputDescription}
|
||||
@@ -82,7 +83,7 @@ class Modal extends Component {
|
||||
}
|
||||
return (
|
||||
<div className="description">
|
||||
<Trans>Url added successfully</Trans>
|
||||
<Trans>url_added_successfully</Trans>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -93,7 +94,7 @@ class Modal extends Component {
|
||||
<ReactModal
|
||||
className="Modal__Bootstrap modal-dialog modal-dialog-centered"
|
||||
closeTimeoutMS={0}
|
||||
isOpen={ isOpen }
|
||||
isOpen={isOpen}
|
||||
onRequestClose={this.closeModal}
|
||||
>
|
||||
<div className="modal-content">
|
||||
@@ -106,14 +107,26 @@ class Modal extends Component {
|
||||
</button>
|
||||
</div>
|
||||
<div className="modal-body">
|
||||
{ renderBody()}
|
||||
{renderBody()}
|
||||
</div>
|
||||
{
|
||||
!this.props.isFilterAdded &&
|
||||
<div className="modal-footer">
|
||||
<button type="button" className="btn btn-secondary" onClick={this.closeModal}><Trans>cancel_btn</Trans></button>
|
||||
<button type="button" className="btn btn-success" onClick={this.handleNext} disabled={isValidForSubmit}><Trans>add_filter_btn</Trans></button>
|
||||
</div>
|
||||
{!this.props.isFilterAdded &&
|
||||
<div className="modal-footer">
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary"
|
||||
onClick={this.closeModal}
|
||||
>
|
||||
<Trans>cancel_btn</Trans>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-success"
|
||||
onClick={this.handleNext}
|
||||
disabled={isValidForSubmit || processingAddFilter}
|
||||
>
|
||||
<Trans>add_filter_btn</Trans>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</ReactModal>
|
||||
@@ -128,6 +141,7 @@ Modal.propTypes = {
|
||||
inputDescription: PropTypes.string,
|
||||
addFilter: PropTypes.func.isRequired,
|
||||
isFilterAdded: PropTypes.bool,
|
||||
processingAddFilter: PropTypes.bool,
|
||||
t: PropTypes.func,
|
||||
};
|
||||
|
||||
|
||||
@@ -22,6 +22,16 @@
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.popover__trigger--address {
|
||||
top: 0;
|
||||
margin: 0;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.popover__trigger--address:after {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.popover__body {
|
||||
content: "";
|
||||
display: flex;
|
||||
@@ -57,6 +67,38 @@
|
||||
border-top: 6px solid #585965;
|
||||
}
|
||||
|
||||
.popover__body--address {
|
||||
top: calc(100% + 10px);
|
||||
right: 0;
|
||||
left: initial;
|
||||
bottom: initial;
|
||||
z-index: 1;
|
||||
min-width: 100px;
|
||||
padding: 12px 18px;
|
||||
font-weight: 700;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
transform: none;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.popover__body--address:after {
|
||||
top: -11px;
|
||||
left: initial;
|
||||
right: 40px;
|
||||
border-top: 6px solid transparent;
|
||||
border-bottom: 6px solid #585965;
|
||||
}
|
||||
|
||||
.popover__body--address:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: -7px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
.popover__trigger:hover + .popover__body,
|
||||
.popover__body:hover {
|
||||
visibility: visible;
|
||||
@@ -73,6 +115,10 @@
|
||||
stroke: #66b574;
|
||||
}
|
||||
|
||||
.popover__list--bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.popover__list-title {
|
||||
margin-bottom: 3px;
|
||||
}
|
||||
|
||||
41
client/src/components/ui/Tab.js
Normal file
41
client/src/components/ui/Tab.js
Normal file
@@ -0,0 +1,41 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
|
||||
class Tab extends Component {
|
||||
handleClick = () => {
|
||||
this.props.onClick(this.props.label);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
activeTab,
|
||||
label,
|
||||
} = this.props;
|
||||
|
||||
const tabClass = classnames({
|
||||
tab__control: true,
|
||||
'tab__control--active': activeTab === label,
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
className={tabClass}
|
||||
onClick={this.handleClick}
|
||||
>
|
||||
<svg className="tab__icon">
|
||||
<use xlinkHref={`#${label.toLowerCase()}`} />
|
||||
</svg>
|
||||
{label}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Tab.propTypes = {
|
||||
activeTab: PropTypes.string.isRequired,
|
||||
label: PropTypes.string.isRequired,
|
||||
onClick: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default Tab;
|
||||
@@ -3783,7 +3783,7 @@ tbody.collapse.show {
|
||||
line-height: 1.5;
|
||||
color: #495057;
|
||||
vertical-align: middle;
|
||||
background: #fff url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 5'%3E%3Cpath fill='#999' d='M0 0L10 0L5 5L0 0'/%3E%3C/svg%3E") no-repeat right 0.75rem center;
|
||||
background: #fff url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAxMCA1Jz48cGF0aCBmaWxsPScjOTk5JyBkPSdNMCAwTDEwIDBMNSA1TDAgMCcvPjwvc3ZnPg==") no-repeat right 0.75rem center;
|
||||
background-size: 8px 10px;
|
||||
border: 1px solid rgba(0, 40, 100, 0.12);
|
||||
border-radius: 3px;
|
||||
|
||||
51
client/src/components/ui/Tabs.css
Normal file
51
client/src/components/ui/Tabs.css
Normal file
@@ -0,0 +1,51 @@
|
||||
.tabs__controls {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 15px;
|
||||
padding: 15px 0;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
}
|
||||
|
||||
.tab__control {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
min-width: 70px;
|
||||
font-size: 13px;
|
||||
color: #555555;
|
||||
cursor: pointer;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.tab__control:hover,
|
||||
.tab__control:focus {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.tab__control--active {
|
||||
font-weight: 700;
|
||||
color: #4a4a4a;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.tab__title {
|
||||
margin-bottom: 10px;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.tab__icon {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-bottom: 6px;
|
||||
fill: #4a4a4a;
|
||||
}
|
||||
|
||||
.tab__text {
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.tab__text li,
|
||||
.tab__text p {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
59
client/src/components/ui/Tabs.js
Normal file
59
client/src/components/ui/Tabs.js
Normal file
@@ -0,0 +1,59 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import Tab from './Tab';
|
||||
import './Tabs.css';
|
||||
|
||||
class Tabs extends Component {
|
||||
state = {
|
||||
activeTab: this.props.children[0].props.label,
|
||||
};
|
||||
|
||||
onClickTabControl = (tab) => {
|
||||
this.setState({ activeTab: tab });
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
props: {
|
||||
children,
|
||||
},
|
||||
state: {
|
||||
activeTab,
|
||||
},
|
||||
} = this;
|
||||
|
||||
return (
|
||||
<div className="tabs">
|
||||
<div className="tabs__controls">
|
||||
{children.map((child) => {
|
||||
const { label } = child.props;
|
||||
|
||||
return (
|
||||
<Tab
|
||||
key={label}
|
||||
label={label}
|
||||
activeTab={activeTab}
|
||||
onClick={this.onClickTabControl}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div className="tabs__content">
|
||||
{children.map((child) => {
|
||||
if (child.props.label !== activeTab) {
|
||||
return false;
|
||||
}
|
||||
return child.props.children;
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Tabs.propTypes = {
|
||||
children: PropTypes.array.isRequired,
|
||||
};
|
||||
|
||||
export default Tabs;
|
||||
@@ -1,4 +1,4 @@
|
||||
.update {
|
||||
.topline {
|
||||
position: relative;
|
||||
z-index: 102;
|
||||
margin-bottom: 0;
|
||||
19
client/src/components/ui/Topline.js
Normal file
19
client/src/components/ui/Topline.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import './Topline.css';
|
||||
|
||||
const Topline = props => (
|
||||
<div className={`alert alert-${props.type} topline`}>
|
||||
<div className="container">
|
||||
{props.children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Topline.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
type: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default Topline;
|
||||
@@ -1,19 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import './Update.css';
|
||||
|
||||
const Update = props => (
|
||||
<div className="alert alert-info update">
|
||||
<div className="container">
|
||||
{props.announcement} <a href={props.announcementUrl} target="_blank" rel="noopener noreferrer">Click here</a> for more info.
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Update.propTypes = {
|
||||
announcement: PropTypes.string.isRequired,
|
||||
announcementUrl: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default Update;
|
||||
27
client/src/components/ui/UpdateTopline.js
Normal file
27
client/src/components/ui/UpdateTopline.js
Normal file
@@ -0,0 +1,27 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
|
||||
import Topline from './Topline';
|
||||
|
||||
const UpdateTopline = props => (
|
||||
<Topline type="info">
|
||||
<Trans
|
||||
values={{ version: props.version }}
|
||||
components={[
|
||||
<a href={props.url} target="_blank" rel="noopener noreferrer" key="0">
|
||||
Click here
|
||||
</a>,
|
||||
]}
|
||||
>
|
||||
update_announcement
|
||||
</Trans>
|
||||
</Topline>
|
||||
);
|
||||
|
||||
UpdateTopline.propTypes = {
|
||||
version: PropTypes.string.isRequired,
|
||||
url: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default withNamespaces()(UpdateTopline);
|
||||
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB |
@@ -3,8 +3,8 @@ import * as actionCreators from '../actions';
|
||||
import App from '../components/App';
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const { dashboard } = state;
|
||||
const props = { dashboard };
|
||||
const { dashboard, encryption } = state;
|
||||
const props = { dashboard, encryption };
|
||||
return props;
|
||||
};
|
||||
|
||||
|
||||
@@ -12,11 +12,26 @@ import {
|
||||
setDhcpConfig,
|
||||
findActiveDhcp,
|
||||
} from '../actions';
|
||||
import {
|
||||
getTlsStatus,
|
||||
setTlsConfig,
|
||||
validateTlsConfig,
|
||||
} from '../actions/encryption';
|
||||
import Settings from '../components/Settings';
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const { settings, dashboard, dhcp } = state;
|
||||
const props = { settings, dashboard, dhcp };
|
||||
const {
|
||||
settings,
|
||||
dashboard,
|
||||
dhcp,
|
||||
encryption,
|
||||
} = state;
|
||||
const props = {
|
||||
settings,
|
||||
dashboard,
|
||||
dhcp,
|
||||
encryption,
|
||||
};
|
||||
return props;
|
||||
};
|
||||
|
||||
@@ -32,6 +47,9 @@ const mapDispatchToProps = {
|
||||
getDhcpInterfaces,
|
||||
setDhcpConfig,
|
||||
findActiveDhcp,
|
||||
getTlsStatus,
|
||||
setTlsConfig,
|
||||
validateTlsConfig,
|
||||
};
|
||||
|
||||
export default connect(
|
||||
|
||||
14
client/src/containers/SetupGuide.js
Normal file
14
client/src/containers/SetupGuide.js
Normal file
@@ -0,0 +1,14 @@
|
||||
import { connect } from 'react-redux';
|
||||
import * as actionCreators from '../actions';
|
||||
import SetupGuide from '../components/SetupGuide';
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const { dashboard } = state;
|
||||
const props = { dashboard };
|
||||
return props;
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
actionCreators,
|
||||
)(SetupGuide);
|
||||
@@ -47,6 +47,10 @@ export const LANGUAGES = [
|
||||
key: 'vi',
|
||||
name: 'Tiếng Việt',
|
||||
},
|
||||
{
|
||||
key: 'bg',
|
||||
name: 'Български',
|
||||
},
|
||||
{
|
||||
key: 'ru',
|
||||
name: 'Русский',
|
||||
@@ -59,4 +63,95 @@ export const LANGUAGES = [
|
||||
key: 'zh-tw',
|
||||
name: '正體中文',
|
||||
},
|
||||
{
|
||||
key: 'zh-cn',
|
||||
name: '简体中文',
|
||||
},
|
||||
];
|
||||
|
||||
export const INSTALL_FIRST_STEP = 1;
|
||||
export const INSTALL_TOTAL_STEPS = 5;
|
||||
|
||||
export const SETTINGS_NAMES = {
|
||||
filtering: 'filtering',
|
||||
safebrowsing: 'safebrowsing',
|
||||
parental: 'parental',
|
||||
safesearch: 'safesearch',
|
||||
};
|
||||
|
||||
export const STANDARD_DNS_PORT = 53;
|
||||
export const STANDARD_WEB_PORT = 80;
|
||||
export const STANDARD_HTTPS_PORT = 443;
|
||||
|
||||
export const EMPTY_DATE = '0001-01-01T00:00:00Z';
|
||||
|
||||
export const DEBOUNCE_TIMEOUT = 300;
|
||||
export const CHECK_TIMEOUT = 1000;
|
||||
export const STOP_TIMEOUT = 10000;
|
||||
|
||||
export const UNSAFE_PORTS = [
|
||||
1,
|
||||
7,
|
||||
9,
|
||||
11,
|
||||
13,
|
||||
15,
|
||||
17,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
25,
|
||||
37,
|
||||
42,
|
||||
43,
|
||||
53,
|
||||
77,
|
||||
79,
|
||||
87,
|
||||
95,
|
||||
101,
|
||||
102,
|
||||
103,
|
||||
104,
|
||||
109,
|
||||
110,
|
||||
111,
|
||||
113,
|
||||
115,
|
||||
117,
|
||||
119,
|
||||
123,
|
||||
135,
|
||||
139,
|
||||
143,
|
||||
179,
|
||||
389,
|
||||
465,
|
||||
512,
|
||||
513,
|
||||
514,
|
||||
515,
|
||||
526,
|
||||
530,
|
||||
531,
|
||||
532,
|
||||
540,
|
||||
556,
|
||||
563,
|
||||
587,
|
||||
601,
|
||||
636,
|
||||
993,
|
||||
995,
|
||||
2049,
|
||||
3659,
|
||||
4045,
|
||||
6000,
|
||||
6665,
|
||||
6666,
|
||||
6667,
|
||||
6668,
|
||||
6669,
|
||||
];
|
||||
|
||||
79
client/src/helpers/form.js
Normal file
79
client/src/helpers/form.js
Normal file
@@ -0,0 +1,79 @@
|
||||
import React, { Fragment } from 'react';
|
||||
import { Trans } from 'react-i18next';
|
||||
|
||||
import { R_IPV4, UNSAFE_PORTS } from '../helpers/constants';
|
||||
|
||||
export const renderField = ({
|
||||
input, id, className, placeholder, type, disabled, meta: { touched, error },
|
||||
}) => (
|
||||
<Fragment>
|
||||
<input
|
||||
{...input}
|
||||
id={id}
|
||||
placeholder={placeholder}
|
||||
type={type}
|
||||
className={className}
|
||||
disabled={disabled}
|
||||
/>
|
||||
{!disabled && touched && (error && <span className="form__message form__message--error">{error}</span>)}
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
export const renderSelectField = ({
|
||||
input, placeholder, disabled, meta: { touched, error },
|
||||
}) => (
|
||||
<Fragment>
|
||||
<label className="checkbox checkbox--form">
|
||||
<span className="checkbox__marker"/>
|
||||
<input
|
||||
{...input}
|
||||
type="checkbox"
|
||||
className="checkbox__input"
|
||||
disabled={disabled}
|
||||
/>
|
||||
<span className="checkbox__label">
|
||||
<span className="checkbox__label-text checkbox__label-text--long">
|
||||
<span className="checkbox__label-title">{placeholder}</span>
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
{!disabled && touched && (error && <span className="form__message form__message--error">{error}</span>)}
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
export const required = (value) => {
|
||||
if (value || value === 0) {
|
||||
return false;
|
||||
}
|
||||
return <Trans>form_error_required</Trans>;
|
||||
};
|
||||
|
||||
export const ipv4 = (value) => {
|
||||
if (value && !new RegExp(R_IPV4).test(value)) {
|
||||
return <Trans>form_error_ip_format</Trans>;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export const isPositive = (value) => {
|
||||
if ((value || value === 0) && (value <= 0)) {
|
||||
return <Trans>form_error_positive</Trans>;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export const port = (value) => {
|
||||
if ((value || value === 0) && (value < 80 || value > 65535)) {
|
||||
return <Trans>form_error_port_range</Trans>;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export const isSafePort = (value) => {
|
||||
if (UNSAFE_PORTS.includes(value)) {
|
||||
return <Trans>form_error_port_unsafe</Trans>;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
export const toNumber = value => value && parseInt(value, 10);
|
||||
@@ -3,8 +3,15 @@ import dateFormat from 'date-fns/format';
|
||||
import subHours from 'date-fns/sub_hours';
|
||||
import addHours from 'date-fns/add_hours';
|
||||
import round from 'lodash/round';
|
||||
import axios from 'axios';
|
||||
|
||||
import { STATS_NAMES } from './constants';
|
||||
import {
|
||||
STATS_NAMES,
|
||||
STANDARD_DNS_PORT,
|
||||
STANDARD_WEB_PORT,
|
||||
STANDARD_HTTPS_PORT,
|
||||
CHECK_TIMEOUT,
|
||||
} from './constants';
|
||||
|
||||
export const formatTime = (time) => {
|
||||
const parsedTime = dateParse(time);
|
||||
@@ -85,3 +92,119 @@ export const getPercent = (amount, number) => {
|
||||
};
|
||||
|
||||
export const captitalizeWords = text => text.split(/[ -_]/g).map(str => str.charAt(0).toUpperCase() + str.substr(1)).join(' ');
|
||||
|
||||
export const getInterfaceIp = (option) => {
|
||||
const onlyIPv6 = option.ip_addresses.every(ip => ip.includes(':'));
|
||||
let interfaceIP = option.ip_addresses[0];
|
||||
|
||||
if (!onlyIPv6) {
|
||||
option.ip_addresses.forEach((ip) => {
|
||||
if (!ip.includes(':')) {
|
||||
interfaceIP = ip;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return interfaceIP;
|
||||
};
|
||||
|
||||
export const getIpList = (interfaces) => {
|
||||
let list = [];
|
||||
|
||||
Object.keys(interfaces).forEach((item) => {
|
||||
list = [...list, ...interfaces[item].ip_addresses];
|
||||
});
|
||||
|
||||
return list.sort();
|
||||
};
|
||||
|
||||
export const getDnsAddress = (ip, port = '') => {
|
||||
const isStandardDnsPort = port === STANDARD_DNS_PORT;
|
||||
let address = ip;
|
||||
|
||||
if (port) {
|
||||
if (ip.includes(':') && !isStandardDnsPort) {
|
||||
address = `[${ip}]:${port}`;
|
||||
} else if (!isStandardDnsPort) {
|
||||
address = `${ip}:${port}`;
|
||||
}
|
||||
}
|
||||
|
||||
return address;
|
||||
};
|
||||
|
||||
export const getWebAddress = (ip, port = '') => {
|
||||
const isStandardWebPort = port === STANDARD_WEB_PORT;
|
||||
let address = `http://${ip}`;
|
||||
|
||||
if (port) {
|
||||
if (ip.includes(':') && !isStandardWebPort) {
|
||||
address = `http://[${ip}]:${port}`;
|
||||
} else if (!isStandardWebPort) {
|
||||
address = `http://${ip}:${port}`;
|
||||
}
|
||||
}
|
||||
|
||||
return address;
|
||||
};
|
||||
|
||||
export const checkRedirect = (url, attempts) => {
|
||||
let count = attempts || 1;
|
||||
|
||||
if (count > 10) {
|
||||
window.location.replace(url);
|
||||
return false;
|
||||
}
|
||||
|
||||
const rmTimeout = t => t && clearTimeout(t);
|
||||
const setRecursiveTimeout = (time, ...args) => setTimeout(
|
||||
checkRedirect,
|
||||
time,
|
||||
...args,
|
||||
);
|
||||
|
||||
let timeout;
|
||||
|
||||
axios.get(url)
|
||||
.then((response) => {
|
||||
rmTimeout(timeout);
|
||||
if (response) {
|
||||
window.location.replace(url);
|
||||
return;
|
||||
}
|
||||
timeout = setRecursiveTimeout(CHECK_TIMEOUT, url, count += 1);
|
||||
})
|
||||
.catch((error) => {
|
||||
rmTimeout(timeout);
|
||||
if (error.response) {
|
||||
window.location.replace(url);
|
||||
return;
|
||||
}
|
||||
timeout = setRecursiveTimeout(CHECK_TIMEOUT, url, count += 1);
|
||||
});
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
export const redirectToCurrentProtocol = (values, httpPort = 80) => {
|
||||
const {
|
||||
protocol, hostname, hash, port,
|
||||
} = window.location;
|
||||
const { enabled, port_https } = values;
|
||||
const httpsPort = port_https !== STANDARD_HTTPS_PORT ? `:${port_https}` : '';
|
||||
|
||||
if (protocol !== 'https:' && enabled && port_https) {
|
||||
checkRedirect(`https://${hostname}${httpsPort}/${hash}`);
|
||||
} else if (protocol === 'https:' && enabled && port_https && port_https !== parseInt(port, 10)) {
|
||||
checkRedirect(`https://${hostname}${httpsPort}/${hash}`);
|
||||
} else if (protocol === 'https:' && (!enabled || !port_https)) {
|
||||
window.location.replace(`http://${hostname}:${httpPort}/${hash}`);
|
||||
}
|
||||
};
|
||||
|
||||
export const normalizeTextarea = text => text && text.replace(/[;, ]/g, '\n').split('\n').filter(n => n);
|
||||
|
||||
export const getClientName = (clients, ip) => {
|
||||
const client = clients.find(item => ip === item.ip);
|
||||
return (client && client.name) || '';
|
||||
};
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -12,6 +12,8 @@ import ja from './__locales/ja.json';
|
||||
import sv from './__locales/sv.json';
|
||||
import ptBR from './__locales/pt-br.json';
|
||||
import zhTW from './__locales/zh-tw.json';
|
||||
import bg from './__locales/bg.json';
|
||||
import zhCN from './__locales/zh-cn.json';
|
||||
|
||||
const resources = {
|
||||
en: {
|
||||
@@ -41,6 +43,12 @@ const resources = {
|
||||
'zh-TW': {
|
||||
translation: zhTW,
|
||||
},
|
||||
bg: {
|
||||
translation: bg,
|
||||
},
|
||||
'zh-CN': {
|
||||
translation: zhCN,
|
||||
},
|
||||
};
|
||||
|
||||
i18n
|
||||
|
||||
60
client/src/install/Setup/AddressList.js
Normal file
60
client/src/install/Setup/AddressList.js
Normal file
@@ -0,0 +1,60 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { getIpList, getDnsAddress, getWebAddress } from '../../helpers/helpers';
|
||||
|
||||
const AddressList = (props) => {
|
||||
let webAddress = getWebAddress(props.address, props.port);
|
||||
let dnsAddress = getDnsAddress(props.address, props.port);
|
||||
|
||||
if (props.address === '0.0.0.0') {
|
||||
return getIpList(props.interfaces).map((ip) => {
|
||||
webAddress = getWebAddress(ip, props.port);
|
||||
dnsAddress = getDnsAddress(ip, props.port);
|
||||
|
||||
if (props.isDns) {
|
||||
return (
|
||||
<li key={ip}>
|
||||
<strong>
|
||||
{dnsAddress}
|
||||
</strong>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<li key={ip}>
|
||||
<a href={webAddress}>
|
||||
{webAddress}
|
||||
</a>
|
||||
</li>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (props.isDns) {
|
||||
return (
|
||||
<strong>
|
||||
{dnsAddress}
|
||||
</strong>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<a href={webAddress}>
|
||||
{webAddress}
|
||||
</a>
|
||||
);
|
||||
};
|
||||
|
||||
AddressList.propTypes = {
|
||||
interfaces: PropTypes.object.isRequired,
|
||||
address: PropTypes.string.isRequired,
|
||||
port: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number,
|
||||
]),
|
||||
isDns: PropTypes.bool,
|
||||
};
|
||||
|
||||
export default AddressList;
|
||||
108
client/src/install/Setup/Auth.js
Normal file
108
client/src/install/Setup/Auth.js
Normal file
@@ -0,0 +1,108 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Field, reduxForm } from 'redux-form';
|
||||
import { withNamespaces, Trans } from 'react-i18next';
|
||||
import flow from 'lodash/flow';
|
||||
|
||||
import i18n from '../../i18n';
|
||||
import Controls from './Controls';
|
||||
import renderField from './renderField';
|
||||
|
||||
const required = (value) => {
|
||||
if (value || value === 0) {
|
||||
return false;
|
||||
}
|
||||
return <Trans>form_error_required</Trans>;
|
||||
};
|
||||
|
||||
const validate = (values) => {
|
||||
const errors = {};
|
||||
|
||||
if (values.confirm_password !== values.password) {
|
||||
errors.confirm_password = i18n.t('form_error_password');
|
||||
}
|
||||
|
||||
return errors;
|
||||
};
|
||||
|
||||
const Auth = (props) => {
|
||||
const {
|
||||
handleSubmit,
|
||||
pristine,
|
||||
invalid,
|
||||
t,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<form className="setup__step" onSubmit={handleSubmit}>
|
||||
<div className="setup__group">
|
||||
<div className="setup__subtitle">
|
||||
<Trans>install_auth_title</Trans>
|
||||
</div>
|
||||
<p className="setup__desc">
|
||||
<Trans>install_auth_desc</Trans>
|
||||
</p>
|
||||
<div className="form-group">
|
||||
<label>
|
||||
<Trans>install_auth_username</Trans>
|
||||
</label>
|
||||
<Field
|
||||
name="username"
|
||||
component={renderField}
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder={ t('install_auth_username_enter') }
|
||||
validate={[required]}
|
||||
autoComplete="username"
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>
|
||||
<Trans>install_auth_password</Trans>
|
||||
</label>
|
||||
<Field
|
||||
name="password"
|
||||
component={renderField}
|
||||
type="password"
|
||||
className="form-control"
|
||||
placeholder={ t('install_auth_password_enter') }
|
||||
validate={[required]}
|
||||
autoComplete="new-password"
|
||||
/>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<label>
|
||||
<Trans>install_auth_confirm</Trans>
|
||||
</label>
|
||||
<Field
|
||||
name="confirm_password"
|
||||
component={renderField}
|
||||
type="password"
|
||||
className="form-control"
|
||||
placeholder={ t('install_auth_confirm') }
|
||||
validate={[required]}
|
||||
autoComplete="new-password"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Controls pristine={pristine} invalid={invalid} />
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
Auth.propTypes = {
|
||||
handleSubmit: PropTypes.func.isRequired,
|
||||
pristine: PropTypes.bool.isRequired,
|
||||
invalid: PropTypes.bool.isRequired,
|
||||
t: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default flow([
|
||||
withNamespaces(),
|
||||
reduxForm({
|
||||
form: 'install',
|
||||
destroyOnUnmount: false,
|
||||
forceUnregisterOnUnmount: true,
|
||||
validate,
|
||||
}),
|
||||
])(Auth);
|
||||
113
client/src/install/Setup/Controls.js
Normal file
113
client/src/install/Setup/Controls.js
Normal file
@@ -0,0 +1,113 @@
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Trans } from 'react-i18next';
|
||||
|
||||
import * as actionCreators from '../../actions/install';
|
||||
|
||||
class Controls extends Component {
|
||||
renderPrevButton(step) {
|
||||
switch (step) {
|
||||
case 2:
|
||||
case 3:
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary btn-lg setup__button"
|
||||
onClick={this.props.prevStep}
|
||||
>
|
||||
<Trans>back</Trans>
|
||||
</button>
|
||||
);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
renderNextButton(step) {
|
||||
switch (step) {
|
||||
case 1:
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-success btn-lg setup__button"
|
||||
onClick={this.props.nextStep}
|
||||
>
|
||||
<Trans>get_started</Trans>
|
||||
</button>
|
||||
);
|
||||
case 2:
|
||||
case 3:
|
||||
return (
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-success btn-lg setup__button"
|
||||
disabled={
|
||||
this.props.invalid
|
||||
|| this.props.pristine
|
||||
|| this.props.install.processingSubmit
|
||||
}
|
||||
>
|
||||
<Trans>next</Trans>
|
||||
</button>
|
||||
);
|
||||
case 4:
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-success btn-lg setup__button"
|
||||
onClick={this.props.nextStep}
|
||||
>
|
||||
<Trans>next</Trans>
|
||||
</button>
|
||||
);
|
||||
case 5:
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-success btn-lg setup__button"
|
||||
onClick={() => this.props.openDashboard(this.props.address)}
|
||||
>
|
||||
<Trans>open_dashboard</Trans>
|
||||
</button>
|
||||
);
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { install } = this.props;
|
||||
|
||||
return (
|
||||
<div className="setup__nav">
|
||||
<div className="btn-list">
|
||||
{this.renderPrevButton(install.step)}
|
||||
{this.renderNextButton(install.step)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Controls.propTypes = {
|
||||
install: PropTypes.object.isRequired,
|
||||
nextStep: PropTypes.func,
|
||||
prevStep: PropTypes.func,
|
||||
openDashboard: PropTypes.func,
|
||||
submitting: PropTypes.bool,
|
||||
invalid: PropTypes.bool,
|
||||
pristine: PropTypes.bool,
|
||||
address: PropTypes.string,
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const { install } = state;
|
||||
const props = { install };
|
||||
return props;
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
actionCreators,
|
||||
)(Controls);
|
||||
63
client/src/install/Setup/Devices.js
Normal file
63
client/src/install/Setup/Devices.js
Normal file
@@ -0,0 +1,63 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { reduxForm, formValueSelector } from 'redux-form';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
import flow from 'lodash/flow';
|
||||
|
||||
import Guide from '../../components/ui/Guide';
|
||||
import Controls from './Controls';
|
||||
import AddressList from './AddressList';
|
||||
|
||||
let Devices = props => (
|
||||
<div className="setup__step">
|
||||
<div className="setup__group">
|
||||
<div className="setup__subtitle">
|
||||
<Trans>install_devices_title</Trans>
|
||||
</div>
|
||||
<div className="setup__desc">
|
||||
<Trans>install_devices_desc</Trans>
|
||||
<div className="mt-1">
|
||||
<Trans>install_devices_address</Trans>:
|
||||
</div>
|
||||
<div className="mt-1">
|
||||
<AddressList
|
||||
interfaces={props.interfaces}
|
||||
address={props.dnsIp}
|
||||
port={props.dnsPort}
|
||||
isDns={true}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<Guide />
|
||||
</div>
|
||||
<Controls />
|
||||
</div>
|
||||
);
|
||||
|
||||
Devices.propTypes = {
|
||||
interfaces: PropTypes.object.isRequired,
|
||||
dnsIp: PropTypes.string.isRequired,
|
||||
dnsPort: PropTypes.number.isRequired,
|
||||
};
|
||||
|
||||
const selector = formValueSelector('install');
|
||||
|
||||
Devices = connect((state) => {
|
||||
const dnsIp = selector(state, 'dns.ip');
|
||||
const dnsPort = selector(state, 'dns.port');
|
||||
|
||||
return {
|
||||
dnsIp,
|
||||
dnsPort,
|
||||
};
|
||||
})(Devices);
|
||||
|
||||
export default flow([
|
||||
withNamespaces(),
|
||||
reduxForm({
|
||||
form: 'install',
|
||||
destroyOnUnmount: false,
|
||||
forceUnregisterOnUnmount: true,
|
||||
}),
|
||||
])(Devices);
|
||||
19
client/src/install/Setup/Greeting.js
Normal file
19
client/src/install/Setup/Greeting.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import React from 'react';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
import Controls from './Controls';
|
||||
|
||||
const Greeting = () => (
|
||||
<div className="setup__step">
|
||||
<div className="setup__group">
|
||||
<h1 className="setup__title">
|
||||
<Trans>install_welcome_title</Trans>
|
||||
</h1>
|
||||
<p className="setup__desc text-center">
|
||||
<Trans>install_welcome_desc</Trans>
|
||||
</p>
|
||||
</div>
|
||||
<Controls />
|
||||
</div>
|
||||
);
|
||||
|
||||
export default withNamespaces()(Greeting);
|
||||
25
client/src/install/Setup/Progress.js
Normal file
25
client/src/install/Setup/Progress.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
|
||||
import { INSTALL_TOTAL_STEPS } from '../../helpers/constants';
|
||||
|
||||
const getProgressPercent = step => (step / INSTALL_TOTAL_STEPS) * 100;
|
||||
|
||||
const Progress = props => (
|
||||
<div className="setup__progress">
|
||||
<Trans>install_step</Trans> {props.step}/{INSTALL_TOTAL_STEPS}
|
||||
<div className="setup__progress-wrap">
|
||||
<div
|
||||
className="setup__progress-inner"
|
||||
style={{ width: `${getProgressPercent(props.step)}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Progress.propTypes = {
|
||||
step: PropTypes.number.isRequired,
|
||||
};
|
||||
|
||||
export default withNamespaces()(Progress);
|
||||
221
client/src/install/Setup/Settings.js
Normal file
221
client/src/install/Setup/Settings.js
Normal file
@@ -0,0 +1,221 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { Field, reduxForm, formValueSelector } from 'redux-form';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
import flow from 'lodash/flow';
|
||||
|
||||
import Controls from './Controls';
|
||||
import AddressList from './AddressList';
|
||||
import renderField from './renderField';
|
||||
import { getInterfaceIp } from '../../helpers/helpers';
|
||||
|
||||
const required = (value) => {
|
||||
if (value || value === 0) {
|
||||
return false;
|
||||
}
|
||||
return <Trans>form_error_required</Trans>;
|
||||
};
|
||||
|
||||
const port = (value) => {
|
||||
if (value < 1 || value > 65535) {
|
||||
return <Trans>form_error_port</Trans>;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const toNumber = value => value && parseInt(value, 10);
|
||||
|
||||
const renderInterfaces = (interfaces => (
|
||||
Object.keys(interfaces).map((item) => {
|
||||
const option = interfaces[item];
|
||||
const { name } = option;
|
||||
|
||||
if (option.ip_addresses && option.ip_addresses.length > 0) {
|
||||
const ip = getInterfaceIp(option);
|
||||
|
||||
return (
|
||||
<option value={ip} key={name}>
|
||||
{name} - {ip}
|
||||
</option>
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
})
|
||||
));
|
||||
|
||||
let Settings = (props) => {
|
||||
const {
|
||||
handleSubmit,
|
||||
webIp,
|
||||
webPort,
|
||||
dnsIp,
|
||||
dnsPort,
|
||||
interfaces,
|
||||
invalid,
|
||||
webWarning,
|
||||
dnsWarning,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<form className="setup__step" onSubmit={handleSubmit}>
|
||||
<div className="setup__group">
|
||||
<div className="setup__subtitle">
|
||||
<Trans>install_settings_title</Trans>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col-8">
|
||||
<div className="form-group">
|
||||
<label>
|
||||
<Trans>install_settings_listen</Trans>
|
||||
</label>
|
||||
<Field
|
||||
name="web.ip"
|
||||
component="select"
|
||||
className="form-control custom-select"
|
||||
>
|
||||
<option value="0.0.0.0">
|
||||
<Trans>install_settings_all_interfaces</Trans>
|
||||
</option>
|
||||
{renderInterfaces(interfaces)}
|
||||
</Field>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-4">
|
||||
<div className="form-group">
|
||||
<label>
|
||||
<Trans>install_settings_port</Trans>
|
||||
</label>
|
||||
<Field
|
||||
name="web.port"
|
||||
component={renderField}
|
||||
type="number"
|
||||
className="form-control"
|
||||
placeholder="80"
|
||||
validate={[port, required]}
|
||||
normalize={toNumber}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="setup__desc">
|
||||
<Trans>install_settings_interface_link</Trans>
|
||||
<div className="mt-1">
|
||||
<AddressList
|
||||
interfaces={interfaces}
|
||||
address={webIp}
|
||||
port={webPort}
|
||||
/>
|
||||
</div>
|
||||
{webWarning &&
|
||||
<div className="text-danger mt-2">
|
||||
{webWarning}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div className="setup__group">
|
||||
<div className="setup__subtitle">
|
||||
<Trans>install_settings_dns</Trans>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col-8">
|
||||
<div className="form-group">
|
||||
<label>
|
||||
<Trans>install_settings_listen</Trans>
|
||||
</label>
|
||||
<Field
|
||||
name="dns.ip"
|
||||
component="select"
|
||||
className="form-control custom-select"
|
||||
>
|
||||
<option value="0.0.0.0">
|
||||
<Trans>install_settings_all_interfaces</Trans>
|
||||
</option>
|
||||
{renderInterfaces(interfaces)}
|
||||
</Field>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-4">
|
||||
<div className="form-group">
|
||||
<label>
|
||||
<Trans>install_settings_port</Trans>
|
||||
</label>
|
||||
<Field
|
||||
name="dns.port"
|
||||
component={renderField}
|
||||
type="number"
|
||||
className="form-control"
|
||||
placeholder="80"
|
||||
validate={[port, required]}
|
||||
normalize={toNumber}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="setup__desc">
|
||||
<Trans>install_settings_dns_desc</Trans>
|
||||
<div className="mt-1">
|
||||
<AddressList
|
||||
interfaces={interfaces}
|
||||
address={dnsIp}
|
||||
port={dnsPort}
|
||||
isDns={true}
|
||||
/>
|
||||
</div>
|
||||
{dnsWarning &&
|
||||
<div className="text-danger mt-2">
|
||||
{dnsWarning}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<Controls invalid={invalid} />
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
Settings.propTypes = {
|
||||
handleSubmit: PropTypes.func.isRequired,
|
||||
webIp: PropTypes.string.isRequired,
|
||||
dnsIp: PropTypes.string.isRequired,
|
||||
webPort: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number,
|
||||
]),
|
||||
dnsPort: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number,
|
||||
]),
|
||||
webWarning: PropTypes.string.isRequired,
|
||||
dnsWarning: PropTypes.string.isRequired,
|
||||
interfaces: PropTypes.object.isRequired,
|
||||
invalid: PropTypes.bool.isRequired,
|
||||
initialValues: PropTypes.object,
|
||||
};
|
||||
|
||||
const selector = formValueSelector('install');
|
||||
|
||||
Settings = connect((state) => {
|
||||
const webIp = selector(state, 'web.ip');
|
||||
const webPort = selector(state, 'web.port');
|
||||
const dnsIp = selector(state, 'dns.ip');
|
||||
const dnsPort = selector(state, 'dns.port');
|
||||
|
||||
return {
|
||||
webIp,
|
||||
webPort,
|
||||
dnsIp,
|
||||
dnsPort,
|
||||
};
|
||||
})(Settings);
|
||||
|
||||
export default flow([
|
||||
withNamespaces(),
|
||||
reduxForm({
|
||||
form: 'install',
|
||||
destroyOnUnmount: false,
|
||||
forceUnregisterOnUnmount: true,
|
||||
}),
|
||||
])(Settings);
|
||||
117
client/src/install/Setup/Setup.css
Normal file
117
client/src/install/Setup/Setup.css
Normal file
@@ -0,0 +1,117 @@
|
||||
.setup {
|
||||
min-height: calc(100vh - 80px);
|
||||
line-height: 1.48;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.setup {
|
||||
padding: 50px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.setup__container {
|
||||
max-width: 650px;
|
||||
margin: 0 auto;
|
||||
padding: 30px 20px;
|
||||
line-height: 1.6;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 1px 4px rgba(74, 74, 74, 0.36);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.setup__container {
|
||||
width: 650px;
|
||||
padding: 40px 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.setup__logo {
|
||||
display: block;
|
||||
margin: 0 auto 40px;
|
||||
max-width: 140px;
|
||||
}
|
||||
|
||||
.setup__nav {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.setup__step {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.setup__title {
|
||||
margin-bottom: 30px;
|
||||
font-size: 28px;
|
||||
text-align: center;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.setup__subtitle {
|
||||
margin-bottom: 10px;
|
||||
font-size: 17px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.setup__desc {
|
||||
margin-bottom: 20px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.setup__group {
|
||||
margin-bottom: 35px;
|
||||
}
|
||||
|
||||
.setup__group:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.setup__progress {
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.setup__progress-wrap {
|
||||
height: 4px;
|
||||
margin: 20px -20px -30px -20px;
|
||||
overflow: hidden;
|
||||
background-color: #eaeaea;
|
||||
border-radius: 0 0 3px 3px;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.setup__progress-wrap {
|
||||
margin: 20px -30px -40px -30px;
|
||||
}
|
||||
}
|
||||
|
||||
.setup__progress-inner {
|
||||
width: 0;
|
||||
height: 100%;
|
||||
font-size: 1.2rem;
|
||||
line-height: 20px;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
|
||||
transition: width 0.6s ease;
|
||||
background: linear-gradient(45deg, rgba(99, 125, 120, 1) 0%, rgba(88, 177, 101, 1) 100%);
|
||||
}
|
||||
|
||||
.btn-standard {
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.form__message {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.form__message--error {
|
||||
color: #cd201f;
|
||||
}
|
||||
|
||||
.setup__button {
|
||||
min-width: 120px;
|
||||
padding-left: 30px;
|
||||
padding-right: 30px;
|
||||
}
|
||||
57
client/src/install/Setup/Submit.js
Normal file
57
client/src/install/Setup/Submit.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
import { reduxForm, formValueSelector } from 'redux-form';
|
||||
import { Trans, withNamespaces } from 'react-i18next';
|
||||
import flow from 'lodash/flow';
|
||||
|
||||
import Controls from './Controls';
|
||||
import { getWebAddress } from '../../helpers/helpers';
|
||||
|
||||
let Submit = props => (
|
||||
<div className="setup__step">
|
||||
<div className="setup__group">
|
||||
<h1 className="setup__title">
|
||||
<Trans>install_submit_title</Trans>
|
||||
</h1>
|
||||
<p className="setup__desc">
|
||||
<Trans>install_submit_desc</Trans>
|
||||
</p>
|
||||
</div>
|
||||
<Controls
|
||||
openDashboard={props.openDashboard}
|
||||
address={getWebAddress(props.webIp, props.webPort)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
Submit.propTypes = {
|
||||
webIp: PropTypes.string.isRequired,
|
||||
webPort: PropTypes.number.isRequired,
|
||||
handleSubmit: PropTypes.func.isRequired,
|
||||
pristine: PropTypes.bool.isRequired,
|
||||
submitting: PropTypes.bool.isRequired,
|
||||
openDashboard: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
const selector = formValueSelector('install');
|
||||
|
||||
Submit = connect((state) => {
|
||||
const webIp = selector(state, 'web.ip');
|
||||
const webPort = selector(state, 'web.port');
|
||||
|
||||
return {
|
||||
webIp,
|
||||
webPort,
|
||||
};
|
||||
})(Submit);
|
||||
|
||||
|
||||
export default flow([
|
||||
withNamespaces(),
|
||||
reduxForm({
|
||||
form: 'install',
|
||||
destroyOnUnmount: false,
|
||||
forceUnregisterOnUnmount: true,
|
||||
}),
|
||||
])(Submit);
|
||||
125
client/src/install/Setup/index.js
Normal file
125
client/src/install/Setup/index.js
Normal file
@@ -0,0 +1,125 @@
|
||||
import React, { Component, Fragment } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import * as actionCreators from '../../actions/install';
|
||||
import { INSTALL_FIRST_STEP, INSTALL_TOTAL_STEPS } from '../../helpers/constants';
|
||||
|
||||
import Loading from '../../components/ui/Loading';
|
||||
import Greeting from './Greeting';
|
||||
import Settings from './Settings';
|
||||
import Auth from './Auth';
|
||||
import Devices from './Devices';
|
||||
import Submit from './Submit';
|
||||
import Progress from './Progress';
|
||||
|
||||
import Toasts from '../../components/Toasts';
|
||||
import Footer from '../../components/ui/Footer';
|
||||
import logo from '../../components/ui/svg/logo.svg';
|
||||
|
||||
import './Setup.css';
|
||||
import '../../components/ui/Tabler.css';
|
||||
|
||||
class Setup extends Component {
|
||||
componentDidMount() {
|
||||
this.props.getDefaultAddresses();
|
||||
}
|
||||
|
||||
handleFormSubmit = (values) => {
|
||||
this.props.setAllSettings(values);
|
||||
};
|
||||
|
||||
openDashboard = (address) => {
|
||||
window.location.replace(address);
|
||||
}
|
||||
|
||||
nextStep = () => {
|
||||
if (this.props.install.step < INSTALL_TOTAL_STEPS) {
|
||||
this.props.nextStep();
|
||||
}
|
||||
}
|
||||
|
||||
prevStep = () => {
|
||||
if (this.props.install.step > INSTALL_FIRST_STEP) {
|
||||
this.props.prevStep();
|
||||
}
|
||||
}
|
||||
|
||||
renderPage(step, config, interfaces) {
|
||||
switch (step) {
|
||||
case 1:
|
||||
return <Greeting />;
|
||||
case 2:
|
||||
return (
|
||||
<Settings
|
||||
initialValues={config}
|
||||
interfaces={interfaces}
|
||||
webWarning={config.web.warning}
|
||||
dnsWarning={config.dns.warning}
|
||||
onSubmit={this.nextStep}
|
||||
/>
|
||||
);
|
||||
case 3:
|
||||
return (
|
||||
<Auth onSubmit={this.handleFormSubmit} />
|
||||
);
|
||||
case 4:
|
||||
return <Devices interfaces={interfaces} />;
|
||||
case 5:
|
||||
return <Submit openDashboard={this.openDashboard} />;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
processingDefault,
|
||||
step,
|
||||
web,
|
||||
dns,
|
||||
interfaces,
|
||||
} = this.props.install;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{processingDefault && <Loading />}
|
||||
{!processingDefault &&
|
||||
<Fragment>
|
||||
<div className="setup">
|
||||
<div className="setup__container">
|
||||
<img src={logo} className="setup__logo" alt="logo" />
|
||||
{this.renderPage(step, { web, dns }, interfaces)}
|
||||
<Progress step={step} />
|
||||
</div>
|
||||
</div>
|
||||
<Footer />
|
||||
<Toasts />
|
||||
</Fragment>
|
||||
}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Setup.propTypes = {
|
||||
getDefaultAddresses: PropTypes.func.isRequired,
|
||||
setAllSettings: PropTypes.func.isRequired,
|
||||
nextStep: PropTypes.func.isRequired,
|
||||
prevStep: PropTypes.func.isRequired,
|
||||
install: PropTypes.object.isRequired,
|
||||
step: PropTypes.number,
|
||||
web: PropTypes.object,
|
||||
dns: PropTypes.object,
|
||||
};
|
||||
|
||||
const mapStateToProps = (state) => {
|
||||
const { install, toasts } = state;
|
||||
const props = { install, toasts };
|
||||
return props;
|
||||
};
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
actionCreators,
|
||||
)(Setup);
|
||||
19
client/src/install/Setup/renderField.js
Normal file
19
client/src/install/Setup/renderField.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import React, { Fragment } from 'react';
|
||||
|
||||
const renderField = ({
|
||||
input, className, placeholder, type, disabled, autoComplete, meta: { touched, error },
|
||||
}) => (
|
||||
<Fragment>
|
||||
<input
|
||||
{...input}
|
||||
placeholder={placeholder}
|
||||
type={type}
|
||||
className={className}
|
||||
disabled={disabled}
|
||||
autoComplete={autoComplete}
|
||||
/>
|
||||
{!disabled && touched && (error && <span className="form__message form__message--error">{error}</span>)}
|
||||
</Fragment>
|
||||
);
|
||||
|
||||
export default renderField;
|
||||
18
client/src/install/index.js
Normal file
18
client/src/install/index.js
Normal file
@@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import '../components/App/index.css';
|
||||
import '../components/ui/ReactTable.css';
|
||||
import configureStore from '../configureStore';
|
||||
import reducers from '../reducers/install';
|
||||
import '../i18n';
|
||||
import Setup from './Setup';
|
||||
|
||||
const store = configureStore(reducers, {}); // set initial state
|
||||
ReactDOM.render(
|
||||
<Provider store={store}>
|
||||
<Setup />
|
||||
</Provider>,
|
||||
document.getElementById('root'),
|
||||
);
|
||||
82
client/src/reducers/encryption.js
Normal file
82
client/src/reducers/encryption.js
Normal file
@@ -0,0 +1,82 @@
|
||||
import { handleActions } from 'redux-actions';
|
||||
|
||||
import * as actions from '../actions/encryption';
|
||||
|
||||
const encryption = handleActions({
|
||||
[actions.getTlsStatusRequest]: state => ({ ...state, processing: true }),
|
||||
[actions.getTlsStatusFailure]: state => ({ ...state, processing: false }),
|
||||
[actions.getTlsStatusSuccess]: (state, { payload }) => {
|
||||
const newState = {
|
||||
...state,
|
||||
...payload,
|
||||
processing: false,
|
||||
};
|
||||
return newState;
|
||||
},
|
||||
|
||||
[actions.setTlsConfigRequest]: state => ({ ...state, processingConfig: true }),
|
||||
[actions.setTlsConfigFailure]: state => ({ ...state, processingConfig: false }),
|
||||
[actions.setTlsConfigSuccess]: (state, { payload }) => {
|
||||
const newState = {
|
||||
...state,
|
||||
...payload,
|
||||
processingConfig: false,
|
||||
};
|
||||
return newState;
|
||||
},
|
||||
|
||||
[actions.validateTlsConfigRequest]: state => ({ ...state, processingValidate: true }),
|
||||
[actions.validateTlsConfigFailure]: state => ({ ...state, processingValidate: false }),
|
||||
[actions.validateTlsConfigSuccess]: (state, { payload }) => {
|
||||
const {
|
||||
issuer = '',
|
||||
key_type = '',
|
||||
not_after = '',
|
||||
not_before = '',
|
||||
subject = '',
|
||||
warning_validation = '',
|
||||
dns_names = '',
|
||||
...values
|
||||
} = payload;
|
||||
|
||||
const newState = {
|
||||
...state,
|
||||
...values,
|
||||
issuer,
|
||||
key_type,
|
||||
not_after,
|
||||
not_before,
|
||||
subject,
|
||||
warning_validation,
|
||||
dns_names,
|
||||
processingValidate: false,
|
||||
};
|
||||
return newState;
|
||||
},
|
||||
}, {
|
||||
processing: true,
|
||||
processingConfig: false,
|
||||
processingValidate: false,
|
||||
enabled: false,
|
||||
dns_names: null,
|
||||
force_https: false,
|
||||
issuer: '',
|
||||
key_type: '',
|
||||
not_after: '',
|
||||
not_before: '',
|
||||
port_dns_over_tls: '',
|
||||
port_https: '',
|
||||
subject: '',
|
||||
valid_chain: false,
|
||||
valid_key: false,
|
||||
valid_cert: false,
|
||||
valid_pair: false,
|
||||
status_cert: '',
|
||||
status_key: '',
|
||||
certificate_chain: '',
|
||||
private_key: '',
|
||||
server_name: '',
|
||||
warning_validation: '',
|
||||
});
|
||||
|
||||
export default encryption;
|
||||
@@ -1,11 +1,12 @@
|
||||
import { combineReducers } from 'redux';
|
||||
import { handleActions } from 'redux-actions';
|
||||
import { loadingBarReducer } from 'react-redux-loading-bar';
|
||||
import nanoid from 'nanoid';
|
||||
import { reducer as formReducer } from 'redux-form';
|
||||
import versionCompare from '../helpers/versionCompare';
|
||||
|
||||
import * as actions from '../actions';
|
||||
import toasts from './toasts';
|
||||
import encryption from './encryption';
|
||||
|
||||
const settings = handleActions({
|
||||
[actions.initSettingsRequest]: state => ({ ...state, processing: true }),
|
||||
@@ -47,11 +48,14 @@ const dashboard = handleActions({
|
||||
version,
|
||||
running,
|
||||
dns_port: dnsPort,
|
||||
dns_address: dnsAddress,
|
||||
dns_addresses: dnsAddresses,
|
||||
querylog_enabled: queryLogEnabled,
|
||||
upstream_dns: upstreamDns,
|
||||
bootstrap_dns: bootstrapDns,
|
||||
all_servers: allServers,
|
||||
protection_enabled: protectionEnabled,
|
||||
language,
|
||||
http_port: httpPort,
|
||||
} = payload;
|
||||
const newState = {
|
||||
...state,
|
||||
@@ -59,11 +63,14 @@ const dashboard = handleActions({
|
||||
processing: false,
|
||||
dnsVersion: version,
|
||||
dnsPort,
|
||||
dnsAddress,
|
||||
dnsAddresses,
|
||||
queryLogEnabled,
|
||||
upstreamDns: upstreamDns.join('\n'),
|
||||
bootstrapDns: bootstrapDns.join('\n'),
|
||||
allServers,
|
||||
protectionEnabled,
|
||||
language,
|
||||
httpPort,
|
||||
};
|
||||
return newState;
|
||||
},
|
||||
@@ -117,13 +124,13 @@ const dashboard = handleActions({
|
||||
|
||||
if (versionCompare(currentVersion, payload.version) === -1) {
|
||||
const {
|
||||
announcement,
|
||||
version,
|
||||
announcement_url: announcementUrl,
|
||||
} = payload;
|
||||
|
||||
const newState = {
|
||||
...state,
|
||||
announcement,
|
||||
version,
|
||||
announcementUrl,
|
||||
isUpdateAvailable: true,
|
||||
};
|
||||
@@ -140,8 +147,14 @@ const dashboard = handleActions({
|
||||
return newState;
|
||||
},
|
||||
|
||||
[actions.toggleProtectionRequest]: state => ({ ...state, processingProtection: true }),
|
||||
[actions.toggleProtectionFailure]: state => ({ ...state, processingProtection: false }),
|
||||
[actions.toggleProtectionSuccess]: (state) => {
|
||||
const newState = { ...state, protectionEnabled: !state.protectionEnabled };
|
||||
const newState = {
|
||||
...state,
|
||||
protectionEnabled: !state.protectionEnabled,
|
||||
processingProtection: false,
|
||||
};
|
||||
return newState;
|
||||
},
|
||||
|
||||
@@ -154,6 +167,17 @@ const dashboard = handleActions({
|
||||
const newState = { ...state, language: payload };
|
||||
return newState;
|
||||
},
|
||||
|
||||
[actions.getClientsRequest]: state => ({ ...state, processingClients: true }),
|
||||
[actions.getClientsFailure]: state => ({ ...state, processingClients: false }),
|
||||
[actions.getClientsSuccess]: (state, { payload }) => {
|
||||
const newState = {
|
||||
...state,
|
||||
clients: payload,
|
||||
processingClients: false,
|
||||
};
|
||||
return newState;
|
||||
},
|
||||
}, {
|
||||
processing: true,
|
||||
isCoreRunning: false,
|
||||
@@ -162,8 +186,17 @@ const dashboard = handleActions({
|
||||
logStatusProcessing: false,
|
||||
processingVersion: true,
|
||||
processingFiltering: true,
|
||||
upstreamDns: [],
|
||||
processingClients: true,
|
||||
upstreamDns: '',
|
||||
bootstrapDns: '',
|
||||
allServers: false,
|
||||
protectionEnabled: false,
|
||||
processingProtection: false,
|
||||
httpPort: 80,
|
||||
dnsPort: 53,
|
||||
dnsAddresses: [],
|
||||
dnsVersion: '',
|
||||
clients: [],
|
||||
});
|
||||
|
||||
const queryLogs = handleActions({
|
||||
@@ -228,38 +261,12 @@ const filtering = handleActions({
|
||||
isFilteringModalOpen: false,
|
||||
processingFilters: false,
|
||||
processingRules: false,
|
||||
processingAddFilter: false,
|
||||
processingRefreshFilters: false,
|
||||
filters: [],
|
||||
userRules: '',
|
||||
});
|
||||
|
||||
const toasts = handleActions({
|
||||
[actions.addErrorToast]: (state, { payload }) => {
|
||||
const errorToast = {
|
||||
id: nanoid(),
|
||||
message: payload.error.toString(),
|
||||
type: 'error',
|
||||
};
|
||||
|
||||
const newState = { ...state, notices: [...state.notices, errorToast] };
|
||||
return newState;
|
||||
},
|
||||
[actions.addSuccessToast]: (state, { payload }) => {
|
||||
const successToast = {
|
||||
id: nanoid(),
|
||||
message: payload,
|
||||
type: 'success',
|
||||
};
|
||||
|
||||
const newState = { ...state, notices: [...state.notices, successToast] };
|
||||
return newState;
|
||||
},
|
||||
[actions.removeToast]: (state, { payload }) => {
|
||||
const filtered = state.notices.filter(notice => notice.id !== payload);
|
||||
const newState = { ...state, notices: filtered };
|
||||
return newState;
|
||||
},
|
||||
}, { notices: [] });
|
||||
|
||||
const dhcp = handleActions({
|
||||
[actions.getDhcpStatusRequest]: state => ({ ...state, processing: true }),
|
||||
[actions.getDhcpStatusFailure]: state => ({ ...state, processing: false }),
|
||||
@@ -291,16 +298,29 @@ const dhcp = handleActions({
|
||||
processingStatus: false,
|
||||
}),
|
||||
|
||||
[actions.toggleDhcpRequest]: state => ({ ...state, processingDhcp: true }),
|
||||
[actions.toggleDhcpFailure]: state => ({ ...state, processingDhcp: false }),
|
||||
[actions.toggleDhcpSuccess]: (state) => {
|
||||
const { config } = state;
|
||||
const newConfig = { ...config, enabled: !config.enabled };
|
||||
const newState = { ...state, config: newConfig };
|
||||
const newState = { ...state, config: newConfig, processingDhcp: false };
|
||||
return newState;
|
||||
},
|
||||
|
||||
[actions.setDhcpConfigRequest]: state => ({ ...state, processingConfig: true }),
|
||||
[actions.setDhcpConfigFailure]: state => ({ ...state, processingConfig: false }),
|
||||
[actions.setDhcpConfigSuccess]: (state, { payload }) => {
|
||||
const { config } = state;
|
||||
const newConfig = { ...config, ...payload };
|
||||
const newState = { ...state, config: newConfig, processingConfig: false };
|
||||
return newState;
|
||||
},
|
||||
}, {
|
||||
processing: true,
|
||||
processingStatus: false,
|
||||
processingInterfaces: false,
|
||||
processingDhcp: false,
|
||||
processingConfig: false,
|
||||
config: {
|
||||
enabled: false,
|
||||
},
|
||||
@@ -315,6 +335,7 @@ export default combineReducers({
|
||||
filtering,
|
||||
toasts,
|
||||
dhcp,
|
||||
encryption,
|
||||
loadingBar: loadingBarReducer,
|
||||
form: formReducer,
|
||||
});
|
||||
|
||||
47
client/src/reducers/install.js
Normal file
47
client/src/reducers/install.js
Normal file
@@ -0,0 +1,47 @@
|
||||
import { combineReducers } from 'redux';
|
||||
import { handleActions } from 'redux-actions';
|
||||
import { reducer as formReducer } from 'redux-form';
|
||||
|
||||
import * as actions from '../actions/install';
|
||||
import toasts from './toasts';
|
||||
import { INSTALL_FIRST_STEP } from '../helpers/constants';
|
||||
|
||||
const install = handleActions({
|
||||
[actions.getDefaultAddressesRequest]: state => ({ ...state, processingDefault: true }),
|
||||
[actions.getDefaultAddressesFailure]: state => ({ ...state, processingDefault: false }),
|
||||
[actions.getDefaultAddressesSuccess]: (state, { payload }) => {
|
||||
const values = payload;
|
||||
values.web.ip = state.web.ip;
|
||||
values.dns.ip = state.dns.ip;
|
||||
const newState = { ...state, ...values, processingDefault: false };
|
||||
return newState;
|
||||
},
|
||||
|
||||
[actions.nextStep]: state => ({ ...state, step: state.step + 1 }),
|
||||
[actions.prevStep]: state => ({ ...state, step: state.step - 1 }),
|
||||
|
||||
[actions.setAllSettingsRequest]: state => ({ ...state, processingSubmit: true }),
|
||||
[actions.setAllSettingsFailure]: state => ({ ...state, processingSubmit: false }),
|
||||
[actions.setAllSettingsSuccess]: state => ({ ...state, processingSubmit: false }),
|
||||
}, {
|
||||
step: INSTALL_FIRST_STEP,
|
||||
processingDefault: true,
|
||||
processingSubmit: false,
|
||||
web: {
|
||||
ip: '0.0.0.0',
|
||||
port: 80,
|
||||
warning: '',
|
||||
},
|
||||
dns: {
|
||||
ip: '0.0.0.0',
|
||||
port: 53,
|
||||
warning: '',
|
||||
},
|
||||
interfaces: {},
|
||||
});
|
||||
|
||||
export default combineReducers({
|
||||
install,
|
||||
toasts,
|
||||
form: formReducer,
|
||||
});
|
||||
34
client/src/reducers/toasts.js
Normal file
34
client/src/reducers/toasts.js
Normal file
@@ -0,0 +1,34 @@
|
||||
import { handleActions } from 'redux-actions';
|
||||
import nanoid from 'nanoid';
|
||||
|
||||
import { addErrorToast, addSuccessToast, removeToast } from '../actions';
|
||||
|
||||
const toasts = handleActions({
|
||||
[addErrorToast]: (state, { payload }) => {
|
||||
const errorToast = {
|
||||
id: nanoid(),
|
||||
message: payload.error.toString(),
|
||||
type: 'error',
|
||||
};
|
||||
|
||||
const newState = { ...state, notices: [...state.notices, errorToast] };
|
||||
return newState;
|
||||
},
|
||||
[addSuccessToast]: (state, { payload }) => {
|
||||
const successToast = {
|
||||
id: nanoid(),
|
||||
message: payload,
|
||||
type: 'success',
|
||||
};
|
||||
|
||||
const newState = { ...state, notices: [...state.notices, successToast] };
|
||||
return newState;
|
||||
},
|
||||
[removeToast]: (state, { payload }) => {
|
||||
const filtered = state.notices.filter(notice => notice.id !== payload);
|
||||
const newState = { ...state, notices: filtered };
|
||||
return newState;
|
||||
},
|
||||
}, { notices: [] });
|
||||
|
||||
export default toasts;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user