Compare commits

..

14 Commits

Author SHA1 Message Date
Ildar Kamalov
4c89cf7209 remove install from initial state 2024-06-06 13:27:18 +03:00
Igor Lobanov
b943f2011f frontend production build fix 2024-06-05 23:10:55 +02:00
Igor Lobanov
cd1be2d66d production build quickfix 2024-06-05 20:23:14 +02:00
Ainar Garipov
7b8ac01fc2 all: upd node docker 2024-06-05 19:57:31 +03:00
Igor Lobanov
02afed66d5 changelog fixes 2024-06-05 18:23:12 +02:00
Igor Lobanov
9c0f736f0c merge 2024-06-05 18:18:29 +02:00
Igor Lobanov
62c4fbf1e3 empty line in changelog 2024-06-05 16:22:22 +02:00
Igor Lobanov
76b1e44a93 changelog 2024-06-05 16:20:37 +02:00
Igor Lobanov
f783e90040 filters.js -> filters.ts 2024-06-05 16:19:13 +02:00
Igor Lobanov
3d4ce6554c generated file removed 2024-06-05 16:18:03 +02:00
Igor Lobanov
e35ba58f2a rollback unwanted changes 2024-06-05 15:45:21 +02:00
Igor Lobanov
1f30d4216d review fix 2024-06-05 15:27:36 +02:00
Igor Lobanov
6cd4e44f07 missing generated file restoresd 2024-06-05 11:55:39 +02:00
Igor Lobanov
2ab738b303 Frontend rewritten in TypeScript, added Node 18 support 2024-06-05 11:40:32 +02:00
301 changed files with 32644 additions and 36715 deletions

View File

@@ -1,7 +1,7 @@
'name': 'build' 'name': 'build'
'env': 'env':
'GO_VERSION': '1.23.5' 'GO_VERSION': '1.22.4'
'NODE_VERSION': '16' 'NODE_VERSION': '16'
'on': 'on':
@@ -95,7 +95,7 @@
'key': "${{ runner.os }}-node-${{ hashFiles('client/package-lock.json') }}" 'key': "${{ runner.os }}-node-${{ hashFiles('client/package-lock.json') }}"
'restore-keys': '${{ runner.os }}-node-' 'restore-keys': '${{ runner.os }}-node-'
- 'name': 'Set up Snapcraft' - 'name': 'Set up Snapcraft'
'run': 'sudo snap install snapcraft --classic' 'run': 'sudo apt-get -yq --no-install-suggests --no-install-recommends install snapcraft'
- 'name': 'Set up QEMU' - 'name': 'Set up QEMU'
'uses': 'docker/setup-qemu-action@v1' 'uses': 'docker/setup-qemu-action@v1'
- 'name': 'Set up Docker Buildx' - 'name': 'Set up Docker Buildx'

View File

@@ -1,7 +1,7 @@
'name': 'lint' 'name': 'lint'
'env': 'env':
'GO_VERSION': '1.23.5' 'GO_VERSION': '1.22.4'
'on': 'on':
'push': 'push':

7
.gitignore vendored
View File

@@ -1,8 +1,3 @@
# This comment is used to simplify checking local copies of the file. Bump
# this number every time a significant change is made to this file.
#
# AdGuard-Project-Version: 1
# Please, DO NOT put your text editors' temporary files here. The more are # Please, DO NOT put your text editors' temporary files here. The more are
# added, the harder it gets to maintain and manage projects' gitignores. Put # added, the harder it gets to maintain and manage projects' gitignores. Put
# them into your global gitignore file instead. # them into your global gitignore file instead.
@@ -13,7 +8,6 @@
# bottom to make sure they take effect. # bottom to make sure they take effect.
*.db *.db
*.log *.log
*.out
*.snap *.snap
*.test *.test
/agh-backup/ /agh-backup/
@@ -27,7 +21,6 @@
/launchpad_credentials /launchpad_credentials
/querylog.json* /querylog.json*
/snapcraft_login /snapcraft_login
/test-reports/
AdGuardHome AdGuardHome
AdGuardHome.exe AdGuardHome.exe
AdGuardHome.yaml* AdGuardHome.yaml*

View File

@@ -1,25 +0,0 @@
{
"ul-indent": {
"indent": 4
},
"ul-style": {
"style": "dash"
},
"emphasis-style": {
"style": "asterisk"
},
"no-duplicate-heading": {
"siblings_only": true
},
"no-inline-html": {
"allowed_elements": [
"a"
]
},
"no-trailing-spaces": {
"br_spaces": 0
},
"line-length": false,
"no-bare-urls": false,
"link-fragments": false
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +1,14 @@
# Keep the Makefile POSIX-compliant. We currently allow hyphens in # Keep the Makefile POSIX-compliant. We currently allow hyphens in
# target names, but that may change in the future. # target names, but that may change in the future.
# #
# See https://pubs.opengroup.org/onlinepubs/9799919799/utilities/make.html. # See https://pubs.opengroup.org/onlinepubs/9699919799/utilities/make.html.
.POSIX: .POSIX:
# This comment is used to simplify checking local copies of the # This comment is used to simplify checking local copies of the
# Makefile. Bump this number every time a significant change is made to # Makefile. Bump this number every time a significant change is made to
# this Makefile. # this Makefile.
# #
# AdGuard-Project-Version: 9 # AdGuard-Project-Version: 4
# Don't name these macros "GO" etc., because GNU Make apparently makes # Don't name these macros "GO" etc., because GNU Make apparently makes
# them exported environment variables with the literal value of # them exported environment variables with the literal value of
@@ -22,12 +22,12 @@ VERBOSE.MACRO = $${VERBOSE:-0}
CHANNEL = development CHANNEL = development
CLIENT_DIR = client CLIENT_DIR = client
DEPLOY_SCRIPT_PATH = not/a/real/path COMMIT = $$( git rev-parse --short HEAD )
DIST_DIR = dist DIST_DIR = dist
GOAMD64 = v1 GOAMD64 = v1
GOPROXY = https://proxy.golang.org|direct GOPROXY = https://goproxy.cn|https://proxy.golang.org|direct
GOTELEMETRY = off GOSUMDB = sum.golang.google.cn
GOTOOLCHAIN = go1.23.5 GOTOOLCHAIN = go1.22.4
GPG_KEY = devteam@adguard.com GPG_KEY = devteam@adguard.com
GPG_KEY_PASSPHRASE = not-a-real-password GPG_KEY_PASSPHRASE = not-a-real-password
NPM = npm NPM = npm
@@ -35,9 +35,7 @@ NPM_FLAGS = --prefix $(CLIENT_DIR)
NPM_INSTALL_FLAGS = $(NPM_FLAGS) --quiet --no-progress --ignore-engines\ NPM_INSTALL_FLAGS = $(NPM_FLAGS) --quiet --no-progress --ignore-engines\
--ignore-optional --ignore-platform --ignore-scripts --ignore-optional --ignore-platform --ignore-scripts
RACE = 0 RACE = 0
REVISION = $${REVISION:-$$(git rev-parse --short HEAD)}
SIGN = 1 SIGN = 1
SIGNER_API_KEY = not-a-real-key
VERSION = v0.0.0 VERSION = v0.0.0
YARN = yarn YARN = yarn
@@ -60,29 +58,21 @@ BUILD_RELEASE_DEPS_1 = go-deps
ENV = env\ ENV = env\
CHANNEL='$(CHANNEL)'\ CHANNEL='$(CHANNEL)'\
DEPLOY_SCRIPT_PATH='$(DEPLOY_SCRIPT_PATH)' \ COMMIT='$(COMMIT)'\
DIST_DIR='$(DIST_DIR)'\ DIST_DIR='$(DIST_DIR)'\
GO="$(GO.MACRO)"\ GO="$(GO.MACRO)"\
GOAMD64='$(GOAMD64)'\ GOAMD64="$(GOAMD64)"\
GOPROXY='$(GOPROXY)'\ GOPROXY='$(GOPROXY)'\
GOTELEMETRY='$(GOTELEMETRY)'\ GOSUMDB='$(GOSUMDB)'\
GOTOOLCHAIN='$(GOTOOLCHAIN)'\ GOTOOLCHAIN='$(GOTOOLCHAIN)'\
GPG_KEY='$(GPG_KEY)'\ GPG_KEY='$(GPG_KEY)'\
GPG_KEY_PASSPHRASE='$(GPG_KEY_PASSPHRASE)'\ GPG_KEY_PASSPHRASE='$(GPG_KEY_PASSPHRASE)'\
NEXTAPI='$(NEXTAPI)'\
PATH="$${PWD}/bin:$$( "$(GO.MACRO)" env GOPATH )/bin:$${PATH}"\ PATH="$${PWD}/bin:$$( "$(GO.MACRO)" env GOPATH )/bin:$${PATH}"\
RACE='$(RACE)'\ RACE='$(RACE)'\
REVISION='$(REVISION)'\
SIGN='$(SIGN)'\ SIGN='$(SIGN)'\
SIGNER_API_KEY='$(SIGNER_API_KEY)' \ NEXTAPI='$(NEXTAPI)'\
VERBOSE="$(VERBOSE.MACRO)"\
VERSION="$(VERSION)"\
# Keep the line above blank.
ENV_MISC = env\
PATH="$${PWD}/bin:$$("$(GO.MACRO)" env GOPATH)/bin:$${PATH}"\
VERBOSE="$(VERBOSE.MACRO)"\ VERBOSE="$(VERBOSE.MACRO)"\
VERSION='$(VERSION)'\
# Keep the line above blank. # Keep the line above blank.
@@ -90,8 +80,6 @@ ENV_MISC = env\
# full build. # full build.
build: deps quick-build build: deps quick-build
init: ; git config core.hooksPath ./scripts/hooks
quick-build: js-build go-build quick-build: js-build go-build
deps: js-deps go-deps deps: js-deps go-deps
@@ -105,38 +93,39 @@ build-docker: ; $(ENV) "$(SHELL)" ./scripts/make/build-docker.sh
build-release: $(BUILD_RELEASE_DEPS_$(FRONTEND_PREBUILT)) build-release: $(BUILD_RELEASE_DEPS_$(FRONTEND_PREBUILT))
$(ENV) "$(SHELL)" ./scripts/make/build-release.sh $(ENV) "$(SHELL)" ./scripts/make/build-release.sh
clean: ; $(ENV) "$(SHELL)" ./scripts/make/clean.sh
init: ; git config core.hooksPath ./scripts/hooks
js-build: ; $(NPM) $(NPM_FLAGS) run build-prod js-build: ; $(NPM) $(NPM_FLAGS) run build-prod
js-deps: ; $(NPM) $(NPM_INSTALL_FLAGS) ci js-deps: ; $(NPM) $(NPM_INSTALL_FLAGS) ci
js-lint: ; $(NPM) $(NPM_FLAGS) run lint js-lint: ; $(NPM) $(NPM_FLAGS) run lint
js-test: ; $(NPM) $(NPM_FLAGS) run test js-test: ; $(NPM) $(NPM_FLAGS) run test
go-bench: ; $(ENV) "$(SHELL)" ./scripts/make/go-bench.sh go-bench: ; $(ENV) "$(SHELL)" ./scripts/make/go-bench.sh
go-build: ; $(ENV) "$(SHELL)" ./scripts/make/go-build.sh go-build: ; $(ENV) "$(SHELL)" ./scripts/make/go-build.sh
go-deps: ; $(ENV) "$(SHELL)" ./scripts/make/go-deps.sh go-deps: ; $(ENV) "$(SHELL)" ./scripts/make/go-deps.sh
go-env: ; $(ENV) "$(GO.MACRO)" env go-fuzz: ; $(ENV) "$(SHELL)" ./scripts/make/go-fuzz.sh
go-fuzz: ; $(ENV) "$(SHELL)" ./scripts/make/go-fuzz.sh go-lint: ; $(ENV) "$(SHELL)" ./scripts/make/go-lint.sh
go-lint: ; $(ENV) "$(SHELL)" ./scripts/make/go-lint.sh go-tools: ; $(ENV) "$(SHELL)" ./scripts/make/go-tools.sh
# TODO(a.garipov): Think about making RACE='1' the default for all # TODO(a.garipov): Think about making RACE='1' the default for all
# targets. # targets.
go-test: ; $(ENV) RACE='1' "$(SHELL)" ./scripts/make/go-test.sh go-test: ; $(ENV) RACE='1' "$(SHELL)" ./scripts/make/go-test.sh
go-tools: ; $(ENV) "$(SHELL)" ./scripts/make/go-tools.sh
go-upd-tools: ; $(ENV) "$(SHELL)" ./scripts/make/go-upd-tools.sh go-upd-tools: ; $(ENV) "$(SHELL)" ./scripts/make/go-upd-tools.sh
go-check: go-tools go-lint go-test go-check: go-tools go-lint go-test
# A quick check to make sure that all operating systems relevant to the # A quick check to make sure that all supported operating systems can be
# development of the project can be typechecked and built successfully. # typechecked and built successfully.
go-os-check: go-os-check:
$(ENV) GOOS='darwin' "$(GO.MACRO)" vet ./internal/... env GOOS='darwin' "$(GO.MACRO)" vet ./internal/...
$(ENV) GOOS='freebsd' "$(GO.MACRO)" vet ./internal/... env GOOS='freebsd' "$(GO.MACRO)" vet ./internal/...
$(ENV) GOOS='openbsd' "$(GO.MACRO)" vet ./internal/... env GOOS='openbsd' "$(GO.MACRO)" vet ./internal/...
$(ENV) GOOS='linux' "$(GO.MACRO)" vet ./internal/... env GOOS='linux' "$(GO.MACRO)" vet ./internal/...
$(ENV) GOOS='windows' "$(GO.MACRO)" vet ./internal/... env GOOS='windows' "$(GO.MACRO)" vet ./internal/...
txt-lint: ; $(ENV) "$(SHELL)" ./scripts/make/txt-lint.sh
md-lint: ; $(ENV_MISC) "$(SHELL)" ./scripts/make/md-lint.sh
sh-lint: ; $(ENV_MISC) "$(SHELL)" ./scripts/make/sh-lint.sh
openapi-lint: ; cd ./openapi/ && $(YARN) test openapi-lint: ; cd ./openapi/ && $(YARN) test
openapi-show: ; cd ./openapi/ && $(YARN) start openapi-show: ; cd ./openapi/ && $(YARN) start
txt-lint: ; $(ENV) "$(SHELL)" ./scripts/make/txt-lint.sh

View File

@@ -114,7 +114,7 @@ If you're running **Linux,** there's a secure and easy way to install AdGuard Ho
[Docker Hub]: https://hub.docker.com/r/adguard/adguardhome [Docker Hub]: https://hub.docker.com/r/adguard/adguardhome
[Snap Store]: https://snapcraft.io/adguard-home [Snap Store]: https://snapcraft.io/adguard-home
[wiki-start]: https://adguard-dns.io/kb/adguard-home/getting-started/ [wiki-start]: https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started
### <a href="#guides" id="guides" name="guides">Guides</a> ### <a href="#guides" id="guides" name="guides">Guides</a>
@@ -205,7 +205,7 @@ Run `make init` to prepare the development environment.
You will need this to build AdGuard Home: You will need this to build AdGuard Home:
- [Go](https://golang.org/dl/) v1.23 or later; - [Go](https://golang.org/dl/) v1.22 or later;
- [Node.js](https://nodejs.org/en/download/) v18.18 or later; - [Node.js](https://nodejs.org/en/download/) v18.18 or later;
- [npm](https://www.npmjs.com/) v8 or later; - [npm](https://www.npmjs.com/) v8 or later;

View File

@@ -7,8 +7,8 @@
# Make sure to sync any changes with the branch overrides below. # Make sure to sync any changes with the branch overrides below.
'variables': 'variables':
'channel': 'edge' 'channel': 'edge'
'dockerFrontend': 'adguard/home-js-builder:2.0' 'dockerFrontend': '${bamboo.adguardRegistryBasePath}/home-js-builder:2.0'
'dockerGo': 'adguard/go-builder:1.23.5--1' 'dockerGo': '${bamboo.adguardRegistryBasePath}/go-builder:1.22.4--1'
'stages': 'stages':
- 'Build frontend': - 'Build frontend':
@@ -91,11 +91,6 @@
'tasks': 'tasks':
- 'checkout': - 'checkout':
'force-clean-build': true 'force-clean-build': true
- 'checkout':
'repository': 'bamboo-deploy-publisher'
# The paths are always relative to the working directory.
'path': 'bamboo-deploy-publisher'
'force-clean-build': true
- 'script': - 'script':
'interpreter': 'SHELL' 'interpreter': 'SHELL'
'scripts': 'scripts':
@@ -104,9 +99,6 @@
set -e -f -u -x set -e -f -u -x
# Explicitly checkout the revision that we need.
git checkout "${bamboo.repository.revision.number}"
# Run the build with the specified channel. # Run the build with the specified channel.
echo "${bamboo.gpgSecretKeyPart1}${bamboo.gpgSecretKeyPart2}"\ echo "${bamboo.gpgSecretKeyPart1}${bamboo.gpgSecretKeyPart2}"\
| awk '{ gsub(/\\n/, "\n"); print; }'\ | awk '{ gsub(/\\n/, "\n"); print; }'\
@@ -115,8 +107,6 @@
make\ make\
CHANNEL=${bamboo.channel}\ CHANNEL=${bamboo.channel}\
GPG_KEY_PASSPHRASE=${bamboo.gpgPassword}\ GPG_KEY_PASSPHRASE=${bamboo.gpgPassword}\
DEPLOY_SCRIPT_PATH="./bamboo-deploy-publisher/deploy.sh"\
SIGNER_API_KEY="${bamboo.adguardHomeWinSignerSecretApiKey}"\
FRONTEND_PREBUILT=1\ FRONTEND_PREBUILT=1\
PARALLELISM=1\ PARALLELISM=1\
VERBOSE=2\ VERBOSE=2\
@@ -142,15 +132,13 @@
# Install Qemu, create builder. # Install Qemu, create builder.
docker version -f '{{ .Server.Experimental }}' docker version -f '{{ .Server.Experimental }}'
docker buildx rm buildx-builder || : docker buildx rm buildx-builder || :
docker buildx create \ docker buildx create --name buildx-builder --driver docker-container\
--name buildx-builder \ --use
--driver docker-container \
--use
docker buildx inspect --bootstrap docker buildx inspect --bootstrap
# Login to DockerHub. # Login to DockerHub.
docker login -u="${bamboo.dockerHubUsername}" \ docker login -u="${bamboo.dockerHubUsername}"\
-p="${bamboo.dockerHubPassword}" -p="${bamboo.dockerHubPassword}"
# Boot the builder. # Boot the builder.
docker buildx inspect --bootstrap docker buildx inspect --bootstrap
@@ -159,14 +147,14 @@
docker info docker info
# Prepare and push the build. # Prepare and push the build.
env \ env\
CHANNEL="${bamboo.channel}" \ CHANNEL="${bamboo.channel}"\
REVISION="${bamboo.repository.revision.number}" \ COMMIT="${bamboo.repository.revision.number}"\
DIST_DIR='dist' \ DIST_DIR='dist'\
DOCKER_IMAGE_NAME='adguard/adguardhome' \ DOCKER_IMAGE_NAME='adguard/adguardhome'\
DOCKER_OUTPUT="type=image,name=adguard/adguardhome,push=true" \ DOCKER_OUTPUT="type=image,name=adguard/adguardhome,push=true"\
VERBOSE='1' \ VERBOSE='1'\
sh ./scripts/make/build-docker.sh sh ./scripts/make/build-docker.sh
'environment': 'environment':
DOCKER_CLI_EXPERIMENTAL=enabled DOCKER_CLI_EXPERIMENTAL=enabled
'final-tasks': 'final-tasks':
@@ -277,8 +265,8 @@
# need to build a few of these. # need to build a few of these.
'variables': 'variables':
'channel': 'beta' 'channel': 'beta'
'dockerFrontend': 'adguard/home-js-builder:2.0' 'dockerFrontend': '${bamboo.adguardRegistryBasePath}/home-js-builder:2.0'
'dockerGo': 'adguard/go-builder:1.23.5--1' 'dockerGo': '${bamboo.adguardRegistryBasePath}/go-builder:1.22.4--1'
# release-vX.Y.Z branches are the branches from which the actual final # release-vX.Y.Z branches are the branches from which the actual final
# release is built. # release is built.
- '^release-v[0-9]+\.[0-9]+\.[0-9]+': - '^release-v[0-9]+\.[0-9]+\.[0-9]+':
@@ -293,5 +281,5 @@
# are the ones that actually get released. # are the ones that actually get released.
'variables': 'variables':
'channel': 'release' 'channel': 'release'
'dockerFrontend': 'adguard/home-js-builder:2.0' 'dockerFrontend': '${bamboo.adguardRegistryBasePath}/home-js-builder:2.0'
'dockerGo': 'adguard/go-builder:1.23.5--1' 'dockerGo': '${bamboo.adguardRegistryBasePath}/go-builder:1.22.4--1'

View File

@@ -5,8 +5,8 @@
'key': 'AHBRTSPECS' 'key': 'AHBRTSPECS'
'name': 'AdGuard Home - Build and run tests' 'name': 'AdGuard Home - Build and run tests'
'variables': 'variables':
'dockerFrontend': 'adguard/home-js-builder:2.0' 'dockerFrontend': '${bamboo.adguardRegistryBasePath}/home-js-builder:2.0'
'dockerGo': 'adguard/go-builder:1.23.5--1' 'dockerGo': '${bamboo.adguardRegistryBasePath}/go-builder:1.22.4--1'
'channel': 'development' 'channel': 'development'
'stages': 'stages':
@@ -54,7 +54,6 @@
'requirements': 'requirements':
- 'adg-docker': 'true' - 'adg-docker': 'true'
# TODO(e.burkov): Add the linting stage for markdown docs and shell scripts.
'Test backend': 'Test backend':
'docker': 'docker':
'image': '${bamboo.dockerGo}' 'image': '${bamboo.dockerGo}'
@@ -195,6 +194,6 @@
# Set the default release channel on the release branch to beta, as we # Set the default release channel on the release branch to beta, as we
# may need to build a few of these. # may need to build a few of these.
'variables': 'variables':
'dockerFrontend': 'adguard/home-js-builder:2.0' 'dockerFrontend': '${bamboo.adguardRegistryBasePath}/home-js-builder:2.0'
'dockerGo': 'adguard/go-builder:1.23.5--1' 'dockerGo': '${bamboo.adguardRegistryBasePath}/go-builder:1.22.4--1'
'channel': 'candidate' 'channel': 'candidate'

View File

@@ -291,7 +291,7 @@
"custom_ip": "عنوان IP مخصص", "custom_ip": "عنوان IP مخصص",
"blocking_ipv4": "حجب عنوان IPv4", "blocking_ipv4": "حجب عنوان IPv4",
"blocking_ipv6": "حجب عنوان IPv6", "blocking_ipv6": "حجب عنوان IPv6",
"blocked_response_ttl": "حظر استجابة TTL", "blocked_response_ttl": "زمن حظر الاستجابة",
"blocked_response_ttl_desc": "تحديد عدد الثواني التي يجب على العملاء تخزين الاستجابة التي تمت تصفيتها مؤقتًا", "blocked_response_ttl_desc": "تحديد عدد الثواني التي يجب على العملاء تخزين الاستجابة التي تمت تصفيتها مؤقتًا",
"form_enter_blocked_response_ttl": "أدخل وقت الاستجابة المحظورة TTL (بالثواني)", "form_enter_blocked_response_ttl": "أدخل وقت الاستجابة المحظورة TTL (بالثواني)",
"dnscrypt": "DNSCrypt", "dnscrypt": "DNSCrypt",
@@ -734,10 +734,10 @@
"thursday": "الخميس", "thursday": "الخميس",
"friday": "الجمعة", "friday": "الجمعة",
"saturday": "السبت", "saturday": "السبت",
"sunday_short": "الأحد", "sunday_short": "الاحد",
"monday_short": "الإثنين", "monday_short": "الإثنين",
"tuesday_short": "الثلاثاء", "tuesday_short": "الثلاثاء",
"wednesday_short": "الأربعاء", "wednesday_short": "الاربعاء",
"thursday_short": "الخميس", "thursday_short": "الخميس",
"friday_short": "الجمعة", "friday_short": "الجمعة",
"saturday_short": "السبت", "saturday_short": "السبت",

View File

@@ -159,8 +159,6 @@
"dns_over_https": "DNS-пред-HTTPS", "dns_over_https": "DNS-пред-HTTPS",
"dns_over_quic": "DNS-over-QUIC", "dns_over_quic": "DNS-over-QUIC",
"plain_dns": "Обикновен DNS", "plain_dns": "Обикновен DNS",
"theme_light": "Светла тема",
"theme_dark": "Тъмна тема",
"source_label": "Източник", "source_label": "Източник",
"found_in_known_domain_db": "Намерен в списъците с домейни.", "found_in_known_domain_db": "Намерен в списъците с домейни.",
"category_label": "Категория", "category_label": "Категория",
@@ -285,12 +283,5 @@
"filter_category_general": "General", "filter_category_general": "General",
"filter_category_security": "Сигурност", "filter_category_security": "Сигурност",
"port_53_faq_link": "Порт 53 често е зает от \"DNSStubListener\" или \"systemd-resolved\" услуги. Моля, прочетете <0>тази инструкция</0> как да решите това.", "port_53_faq_link": "Порт 53 често е зает от \"DNSStubListener\" или \"systemd-resolved\" услуги. Моля, прочетете <0>тази инструкция</0> как да решите това.",
"parental_control": "Родителски контрол", "parental_control": "Родителски контрол"
"sunday_short": "Нд",
"monday_short": "Пон",
"tuesday_short": "Вт",
"wednesday_short": "Ср",
"thursday_short": "Чт",
"friday_short": "Пт",
"saturday_short": "Съб"
} }

View File

@@ -6,7 +6,7 @@
"upstream_parallel": "Použijte paralelní požadavky na urychlení řešení simultánním dotazováním na všechny navazující servery.", "upstream_parallel": "Použijte paralelní požadavky na urychlení řešení simultánním dotazováním na všechny navazující servery.",
"parallel_requests": "Paralelní požadavky", "parallel_requests": "Paralelní požadavky",
"load_balancing": "Optimalizace vytížení", "load_balancing": "Optimalizace vytížení",
"load_balancing_desc": "Dotazy jednoho odchozího serveru ve stejný čas. AdGuard Home používá náhodný algoritmus pro výběr serverů s nejnižším počtem neúspěšných vyhledávání a nejnižší průměrnou dobou vyhledávání.", "load_balancing_desc": "Optimalizovaný dotaz na odchozí server. AdGuard Home použije vážený náhodný algoritmus k výběru serveru, takže nejrychlejší server je používán častěji.",
"bootstrap_dns": "Bootstrap DNS servery", "bootstrap_dns": "Bootstrap DNS servery",
"bootstrap_dns_desc": "IP adresy DNS serverů používaných k překladu IP adres řešitelů DoH/DoT, které zadáte jako odchozí servery. Komentáře nejsou povoleny.", "bootstrap_dns_desc": "IP adresy DNS serverů používaných k překladu IP adres řešitelů DoH/DoT, které zadáte jako odchozí servery. Komentáře nejsou povoleny.",
"fallback_dns_title": "Záložní DNS servery", "fallback_dns_title": "Záložní DNS servery",
@@ -154,7 +154,7 @@
"use_adguard_parental": "Použít službu AdGuard Rodičovská kontrola", "use_adguard_parental": "Použít službu AdGuard Rodičovská kontrola",
"use_adguard_parental_hint": "AdGuard Home zkontroluje, zda doména obsahuje materiály pro dospělé. Používá stejné API přátelské k ochraně osobních údajů jako služba Bezpečnost prohlížení.", "use_adguard_parental_hint": "AdGuard Home zkontroluje, zda doména obsahuje materiály pro dospělé. Používá stejné API přátelské k ochraně osobních údajů jako služba Bezpečnost prohlížení.",
"enforce_safe_search": "Použít bezpečné vyhledávání", "enforce_safe_search": "Použít bezpečné vyhledávání",
"enforce_save_search_hint": "AdGuard Home vynutí bezpečné vyhledávání v následujících vyhledávačích: Google, YouTube, Bing, DuckDuckGo, Ecosia, Yandex, Pixabay.", "enforce_save_search_hint": "AdGuard Home vynutí bezpečné vyhledávání v následujících vyhledávačích: Google, YouTube, Bing, DuckDuckGo, Yandex, Pixabay.",
"no_servers_specified": "Nebyly specifikovány žádné servery", "no_servers_specified": "Nebyly specifikovány žádné servery",
"general_settings": "Obecná nastavení", "general_settings": "Obecná nastavení",
"dns_settings": "Nastavení DNS", "dns_settings": "Nastavení DNS",

View File

@@ -6,7 +6,7 @@
"upstream_parallel": "Brug parallelforespørgsler til at accelerere fortolkningen ved at forespørge alle upstream-servere samtidigt.", "upstream_parallel": "Brug parallelforespørgsler til at accelerere fortolkningen ved at forespørge alle upstream-servere samtidigt.",
"parallel_requests": "Parallelle forespørgsler", "parallel_requests": "Parallelle forespørgsler",
"load_balancing": "Belastningsfordeling", "load_balancing": "Belastningsfordeling",
"load_balancing_desc": "Forespørg én upstream-server ad gangen. AdGuard Home bruger en vægtet tilfældighedsalgoritme til vælg af servere med det laveste antal fejlslagne opslag og den laveste gennemsnitlige opslagstid.", "load_balancing_desc": "Forespørg én server ad gangen. AdGuard Home vil bruge en vægtet randomiseringsalgoritme til valg af server, så den hurtigste server oftere anvendes.",
"bootstrap_dns": "Bootstrap DNS-servere", "bootstrap_dns": "Bootstrap DNS-servere",
"bootstrap_dns_desc": "IP-adresser på DNS-servere, som bruges til at opløse IP-adresser på de DoH/DoT-opløsere, som angives som upstreams. Kommentarer er ikke tilladt.", "bootstrap_dns_desc": "IP-adresser på DNS-servere, som bruges til at opløse IP-adresser på de DoH/DoT-opløsere, som angives som upstreams. Kommentarer er ikke tilladt.",
"fallback_dns_title": "Reserve DNS-servere", "fallback_dns_title": "Reserve DNS-servere",
@@ -154,7 +154,7 @@
"use_adguard_parental": "Brug AdGuards forældrekontrolwebtjeneste", "use_adguard_parental": "Brug AdGuards forældrekontrolwebtjeneste",
"use_adguard_parental_hint": "AdGuard Home vil tjekke, om domænet indeholder voksenindhold vha. den samme fortrolighedsvenlige API som browsingsikkerhedswebtjenesten.", "use_adguard_parental_hint": "AdGuard Home vil tjekke, om domænet indeholder voksenindhold vha. den samme fortrolighedsvenlige API som browsingsikkerhedswebtjenesten.",
"enforce_safe_search": "Brug sikker søgning", "enforce_safe_search": "Brug sikker søgning",
"enforce_save_search_hint": "AdGuard Home vil håndhæve sikker søgning i flg. søgemaskiner: Google, YouTube, Bing, DuckDuckGo, Ecosia, Yandex, Pixabay.", "enforce_save_search_hint": "AdGuard Home vil håndhæve sikker søgning i flg. søgemaskiner: Google, YouTube, Bing, DuckDuckGo, Yandex og Pixabay.",
"no_servers_specified": "Ingen servere angivet", "no_servers_specified": "Ingen servere angivet",
"general_settings": "Generelle indstillinger", "general_settings": "Generelle indstillinger",
"dns_settings": "DNS-indstillinger", "dns_settings": "DNS-indstillinger",

View File

@@ -6,7 +6,7 @@
"upstream_parallel": "Parallele Abfragen verwenden, um das Auflösen zu beschleunigen, indem alle Upstream-Server gleichzeitig abgefragt werden.", "upstream_parallel": "Parallele Abfragen verwenden, um das Auflösen zu beschleunigen, indem alle Upstream-Server gleichzeitig abgefragt werden.",
"parallel_requests": "Paralleles Abfragen", "parallel_requests": "Paralleles Abfragen",
"load_balancing": "Lastverteilung", "load_balancing": "Lastverteilung",
"load_balancing_desc": "Es wird jeweils ein Upstream-Server abgefragt. AdGuard Home verwendet einen gewichteten Zufallsalgorithmus, um die Server mit der geringsten Anzahl an fehlgeschlagenen Suchvorgängen und der niedrigsten durchschnittlichen Suchzeit auszuwählen.", "load_balancing_desc": "Einen Server nach dem anderen abfragen. AdGuard Home verwendet den gewichteten Zufallsalgorithmus, um den Server so auszuwählen, dass der schnellste Server häufiger verwendet wird.",
"bootstrap_dns": "Bootstrap-DNS-Server", "bootstrap_dns": "Bootstrap-DNS-Server",
"bootstrap_dns_desc": "IP-Adressen der DNS-Server, die zum Auflösen der IP-Adressen von DoH/DoT Upstream-Servern verwendet werden, die Sie angegeben haben. Kommentare sind nicht erlaubt.", "bootstrap_dns_desc": "IP-Adressen der DNS-Server, die zum Auflösen der IP-Adressen von DoH/DoT Upstream-Servern verwendet werden, die Sie angegeben haben. Kommentare sind nicht erlaubt.",
"fallback_dns_title": "Fallback-DNS-Server", "fallback_dns_title": "Fallback-DNS-Server",
@@ -154,7 +154,7 @@
"use_adguard_parental": "AdGuard Webservice für Kindersicherung verwenden", "use_adguard_parental": "AdGuard Webservice für Kindersicherung verwenden",
"use_adguard_parental_hint": "AdGuard Home wird prüfen, ob die Domain jugendgefährdende Inhalte enthält. Zum Schutz Ihrer Privatsphäre wird die selbe API wie für den Webservice für Internetsicherheit verwendet.", "use_adguard_parental_hint": "AdGuard Home wird prüfen, ob die Domain jugendgefährdende Inhalte enthält. Zum Schutz Ihrer Privatsphäre wird die selbe API wie für den Webservice für Internetsicherheit verwendet.",
"enforce_safe_search": "Sichere Suche verwenden", "enforce_safe_search": "Sichere Suche verwenden",
"enforce_save_search_hint": "AdGuard kann Sichere Suche für folgende Suchmaschinen erzwingen: Google, YouTube, Bing, DuckDuckGo, Ecosia, Yandex und Pixabay.", "enforce_save_search_hint": "AdGuard kann Sichere Suche für folgende Suchmaschinen erzwingen: Google, YouTube, Bing, DuckDuckGo, Yandex und Pixabay.",
"no_servers_specified": "Keine Server festgelegt", "no_servers_specified": "Keine Server festgelegt",
"general_settings": "Allgemeine Einstellungen", "general_settings": "Allgemeine Einstellungen",
"dns_settings": "DNS-Einstellungen", "dns_settings": "DNS-Einstellungen",

View File

@@ -6,7 +6,7 @@
"upstream_parallel": "Use parallel queries to speed up resolving by querying all upstream servers simultaneously.", "upstream_parallel": "Use parallel queries to speed up resolving by querying all upstream servers simultaneously.",
"parallel_requests": "Parallel requests", "parallel_requests": "Parallel requests",
"load_balancing": "Load-balancing", "load_balancing": "Load-balancing",
"load_balancing_desc": "Query one upstream server at a time. AdGuard Home uses a weighted random algorithm to select servers with the lowest number of failed lookups and the lowest average lookup time.", "load_balancing_desc": "Query one upstream server at a time. AdGuard Home uses its weighted random algorithm to pick the server so that the fastest server is used more often.",
"bootstrap_dns": "Bootstrap DNS servers", "bootstrap_dns": "Bootstrap DNS servers",
"bootstrap_dns_desc": "IP addresses of DNS servers used to resolve IP addresses of the DoH/DoT resolvers you specify as upstreams. Comments are not permitted.", "bootstrap_dns_desc": "IP addresses of DNS servers used to resolve IP addresses of the DoH/DoT resolvers you specify as upstreams. Comments are not permitted.",
"fallback_dns_title": "Fallback DNS servers", "fallback_dns_title": "Fallback DNS servers",
@@ -154,7 +154,7 @@
"use_adguard_parental": "Use AdGuard parental control web service", "use_adguard_parental": "Use AdGuard parental control web service",
"use_adguard_parental_hint": "AdGuard Home will check if domain contains adult materials. It uses the same privacy-friendly API as the browsing security web service.", "use_adguard_parental_hint": "AdGuard Home will check if domain contains adult materials. It uses the same privacy-friendly API as the browsing security web service.",
"enforce_safe_search": "Use Safe Search", "enforce_safe_search": "Use Safe Search",
"enforce_save_search_hint": "AdGuard Home will enforce safe search in the following search engines: Google, YouTube, Bing, DuckDuckGo, Ecosia, Yandex, Pixabay.", "enforce_save_search_hint": "AdGuard Home will enforce safe search in the following search engines: Google, YouTube, Bing, DuckDuckGo, Yandex, Pixabay.",
"no_servers_specified": "No servers specified", "no_servers_specified": "No servers specified",
"general_settings": "General settings", "general_settings": "General settings",
"dns_settings": "DNS settings", "dns_settings": "DNS settings",

View File

@@ -6,7 +6,7 @@
"upstream_parallel": "Usar consultas paralelas para acelerar la resolución al consultar simultáneamente a todos los servidores DNS de subida.", "upstream_parallel": "Usar consultas paralelas para acelerar la resolución al consultar simultáneamente a todos los servidores DNS de subida.",
"parallel_requests": "Consultas paralelas", "parallel_requests": "Consultas paralelas",
"load_balancing": "Balanceo de carga", "load_balancing": "Balanceo de carga",
"load_balancing_desc": "Consulta un servidor upstream a la vez. AdGuard Home utiliza un algoritmo aleatorio ponderado para seleccionar los servidores con el menor número de fallos y el menor tiempo medio de búsqueda.", "load_balancing_desc": "Consulta un servidor DNS de subida a la vez. AdGuard Home utiliza su algoritmo aleatorio ponderado para elegir el servidor más rápido y sea utilizado con más frecuencia.",
"bootstrap_dns": "Servidores DNS de arranque", "bootstrap_dns": "Servidores DNS de arranque",
"bootstrap_dns_desc": "Direcciones IP de servidores DNS utilizadas para resolver direcciones IP de los solucionadores DoH/DoT que especifiques como ascendentes. No se permiten comentarios.", "bootstrap_dns_desc": "Direcciones IP de servidores DNS utilizadas para resolver direcciones IP de los solucionadores DoH/DoT que especifiques como ascendentes. No se permiten comentarios.",
"fallback_dns_title": "Servidores DNS alternativos", "fallback_dns_title": "Servidores DNS alternativos",
@@ -154,7 +154,7 @@
"use_adguard_parental": "Usar el control parental de AdGuard", "use_adguard_parental": "Usar el control parental de AdGuard",
"use_adguard_parental_hint": "AdGuard Home comprobará si el dominio contiene materiales para adultos. Utiliza la misma API amigable con la privacidad del servicio web de seguridad de navegación.", "use_adguard_parental_hint": "AdGuard Home comprobará si el dominio contiene materiales para adultos. Utiliza la misma API amigable con la privacidad del servicio web de seguridad de navegación.",
"enforce_safe_search": "Usar búsqueda segura", "enforce_safe_search": "Usar búsqueda segura",
"enforce_save_search_hint": "AdGuard Home reforzará la búsqueda segura en los siguientes motores de búsqueda: Google, YouTube, Bing, DuckDuckGo, Ecosia, Yandex y Pixabay.", "enforce_save_search_hint": "AdGuard Home reforzará la búsqueda segura en los siguientes motores de búsqueda: Google, YouTube, Bing, DuckDuckGo, Yandex y Pixabay.",
"no_servers_specified": "No hay servidores especificados", "no_servers_specified": "No hay servidores especificados",
"general_settings": "Configuración general", "general_settings": "Configuración general",
"dns_settings": "Configuración del DNS", "dns_settings": "Configuración del DNS",

View File

@@ -589,7 +589,6 @@
"cache_optimistic_desc": "AdGuard Home را وادار می کند که از سمت حافظه پنهان پاسخ دهد حتی وقتی که موارد وارد شده منقضی شده باشد و همچنین سعی بر تازه کردن آنها می کند.", "cache_optimistic_desc": "AdGuard Home را وادار می کند که از سمت حافظه پنهان پاسخ دهد حتی وقتی که موارد وارد شده منقضی شده باشد و همچنین سعی بر تازه کردن آنها می کند.",
"filter_category_general": "General", "filter_category_general": "General",
"filter_category_security": "مسدودسازی بدافزار و فیشینگ", "filter_category_security": "مسدودسازی بدافزار و فیشینگ",
"filter_category_regional": "منطقه‌ای",
"filter_category_other": "ساير", "filter_category_other": "ساير",
"use_saved_key": "از کلید ذخیره شده قبلی استفاده کنید", "use_saved_key": "از کلید ذخیره شده قبلی استفاده کنید",
"parental_control": "نظارت والدین", "parental_control": "نظارت والدین",

View File

@@ -6,7 +6,7 @@
"upstream_parallel": "Käytä rinnakkaisia pyyntöjä ja nopeuta selvitystä käyttämällä kaikkia ylävirtapalvelimia samanaikaisesti.", "upstream_parallel": "Käytä rinnakkaisia pyyntöjä ja nopeuta selvitystä käyttämällä kaikkia ylävirtapalvelimia samanaikaisesti.",
"parallel_requests": "Rinnakkaiset pyynnöt", "parallel_requests": "Rinnakkaiset pyynnöt",
"load_balancing": "Kuormantasaus", "load_balancing": "Kuormantasaus",
"load_balancing_desc": "Lähetä kysely kerrallaan yhdelle ylävirtapalvelimelle. AdGuard Home valitsee painotetun satunnaisalgoritmin avulla palvelimet, joilla on vähiten epäonnistuneita hakuja ja keskimääräisesti lyhin hakuaika.", "load_balancing_desc": "Lähetä pyyntö yhdelle ylävirtapalvelimelle kerrallaan. AdGuard Home pyrkii valitsemaan nopeimman palvelimen painotetun satunnaisalgoritminsa avulla.",
"bootstrap_dns": "Bootstrap DNS-palvelimet", "bootstrap_dns": "Bootstrap DNS-palvelimet",
"bootstrap_dns_desc": "Ylävirroiksi määrittämiesi DoH/DoT-resolverien IP-osoitteiden selvitykseen käytettävien DNS-palvelimien IP-osoitteet. Kommentteja ei sallita.", "bootstrap_dns_desc": "Ylävirroiksi määrittämiesi DoH/DoT-resolverien IP-osoitteiden selvitykseen käytettävien DNS-palvelimien IP-osoitteet. Kommentteja ei sallita.",
"fallback_dns_title": "DNS-varapalvelimet", "fallback_dns_title": "DNS-varapalvelimet",
@@ -154,7 +154,7 @@
"use_adguard_parental": "Käytä AdGuardin lapsilukko-palvelua", "use_adguard_parental": "Käytä AdGuardin lapsilukko-palvelua",
"use_adguard_parental_hint": "AdGuard Home tarkistaa, sisältääkö verkkotunnus aikuisille tarkoitettua sisältöä. Se käyttää samaa tietosuojapainotteista rajapintaa, kuin turvallisen selauksen palvelu.", "use_adguard_parental_hint": "AdGuard Home tarkistaa, sisältääkö verkkotunnus aikuisille tarkoitettua sisältöä. Se käyttää samaa tietosuojapainotteista rajapintaa, kuin turvallisen selauksen palvelu.",
"enforce_safe_search": "Käytä turvallista hakua", "enforce_safe_search": "Käytä turvallista hakua",
"enforce_save_search_hint": "AdGuard Home pakottaa turvallisen haun käyttöön seuraavissa hakukoneissa: Google, YouTube, Bing, DuckDuckGo, Ecosia, Yandex ja Pixabay.", "enforce_save_search_hint": "AdGuard Home voi pakottaa turvallisen haun käyttöön seuraavissa hakukoneissa: Google, YouTube, Bing, DuckDuckGo, Yandex, Pixabay.",
"no_servers_specified": "Palvelimia ei ole määritetty", "no_servers_specified": "Palvelimia ei ole määritetty",
"general_settings": "Yleiset asetukset", "general_settings": "Yleiset asetukset",
"dns_settings": "DNS-asetukset", "dns_settings": "DNS-asetukset",
@@ -542,7 +542,7 @@
"stats_params": "Tilastoinnin määritys", "stats_params": "Tilastoinnin määritys",
"config_successfully_saved": "Asetukset tallennettiin", "config_successfully_saved": "Asetukset tallennettiin",
"interval_6_hour": "6 tuntia", "interval_6_hour": "6 tuntia",
"interval_24_hour": "24 tunnilta", "interval_24_hour": "24 tuntia",
"interval_days": "{{count}} päivä", "interval_days": "{{count}} päivä",
"interval_days_plural": "{{count}} päivää", "interval_days_plural": "{{count}} päivää",
"domain": "Verkkotunnus", "domain": "Verkkotunnus",
@@ -709,9 +709,9 @@
"log_and_stats_section_label": "Pyyntöhistoria ja tilastot", "log_and_stats_section_label": "Pyyntöhistoria ja tilastot",
"ignore_query_log": "Älä huomioi tätä päätelaitetta pyyntöhistoriassa", "ignore_query_log": "Älä huomioi tätä päätelaitetta pyyntöhistoriassa",
"ignore_statistics": "Älä huomioi tätä päätettä tilastoissa", "ignore_statistics": "Älä huomioi tätä päätettä tilastoissa",
"schedule_services": "Pysäytä palveluesto", "schedule_services": "Keskeytä palveluesto",
"schedule_services_desc": "Määritä palvelunestosuodattimen pysäytysajoitus.", "schedule_services_desc": "Määritä palvelunestosuodattimen keskeytysajoitus.",
"schedule_services_desc_client": "Määritä palvelunestosuodattimen pysäytysajoitus tälle päätteelle.", "schedule_services_desc_client": "Määritä palvelunestosuodattimen keskeytysajoitus tälle päätteelle.",
"schedule_desc": "Aseta estettujen palveluiden käyttämättömyysjaksot", "schedule_desc": "Aseta estettujen palveluiden käyttämättömyysjaksot",
"schedule_invalid_select": "Aloitusaika on oltava ennen lopetusaikaa", "schedule_invalid_select": "Aloitusaika on oltava ennen lopetusaikaa",
"schedule_select_days": "Valitse päivät", "schedule_select_days": "Valitse päivät",

View File

@@ -6,7 +6,7 @@
"upstream_parallel": "Utilisez des requêtes parallèles pour accélérer la résolution en requêtant simultanément tous les serveurs en amont.", "upstream_parallel": "Utilisez des requêtes parallèles pour accélérer la résolution en requêtant simultanément tous les serveurs en amont.",
"parallel_requests": "Requêtes en parallèle", "parallel_requests": "Requêtes en parallèle",
"load_balancing": "Équilibrage de charge", "load_balancing": "Équilibrage de charge",
"load_balancing_desc": "Une requête par serveur en amont à la fois. AdGuard Home utilise un algorithme aléatoire pondéré pour sélectionner les serveurs avec le plus petit nombre d'échecs de recherche et le temps de recherche moyen le plus bas.", "load_balancing_desc": "Interroger un serveur en amont à la fois. AdGuard Home utilise son algorithme aléatoire pondéré pour choisir le serveur de sorte que le serveur le plus rapide soit utilisé plus souvent.",
"bootstrap_dns": "Serveurs DNS d'amorçage", "bootstrap_dns": "Serveurs DNS d'amorçage",
"bootstrap_dns_desc": "Les adresses IP des serveurs DNS utilisées pour résoudre les adresses IP des résolveurs DoH/DoT que vous spécifiez comme en amont. Les commentaires ne sont pas autorisés.", "bootstrap_dns_desc": "Les adresses IP des serveurs DNS utilisées pour résoudre les adresses IP des résolveurs DoH/DoT que vous spécifiez comme en amont. Les commentaires ne sont pas autorisés.",
"fallback_dns_title": "Serveurs DNS de repli", "fallback_dns_title": "Serveurs DNS de repli",
@@ -154,7 +154,7 @@
"use_adguard_parental": "Utiliser le contrôle parental d'AdGuard", "use_adguard_parental": "Utiliser le contrôle parental d'AdGuard",
"use_adguard_parental_hint": "AdGuard Home va vérifier s'il y a du contenu pour adultes sur le domaine. Ce sera fait par aide du même API discret que celui utilisé par le service de Sécurité de navigation.", "use_adguard_parental_hint": "AdGuard Home va vérifier s'il y a du contenu pour adultes sur le domaine. Ce sera fait par aide du même API discret que celui utilisé par le service de Sécurité de navigation.",
"enforce_safe_search": "Utiliser la Recherche Sécurisée", "enforce_safe_search": "Utiliser la Recherche Sécurisée",
"enforce_save_search_hint": "AdGuard Home appliquera la recherche sécurisée dans les moteurs de recherche suivants : Google, YouTube, Bing, DuckDuckGo, Ecosia, Yandex, Pixabay.", "enforce_save_search_hint": "AdGuard Home appliquera la recherche sécurisée dans les moteurs de recherche suivants : Google, YouTube, Bing, DuckDuckGo, Yandex, Pixabay.",
"no_servers_specified": "Pas de serveurs spécifiés", "no_servers_specified": "Pas de serveurs spécifiés",
"general_settings": "Paramètres généraux", "general_settings": "Paramètres généraux",
"dns_settings": "Paramètres DNS", "dns_settings": "Paramètres DNS",
@@ -676,7 +676,7 @@
"filter_allowlist": "ATTENTION : Cette action exclura également la règle « {{disallowed_rule}} » de la liste des clients autorisés.", "filter_allowlist": "ATTENTION : Cette action exclura également la règle « {{disallowed_rule}} » de la liste des clients autorisés.",
"last_rule_in_allowlist": "Impossible dinterdire ce client, car lexclusion de la règle « {{disallowed_rule}} » DÉSACTIVERA la liste des « clients autorisés ».", "last_rule_in_allowlist": "Impossible dinterdire ce client, car lexclusion de la règle « {{disallowed_rule}} » DÉSACTIVERA la liste des « clients autorisés ».",
"use_saved_key": "Utiliser la clef précédemment enregistrée", "use_saved_key": "Utiliser la clef précédemment enregistrée",
"parental_control": "Contrôle Parental", "parental_control": "Contrôle parental",
"safe_browsing": "Navigation sécurisée", "safe_browsing": "Navigation sécurisée",
"served_from_cache_label": "Servi depuis le cache", "served_from_cache_label": "Servi depuis le cache",
"form_error_password_length": "Le mot de passe doit comporter entre {{min}} et {{max}}  caractères", "form_error_password_length": "Le mot de passe doit comporter entre {{min}} et {{max}}  caractères",

View File

@@ -13,14 +13,14 @@
"fallback_dns_desc": "Popis rezervnih DNS poslužitelja koji se koriste kada uzvodni DNS poslužitelji ne odgovaraju. Sintaksa je ista kao u gornjem polju glavnog uzvodnog toka.", "fallback_dns_desc": "Popis rezervnih DNS poslužitelja koji se koriste kada uzvodni DNS poslužitelji ne odgovaraju. Sintaksa je ista kao u gornjem polju glavnog uzvodnog toka.",
"fallback_dns_placeholder": "Unesite jedan rezervni DNS poslužitelj po retku", "fallback_dns_placeholder": "Unesite jedan rezervni DNS poslužitelj po retku",
"local_ptr_title": "Privatni obrnuti DNS poslužitelji", "local_ptr_title": "Privatni obrnuti DNS poslužitelji",
"local_ptr_desc": "DNS poslužitelji koje koristi AdGuard Home za privatne PTR, SOA i NS zahtjeve. Zahtjev se smatra privatnim ako traži ARPA domenu koja sadrži podmrežu unutar privatnih IP raspona (kao što je \"192.168.12.34\") i dolazi od klijenta s privatnom IP adresom. Ako nije postavljeno, koristit će se zadani DNS rezolveri vašeg OS-a, osim za AdGuard Home IP adrese.", "local_ptr_desc": "DNS poslužitelji koje AdGuard Home koristi za lokalne PTR upite. Ti se poslužitelji koriste za razrješavanje naziva glavnog računala klijenata s privatnim IP adresama, na primjer \"192.168.12.34\", koristeći obrnuti DNS. Ako nije postavljeno, AdGuard Home koristi adrese zadanih DNS razrješivača vašeg OS-a, osim za adrese samog AdGuard Homea.",
"local_ptr_default_resolver": "Prema zadanim postavkama AdGuard Home koristi sljedeće obrnute DNS razrješivače: {{ip}}.", "local_ptr_default_resolver": "Prema zadanim postavkama AdGuard Home koristi sljedeće obrnute DNS razrješivače: {{ip}}.",
"local_ptr_no_default_resolver": "AdGuard Home nije mogao odrediti prikladne privatne obrnute DNS razrješivače za ovaj sustav.", "local_ptr_no_default_resolver": "AdGuard Home nije mogao odrediti prikladne privatne obrnute DNS razrješivače za ovaj sustav.",
"local_ptr_placeholder": "Unesite jednu adresu poslužitelja po retku", "local_ptr_placeholder": "Unesite jednu adresu poslužitelja po retku",
"resolve_clients_title": "Omogući obrnuto rješavanje IP adresa klijenata", "resolve_clients_title": "Omogući obrnuto rješavanje IP adresa klijenata",
"resolve_clients_desc": "Obrnuto razriješite IP adrese klijenata u nazive glavnih računala slanjem PTR upita odgovarajućim razrješivačima (privatni DNS poslužitelji za lokalne klijente, uzvodni poslužitelji za klijente s javnim IP adresama).", "resolve_clients_desc": "Obrnuto razriješite IP adrese klijenata u nazive glavnih računala slanjem PTR upita odgovarajućim razrješivačima (privatni DNS poslužitelji za lokalne klijente, uzvodni poslužitelji za klijente s javnim IP adresama).",
"use_private_ptr_resolvers_title": "Koristi privatne reverzne DNS razrješivače", "use_private_ptr_resolvers_title": "Koristi privatne reverzne DNS razrješivače",
"use_private_ptr_resolvers_desc": "Razriješi PTR, SOA i NS zahtjeve za ARPA domene koje sadrže privatne IP adrese putem privatnih uzvodnih poslužitelja, DHCP-a, /etc/hostova itd. Ako je onemogućeno, AdGuard Home će na sve takve zahtjeve odgovoriti s NXDOMAIN.", "use_private_ptr_resolvers_desc": "Izvršite obrnuta DNS traženja za lokalno poslužene adrese pomoću ovih uzlaznih poslužitelja. Ako je onemogućen, AdGuard Home odgovara S NXDOMAIN-om na sve takve PTR zahtjeve osim za klijente poznate iz DHCP-a, /etc/hosts i tako dalje.",
"check_dhcp_servers": "Provjera DHCP poslužitelja", "check_dhcp_servers": "Provjera DHCP poslužitelja",
"save_config": "Spremi konfiguraciju", "save_config": "Spremi konfiguraciju",
"enabled_dhcp": "DHCP poslužitelj je omogućen", "enabled_dhcp": "DHCP poslužitelj je omogućen",
@@ -425,9 +425,6 @@
"encryption_hostnames": "Nazivi računala", "encryption_hostnames": "Nazivi računala",
"encryption_reset": "Jeste li sigurni da želite poništiti postavke šifriranja?", "encryption_reset": "Jeste li sigurni da želite poništiti postavke šifriranja?",
"encryption_warning": "Upozorenje", "encryption_warning": "Upozorenje",
"encryption_plain_dns_enable": "Omogući obični DNS",
"encryption_plain_dns_desc": "Obični DNS je omogućen prema zadanim postavkama. Možete ga onemogućiti kako biste prisilili sve uređaje da koriste šifrirani DNS. Da biste to učinili, morate omogućiti barem jedan kriptirani DNS protokol",
"encryption_plain_dns_error": "Da biste onemogućili obični DNS, omogućite barem jedan kriptirani DNS protokol",
"topline_expiring_certificate": "Vaš SSL certifikat uskoro ističe. Ažurirajte <0>Postavke šifriranja</0>.", "topline_expiring_certificate": "Vaš SSL certifikat uskoro ističe. Ažurirajte <0>Postavke šifriranja</0>.",
"topline_expired_certificate": "Vaš SSL certifikat je istekao. Ažurirajte <0>Postavke šifriranja</0>.", "topline_expired_certificate": "Vaš SSL certifikat je istekao. Ažurirajte <0>Postavke šifriranja</0>.",
"form_error_port_range": "Unesite broj porta od 80 do 65536", "form_error_port_range": "Unesite broj porta od 80 do 65536",
@@ -678,7 +675,7 @@
"use_saved_key": "Korištenje prethodno spremljenog ključa", "use_saved_key": "Korištenje prethodno spremljenog ključa",
"parental_control": "Roditeljska zaštita", "parental_control": "Roditeljska zaštita",
"safe_browsing": "Sigurno surfanje", "safe_browsing": "Sigurno surfanje",
"served_from_cache_label": "Posluženo iz predmemorije", "served_from_cache": "{{value}} <i>(dohvaćeno iz predmemorije)</i>",
"form_error_password_length": "Lozinka mora sadržavati od {{min}} do {{max}} znakova", "form_error_password_length": "Lozinka mora sadržavati od {{min}} do {{max}} znakova",
"anonymizer_notification": "<0>Napomena:</0>IP anonimizacija je omogućena. Možete ju onemogućiti u <1>općim postavkama</1>.", "anonymizer_notification": "<0>Napomena:</0>IP anonimizacija je omogućena. Možete ju onemogućiti u <1>općim postavkama</1>.",
"confirm_dns_cache_clear": "Jeste li sigurni da želite očistiti DNS predmemoriju?", "confirm_dns_cache_clear": "Jeste li sigurni da želite očistiti DNS predmemoriju?",

View File

@@ -147,7 +147,7 @@
"average_upstream_response_time": "Rata-rata waktu respons hulu", "average_upstream_response_time": "Rata-rata waktu respons hulu",
"response_time": "Waktu respons", "response_time": "Waktu respons",
"average_processing_time_hint": "Rata-rata waktu dalam milidetik untuk pemrosesan sebuah permintaan DNS", "average_processing_time_hint": "Rata-rata waktu dalam milidetik untuk pemrosesan sebuah permintaan DNS",
"block_domain_use_filters_and_hosts": "Blokir domain menggunakan filter dan berkas host", "block_domain_use_filters_and_hosts": "Blokir domain menggunakan filter dan file hosts",
"filters_block_toggle_hint": "Anda dapat menyiapkan aturan pemblokiran dalam pengaturan <a>Filter</a>.", "filters_block_toggle_hint": "Anda dapat menyiapkan aturan pemblokiran dalam pengaturan <a>Filter</a>.",
"use_adguard_browsing_sec": "Gunakan layanan web Keamanan Penjelajahan AdGuard", "use_adguard_browsing_sec": "Gunakan layanan web Keamanan Penjelajahan AdGuard",
"use_adguard_browsing_sec_hint": "AdGuard Home akan memeriksa apakah domain diblokir oleh layanan web keamanan penjelajahan. Ini akan menggunakan API pencarian yang ramah privasi untuk melakukan pemeriksaan: hanya awalan singkat dari hash nama domain SHA256 yang dikirim ke server.", "use_adguard_browsing_sec_hint": "AdGuard Home akan memeriksa apakah domain diblokir oleh layanan web keamanan penjelajahan. Ini akan menggunakan API pencarian yang ramah privasi untuk melakukan pemeriksaan: hanya awalan singkat dari hash nama domain SHA256 yang dikirim ke server.",
@@ -191,7 +191,7 @@
"edit_table_action": "Ubah", "edit_table_action": "Ubah",
"delete_table_action": "Hapus", "delete_table_action": "Hapus",
"elapsed": "Berlalu", "elapsed": "Berlalu",
"filters_and_hosts_hint": "AdGuard Home memahami aturan dasar adblock dan sintak berkas host.", "filters_and_hosts_hint": "AdGuard Home memahami aturan dasar adblock dan sintak file hosts.",
"no_blocklist_added": "Tidak ada daftar hitam yang ditambahkan", "no_blocklist_added": "Tidak ada daftar hitam yang ditambahkan",
"no_whitelist_added": "Tidak ada daftar putih yang ditambahkan", "no_whitelist_added": "Tidak ada daftar putih yang ditambahkan",
"add_blocklist": "Tambahkan daftar hitam", "add_blocklist": "Tambahkan daftar hitam",
@@ -211,8 +211,8 @@
"form_error_url_format": "Format URL tidak valid", "form_error_url_format": "Format URL tidak valid",
"form_error_url_or_path_format": "URL atau jalur absolut dari daftar tidak valid", "form_error_url_or_path_format": "URL atau jalur absolut dari daftar tidak valid",
"custom_filter_rules": "Aturan penyaringan khusus", "custom_filter_rules": "Aturan penyaringan khusus",
"custom_filter_rules_hint": "Masukkan satu aturan pada satu baris. Anda dapat menggunakan aturan adblock atau sintaks berkas host.", "custom_filter_rules_hint": "Masukkan satu aturan dalam sebuah baris. Anda dapat menggunakan baik aturan adblock maupun sintaks file hosts.",
"system_host_files": "Berkas host sistem", "system_host_files": "File host sistem",
"examples_title": "Contoh", "examples_title": "Contoh",
"example_meaning_filter_block": "blokir akses ke example.org dan seluruh subdomainnya;", "example_meaning_filter_block": "blokir akses ke example.org dan seluruh subdomainnya;",
"example_meaning_filter_whitelist": "buka blokir akses ke domain example.orf dan seluruh subdomainnya;", "example_meaning_filter_whitelist": "buka blokir akses ke domain example.orf dan seluruh subdomainnya;",
@@ -476,7 +476,7 @@
"client_confirm_delete": "Apakah anda yakin ingin menghapus klien \"{{key}}\"?", "client_confirm_delete": "Apakah anda yakin ingin menghapus klien \"{{key}}\"?",
"list_confirm_delete": "Anda yakin ingin menghapus daftar ini?", "list_confirm_delete": "Anda yakin ingin menghapus daftar ini?",
"auto_clients_title": "Klien (waktu berjalan)", "auto_clients_title": "Klien (waktu berjalan)",
"auto_clients_desc": "Informasi tentang alamat IP perangkat yang menggunakan atau mungkin menggunakan AdGuard Home. Informasi ini dikumpulkan dari beberapa sumber, termasuk berkas host, DNS terbalik, dll.", "auto_clients_desc": "Informasi tentang alamat IP perangkat yang menggunakan atau mungkin menggunakan AdGuard Home. Informasi ini dikumpulkan dari beberapa sumber, termasuk file host, reverse DNS, dll.",
"access_title": "Pengaturan akses", "access_title": "Pengaturan akses",
"access_desc": "Disini anda dapat mengatur aturan akses untuk server AdGuard Home DNS", "access_desc": "Disini anda dapat mengatur aturan akses untuk server AdGuard Home DNS",
"access_allowed_title": "Klien yang diizinkan", "access_allowed_title": "Klien yang diizinkan",
@@ -494,7 +494,7 @@
"setup_dns_privacy_1": "<0>DNS melalui TLS:</0> Gunakan <1>{{address}}</1> string.", "setup_dns_privacy_1": "<0>DNS melalui TLS:</0> Gunakan <1>{{address}}</1> string.",
"setup_dns_privacy_2": "<0>DNS-over-TLS:</0> Memakai <1>{{address}}</1> string.", "setup_dns_privacy_2": "<0>DNS-over-TLS:</0> Memakai <1>{{address}}</1> string.",
"setup_dns_privacy_3": "<0>Berikut daftar perangkat lunak yang dapat Anda gunakan.</0>", "setup_dns_privacy_3": "<0>Berikut daftar perangkat lunak yang dapat Anda gunakan.</0>",
"setup_dns_privacy_4": "Pada perangkat iOS 14 atau macOS Big Sur, Anda dapat mengunduh berkas khusus '.mobileconfig' yang menambahkan server <highlight>DNS melalui HTTPS</highlight> atau <highlight>DNS melalui TLS</highlight> ke pengaturan DNS.", "setup_dns_privacy_4": "Di perangkat iOS 14 atau macOS Big Sur, Anda dapat mengunduh file '.mobileconfig' khusus yang menambahkan server <highlight>DNS-over-HTTPS</highlight> atau <highlight>DNS-over-TLS</highlight> ke pengaturan DNS.",
"setup_dns_privacy_android_1": "Android 9 mendukung DNS-over-TLS secara asli. Untuk mengkonfigurasinya, buka Pengaturan → Jaringan & internet → Tingkat Lanjut → DNS Pribadi dan masukkan nama domain Anda di sana.", "setup_dns_privacy_android_1": "Android 9 mendukung DNS-over-TLS secara asli. Untuk mengkonfigurasinya, buka Pengaturan → Jaringan & internet → Tingkat Lanjut → DNS Pribadi dan masukkan nama domain Anda di sana.",
"setup_dns_privacy_android_2": "<0>AdGuard untuk Android</0> mendukung <1>DNS-over-HTTPS</1> dan <1>DNS-over-TLS</1>.", "setup_dns_privacy_android_2": "<0>AdGuard untuk Android</0> mendukung <1>DNS-over-HTTPS</1> dan <1>DNS-over-TLS</1>.",
"setup_dns_privacy_android_3": "<0>Intra</0> menambahkan dukungan <1>DNS-over-HTTPS</1> untuk Android.", "setup_dns_privacy_android_3": "<0>Intra</0> menambahkan dukungan <1>DNS-over-HTTPS</1> untuk Android.",
@@ -517,7 +517,7 @@
"rewrite_confirm_delete": "Apakah anda yakin ingin menghapus DNS rewrite untuk \"{{key}}\"?", "rewrite_confirm_delete": "Apakah anda yakin ingin menghapus DNS rewrite untuk \"{{key}}\"?",
"rewrite_desc": "Memungkinkan untuk dengan mudah mengkonfigurasi respons DNS kustom untuk nama domain tertentu.", "rewrite_desc": "Memungkinkan untuk dengan mudah mengkonfigurasi respons DNS kustom untuk nama domain tertentu.",
"rewrite_applied": "Aturan Rewrite yang diterapkan", "rewrite_applied": "Aturan Rewrite yang diterapkan",
"rewrite_hosts_applied": "Ditulis ulang oleh aturan berkas host", "rewrite_hosts_applied": "Ditulis ulang oleh aturan file hosts",
"dns_rewrites": "DNS rewrite", "dns_rewrites": "DNS rewrite",
"form_domain": "Masukkan nama domain", "form_domain": "Masukkan nama domain",
"form_answer": "Masaukan alamat IP atau nama domain", "form_answer": "Masaukan alamat IP atau nama domain",
@@ -676,7 +676,7 @@
"filter_allowlist": "PERINGATAN: Tindakan ini juga akan mengecualikan aturan \"{{disallowed_rule}}\" dari daftar klien yang diizinkan.", "filter_allowlist": "PERINGATAN: Tindakan ini juga akan mengecualikan aturan \"{{disallowed_rule}}\" dari daftar klien yang diizinkan.",
"last_rule_in_allowlist": "Tidak dapat melarang klien ini karena mengecualikan aturan \"{{disallowed_rule}}\" akan MENONAKTIFKAN daftar \"Klien yang diizinkan\".", "last_rule_in_allowlist": "Tidak dapat melarang klien ini karena mengecualikan aturan \"{{disallowed_rule}}\" akan MENONAKTIFKAN daftar \"Klien yang diizinkan\".",
"use_saved_key": "Gunakan kunci yang disimpan sebelumnya", "use_saved_key": "Gunakan kunci yang disimpan sebelumnya",
"parental_control": "Pengawasan Orang Tua", "parental_control": "Kontrol Orang Tua",
"safe_browsing": "Penjelajahan Aman", "safe_browsing": "Penjelajahan Aman",
"served_from_cache": "{{value}} <i>(disajikan dari cache)</i>", "served_from_cache": "{{value}} <i>(disajikan dari cache)</i>",
"form_error_password_length": "Kata sandi harus terdiri dari {{min}} hingga {{max}}", "form_error_password_length": "Kata sandi harus terdiri dari {{min}} hingga {{max}}",

View File

@@ -6,7 +6,7 @@
"upstream_parallel": "Utilizza richieste parallele per accelerare la risoluzione interrogando simultaneamente tutti i server upstream.", "upstream_parallel": "Utilizza richieste parallele per accelerare la risoluzione interrogando simultaneamente tutti i server upstream.",
"parallel_requests": "Richieste parallele", "parallel_requests": "Richieste parallele",
"load_balancing": "Bilanciamento del carico", "load_balancing": "Bilanciamento del carico",
"load_balancing_desc": "Esegui una query su un server upstream alla volta. AdGuard Home utilizza un algoritmo casuale ponderato per selezionare i server con il minor numero di ricerche fallite e il tempo medio di ricerca più basso.", "load_balancing_desc": "Interroga un server upstream per volta. AdGuard Home utilizzerà un algoritmo casuale ponderato per la selezione del server, in maniera tale da scegliere spesso il più veloce.",
"bootstrap_dns": "Server DNS bootstrap", "bootstrap_dns": "Server DNS bootstrap",
"bootstrap_dns_desc": "Indirizzi IP dei server DNS utilizzati per risolvere gli indirizzi IP dei resolver DoH/DoT specificati come upstream. I commenti non sono ammessi.", "bootstrap_dns_desc": "Indirizzi IP dei server DNS utilizzati per risolvere gli indirizzi IP dei resolver DoH/DoT specificati come upstream. I commenti non sono ammessi.",
"fallback_dns_title": "Server DNS di fallback", "fallback_dns_title": "Server DNS di fallback",
@@ -154,7 +154,7 @@
"use_adguard_parental": "Utilizza il Controllo Parentale di AdGuard", "use_adguard_parental": "Utilizza il Controllo Parentale di AdGuard",
"use_adguard_parental_hint": "AdGuard Home verificherà se il dominio contiene materiale per adulti. Utilizza le stesse API privacy-friendly del servizio web 'sicurezza di navigazione'.", "use_adguard_parental_hint": "AdGuard Home verificherà se il dominio contiene materiale per adulti. Utilizza le stesse API privacy-friendly del servizio web 'sicurezza di navigazione'.",
"enforce_safe_search": "Utilizza Ricerca Sicura", "enforce_safe_search": "Utilizza Ricerca Sicura",
"enforce_save_search_hint": "AdGuard Home applicherà la ricerca sicura nei seguenti motori di ricerca: Google, YouTube, Bing, DuckDuckGo, Ecosia, Yandex, Pixabay.", "enforce_save_search_hint": "AdGuard Home forzerà la ricerca sicura sui seguenti motori di ricerca: Google, YouTube, Bing, DuckDuckGo, Yandex, Pixabay.",
"no_servers_specified": "Nessun server specificato", "no_servers_specified": "Nessun server specificato",
"general_settings": "Impostazioni generali", "general_settings": "Impostazioni generali",
"dns_settings": "Impostazioni DNS", "dns_settings": "Impostazioni DNS",

View File

@@ -6,7 +6,7 @@
"upstream_parallel": "並列リクエストを使用する(同時にすべてのアップストリームサーバーに処理要求することで解決スピードが向上)", "upstream_parallel": "並列リクエストを使用する(同時にすべてのアップストリームサーバーに処理要求することで解決スピードが向上)",
"parallel_requests": "並列リクエスト", "parallel_requests": "並列リクエスト",
"load_balancing": "ロードバランシング", "load_balancing": "ロードバランシング",
"load_balancing_desc": "一度に1つのアップストリームサーバーをクエリします。AdGuard Home は、重み付き乱択アルゴリズムを使用して、ルックアップに失敗した回数が最も少なく、平均ルックアップ時間が最も短いサーバを選択ます。", "load_balancing_desc": "一度に1つのアップストリームサーバに処理要求します。 AdGuard Homeは、重み付きランダムアルゴリズムweighted random algorithmを使用してサーバを選択するため、最速のサーバがより頻繁に使用されます。",
"bootstrap_dns": "ブートストラップDNSサーバ", "bootstrap_dns": "ブートストラップDNSサーバ",
"bootstrap_dns_desc": "アップストリームとして指定したDoH/DoTリゾルバのIPアドレスを解決するために使用されるDNSサーバーのIPアドレスです。コメントは許可されていません", "bootstrap_dns_desc": "アップストリームとして指定したDoH/DoTリゾルバのIPアドレスを解決するために使用されるDNSサーバーのIPアドレスです。コメントは許可されていません",
"fallback_dns_title": "フォールバックDNSサーバー", "fallback_dns_title": "フォールバックDNSサーバー",
@@ -154,7 +154,7 @@
"use_adguard_parental": "AdGuardペアレンタルコントロール・ウェブサービスを使用する", "use_adguard_parental": "AdGuardペアレンタルコントロール・ウェブサービスを使用する",
"use_adguard_parental_hint": "AdGuard Homeは、ドメインにアダルトコンテンツが含まれているかどうかを確認します。 ブラウジングセキュリティ・ウェブサービスと同じプライバシーに優しいAPIを使用します。", "use_adguard_parental_hint": "AdGuard Homeは、ドメインにアダルトコンテンツが含まれているかどうかを確認します。 ブラウジングセキュリティ・ウェブサービスと同じプライバシーに優しいAPIを使用します。",
"enforce_safe_search": "セーフサーチを使用する", "enforce_safe_search": "セーフサーチを使用する",
"enforce_save_search_hint": "AdGuard Homeは、次の検索エンジンでセーフサーチを強制適用します: Google, YouTube, Bing, DuckDuckGo, Ecosia, Yandex, Pixabay", "enforce_save_search_hint": "AdGuard Homeは、次の検索エンジンでセーフサーチを強制適用します: Google, YouTube, Bing, DuckDuckGo, Yandex, Pixabay",
"no_servers_specified": "サーバが指定されていません", "no_servers_specified": "サーバが指定されていません",
"general_settings": "一般設定", "general_settings": "一般設定",
"dns_settings": "DNS設定", "dns_settings": "DNS設定",

View File

@@ -6,7 +6,7 @@
"upstream_parallel": "쿼리 처리 속도를 높이려면 모든 업스트림 서버에서 동시에 병렬 쿼리를 사용해주세요.", "upstream_parallel": "쿼리 처리 속도를 높이려면 모든 업스트림 서버에서 동시에 병렬 쿼리를 사용해주세요.",
"parallel_requests": "병렬 처리 요청", "parallel_requests": "병렬 처리 요청",
"load_balancing": "로드 밸런싱", "load_balancing": "로드 밸런싱",
"load_balancing_desc": "한 번에 하나의 업스트림 서버를 쿼리합니다. AdGuard Home은 가중 무작위 알고리즘 사용하여 조회 실패 횟수가 가장 적고 평균 조회 시간이 가장 짧은 서버를 선택합니다.", "load_balancing_desc": "한 번에 하나의 서버씩 질의합니다. AdGuard Home은 가중 랜덤 알고리즘 사용해서 가장 빠른 서버가 자주 사용되도록 서버를 선택합니다.",
"bootstrap_dns": "부트스트랩 DNS 서버", "bootstrap_dns": "부트스트랩 DNS 서버",
"bootstrap_dns_desc": "업스트림으로 지정한 DoH/DoT 리졸버의 IP 주소를 확인하는 데 사용되는 DNS 서버의 IP 주소입니다. 주석은 허용되지 않습니다.", "bootstrap_dns_desc": "업스트림으로 지정한 DoH/DoT 리졸버의 IP 주소를 확인하는 데 사용되는 DNS 서버의 IP 주소입니다. 주석은 허용되지 않습니다.",
"fallback_dns_title": "폴백 DNS 서버", "fallback_dns_title": "폴백 DNS 서버",
@@ -108,7 +108,7 @@
"off": "OFF", "off": "OFF",
"copyright": "Copyright", "copyright": "Copyright",
"homepage": "홈페이지", "homepage": "홈페이지",
"report_an_issue": "문제 신고", "report_an_issue": "문제를 보고합니다",
"privacy_policy": "개인정보취급방침", "privacy_policy": "개인정보취급방침",
"enable_protection": "보호 활성화", "enable_protection": "보호 활성화",
"enabled_protection": "보호 활성화됨", "enabled_protection": "보호 활성화됨",
@@ -154,7 +154,7 @@
"use_adguard_parental": "AdGuard 자녀 보호 웹 서비스 사용", "use_adguard_parental": "AdGuard 자녀 보호 웹 서비스 사용",
"use_adguard_parental_hint": "AdGuard Home은 도메인에 성인 자료가 포함되어 있는지 확인합니다. 브라우징 보안 웹 서비스와 동일한 개인정보 보호 API를 사용함.", "use_adguard_parental_hint": "AdGuard Home은 도메인에 성인 자료가 포함되어 있는지 확인합니다. 브라우징 보안 웹 서비스와 동일한 개인정보 보호 API를 사용함.",
"enforce_safe_search": "세이프서치 사용", "enforce_safe_search": "세이프서치 사용",
"enforce_save_search_hint": "AdGuard Home은 Google, YouTube, Bing, DuckDuckGo, Ecosia, Yandex, Pixabay와 같은 검색 엔진에서 세이프서치를 시행합니다.", "enforce_save_search_hint": "AdGuard Home은 Google, YouTube, Bing, DuckDuckGo, Yandex, Pixabay와 같은 검색 엔진에서 세이프서치를 시행합니다.",
"no_servers_specified": "지정된 서버 없음", "no_servers_specified": "지정된 서버 없음",
"general_settings": "일반 설정", "general_settings": "일반 설정",
"dns_settings": "DNS 설정", "dns_settings": "DNS 설정",

View File

@@ -6,7 +6,7 @@
"upstream_parallel": "Parallelle verzoeken gebruiken om te versnellen door gelijktijdig verzoeken te sturen naar alle upstream servers.", "upstream_parallel": "Parallelle verzoeken gebruiken om te versnellen door gelijktijdig verzoeken te sturen naar alle upstream servers.",
"parallel_requests": "Parallelle verzoeken", "parallel_requests": "Parallelle verzoeken",
"load_balancing": "Volume balanceren", "load_balancing": "Volume balanceren",
"load_balancing_desc": "Voer zoekopdrachten uit op één upstream-server tegelijk. AdGuard Home gebruikt een gewogen willekeurig algoritme om servers te selecteren met het laagste aantal mislukte zoekopdrachten en de laagste gemiddelde opzoektijd.", "load_balancing_desc": "Eén server per keer bevragen. AdGuard Home gebruikt hiervoor een gewogen willekeurig algoritme om de server te kiezen zodat de snelste server meer zal gebruikt worden.",
"bootstrap_dns": "Bootstrap DNS-servers", "bootstrap_dns": "Bootstrap DNS-servers",
"bootstrap_dns_desc": "IP-adressen van DNS-servers die worden gebruikt om IP-adressen om te zetten van de DoH/DoT-resolvers die je opgeeft als upstreams. Opmerkingen zijn niet toegestaan.", "bootstrap_dns_desc": "IP-adressen van DNS-servers die worden gebruikt om IP-adressen om te zetten van de DoH/DoT-resolvers die je opgeeft als upstreams. Opmerkingen zijn niet toegestaan.",
"fallback_dns_title": "Back-up DNS-servers", "fallback_dns_title": "Back-up DNS-servers",
@@ -154,7 +154,7 @@
"use_adguard_parental": "Gebruik AdGuard Ouderlijk toezicht web service", "use_adguard_parental": "Gebruik AdGuard Ouderlijk toezicht web service",
"use_adguard_parental_hint": "AdGuard Home controleert of het domein 18+ content bevat. Dit gebeurt dmv dezelfde privacy vriendelijke API als de Browsing Security web service.", "use_adguard_parental_hint": "AdGuard Home controleert of het domein 18+ content bevat. Dit gebeurt dmv dezelfde privacy vriendelijke API als de Browsing Security web service.",
"enforce_safe_search": "Veilig zoeken gebruiken", "enforce_safe_search": "Veilig zoeken gebruiken",
"enforce_save_search_hint": "AdGuard Home dwingt veilig zoeken af in de volgende zoekmachines: Google, YouTube, Bing, DuckDuckGo, Ecosia, Yandex, Pixabay.", "enforce_save_search_hint": "AdGuard Home kan veilig zoeken forceren voor de volgende zoekmachines: Google, Youtube, Bing, en DuckDuckGo, Yandex, Pixabay.",
"no_servers_specified": "Geen servers gespecificeerd", "no_servers_specified": "Geen servers gespecificeerd",
"general_settings": "Algemene instellingen", "general_settings": "Algemene instellingen",
"dns_settings": "DNS instellingen", "dns_settings": "DNS instellingen",
@@ -166,7 +166,7 @@
"encryption_settings": "Encryptie instellingen", "encryption_settings": "Encryptie instellingen",
"dhcp_settings": "DHCP instellingen", "dhcp_settings": "DHCP instellingen",
"upstream_dns": "Upstream DNS-servers", "upstream_dns": "Upstream DNS-servers",
"upstream_dns_help": "Een server-adres per regel invoeren. <a>Meer informatie</a> over het configureren van upstream DNS-servers.", "upstream_dns_help": "Een server-adres per regel invoeren. <a>Meer weten</a> over het configureren van upstream DNS-servers.",
"upstream_dns_configured_in_file": "Geconfigureerd in {{path}}", "upstream_dns_configured_in_file": "Geconfigureerd in {{path}}",
"test_upstream_btn": "Test upstream", "test_upstream_btn": "Test upstream",
"upstreams": "Upstreams", "upstreams": "Upstreams",
@@ -495,7 +495,7 @@
"setup_dns_privacy_2": "<0>DNS-via-HTTPS:</0> Gebruik <1>{{address}}</1> string.", "setup_dns_privacy_2": "<0>DNS-via-HTTPS:</0> Gebruik <1>{{address}}</1> string.",
"setup_dns_privacy_3": "<0>Hou er rekening mee dat het beveiligde DNS protocol alleen beschikbaar is voor Android 9. U moet dus extra software installeren voor andere besturingssystemen.</0><0>Hier is een lijst van te gebruiken software.</0>", "setup_dns_privacy_3": "<0>Hou er rekening mee dat het beveiligde DNS protocol alleen beschikbaar is voor Android 9. U moet dus extra software installeren voor andere besturingssystemen.</0><0>Hier is een lijst van te gebruiken software.</0>",
"setup_dns_privacy_4": "Op een iOS 14 of macOS Big Sur apparaat kan je een speciaal '.mobileconfig'-bestand downloaden dat <highlight>DNS-via-HTTPS</highlight> of <highlight>DNS-via-TLS</highlight> servers aan de DNS-instellingen toevoegt.", "setup_dns_privacy_4": "Op een iOS 14 of macOS Big Sur apparaat kan je een speciaal '.mobileconfig'-bestand downloaden dat <highlight>DNS-via-HTTPS</highlight> of <highlight>DNS-via-TLS</highlight> servers aan de DNS-instellingen toevoegt.",
"setup_dns_privacy_android_1": "Android 9 ondersteunt native DNS-via-TLS. Om het te configureren, ga naar Instellingen → Netwerk & internet → Geavanceerd → Privé-DNS en voer daar je domeinnaam in.", "setup_dns_privacy_android_1": "Android 9 ondersteunt native DNS-via-TLS. Om het te configureren, ga naar Instellingen → Netwerk & internet → Geavanceerd → Privé DNS en voer daar je domeinnaam in.",
"setup_dns_privacy_android_2": "<0>AdGuard voor Android</0>ondersteunt<1>DNS-via-HTTPS </1>en<1>DNS-via-TLS</1>.", "setup_dns_privacy_android_2": "<0>AdGuard voor Android</0>ondersteunt<1>DNS-via-HTTPS </1>en<1>DNS-via-TLS</1>.",
"setup_dns_privacy_android_3": "<0> Intra </0> voegt <1> DNS-via-HTTPS</1> ondersteuning toe aan Android.", "setup_dns_privacy_android_3": "<0> Intra </0> voegt <1> DNS-via-HTTPS</1> ondersteuning toe aan Android.",
"setup_dns_privacy_ios_1": "<0>DNSCloak</0> ondersteunt <1> DNS-via-HTTPS </1>, maar om het te configureren op jouw eigen server moet er een <2> DNS-stempel </2> gegenereerd worden.", "setup_dns_privacy_ios_1": "<0>DNSCloak</0> ondersteunt <1> DNS-via-HTTPS </1>, maar om het te configureren op jouw eigen server moet er een <2> DNS-stempel </2> gegenereerd worden.",

View File

@@ -128,7 +128,6 @@
"enforced_save_search": "Påtvungede barnevennlige søk", "enforced_save_search": "Påtvungede barnevennlige søk",
"number_of_dns_query_to_safe_search": "Antall DNS-forespørsler til søkemotorer der \"Safe Search\" ble fremtvunget", "number_of_dns_query_to_safe_search": "Antall DNS-forespørsler til søkemotorer der \"Safe Search\" ble fremtvunget",
"average_processing_time": "Gjennomsnittlig behandlingstid", "average_processing_time": "Gjennomsnittlig behandlingstid",
"response_time": "Responstid",
"average_processing_time_hint": "Gjennomsnittstid for behandling av DNS-forespørsler i millisekunder", "average_processing_time_hint": "Gjennomsnittstid for behandling av DNS-forespørsler i millisekunder",
"block_domain_use_filters_and_hosts": "Blokker domener ved hjelp av filtre, «hosts»-filer, og rå domener", "block_domain_use_filters_and_hosts": "Blokker domener ved hjelp av filtre, «hosts»-filer, og rå domener",
"filters_block_toggle_hint": "Du kan sette opp blokkeringsoppføringer i <a>Filtre</a>-innstillingene.", "filters_block_toggle_hint": "Du kan sette opp blokkeringsoppføringer i <a>Filtre</a>-innstillingene.",

View File

@@ -6,7 +6,7 @@
"upstream_parallel": "Użyj zapytań równoległych, aby przyspieszyć rozwiązywanie przez jednoczesne wysyłanie zapytań do wszystkich serwerów nadrzędnych.", "upstream_parallel": "Użyj zapytań równoległych, aby przyspieszyć rozwiązywanie przez jednoczesne wysyłanie zapytań do wszystkich serwerów nadrzędnych.",
"parallel_requests": "Równoległe żądania", "parallel_requests": "Równoległe żądania",
"load_balancing": "Równoważenie obciążenia", "load_balancing": "Równoważenie obciążenia",
"load_balancing_desc": "Zapytaj jeden serwer nadrzędny na raz. AdGuard Home używa ważonego, losowego algorytmu do wybierania serwerów z najmniejszą liczbą nieudanych wyszukiwań i najniższym uśrednionym czasem wyszukiwania.", "load_balancing_desc": "Wysyłaj zapytania do jednego serwera nadrzędnego na raz. AdGuard Home używa swojego losowego algorytmu ważonego, aby wybrać serwer, tak aby najszybszy serwer był używany częściej.",
"bootstrap_dns": "Serwery DNS Bootstrap", "bootstrap_dns": "Serwery DNS Bootstrap",
"bootstrap_dns_desc": "Adresy IP serwerów DNS używanych do rozpoznawania adresów IP programów rozpoznawania nazw DoH/DoT określonych jako nadrzędne. Komentarze są niedozwolone.", "bootstrap_dns_desc": "Adresy IP serwerów DNS używanych do rozpoznawania adresów IP programów rozpoznawania nazw DoH/DoT określonych jako nadrzędne. Komentarze są niedozwolone.",
"fallback_dns_title": "Rezerwowe serwery DNS", "fallback_dns_title": "Rezerwowe serwery DNS",
@@ -122,7 +122,7 @@
"stats_query_domain": "Najczęściej wyszukiwane domeny", "stats_query_domain": "Najczęściej wyszukiwane domeny",
"for_last_hours": "w ciągu ostatniej {{count}} godziny", "for_last_hours": "w ciągu ostatniej {{count}} godziny",
"for_last_hours_plural": "w ciągu ostatnich {{count}} godzin", "for_last_hours_plural": "w ciągu ostatnich {{count}} godzin",
"for_last_days": "za ostatni {{count}} dzień", "for_last_days": "za ostatni dzień {{count}}",
"for_last_days_plural": "z ostatnich {{count}} dni", "for_last_days_plural": "z ostatnich {{count}} dni",
"stats_disabled": "Statystyki zostały wyłączone. Można je włączyć na <0>stronie ustawień</0>.", "stats_disabled": "Statystyki zostały wyłączone. Można je włączyć na <0>stronie ustawień</0>.",
"stats_disabled_short": "Statystyki zostały wyłączone", "stats_disabled_short": "Statystyki zostały wyłączone",
@@ -130,7 +130,7 @@
"requests_count": "Licznik żądań", "requests_count": "Licznik żądań",
"top_blocked_domains": "Najpopularniejsze zablokowane domeny", "top_blocked_domains": "Najpopularniejsze zablokowane domeny",
"top_clients": "Główni klienci", "top_clients": "Główni klienci",
"no_clients_found": "Nie znaleziono klientów", "no_clients_found": "Nie znaleziono klienta",
"general_statistics": "Ogólne statystyki", "general_statistics": "Ogólne statystyki",
"top_upstreams": "Często żądane serwery nadrzędne", "top_upstreams": "Często żądane serwery nadrzędne",
"no_upstreams_data_found": "Brak danych dotyczących serwerów nadrzędnych", "no_upstreams_data_found": "Brak danych dotyczących serwerów nadrzędnych",
@@ -154,7 +154,7 @@
"use_adguard_parental": "Użyj usługi Kontrola Rodzicielska AdGuard", "use_adguard_parental": "Użyj usługi Kontrola Rodzicielska AdGuard",
"use_adguard_parental_hint": "AdGuard Home sprawdzi, czy domena zawiera materiały dla dorosłych. Używa tego samego interfejsu API przyjaznego prywatności, co usługa sieciowa Bezpieczne Przeglądanie. ", "use_adguard_parental_hint": "AdGuard Home sprawdzi, czy domena zawiera materiały dla dorosłych. Używa tego samego interfejsu API przyjaznego prywatności, co usługa sieciowa Bezpieczne Przeglądanie. ",
"enforce_safe_search": "Użyj bezpiecznego wyszukiwania", "enforce_safe_search": "Użyj bezpiecznego wyszukiwania",
"enforce_save_search_hint": "AdGuard Home wymusza bezpieczne wyszukiwanie w następujących wyszukiwarkach: Google, YouTube, Bing, DuckDuckGo, Ecosia, Yandex, Pixabay.", "enforce_save_search_hint": "AdGuard Home wymusza bezpieczne wyszukiwanie w następujących wyszukiwarkach: Google, YouTube, Bing, DuckDuckGo, Yandex, Pixabay.",
"no_servers_specified": "Nie określono serwerów", "no_servers_specified": "Nie określono serwerów",
"general_settings": "Ustawienia główne", "general_settings": "Ustawienia główne",
"dns_settings": "Ustawienia DNS", "dns_settings": "Ustawienia DNS",

View File

@@ -6,7 +6,7 @@
"upstream_parallel": "Usar consultas paralelas para acelerar a resolução consultando simultaneamente todos os servidores DNS primário", "upstream_parallel": "Usar consultas paralelas para acelerar a resolução consultando simultaneamente todos os servidores DNS primário",
"parallel_requests": "Solicitações paralelas", "parallel_requests": "Solicitações paralelas",
"load_balancing": "Balanceamento de carga", "load_balancing": "Balanceamento de carga",
"load_balancing_desc": "Consulte um servidor upstream por vez. O AdGuard Home usa um algoritmo aleatório ponderado para selecionar servidores com o menor número de falhas e o menor tempo médio de consulta.", "load_balancing_desc": "Consulte um servidor DNS primário por vez. O AdGuard Home usa seu algoritmo aleatório ponderado para escolher o servidor para que o servidor mais rápido seja usado com mais frequência.",
"bootstrap_dns": "Servidores DNS de inicialização", "bootstrap_dns": "Servidores DNS de inicialização",
"bootstrap_dns_desc": "Endereços IP de servidores DNS usados para resolver endereços IP dos resolvedores DoH/DoT que você especifica como upstreams. Comentários não são permitidos.", "bootstrap_dns_desc": "Endereços IP de servidores DNS usados para resolver endereços IP dos resolvedores DoH/DoT que você especifica como upstreams. Comentários não são permitidos.",
"fallback_dns_title": "Servidores DNS Fallback", "fallback_dns_title": "Servidores DNS Fallback",
@@ -154,7 +154,7 @@
"use_adguard_parental": "Usar o serviço de controle parental do AdGuard", "use_adguard_parental": "Usar o serviço de controle parental do AdGuard",
"use_adguard_parental_hint": "O AdGuard Home irá verificar se o domínio contém conteúdo adulto. Ele usa a mesma API amigável de privacidade que o serviço de segurança da navegação.", "use_adguard_parental_hint": "O AdGuard Home irá verificar se o domínio contém conteúdo adulto. Ele usa a mesma API amigável de privacidade que o serviço de segurança da navegação.",
"enforce_safe_search": "Usar pesquisa segura", "enforce_safe_search": "Usar pesquisa segura",
"enforce_save_search_hint": "O AdGuard Home forcará a pesquisa segura nos seguintes motores de busca: Google, YouTube, Bing, DuckDuckGo, Ecosia, Yandex, Pixabay.", "enforce_save_search_hint": "O AdGuard Home forcará a pesquisa segura nos seguintes motores de busca: Google, YouTube, Bing, DuckDuckGo, Yandex, Pixabay.",
"no_servers_specified": "Nenhum servidor especificado", "no_servers_specified": "Nenhum servidor especificado",
"general_settings": "Configurações gerais", "general_settings": "Configurações gerais",
"dns_settings": "Configurações de DNS", "dns_settings": "Configurações de DNS",

View File

@@ -6,7 +6,7 @@
"upstream_parallel": "Usar consultas paralelas para acelerar a resolução consultando simultaneamente todos os servidores DNS", "upstream_parallel": "Usar consultas paralelas para acelerar a resolução consultando simultaneamente todos os servidores DNS",
"parallel_requests": "Solicitações paralelas", "parallel_requests": "Solicitações paralelas",
"load_balancing": "Balanceamento de carga", "load_balancing": "Balanceamento de carga",
"load_balancing_desc": "Consulta um servidor a montante de cada vez. O AdGuard Home usa um algoritmo aleatório ponderado para selecionar servidores com o menor número de pesquisas com falha e o menor tempo médio de pesquisa.", "load_balancing_desc": "Consulte um servidor DNS primário por vez. O AdGuard Home usa seu algoritmo aleatório ponderado para escolher o servidor para que o servidor mais rápido seja usado com mais frequência.",
"bootstrap_dns": "Servidores DNS de arranque", "bootstrap_dns": "Servidores DNS de arranque",
"bootstrap_dns_desc": "Endereços IP de servidores DNS usados para resolver endereços IP dos resolvedores DoH/DoT que você especifica como upstreams. Comentários não são permitidos.", "bootstrap_dns_desc": "Endereços IP de servidores DNS usados para resolver endereços IP dos resolvedores DoH/DoT que você especifica como upstreams. Comentários não são permitidos.",
"fallback_dns_title": "Servidores DNS de fallback", "fallback_dns_title": "Servidores DNS de fallback",
@@ -154,7 +154,7 @@
"use_adguard_parental": "Usar o serviço de controlo parental do AdGuard", "use_adguard_parental": "Usar o serviço de controlo parental do AdGuard",
"use_adguard_parental_hint": "O AdGuard Home irá verificar se o domínio contém conteúdo adulto. Usa a mesma API amigável de privacidade que o serviço de segurança da navegação.", "use_adguard_parental_hint": "O AdGuard Home irá verificar se o domínio contém conteúdo adulto. Usa a mesma API amigável de privacidade que o serviço de segurança da navegação.",
"enforce_safe_search": "Usar pesquisa segura", "enforce_safe_search": "Usar pesquisa segura",
"enforce_save_search_hint": "O AdGuard Home aplicará pesquisa segura nos seguintes motores de busca: Google, YouTube, Bing, DuckDuckGo, Ecosia, Yandex, Pixabay.", "enforce_save_search_hint": "O AdGuard Home forçará a pesquisa segura nos seguintes motores de busca: Google, Youtube, Bing, DuckDuckGo, Yandex, Pixabay.",
"no_servers_specified": "Nenhum servidor especificado", "no_servers_specified": "Nenhum servidor especificado",
"general_settings": "Definições gerais", "general_settings": "Definições gerais",
"dns_settings": "Definições de DNS", "dns_settings": "Definições de DNS",

View File

@@ -6,7 +6,7 @@
"upstream_parallel": "Использовать параллельные запросы ко всем серверам одновременно для ускорения обработки запроса.", "upstream_parallel": "Использовать параллельные запросы ко всем серверам одновременно для ускорения обработки запроса.",
"parallel_requests": "Параллельные запросы", "parallel_requests": "Параллельные запросы",
"load_balancing": "Распределение нагрузки\n", "load_balancing": "Распределение нагрузки\n",
"load_balancing_desc": "Запрашивайте по одному серверу за раз. AdGuard Home использует алгоритм случайной выборки с учётом веса для выбора серверов с наименьшим количеством неудачных запросов и наименьшим средним временем выполнения запроса.", "load_balancing_desc": "Запрашивать по одному серверу за раз. AdGuard Home использует алгоритм взвешенного случайного выбора сервера, так что самый быстрый сервер используется чаще.",
"bootstrap_dns": "Bootstrap DNS-серверы", "bootstrap_dns": "Bootstrap DNS-серверы",
"bootstrap_dns_desc": "IP-адреса DNS-серверов, используемых для поиска IP-адресов DoH/DoT upstream-серверов, которые вы указали. Комментарии не допускаются.", "bootstrap_dns_desc": "IP-адреса DNS-серверов, используемых для поиска IP-адресов DoH/DoT upstream-серверов, которые вы указали. Комментарии не допускаются.",
"fallback_dns_title": "Резервные DNS-серверы", "fallback_dns_title": "Резервные DNS-серверы",
@@ -154,7 +154,7 @@
"use_adguard_parental": "Включить модуль Родительского контроля AdGuard ", "use_adguard_parental": "Включить модуль Родительского контроля AdGuard ",
"use_adguard_parental_hint": "AdGuard Home проверит, содержит ли домен материалы 18+. Он использует тот же API для обеспечения конфиденциальности, что и веб-служба безопасности браузера.", "use_adguard_parental_hint": "AdGuard Home проверит, содержит ли домен материалы 18+. Он использует тот же API для обеспечения конфиденциальности, что и веб-служба безопасности браузера.",
"enforce_safe_search": "Включить безопасный поиск", "enforce_safe_search": "Включить безопасный поиск",
"enforce_save_search_hint": "AdGuard Home будет обеспечивать безопасный поиск в следующих поисковых системах: Google, YouTube, Bing, DuckDuckGo, Ecosia, Yandex, Pixabay.", "enforce_save_search_hint": "AdGuard Home может обеспечить безопасный поиск в следующих поисковых системах: Google, YouTube, Bing, DuckDuckGo, Yandex и Pixabay.",
"no_servers_specified": "Нет указанных серверов", "no_servers_specified": "Нет указанных серверов",
"general_settings": "Основные настройки", "general_settings": "Основные настройки",
"dns_settings": "Настройки DNS", "dns_settings": "Настройки DNS",

View File

@@ -7,7 +7,7 @@
"local_ptr_desc": "ස්ථානීය PTR විමසුම් සඳහා ඇඩ්ගාර්ඩ් හෝම් භාවිතා කරන ව.නා.ප. සේවාදායක. මෙම සේවාදායක පුද්ගලික අ.ජා.කෙ. ලිපින පරාසවල PTR විමසුම් විසඳීමට භාවිතා කරයි, උදාහරණයක් ලෙස ප්‍රතිවර්ත ව.නා.ප. භාවිතයෙන් \"192.168.12.34\". මෙය සකසා නැති නම්, ඇඩ්ගාර්ඩ් හෝම් හි ලිපින සඳහා හැරුනු විට ඔබගේ මෙහෙයුම් පද්ධතියේ පෙරනිමි ව.නා.ප. විසදුම්වල ලිපින භාවිතා කරයි.", "local_ptr_desc": "ස්ථානීය PTR විමසුම් සඳහා ඇඩ්ගාර්ඩ් හෝම් භාවිතා කරන ව.නා.ප. සේවාදායක. මෙම සේවාදායක පුද්ගලික අ.ජා.කෙ. ලිපින පරාසවල PTR විමසුම් විසඳීමට භාවිතා කරයි, උදාහරණයක් ලෙස ප්‍රතිවර්ත ව.නා.ප. භාවිතයෙන් \"192.168.12.34\". මෙය සකසා නැති නම්, ඇඩ්ගාර්ඩ් හෝම් හි ලිපින සඳහා හැරුනු විට ඔබගේ මෙහෙයුම් පද්ධතියේ පෙරනිමි ව.නා.ප. විසදුම්වල ලිපින භාවිතා කරයි.",
"local_ptr_default_resolver": "පෙරනිමි පරිදි, ඇඩ්ගාර්ඩ් හෝම් පහත ප්‍රතිවර්ත ව.නා.ප. පිළිවිසඳු භාවිතා කරයි: {{ip}}.", "local_ptr_default_resolver": "පෙරනිමි පරිදි, ඇඩ්ගාර්ඩ් හෝම් පහත ප්‍රතිවර්ත ව.නා.ප. පිළිවිසඳු භාවිතා කරයි: {{ip}}.",
"local_ptr_no_default_resolver": "ඇඩ්ගාර්ඩ් හෝම් හට මෙම පද්ධතිය සඳහා සුදුසු පුද්ගලික ප්‍රතිවර්ත ව.නා.ප. පිළිවිසඳු නිශ්චය කරගත නොහැකි විය.", "local_ptr_no_default_resolver": "ඇඩ්ගාර්ඩ් හෝම් හට මෙම පද්ධතිය සඳහා සුදුසු පුද්ගලික ප්‍රතිවර්ත ව.නා.ප. පිළිවිසඳු නිශ්චය කරගත නොහැකි විය.",
"local_ptr_placeholder": "පේළියකට අ.ජා.කෙ. ලිපිනය බැගින් ලියන්න", "local_ptr_placeholder": "පේළියකට එක් සේවාදායක ලිපිනය බැගින් යොදන්න",
"resolve_clients_title": "අනුග්‍රාහකවල අ.ජා.කෙ. ලිපින ප්‍රතිවර්ත විසඳීම සබල කරන්න", "resolve_clients_title": "අනුග්‍රාහකවල අ.ජා.කෙ. ලිපින ප්‍රතිවර්ත විසඳීම සබල කරන්න",
"use_private_ptr_resolvers_title": "පෞද්. ප්‍රතිවර්ත ව.නා.ප. පිළිවිසඳු භාවිතය", "use_private_ptr_resolvers_title": "පෞද්. ප්‍රතිවර්ත ව.නා.ප. පිළිවිසඳු භාවිතය",
"check_dhcp_servers": "ග.ධා.වි.කෙ. සේවාදායක පරීක්‍ෂා කරන්න", "check_dhcp_servers": "ග.ධා.වි.කෙ. සේවාදායක පරීක්‍ෂා කරන්න",
@@ -102,6 +102,7 @@
"stats_malware_phishing": "අවහිර කළ ද්වේශාංග/තතුබෑම්", "stats_malware_phishing": "අවහිර කළ ද්වේශාංග/තතුබෑම්",
"stats_adult": "අවහිර කළ වැඩිහිටි වියමන අඩවි", "stats_adult": "අවහිර කළ වැඩිහිටි වියමන අඩවි",
"stats_query_domain": "ප්‍රචලිත විමසන ලද වසම්", "stats_query_domain": "ප්‍රචලිත විමසන ලද වසම්",
"for_last_24_hours": "පසුගිය පැය 24 සඳහා",
"for_last_days": "පසුගිය දවස් {{count}} සඳහා", "for_last_days": "පසුගිය දවස් {{count}} සඳහා",
"for_last_days_plural": "පසුගිය දවස් {{count}} සඳහා", "for_last_days_plural": "පසුගිය දවස් {{count}} සඳහා",
"stats_disabled": "සංඛ්‍යාලේඛන අබල කර ඇත. එය <0>සැකසුම් පිටුවෙන්</0> සබල කළ හැකිය.", "stats_disabled": "සංඛ්‍යාලේඛන අබල කර ඇත. එය <0>සැකසුම් පිටුවෙන්</0> සබල කළ හැකිය.",
@@ -114,15 +115,13 @@
"general_statistics": "පොදු සංඛ්‍යාලේඛන", "general_statistics": "පොදු සංඛ්‍යාලේඛන",
"number_of_dns_query_days": "පසුගිය දවස් {{count}} සඳහා සැකසූ ව.නා.ප. විමසුම් ගණන", "number_of_dns_query_days": "පසුගිය දවස් {{count}} සඳහා සැකසූ ව.නා.ප. විමසුම් ගණන",
"number_of_dns_query_days_plural": "පසුගිය දවස් {{count}} සඳහා සැකසූ ව.නා.ප. විමසුම් ගණන", "number_of_dns_query_days_plural": "පසුගිය දවස් {{count}} සඳහා සැකසූ ව.නා.ප. විමසුම් ගණන",
"number_of_dns_query_hours": "පසුගිය පැය {{count}} සඳහා සැකසූ ව.නා.ප. විමසුම් ගණන", "number_of_dns_query_24_hours": "පසුගිය පැය 24 සඳහා සැකසූ ව.නා.ප. විමසුම් ගණන",
"number_of_dns_query_hours_plural": "පසුගිය පැය {{count}} සඳහා සැකසූ ව.නා.ප. විමසුම් ගණන",
"number_of_dns_query_blocked_24_hours": "දැන්වීම් වාරණ පෙරහන් සහ සත්කාරක වාරණ ලැයිස්තු මගින් අවහිර කළ ව.නා.ප. ඉල්ලීම් ගණන", "number_of_dns_query_blocked_24_hours": "දැන්වීම් වාරණ පෙරහන් සහ සත්කාරක වාරණ ලැයිස්තු මගින් අවහිර කළ ව.නා.ප. ඉල්ලීම් ගණන",
"number_of_dns_query_blocked_24_hours_by_sec": "ඇඩ්ගාර්ඩ් පිරික්සුම් ආරක්‍ෂණ ඒකකය මගින් අවහිර කළ ව.නා.ප. ඉල්ලීම් ගණන", "number_of_dns_query_blocked_24_hours_by_sec": "ඇඩ්ගාර්ඩ් පිරික්සුම් ආරක්‍ෂණ ඒකකය මගින් අවහිර කළ ව.නා.ප. ඉල්ලීම් ගණන",
"number_of_dns_query_blocked_24_hours_adult": "අවහිර කළ වැඩිහිටි වියමන අඩවි ගණන", "number_of_dns_query_blocked_24_hours_adult": "අවහිර කළ වැඩිහිටි වියමන අඩවි ගණන",
"enforced_save_search": "ආරක්‍ෂිත සෙවීම බලාත්මක කළ", "enforced_save_search": "ආරක්‍ෂිත සෙවීම බලාත්මක කළ",
"number_of_dns_query_to_safe_search": "ආරක්‍ෂිත සෙවීම බලාත්මක කළ සෙවුම් යන්ත්‍ර සඳහා ව.නා.ප. ඉල්ලීම් ගණන", "number_of_dns_query_to_safe_search": "ආරක්‍ෂිත සෙවීම බලාත්මක කළ සෙවුම් යන්ත්‍ර සඳහා ව.නා.ප. ඉල්ලීම් ගණන",
"average_processing_time": "සාමාන්‍ය සැකසුම් කාලය", "average_processing_time": "සාමාන්‍ය සැකසුම් කාලය",
"response_time": "ප්‍රතිචාර කාලය",
"average_processing_time_hint": "ව.නා.ප. ඉල්ලීමක් සැකසීමේ සාමාන්‍ය කාලය මිලි තත්පර වලින්", "average_processing_time_hint": "ව.නා.ප. ඉල්ලීමක් සැකසීමේ සාමාන්‍ය කාලය මිලි තත්පර වලින්",
"block_domain_use_filters_and_hosts": "පෙරහන් හා සත්කාරක ගොනු භාවිතයෙන් වසම් අවහිර කරන්න", "block_domain_use_filters_and_hosts": "පෙරහන් හා සත්කාරක ගොනු භාවිතයෙන් වසම් අවහිර කරන්න",
"filters_block_toggle_hint": "ඔබට අවහිර කිරීමේ නීති <a>පෙරහන්</a> තුළ පිහිටුවිය හැකිය.", "filters_block_toggle_hint": "ඔබට අවහිර කිරීමේ නීති <a>පෙරහන්</a> තුළ පිහිටුවිය හැකිය.",
@@ -131,7 +130,7 @@
"use_adguard_parental": "ඇඩ්ගාර්ඩ් දෙමාපිය පාලන වියමන සේවාව භාවිතා කරන්න", "use_adguard_parental": "ඇඩ්ගාර්ඩ් දෙමාපිය පාලන වියමන සේවාව භාවිතා කරන්න",
"use_adguard_parental_hint": "වසමේ වැඩිහිටියන්ට අදාල කරුණු අඩංගු දැයි ඇඩ්ගාර්ඩ් හෝම් විසින් පරීක්‍ෂා කරනු ඇත. එය පිරික්සුම් ආරක්‍ෂණ වියමන සේවාව මෙන් රහස්‍යතා හිතකාමී යෙ.ක්‍ර. අ.මු. (API) භාවිතා කරයි.", "use_adguard_parental_hint": "වසමේ වැඩිහිටියන්ට අදාල කරුණු අඩංගු දැයි ඇඩ්ගාර්ඩ් හෝම් විසින් පරීක්‍ෂා කරනු ඇත. එය පිරික්සුම් ආරක්‍ෂණ වියමන සේවාව මෙන් රහස්‍යතා හිතකාමී යෙ.ක්‍ර. අ.මු. (API) භාවිතා කරයි.",
"enforce_safe_search": "ආරක්‍ෂිත සෙවුම භාවිතා කරන්න", "enforce_safe_search": "ආරක්‍ෂිත සෙවුම භාවිතා කරන්න",
"enforce_save_search_hint": "ඇඩ්ගාර්ඩ් හෝම් පහත සෙවුම් යන්ත්‍ර තුළ ආරක්‍ෂිත සෙවුම බලාත්මක කරනු ඇත: ගූගල්, යූටියුබ්, බින්ග්, ඩක්ඩක්ගෝ, එකොසියා, යාන්ඩෙක්ස් සහ පික්සාබේ.", "enforce_save_search_hint": "ඇඩ්ගාර්ඩ් හෝම් පහත සෙවුම් යන්ත්‍ර තුළ ආරක්‍ෂිත සෙවුම බලාත්මක කරනු ඇත: ගූගල්, යූටියුබ්, බින්ග්, ඩක්ඩක්ගෝ, යාන්ඩෙක්ස් සහ පික්සාබේ.",
"no_servers_specified": "සේවාදායක කිසිවක් නිශ්චිතව දක්වා නැත", "no_servers_specified": "සේවාදායක කිසිවක් නිශ්චිතව දක්වා නැත",
"general_settings": "පොදු සැකසුම්", "general_settings": "පොදු සැකසුම්",
"dns_settings": "ව.නා.ප. සැකසුම්", "dns_settings": "ව.නා.ප. සැකසුම්",
@@ -197,14 +196,12 @@
"example_comment_hash": "# එසේම අදහස් දැක්වීමක්.", "example_comment_hash": "# එසේම අදහස් දැක්වීමක්.",
"example_regex_meaning": "නිශ්චිතව දක්වා ඇති නිත්‍ය වාක්‍යවිධියට ගැළපෙන වසම් වෙත ප්‍රවේශය අවහිර කරයි.", "example_regex_meaning": "නිශ්චිතව දක්වා ඇති නිත්‍ය වාක්‍යවිධියට ගැළපෙන වසම් වෙත ප්‍රවේශය අවහිර කරයි.",
"example_upstream_regular": "සාමාන්‍ය ව.නා.ප. (UDP හරහා);", "example_upstream_regular": "සාමාන්‍ය ව.නා.ප. (UDP හරහා);",
"example_upstream_regular_port": "සාමාන්‍ය ව.නා.ප. (UDP හරහා, තොට සමඟ);",
"example_upstream_udp": "සාමාන්‍ය ව.නා.ප. (UDP, සත්කාරක-නම හරහා);", "example_upstream_udp": "සාමාන්‍ය ව.නා.ප. (UDP, සත්කාරක-නම හරහා);",
"example_upstream_dot": "සංකේතිත <0>TLS-මගින්-ව.නා.ප.</0>;", "example_upstream_dot": "සංකේතිත <0>TLS-මගින්-ව.නා.ප.</0>;",
"example_upstream_doh": "සංකේතිත <0>HTTPS-මගින්-ව.නා.ප.</0>;", "example_upstream_doh": "සංකේතිත <0>HTTPS-මගින්-ව.නා.ප.</0>;",
"example_upstream_doq": "සංකේතිත <0>QUIC-මගින්-ව.නා.ප.</0>;", "example_upstream_doq": "සංකේතිත <0>QUIC-මගින්-ව.නා.ප.</0>;",
"example_upstream_sdns": "<1>DNSCrypt</1> හෝ <2>HTTPS-මගින්-ව.නා.ප.</2> පිළිවිසඳු සඳහා <0>ව.නා.ප. මුද්දර</0>;", "example_upstream_sdns": "<1>DNSCrypt</1> හෝ <2>HTTPS-මගින්-ව.නා.ප.</2> පිළිවිසඳු සඳහා <0>ව.නා.ප. මුද්දර</0>;",
"example_upstream_tcp": "සාමාන්‍ය ව.නා.ප. (TCP/ස.පා.කෙ. හරහා);", "example_upstream_tcp": "සාමාන්‍ය ව.නා.ප. (TCP/ස.පා.කෙ. හරහා);",
"example_upstream_tcp_port": "සාමාන්‍ය ව.නා.ප. (TCP හරහා, තොට සමඟ);",
"example_upstream_tcp_hostname": "සාමාන්‍ය ව.නා.ප. (ස.පා.කෙ., සත්කාරක-නම හරහා);", "example_upstream_tcp_hostname": "සාමාන්‍ය ව.නා.ප. (ස.පා.කෙ., සත්කාරක-නම හරහා);",
"all_lists_up_to_date_toast": "සියළුම ලැයිස්තු දැනටමත් යාවත්කාලීනයි", "all_lists_up_to_date_toast": "සියළුම ලැයිස්තු දැනටමත් යාවත්කාලීනයි",
"dns_test_ok_toast": "සඳහන් කළ ව.නා.ප. සේවාදායක නිවැරදිව ක්‍රියා කරයි", "dns_test_ok_toast": "සඳහන් කළ ව.නා.ප. සේවාදායක නිවැරදිව ක්‍රියා කරයි",
@@ -278,7 +275,6 @@
"edns_use_custom_ip": "EDNS සඳහා අභිරුචි අ.ජා.කෙ. යොදාගන්න", "edns_use_custom_ip": "EDNS සඳහා අභිරුචි අ.ජා.කෙ. යොදාගන්න",
"edns_use_custom_ip_desc": "EDNS සඳහා අභිරුචි අ.ජා.කෙ. භාවිතයට ඉඩදෙන්න", "edns_use_custom_ip_desc": "EDNS සඳහා අභිරුචි අ.ජා.කෙ. භාවිතයට ඉඩදෙන්න",
"rate_limit_desc": "එක් අනුග්‍රාහකයකට ඉඩ දී ඇති තත්පරයට ඉල්ලීම් ගණන. එය 0 ලෙස සැකසීම යනුවෙන් අදහස් කරන්නේ සීමාවක් නැති බවයි.", "rate_limit_desc": "එක් අනුග්‍රාහකයකට ඉඩ දී ඇති තත්පරයට ඉල්ලීම් ගණන. එය 0 ලෙස සැකසීම යනුවෙන් අදහස් කරන්නේ සීමාවක් නැති බවයි.",
"rate_limit_whitelist_placeholder": "පේළියකට අ.ජා.කෙ. ලිපිනය බැගින් ලියන්න",
"blocking_ipv4_desc": "අවහිර කළ A ඉල්ලීමක් සඳහා ආපසු එවිය යුතු අ.ජා.කෙ. (IP) ලිපිනය", "blocking_ipv4_desc": "අවහිර කළ A ඉල්ලීමක් සඳහා ආපසු එවිය යුතු අ.ජා.කෙ. (IP) ලිපිනය",
"blocking_ipv6_desc": "අවහිර කළ AAAA ඉල්ලීමක් සඳහා ආපසු එවිය යුතු අ.ජා.කෙ. (IP) ලිපිනය", "blocking_ipv6_desc": "අවහිර කළ AAAA ඉල්ලීමක් සඳහා ආපසු එවිය යුතු අ.ජා.කෙ. (IP) ලිපිනය",
"blocking_mode_default": "පොදු: දැන්වීම් අවහිර කරන ආකාරයේ නීතියක් මගින් අවහිර කළ විට REFUSED සමඟ ප්‍රතිචාර දක්වයි; /etc/host-style ආකාරයේ නීතියක් මගින් අවහිර කළ විට නීතියේ දක්වා ඇති අ.ජා.කෙ. ලිපිනය සමඟ ප්‍රතිචාර දක්වයි", "blocking_mode_default": "පොදු: දැන්වීම් අවහිර කරන ආකාරයේ නීතියක් මගින් අවහිර කළ විට REFUSED සමඟ ප්‍රතිචාර දක්වයි; /etc/host-style ආකාරයේ නීතියක් මගින් අවහිර කළ විට නීතියේ දක්වා ඇති අ.ජා.කෙ. ලිපිනය සමඟ ප්‍රතිචාර දක්වයි",
@@ -509,8 +505,8 @@
"statistics_enable": "සංඛ්‍යාලේඛන සබල කරන්න", "statistics_enable": "සංඛ්‍යාලේඛන සබල කරන්න",
"ignore_domains": "නොසලකන වසම් (පේළියකට එක බැගින්)", "ignore_domains": "නොසලකන වසම් (පේළියකට එක බැගින්)",
"ignore_domains_title": "නොසලකන වසම්", "ignore_domains_title": "නොසලකන වසම්",
"ignore_domains_desc_stats": "මෙම නීති වලට ගැළපෙන විමසුම් සංඛ්‍යාලේඛනයට නොලියැවෙයි", "ignore_domains_desc_stats": "සංඛ්‍යාලේඛනයෙහි මෙම වසම් සඳහා විමසුම් නොලියැවෙයි",
"ignore_domains_desc_query": "විමසුම් සටහන මෙම නීති වලට ගැළපෙන විමසුම් නොලියැවෙයි", "ignore_domains_desc_query": "විමසුම් සටහනෙහි මෙම වසම් සඳහා විමසුම් නොලියැවෙයි",
"interval_hours": "පැය {{count}}", "interval_hours": "පැය {{count}}",
"interval_hours_plural": "පැය {{count}}", "interval_hours_plural": "පැය {{count}}",
"filters_configuration": "පෙරහන් වින්‍යාසය", "filters_configuration": "පෙරහන් වින්‍යාසය",
@@ -619,8 +615,8 @@
"use_saved_key": "පෙර සුරැකි යතුර භාවිතා කරන්න", "use_saved_key": "පෙර සුරැකි යතුර භාවිතා කරන්න",
"parental_control": "දෙමාපිය පාලනය", "parental_control": "දෙමාපිය පාලනය",
"safe_browsing": "ආරක්‍ෂිත පිරික්සුම", "safe_browsing": "ආරක්‍ෂිත පිරික්සුම",
"served_from_cache_label": "නිහිතයෙන් සැපයිණි", "served_from_cache": "{{value}} <i>(නිහිතයෙන් ගැනිණි)</i>",
"form_error_password_length": "මුරපදය අකුරු {{min}} සහ {{value}} ක් අතර විය යුතු", "form_error_password_length": "මුරපදය අවම වශයෙන් අකුරු {{value}} ක් දිගු විය යුතුමයි",
"anonymizer_notification": "<0>සටහන:</0> අ.ජා.කෙ. නිර්නාමිකකරණය සබලයි. ඔබට එය <1>පොදු සැකසුම්</1> හරහා අබල කිරීමට හැකිය .", "anonymizer_notification": "<0>සටහන:</0> අ.ජා.කෙ. නිර්නාමිකකරණය සබලයි. ඔබට එය <1>පොදු සැකසුම්</1> හරහා අබල කිරීමට හැකිය .",
"confirm_dns_cache_clear": "ඔබට ව.නා.ප. නිහිතය හිස් කිරීමට වුවමනාද?", "confirm_dns_cache_clear": "ඔබට ව.නා.ප. නිහිතය හිස් කිරීමට වුවමනාද?",
"cache_cleared": "ව.නා.ප. නිහිතය හිස් කෙරිණි", "cache_cleared": "ව.නා.ප. නිහිතය හිස් කෙරිණි",
@@ -650,7 +646,6 @@
"log_and_stats_section_label": "විමසුම් සටහන හා සංඛ්‍යාලේඛන", "log_and_stats_section_label": "විමසුම් සටහන හා සංඛ්‍යාලේඛන",
"ignore_query_log": "විමසුම් සටහනට මෙම අනුග්‍රාහකය යොදන්න එපා", "ignore_query_log": "විමසුම් සටහනට මෙම අනුග්‍රාහකය යොදන්න එපා",
"ignore_statistics": "සංඛ්‍යාලේඛනයට මෙම අනුග්‍රාහකය යොදන්න එපා", "ignore_statistics": "සංඛ්‍යාලේඛනයට මෙම අනුග්‍රාහකය යොදන්න එපා",
"schedule_services": "සේවා අවහිර විරාමය",
"schedule_invalid_select": "ආරම්භක වේලාව අවසන් වේලාවට කලින් විය යුතුය", "schedule_invalid_select": "ආරම්භක වේලාව අවසන් වේලාවට කලින් විය යුතුය",
"schedule_select_days": "දවස් තෝරන්න", "schedule_select_days": "දවස් තෝරන්න",
"schedule_timezone": "වේලා කලාපයක් තෝරන්න", "schedule_timezone": "වේලා කලාපයක් තෝරන්න",

View File

@@ -6,7 +6,7 @@
"upstream_parallel": "Používať paralelné dopyty na zrýchlenie súčasným dopytovaním všetkých upstream serverov súčasne.", "upstream_parallel": "Používať paralelné dopyty na zrýchlenie súčasným dopytovaním všetkých upstream serverov súčasne.",
"parallel_requests": "Paralelné dopyty", "parallel_requests": "Paralelné dopyty",
"load_balancing": "Vyrovnávanie záťaže", "load_balancing": "Vyrovnávanie záťaže",
"load_balancing_desc": "Dopytuje sa súčasne len jeden upstream server. AdGuard Home používa vážený náhodný algoritmus na výber serverov s najnižším počtom neúspešných vyhľadávaní a najnižším priemerným časom vyhľadávania.", "load_balancing_desc": "Dopytovať len jeden server v danom čase. AdGuard Home použije na výber servera vážený náhodný algoritmus, aby sa najrýchlejší server používal častejšie.",
"bootstrap_dns": "Bootstrap DNS servery", "bootstrap_dns": "Bootstrap DNS servery",
"bootstrap_dns_desc": "IP adresy serverov DNS používaných na rozlíšenie IP adries prekladačov DoH/DoT, ktoré zadáte ako upstream. Komentáre nie sú povolené.", "bootstrap_dns_desc": "IP adresy serverov DNS používaných na rozlíšenie IP adries prekladačov DoH/DoT, ktoré zadáte ako upstream. Komentáre nie sú povolené.",
"fallback_dns_title": "Záložné servery DNS", "fallback_dns_title": "Záložné servery DNS",
@@ -89,7 +89,7 @@
"form_enter_hostname": "Zadajte meno hostiteľa", "form_enter_hostname": "Zadajte meno hostiteľa",
"error_details": "Podrobnosti chyby", "error_details": "Podrobnosti chyby",
"response_details": "Podrobnosti odpovede", "response_details": "Podrobnosti odpovede",
"request_details": "Podrobnosti dopytu", "request_details": "Podrobnosti požiadavky",
"client_details": "Podrobnosti klienta", "client_details": "Podrobnosti klienta",
"details": "Podrobnosti", "details": "Podrobnosti",
"back": "Naspäť", "back": "Naspäť",
@@ -154,7 +154,7 @@
"use_adguard_parental": "Použiť AdGuard službu Rodičovská kontrola", "use_adguard_parental": "Použiť AdGuard službu Rodičovská kontrola",
"use_adguard_parental_hint": "AdGuard Home skontroluje, či doména obsahuje materiály pre dospelých. Používa rovnaké API priateľské k ochrane osobných údajov ako služba Bezpečného prehliadania.", "use_adguard_parental_hint": "AdGuard Home skontroluje, či doména obsahuje materiály pre dospelých. Používa rovnaké API priateľské k ochrane osobných údajov ako služba Bezpečného prehliadania.",
"enforce_safe_search": "Používať bezpečné vyhľadávanie", "enforce_safe_search": "Používať bezpečné vyhľadávanie",
"enforce_save_search_hint": "AdGuard Home vynúti bezpečné vyhľadávanie v nasledujúcich vyhľadávačoch: Google, YouTube, Bing, DuckDuckGo, Ecosia, Yandex, Pixabay.", "enforce_save_search_hint": "AdGuard Home vynucuje bezpečné vyhľadávanie v nasledujúcich vyhľadávačoch: Google, YouTube, Bing, DuckDuckGo, Yandex a Pixabay.",
"no_servers_specified": "Neboli špecifikované žiadne servery", "no_servers_specified": "Neboli špecifikované žiadne servery",
"general_settings": "Všeobecné nastavenia", "general_settings": "Všeobecné nastavenia",
"dns_settings": "Nastavenia DNS", "dns_settings": "Nastavenia DNS",
@@ -308,7 +308,7 @@
"form_enter_rate_limit": "Zadajte rýchlostný limit", "form_enter_rate_limit": "Zadajte rýchlostný limit",
"rate_limit": "Rýchlostný limit", "rate_limit": "Rýchlostný limit",
"edns_enable": "Povoliť klientsku podsiete EDNS", "edns_enable": "Povoliť klientsku podsiete EDNS",
"edns_cs_desc": "Pridáva možnosť EDNS Client Subnet (ECS) do upstream dopytov a zapíše hodnoty odoslané klientami do denníka dopytov.", "edns_cs_desc": "Pridáva možnosť EDNS Client Subnet (ECS) do upstream požiadaviek a zapíše hodnoty odoslané klientmi do denníka dopytov.",
"edns_use_custom_ip": "Použiť vlastnú IP adresu pre EDNS", "edns_use_custom_ip": "Použiť vlastnú IP adresu pre EDNS",
"edns_use_custom_ip_desc": "Povoliť používanie vlastnej IP adresy pre EDNS", "edns_use_custom_ip_desc": "Povoliť používanie vlastnej IP adresy pre EDNS",
"rate_limit_desc": "Počet požiadaviek za sekundu, ktoré môže jeden klient vykonať. Nastavenie na hodnotu 0 znamená neobmedzene.", "rate_limit_desc": "Počet požiadaviek za sekundu, ktoré môže jeden klient vykonať. Nastavenie na hodnotu 0 znamená neobmedzene.",
@@ -480,11 +480,11 @@
"access_title": "Nastavenia prístupu", "access_title": "Nastavenia prístupu",
"access_desc": "Tu môžete konfigurovať pravidlá prístupu pre server DNS AdGuard Home.", "access_desc": "Tu môžete konfigurovať pravidlá prístupu pre server DNS AdGuard Home.",
"access_allowed_title": "Povolení klienti", "access_allowed_title": "Povolení klienti",
"access_allowed_desc": "Zoznam CIDR, IP adries alebo <a>ClientID</a>. Ak tento zoznam obsahuje položky, AdGuard Home bude akceptovať dopyty iba od týchto klientov.", "access_allowed_desc": "Zoznam CIDR, IP adries alebo <a>ClientID</a>. Ak tento zoznam obsahuje položky, AdGuard Home bude akceptovať požiadavky iba od týchto klientov.",
"access_disallowed_title": "Nepovolení klienti", "access_disallowed_title": "Nepovolení klienti",
"access_disallowed_desc": "Zoznam CIDR, IP adries alebo <a>ClientID</a>. Ak tento zoznam obsahuje položky, AdGuard Home zruší dopyty od týchto klientov. Toto pole sa ignoruje, ak sú v poli Povolení klienti položky.", "access_disallowed_desc": "Zoznam CIDR, IP adries alebo <a>ClientID</a>. Ak tento zoznam obsahuje položky, AdGuard Home zruší požiadavky od týchto klientov. Toto pole sa ignoruje, ak sú v poli Povolení klienti položky.",
"access_blocked_title": "Nepovolené domény", "access_blocked_title": "Nepovolené domény",
"access_blocked_desc": "Nesmie byť zamieňaná s filtrami. AdGuard Home zruší DNS dopyty, ktoré sa zhodujú s týmito doménami, a tieto dopyty sa nezobrazia ani v denníku dopytov. Môžete určiť presné názvy domén, zástupné znaky alebo pravidlá filtrácie URL adries, napr. \"example.org\", \"*.example.org\" alebo ||example.org^\" zodpovedajúcim spôsobom.", "access_blocked_desc": "Nesmie byť zamieňaná s filtrami. AdGuard Home zruší DNS dopyty, ktoré sa zhodujú s týmito doménami, a tieto dopyty sa nezobrazia ani v denníku dopytov. Môžete určiť presné názvy domén, zástupné znaky alebo pravidlá filtrovania URL adries, napr. \"example.org\", \"*.example.org\" alebo ||example.org^\" zodpovedajúcim spôsobom.",
"access_settings_saved": "Nastavenia prístupu úspešne uložené", "access_settings_saved": "Nastavenia prístupu úspešne uložené",
"updates_checked": "K dispozícii je nová verzia aplikácie AdGuard Home\n", "updates_checked": "K dispozícii je nová verzia aplikácie AdGuard Home\n",
"updates_version_equal": "AdGuard Home je aktuálny", "updates_version_equal": "AdGuard Home je aktuálny",

View File

@@ -461,7 +461,7 @@
"form_enter_mac": "Skriv in MAC", "form_enter_mac": "Skriv in MAC",
"form_enter_id": "Ange identifierare", "form_enter_id": "Ange identifierare",
"form_add_id": "Lägg till identifierare", "form_add_id": "Lägg till identifierare",
"form_client_name": "Ange klientnamn", "form_client_name": "Skriv in klientnamn",
"name": "Namn", "name": "Namn",
"client_name": "Klient {{id}}", "client_name": "Klient {{id}}",
"client_global_settings": "Använda globala inställningar", "client_global_settings": "Använda globala inställningar",
@@ -674,6 +674,7 @@
"use_saved_key": "Använd den tidigare sparade nyckeln", "use_saved_key": "Använd den tidigare sparade nyckeln",
"parental_control": "Föräldrakontroll", "parental_control": "Föräldrakontroll",
"safe_browsing": "Säker surfning", "safe_browsing": "Säker surfning",
"served_from_cache": "{{value}} <i>(levereras från cache)</i>",
"form_error_password_length": "Lösenordet måste vara {{min}} till {{max}} tecken långt", "form_error_password_length": "Lösenordet måste vara {{min}} till {{max}} tecken långt",
"anonymizer_notification": "<0>Observera:</0> IP-anonymisering är aktiverad. Du kan inaktivera den i <1>Allmänna inställningar</1>.", "anonymizer_notification": "<0>Observera:</0> IP-anonymisering är aktiverad. Du kan inaktivera den i <1>Allmänna inställningar</1>.",
"confirm_dns_cache_clear": "Är du säker på att du vill rensa DNS-cache?", "confirm_dns_cache_clear": "Är du säker på att du vill rensa DNS-cache?",

View File

@@ -46,7 +46,6 @@
"settings": "การตั้งค่า", "settings": "การตั้งค่า",
"filters": "ตัวกรอง", "filters": "ตัวกรอง",
"query_log": "บันทึกการสืบค้น", "query_log": "บันทึกการสืบค้น",
"nothing_found": "ไม่พบอะไร",
"faq": "คำถามที่พบบ่อย", "faq": "คำถามที่พบบ่อย",
"version": "รุ่น", "version": "รุ่น",
"address": "ที่อยู่", "address": "ที่อยู่",
@@ -350,7 +349,7 @@
"statistics_configuration": "การกำหนดค่าสถิติ", "statistics_configuration": "การกำหนดค่าสถิติ",
"statistics_retention": "การเก็บรักษาสถิติ", "statistics_retention": "การเก็บรักษาสถิติ",
"statistics_retention_desc": "หากคุณลดค่าช่วงเวลาข้อมูลบางอย่างจะหายไป", "statistics_retention_desc": "หากคุณลดค่าช่วงเวลาข้อมูลบางอย่างจะหายไป",
"statistics_clear": "ล้างสถิติ", "statistics_clear": " ล้างค่าสถิติ",
"statistics_clear_confirm": "คุณแน่ใจหรือไม่ว่าต้องการล้างสถิติ?", "statistics_clear_confirm": "คุณแน่ใจหรือไม่ว่าต้องการล้างสถิติ?",
"statistics_retention_confirm": "คุณแน่ใจหรือไม่ว่าต้องการเปลี่ยนการเก็บรักษาสถิติ? หากคุณลดค่าช่วงเวลา ข้อมูลบางอย่างจะหายไป", "statistics_retention_confirm": "คุณแน่ใจหรือไม่ว่าต้องการเปลี่ยนการเก็บรักษาสถิติ? หากคุณลดค่าช่วงเวลา ข้อมูลบางอย่างจะหายไป",
"statistics_cleared": "สถิติได้ถูกล้างเรียบร้อยแล้ว", "statistics_cleared": "สถิติได้ถูกล้างเรียบร้อยแล้ว",
@@ -391,13 +390,10 @@
"check_title": "ตรวจสอบการกรอง", "check_title": "ตรวจสอบการกรอง",
"check_desc": "ตรวจสอบว่าชื่อโฮสต์ถูกกรอง", "check_desc": "ตรวจสอบว่าชื่อโฮสต์ถูกกรอง",
"form_enter_host": "ป้อนชื่อโฮสต์", "form_enter_host": "ป้อนชื่อโฮสต์",
"show_blocked_responses": "ปิดกั้นแล้ว", "show_processed_responses": "การประมวลผล",
"show_whitelisted_responses": "รายการที่อนุญาต",
"show_processed_responses": "ประมวลผลแล้ว",
"blocked_adult_websites": "ถูกปิดกั้นโดยการควบคุมของผู้ปกครอง", "blocked_adult_websites": "ถูกปิดกั้นโดยการควบคุมของผู้ปกครอง",
"safe_search": "ค้นหาอย่างปลอดภัย", "safe_search": "ค้นหาอย่างปลอดภัย",
"blocklist": "บัญชีดำ", "blocklist": "บัญชีดำ",
"allowed": "รายการที่อนุญาต",
"filter_category_other": "อื่น ๆ", "filter_category_other": "อื่น ๆ",
"parental_control": "ควบคุมโดยผู้ปกครอง", "parental_control": "ควบคุมโดยผู้ปกครอง",
"sunday_short": "อาทิตย์", "sunday_short": "อาทิตย์",

View File

@@ -6,7 +6,7 @@
"upstream_parallel": "Tüm üst sunucuları eş zamanlı sorgulayarak çözümlemeyi hızlandırmak için paralel sorgular kullanın.", "upstream_parallel": "Tüm üst sunucuları eş zamanlı sorgulayarak çözümlemeyi hızlandırmak için paralel sorgular kullanın.",
"parallel_requests": "Paralel istekler", "parallel_requests": "Paralel istekler",
"load_balancing": "Yük dengeleme", "load_balancing": "Yük dengeleme",
"load_balancing_desc": "Aynı anda bir üst kaynak sunucusunu sorgulayın. AdGuard Home, en düşük başarısız arama sayısına ve en düşük ortalama arama süresine sahip sunucuları seçmek için ağırlıklı rastgele bir algoritma kullanır.", "load_balancing_desc": "Her seferde bir üst sunucuyu sorgulayın. AdGuard Home, sunucuyu seçmek için ağırlıklı rastgele algoritmasını kullanır, böylece en hızlı sunucu daha sık kullanılır.",
"bootstrap_dns": "DNS Önyükleme sunucuları", "bootstrap_dns": "DNS Önyükleme sunucuları",
"bootstrap_dns_desc": "Üst kaynak olarak belirttiğiniz DoH/DoT çözümleyicilerin IP adreslerini çözümlemek için kullanılan DNS sunucularının IP adresleri. Yorumlara izin verilmez.", "bootstrap_dns_desc": "Üst kaynak olarak belirttiğiniz DoH/DoT çözümleyicilerin IP adreslerini çözümlemek için kullanılan DNS sunucularının IP adresleri. Yorumlara izin verilmez.",
"fallback_dns_title": "Yedek DNS sunucuları", "fallback_dns_title": "Yedek DNS sunucuları",
@@ -68,7 +68,7 @@
"ip": "IP", "ip": "IP",
"dhcp_table_hostname": "Ana makine Adı", "dhcp_table_hostname": "Ana makine Adı",
"dhcp_table_expires": "Bitiş tarihi", "dhcp_table_expires": "Bitiş tarihi",
"dhcp_warning": "DHCP sunucusunu yine de etkinleştirmek istiyorsanız, ağınızda başka aktif DHCP sunucusu olmadığından emin olun, aksi takdirde ağa bağlı cihazların internet bağlantısı kesilebilir!", "dhcp_warning": "DHCP sunucusunu yine de etkinleştirmek istiyorsanız, ağınızda başka aktif DHCP sunucusu olmadığından emin olun, aksi takdirde ağa bağlı cihazların İnternet bağlantısı kesilebilir!",
"dhcp_error": "AdGuard Home, ağda başka bir etkin DHCP sunucusu olup olmadığını belirleyemedi", "dhcp_error": "AdGuard Home, ağda başka bir etkin DHCP sunucusu olup olmadığını belirleyemedi",
"dhcp_static_ip_error": "DHCP sunucusunu kullanmak için sabit bir IP adresi ayarlanmalıdır. AdGuard Home, bu ağ arayüzünün sabit bir IP adresi kullanılarak yapılandırılıp yapılandırılmadığını belirleyemedi. Lütfen sabit IP adresini elle ayarlayın.", "dhcp_static_ip_error": "DHCP sunucusunu kullanmak için sabit bir IP adresi ayarlanmalıdır. AdGuard Home, bu ağ arayüzünün sabit bir IP adresi kullanılarak yapılandırılıp yapılandırılmadığını belirleyemedi. Lütfen sabit IP adresini elle ayarlayın.",
"dhcp_dynamic_ip_found": "Sisteminiz, <0>{{interfaceName}}</0> arayüzü için dinamik IP adresi yapılandırması kullanıyor. DHCP sunucusunu kullanmak için sabit bir IP adresi ayarlanmalıdır. Geçerli olan IP adresiniz <0>{{ipAddress}}</0>. \"DHCP sunucusunu etkinleştir\" düğmesine basarsanız, AdGuard Home bu IP adresini otomatik bir şekilde sabit olarak ayarlayacaktır.", "dhcp_dynamic_ip_found": "Sisteminiz, <0>{{interfaceName}}</0> arayüzü için dinamik IP adresi yapılandırması kullanıyor. DHCP sunucusunu kullanmak için sabit bir IP adresi ayarlanmalıdır. Geçerli olan IP adresiniz <0>{{ipAddress}}</0>. \"DHCP sunucusunu etkinleştir\" düğmesine basarsanız, AdGuard Home bu IP adresini otomatik bir şekilde sabit olarak ayarlayacaktır.",
@@ -154,7 +154,7 @@
"use_adguard_parental": "AdGuard ebeveyn denetimi web hizmetini kullan", "use_adguard_parental": "AdGuard ebeveyn denetimi web hizmetini kullan",
"use_adguard_parental_hint": "AdGuard Home, alan adının yetişkin içerik bulundurup bulundurmadığını kontrol eder. Gezinti koruması web hizmeti ile kullandığımız aynı gizlilik dostu API'yi kullanır.", "use_adguard_parental_hint": "AdGuard Home, alan adının yetişkin içerik bulundurup bulundurmadığını kontrol eder. Gezinti koruması web hizmeti ile kullandığımız aynı gizlilik dostu API'yi kullanır.",
"enforce_safe_search": "Güvenli Aramayı kullan", "enforce_safe_search": "Güvenli Aramayı kullan",
"enforce_save_search_hint": "AdGuard Home, şu arama motorlarında güvenli aramayı uygular: Google, YouTube, Bing, DuckDuckGo, Ecosia, Yandex, Pixabay.", "enforce_save_search_hint": "AdGuard Home, şu arama motorlarında güvenli aramayı uygular: Google, YouTube, Bing, DuckDuckGo, Yandex ve Pixabay.",
"no_servers_specified": "Sunucu belirtilmedi", "no_servers_specified": "Sunucu belirtilmedi",
"general_settings": "Genel ayarlar", "general_settings": "Genel ayarlar",
"dns_settings": "DNS ayarları", "dns_settings": "DNS ayarları",
@@ -476,7 +476,7 @@
"client_confirm_delete": "\"{{key}}\" istemcisini silmek istediğinizden emin misiniz?", "client_confirm_delete": "\"{{key}}\" istemcisini silmek istediğinizden emin misiniz?",
"list_confirm_delete": "Bu listeyi silmek istediğinizden emin misiniz?", "list_confirm_delete": "Bu listeyi silmek istediğinizden emin misiniz?",
"auto_clients_title": "Çalışma zamanı istemcileri", "auto_clients_title": "Çalışma zamanı istemcileri",
"auto_clients_desc": "AdGuard Home'u kullanan veya kullanabilecek cihazların IP adresleri hakkında bilgiler. Bu bilgiler, hosts dosyaları, ters DNS, vb. dâhil olmak üzere çeşitli kaynaklardan toplanır.", "auto_clients_desc": "AdGuard Home'u kullanan veya kullanabilecek cihazların IP adresleri hakkında bilgiler. Bu bilgiler, hosts dosyaları, ters DNS, vb. dahil olmak üzere çeşitli kaynaklardan toplanır.",
"access_title": "Erişim ayarları", "access_title": "Erişim ayarları",
"access_desc": "AdGuard Home DNS sunucusu için erişim kurallarını buradan yapılandırabilirsiniz", "access_desc": "AdGuard Home DNS sunucusu için erişim kurallarını buradan yapılandırabilirsiniz",
"access_allowed_title": "İzin verilen istemciler", "access_allowed_title": "İzin verilen istemciler",
@@ -603,7 +603,7 @@
"autofix_warning_list": "Bu görevleri gerçekleştirir: <0>Sistem DNSStubListener'ı devre dışı bırakın</0> <0>DNS sunucusu adresini 127.0.0.1 olarak ayarlayın</0> <0>/etc/resolv.conf'un sembolik bağlantı hedefini /run/systemd/resolve/resolv.conf ile değiştirin<0> <0>DNSStubListener'ı durdurun (systemd çözümlenmiş hizmeti yeniden yükleyin)</0>", "autofix_warning_list": "Bu görevleri gerçekleştirir: <0>Sistem DNSStubListener'ı devre dışı bırakın</0> <0>DNS sunucusu adresini 127.0.0.1 olarak ayarlayın</0> <0>/etc/resolv.conf'un sembolik bağlantı hedefini /run/systemd/resolve/resolv.conf ile değiştirin<0> <0>DNSStubListener'ı durdurun (systemd çözümlenmiş hizmeti yeniden yükleyin)</0>",
"autofix_warning_result": "Sonuç olarak, sisteminizden gelen tüm DNS istekleri varsayılan olarak AdGuard Home tarafından işlenecektir.", "autofix_warning_result": "Sonuç olarak, sisteminizden gelen tüm DNS istekleri varsayılan olarak AdGuard Home tarafından işlenecektir.",
"tags_title": "Etiketler", "tags_title": "Etiketler",
"tags_desc": "İstemciye karşılık gelen etiketleri seçebilirsiniz. Etiketleri daha kesin olarak uygulamak için filtreleme kurallarına dâhil edin. <0>Daha fazla bilgi edinin</0>.", "tags_desc": "İstemciye karşılık gelen etiketleri seçebilirsiniz. Etiketleri daha kesin olarak uygulamak için filtreleme kurallarına dahil edin. <0>Daha fazla bilgi edinin</0>.",
"form_select_tags": "İstemci etiketlerini seçin", "form_select_tags": "İstemci etiketlerini seçin",
"check_title": "Filtrelemeyi denetleyin", "check_title": "Filtrelemeyi denetleyin",
"check_desc": "Ana makine adının filtreleme durumunu kontrol edin.", "check_desc": "Ana makine adının filtreleme durumunu kontrol edin.",

View File

@@ -20,7 +20,7 @@
"resolve_clients_title": "Увімкнути зворотне вирішення IP-адрес клієнтів", "resolve_clients_title": "Увімкнути зворотне вирішення IP-адрес клієнтів",
"resolve_clients_desc": "Визначати доменні імена клієнтів за допомогою PTR-запитів до відповідних серверів — приватних DNS-серверів для локальних клієнтів та upstream-серверів для клієнтів з публічними IP-адресами.", "resolve_clients_desc": "Визначати доменні імена клієнтів за допомогою PTR-запитів до відповідних серверів — приватних DNS-серверів для локальних клієнтів та upstream-серверів для клієнтів з публічними IP-адресами.",
"use_private_ptr_resolvers_title": "Використовувати приватні зворотні DNS-резолвери", "use_private_ptr_resolvers_title": "Використовувати приватні зворотні DNS-резолвери",
"use_private_ptr_resolvers_desc": "Розвʼязувати запити PTR, SOA та NS для доменів ARPA, що містять приватні IP-адреси, через приватні вихідні сервери, DHCP, /etc/hosts тощо. Якщо вимкнено, AdGuard Home відповідатиме на всі такі запити з NXDOMAIN.", "use_private_ptr_resolvers_desc": "Надсилати зворотні DNS-запити до вказаних серверів для клієнтів, що обслуговуються локально. Якщо вимкнено, AdGuard Home буде відповідати NXDOMAIN на всі такі PTR-запити, окрім запитів про клієнтів, що уже відомі завдяки DHCP, /etc/hosts тощо.",
"check_dhcp_servers": "Перевірити DHCP-сервери", "check_dhcp_servers": "Перевірити DHCP-сервери",
"save_config": "Зберегти конфігурацію", "save_config": "Зберегти конфігурацію",
"enabled_dhcp": "DHCP-сервер увімкнено", "enabled_dhcp": "DHCP-сервер увімкнено",
@@ -343,10 +343,10 @@
"known_tracker": "Відомі трекери", "known_tracker": "Відомі трекери",
"install_welcome_title": "Вітаємо в AdGuard Home!", "install_welcome_title": "Вітаємо в AdGuard Home!",
"install_welcome_desc": "AdGuard Home — це мережевий DNS-сервер, що блокує рекламу та відстеження. Його мета — надати вам контроль над усією мережею та всіма пристроями в ній без потреби використання програми на стороні клієнта.", "install_welcome_desc": "AdGuard Home — це мережевий DNS-сервер, що блокує рекламу та відстеження. Його мета — надати вам контроль над усією мережею та всіма пристроями в ній без потреби використання програми на стороні клієнта.",
"install_settings_title": "Вебінтерфейс адміністратора", "install_settings_title": "Веб-інтерфейс адміністратора",
"install_settings_listen": "Мережевий інтерфейс", "install_settings_listen": "Мережевий інтерфейс",
"install_settings_port": "Порт", "install_settings_port": "Порт",
"install_settings_interface_link": "Вебінтерфейс адміністратора AdGuard Home буде доступний за такими адресами:", "install_settings_interface_link": "Веб-інтерфейс адміністратора AdGuard Home буде доступний за такими адресами:",
"form_error_port": "Уведіть правильне значення порту", "form_error_port": "Уведіть правильне значення порту",
"install_settings_dns": "DNS-сервер", "install_settings_dns": "DNS-сервер",
"install_settings_dns_desc": "Вам потрібно буде налаштувати свої пристрої або маршрутизатор для використання DNS-сервера за такими адресами:", "install_settings_dns_desc": "Вам потрібно буде налаштувати свої пристрої або маршрутизатор для використання DNS-сервера за такими адресами:",

View File

@@ -6,21 +6,21 @@
"upstream_parallel": "Sử dụng truy vấn song song để tăng tốc độ giải quyết bằng cách truy vấn đồng thời tất cả các máy chủ ngược tuyến", "upstream_parallel": "Sử dụng truy vấn song song để tăng tốc độ giải quyết bằng cách truy vấn đồng thời tất cả các máy chủ ngược tuyến",
"parallel_requests": "Yêu cầu song song", "parallel_requests": "Yêu cầu song song",
"load_balancing": "Cân bằng tải", "load_balancing": "Cân bằng tải",
"load_balancing_desc": "Truy vấn một máy chủ thượng nguồn tại một thời điểm. AdGuard Home sử dụng thuật toán ngẫu nhiên có trọng số để chọn máy chủ có số lần tìm kiếm không thành công thấp nhất và thời gian tìm kiếm trung bình thấp nhất.", "load_balancing_desc": "Chỉ truy xuất một máy chủ trong cùng thời điểm. AdGuard Home sẽ sử dụng thuật toán trọng số ngẫu nhiên để chọn một máy chủ nhanh nhất và sử dụng máy chủ đó thường xuyên hơn.",
"bootstrap_dns": "Máy chủ DNS Bootstrap", "bootstrap_dns": "Máy chủ DNS Bootstrap",
"bootstrap_dns_desc": "Địa chỉ IP của máy chủ DNS được sử dụng để phân giải địa chỉ IP của trình phân giải DoH/DoT mà bạn chỉ định làm thượng nguồn. Bình luận không được phép.", "bootstrap_dns_desc": "Địa chỉ IP của máy chủ DNS được sử dụng để phân giải địa chỉ IP của trình phân giải DoH/DoT mà bạn chỉ định làm thượng nguồn. Bình luận không được phép.",
"fallback_dns_title": "Máy chủ DNS dự phòng", "fallback_dns_title": "Máy chủ DNS dự phòng",
"fallback_dns_desc": "Danh sách máy chủ DNS dự phòng được sử dụng khi máy chủ DNS ngược tuyến không phản hồi. Cú pháp tương tự như trong trường ngược dòng chính ở trên.", "fallback_dns_desc": "Danh sách máy chủ DNS dự phòng được sử dụng khi máy chủ DNS ngược tuyến không phản hồi. Cú pháp tương tự như trong trường ngược dòng chính ở trên.",
"fallback_dns_placeholder": "Nhập một máy chủ DNS dự phòng trên mỗi dòng", "fallback_dns_placeholder": "Nhập một máy chủ DNS dự phòng trên mỗi dòng",
"local_ptr_title": "Máy chủ DNS riêng tư", "local_ptr_title": "Máy chủ DNS riêng tư",
"local_ptr_desc": "Máy chủ DNS được AdGuard Home sử dụng cho các yêu cầu PTR, SOA và NS riêng tư. Một yêu cầu được coi là riêng tư nếu nó yêu cầu một miền ARPA chứa một mạng con trong phạm vi IP riêng tư (chẳng hạn như \"192.168.12.34\") và đến từ một máy khách có địa chỉ IP riêng. Nếu không được thiết lập, các trình phân giải DNS mặc định của hệ điều hành của bạn sẽ được sử dụng, ngoại trừ các địa chỉ IP của AdGuard Home.", "local_ptr_desc": "Máy chủ DNS hoặc các máy chủ mà AdGuard Home sẽ sử dụng cho các truy vấn về tài nguyên được phân phối cục bộ. Ví dụ: máy chủ này sẽ được sử dụng để phân giải tên máy khách của máy khách cho các máy khách có địa chỉ IP riêng. Nếu không được cài đặt, AdGuard Home sẽ tự động sử dụng trình phân giải DNS mặc định của bạn.",
"local_ptr_default_resolver": "Theo mặc định, AdGuard Home sử dụng các hệ thống phân giải tên miền ngược sau: {{ip}}.", "local_ptr_default_resolver": "Theo mặc định, AdGuard Home sử dụng các hệ thống phân giải tên miền ngược sau: {{ip}}.",
"local_ptr_no_default_resolver": "AdGuard Home không thể xác định hệ thống phân giải tên miền ngược riêng phù hợp cho hệ thống này.", "local_ptr_no_default_resolver": "AdGuard Home không thể xác định hệ thống phân giải tên miền ngược riêng phù hợp cho hệ thống này.",
"local_ptr_placeholder": "Nhập một địa chỉ IP trên mỗi dòng", "local_ptr_placeholder": "Nhập một địa chỉ IP trên mỗi dòng",
"resolve_clients_title": "Kích hoạt cho phép phân giải ngược về địa chỉ IP của máy khách", "resolve_clients_title": "Kích hoạt cho phép phân giải ngược về địa chỉ IP của máy khách",
"resolve_clients_desc": "Nếu được bật, AdGuard Home sẽ cố gắng phân giải ngược lại địa chỉ IP của khách hàng thành tên máy chủ của họ bằng cách gửi các truy vấn PTR tới trình phân giải tương ứng (máy chủ DNS riêng cho máy khách cục bộ, máy chủ ngược dòng cho máy khách có địa chỉ IP công cộng).", "resolve_clients_desc": "Nếu được bật, AdGuard Home sẽ cố gắng phân giải ngược lại địa chỉ IP của khách hàng thành tên máy chủ của họ bằng cách gửi các truy vấn PTR tới trình phân giải tương ứng (máy chủ DNS riêng cho máy khách cục bộ, máy chủ ngược dòng cho máy khách có địa chỉ IP công cộng).",
"use_private_ptr_resolvers_title": "Sử dụng trình rDNS riêng tư", "use_private_ptr_resolvers_title": "Sử dụng trình rDNS riêng tư",
"use_private_ptr_resolvers_desc": "Giải quyết các yêu cầu PTR, SOA và NS cho các miền ARPA chứa địa chỉ IP riêng thông qua máy chủ thượng nguồn riêng, DHCP, /etc/hosts, v. v. Nếu bị vô hiệu hóa, AdGuard Home sẽ phản hồi tất cả các yêu cầu đó bằng NXDOMAIN.", "use_private_ptr_resolvers_desc": "Thực hiện tra cứu ngược DNS cho các địa chỉ được phân phối cục bộ bằng cách sử dụng các máy chủ nguồn. Nếu bị vô hiệu hóa, AdGuard Home sẽ phản hồi với NXDOMAIN cho tất cả các yêu cầu PTR ngoại trừ các ứng dụng khách được biết đến bởi DHCP, / etc / hosts, v. v.",
"check_dhcp_servers": "Kiểm tra máy chủ DHCP", "check_dhcp_servers": "Kiểm tra máy chủ DHCP",
"save_config": "Lưu thiết lập", "save_config": "Lưu thiết lập",
"enabled_dhcp": "Máy chủ DHCP đã kích hoạt", "enabled_dhcp": "Máy chủ DHCP đã kích hoạt",
@@ -154,7 +154,7 @@
"use_adguard_parental": "Sử dụng dịch vụ quản lý của phụ huynh AdGuard", "use_adguard_parental": "Sử dụng dịch vụ quản lý của phụ huynh AdGuard",
"use_adguard_parental_hint": "AdGuard Home sẽ kiểm tra nếu tên miền chứa từ khoá người lớn. Tính năng sử dụng API thân thiện với quyền riêng tư tương tự với dịch vụ bảo vệ duyệt web", "use_adguard_parental_hint": "AdGuard Home sẽ kiểm tra nếu tên miền chứa từ khoá người lớn. Tính năng sử dụng API thân thiện với quyền riêng tư tương tự với dịch vụ bảo vệ duyệt web",
"enforce_safe_search": "Bắt buộc tìm kiếm an toàn", "enforce_safe_search": "Bắt buộc tìm kiếm an toàn",
"enforce_save_search_hint": "AdGuard Home sẽ thực thi tìm kiếm an toàn trong các công cụ tìm kiếm sau: Google, YouTube, Bing, DuckDuckGo, Ecosia, Yandex, Pixabay.", "enforce_save_search_hint": "AdGuard Home thể bắt buộc tìm kiếm an toàn với các dịch vụ tìm kiếm: Google, Youtube, Bing, Yandex.",
"no_servers_specified": "Không có máy chủ nào được liệt kê", "no_servers_specified": "Không có máy chủ nào được liệt kê",
"general_settings": "Cài đặt chung", "general_settings": "Cài đặt chung",
"dns_settings": "Cài đặt DNS", "dns_settings": "Cài đặt DNS",
@@ -425,9 +425,6 @@
"encryption_hostnames": "Tên máy chủ", "encryption_hostnames": "Tên máy chủ",
"encryption_reset": "Bạn có chắc chắn muốn đặt lại cài đặt mã hóa?", "encryption_reset": "Bạn có chắc chắn muốn đặt lại cài đặt mã hóa?",
"encryption_warning": "Cảnh báo", "encryption_warning": "Cảnh báo",
"encryption_plain_dns_enable": "Kích hoạt DNS đơn giản",
"encryption_plain_dns_desc": "DNS đơn giản được bật theo mặc định. Bạn có thể vô hiệu hóa nó để buộc tất cả các thiết bị sử dụng DNS được mã hóa. Để thực hiện việc này, bạn phải kích hoạt ít nhất một giao thức DNS được mã hóa",
"encryption_plain_dns_error": "Để tắt DNS đơn giản, hãy bật ít nhất một giao thức DNS được mã hóa",
"topline_expiring_certificate": "Chứng chỉ SSL của bạn sắp hết hạn. Cập nhật <0>Cài đặt mã hóa</0>.", "topline_expiring_certificate": "Chứng chỉ SSL của bạn sắp hết hạn. Cập nhật <0>Cài đặt mã hóa</0>.",
"topline_expired_certificate": "Chứng chỉ SSL của bạn đã hết hạn. Cập nhật <0>Cài đặt mã hóa</0>.", "topline_expired_certificate": "Chứng chỉ SSL của bạn đã hết hạn. Cập nhật <0>Cài đặt mã hóa</0>.",
"form_error_port_range": "Nhập giá trị cổng trong phạm vi 80-65535", "form_error_port_range": "Nhập giá trị cổng trong phạm vi 80-65535",
@@ -678,7 +675,7 @@
"use_saved_key": "Sử dụng khóa đã lưu trước đó", "use_saved_key": "Sử dụng khóa đã lưu trước đó",
"parental_control": "Quản lý của phụ huynh", "parental_control": "Quản lý của phụ huynh",
"safe_browsing": "Duyệt web an toàn", "safe_browsing": "Duyệt web an toàn",
"served_from_cache_label": "Được phục vụ từ bộ nhớ đệm", "served_from_cache": "{{value}} <i>(được phục vụ từ bộ nhớ cache)</i>",
"form_error_password_length": "Mật khẩu phải dài từ {{min}} đến {{max}} ký tự", "form_error_password_length": "Mật khẩu phải dài từ {{min}} đến {{max}} ký tự",
"anonymizer_notification": "<0> Lưu ý:</0> Tính năng ẩn danh IP được bật. Bạn có thể tắt nó trong <1> Cài đặt chung</1>.", "anonymizer_notification": "<0> Lưu ý:</0> Tính năng ẩn danh IP được bật. Bạn có thể tắt nó trong <1> Cài đặt chung</1>.",
"confirm_dns_cache_clear": "Bạn có chắc chắn muốn xóa bộ đệm ẩn DNS không?", "confirm_dns_cache_clear": "Bạn có chắc chắn muốn xóa bộ đệm ẩn DNS không?",

View File

@@ -6,7 +6,7 @@
"upstream_parallel": "使用并行请求以同时查询所有上游服务器来加快解析速度。", "upstream_parallel": "使用并行请求以同时查询所有上游服务器来加快解析速度。",
"parallel_requests": "并行请求", "parallel_requests": "并行请求",
"load_balancing": "负载均衡", "load_balancing": "负载均衡",
"load_balancing_desc": "一次查询一台服务器。AdGuard Home 使用加权随机算法来选择具有最少失败查找和最低平均查找时间的服务器。", "load_balancing_desc": "一次查询一台服务器。AdGuard Home 使用加权随机算法来选择服务器,以便更常使用最快的服务器。",
"bootstrap_dns": "Bootstrap DNS 服务器", "bootstrap_dns": "Bootstrap DNS 服务器",
"bootstrap_dns_desc": "DNS 服务器的 IP 地址,用于解析指定为上游的 DoH/DoT 解析器的 IP 地址。不允许添加注释。", "bootstrap_dns_desc": "DNS 服务器的 IP 地址,用于解析指定为上游的 DoH/DoT 解析器的 IP 地址。不允许添加注释。",
"fallback_dns_title": "后备 DNS 服务器", "fallback_dns_title": "后备 DNS 服务器",
@@ -154,7 +154,7 @@
"use_adguard_parental": "使用 AdGuard 【家长控制】服务", "use_adguard_parental": "使用 AdGuard 【家长控制】服务",
"use_adguard_parental_hint": "AdGuard Home 将使用与浏览安全服务相同的隐私性强的 API 来检查域名指向的网站是否包含成人内容。", "use_adguard_parental_hint": "AdGuard Home 将使用与浏览安全服务相同的隐私性强的 API 来检查域名指向的网站是否包含成人内容。",
"enforce_safe_search": "使用安全搜索", "enforce_safe_search": "使用安全搜索",
"enforce_save_search_hint": "AdGuard Home 将会在下列搜索引擎强制启用安全搜索Google、YouTube、Bing、DuckDuckGo、Ecosia、Yandex、Pixabay。", "enforce_save_search_hint": "AdGuard Home 对以下搜索引擎强制启用安全搜索Google、YouTube、Bing、DuckDuckGo、Yandex、Pixabay。",
"no_servers_specified": "未找到指定的服务器", "no_servers_specified": "未找到指定的服务器",
"general_settings": "常规设置", "general_settings": "常规设置",
"dns_settings": "DNS 设置", "dns_settings": "DNS 设置",

View File

@@ -6,7 +6,7 @@
"upstream_parallel": "透過同時地查詢所有上游的伺服器,使用並行的查詢以加速解析。", "upstream_parallel": "透過同時地查詢所有上游的伺服器,使用並行的查詢以加速解析。",
"parallel_requests": "並行的請求", "parallel_requests": "並行的請求",
"load_balancing": "負載平衡", "load_balancing": "負載平衡",
"load_balancing_desc": "次查詢一伺服器。AdGuard Home 使用加權隨機演算法來選擇具有最少失敗查詢和最低平均查詢時間的伺服器。", "load_balancing_desc": "次查詢一個上游伺服器。AdGuard Home 使用它的加權隨機演算法來選擇伺服器,以便最快的伺服器被更常使用。",
"bootstrap_dns": "自我啟動BootstrapDNS 伺服器", "bootstrap_dns": "自我啟動BootstrapDNS 伺服器",
"bootstrap_dns_desc": "DNS 伺服器的 IP 位址,用於解析您指定為上游伺服器的 DoH/DoT 解析器的 IP 位址。不允許註釋。", "bootstrap_dns_desc": "DNS 伺服器的 IP 位址,用於解析您指定為上游伺服器的 DoH/DoT 解析器的 IP 位址。不允許註釋。",
"fallback_dns_title": "應變 DNS 伺服器", "fallback_dns_title": "應變 DNS 伺服器",
@@ -20,7 +20,7 @@
"resolve_clients_title": "啟用用戶端的 IP 位址之反向的解析", "resolve_clients_title": "啟用用戶端的 IP 位址之反向的解析",
"resolve_clients_desc": "透過傳送指標PTR查詢到對應的解析器私人 DNS 伺服器供區域的用戶端,上游的伺服器供有公共 IP 位址的用戶端),反向地解析用戶端的 IP 位址變為它們的主機名稱。", "resolve_clients_desc": "透過傳送指標PTR查詢到對應的解析器私人 DNS 伺服器供區域的用戶端,上游的伺服器供有公共 IP 位址的用戶端),反向地解析用戶端的 IP 位址變為它們的主機名稱。",
"use_private_ptr_resolvers_title": "使用私人反向的 DNS 解析器", "use_private_ptr_resolvers_title": "使用私人反向的 DNS 解析器",
"use_private_ptr_resolvers_desc": "通過私人上游伺服器、DHCP、/etc/hosts 等等,對包含私人 IP 位址的 ARPA 網域解析 PTR、SOA 和 NS 請求。如果禁用AdGuard Home 將對所有此類請求以 NXDOMAIN 回。", "use_private_ptr_resolvers_desc": "使用私人上游伺服器、DHCP、/etc/hosts 等方式解析包含私人 IP 位址的 ARPA 網域 PTR、SOA 和 NS 請求。如果禁用AdGuard Home 將對所有此類請求以 NXDOMAIN 回。",
"check_dhcp_servers": "檢查動態主機設定協定DHCP伺服器", "check_dhcp_servers": "檢查動態主機設定協定DHCP伺服器",
"save_config": "儲存配置", "save_config": "儲存配置",
"enabled_dhcp": "動態主機設定協定DHCP伺服器被啟用", "enabled_dhcp": "動態主機設定協定DHCP伺服器被啟用",
@@ -154,7 +154,7 @@
"use_adguard_parental": "使用 AdGuard 家長控制之網路服務", "use_adguard_parental": "使用 AdGuard 家長控制之網路服務",
"use_adguard_parental_hint": "AdGuard Home 將檢查網域是否包含成人資料。它使用如同瀏覽安全網路服務一樣之對隱私友好的應用程式介面API。", "use_adguard_parental_hint": "AdGuard Home 將檢查網域是否包含成人資料。它使用如同瀏覽安全網路服務一樣之對隱私友好的應用程式介面API。",
"enforce_safe_search": "使用安全搜尋", "enforce_safe_search": "使用安全搜尋",
"enforce_save_search_hint": "AdGuard Home 將在下列的搜尋引擎Google、YouTube、Bing、DuckDuckGo、Ecosia、Yandex 和 Pixabay 中強制執行安全搜尋。", "enforce_save_search_hint": "AdGuard Home 將在下列的搜尋引擎Google、YouTube、Bing、DuckDuckGo、Yandex 和 Pixabay 中強制執行安全搜尋。",
"no_servers_specified": "無已明確指定的伺服器", "no_servers_specified": "無已明確指定的伺服器",
"general_settings": "一般設定", "general_settings": "一般設定",
"dns_settings": "DNS 設定", "dns_settings": "DNS 設定",

View File

@@ -46,7 +46,7 @@ export const getStats = () => async (dispatch: any) => {
const normalizedTopClients = normalizeTopStats(stats.top_clients); const normalizedTopClients = normalizeTopStats(stats.top_clients);
const clientsParams = getParamsForClientsSearch(normalizedTopClients, 'name'); const clientsParams = getParamsForClientsSearch(normalizedTopClients, 'name');
const clients = await apiClient.searchClients(clientsParams); const clients = await apiClient.findClients(clientsParams);
const topClientsWithInfo = addClientInfo(normalizedTopClients, clients, 'name'); const topClientsWithInfo = addClientInfo(normalizedTopClients, clients, 'name');
const normalizedStats = { const normalizedStats = {

View File

@@ -415,7 +415,7 @@ class Api {
// Per-client settings // Per-client settings
GET_CLIENTS = { path: 'clients', method: 'GET' }; GET_CLIENTS = { path: 'clients', method: 'GET' };
SEARCH_CLIENTS = { path: 'clients/search', method: 'POST' }; FIND_CLIENTS = { path: 'clients/find', method: 'GET' };
ADD_CLIENT = { path: 'clients/add', method: 'POST' }; ADD_CLIENT = { path: 'clients/add', method: 'POST' };
@@ -453,12 +453,11 @@ class Api {
return this.makeRequest(path, method, parameters); return this.makeRequest(path, method, parameters);
} }
searchClients(config: any) { findClients(params: any) {
const { path, method } = this.SEARCH_CLIENTS; const { path, method } = this.FIND_CLIENTS;
const parameters = { const url = getPathWithQueryString(path, params);
data: config,
}; return this.makeRequest(url, method);
return this.makeRequest(path, method, parameters);
} }
// DNS access settings // DNS access settings

View File

@@ -23,7 +23,7 @@ interface RowProps {
const Row = ({ label, count, response_status, tooltipTitle, translationComponents }: RowProps) => { const Row = ({ label, count, response_status, tooltipTitle, translationComponents }: RowProps) => {
const content = response_status ? ( const content = response_status ? (
<LogsSearchLink response_status={response_status}>{count}</LogsSearchLink> <LogsSearchLink response_status={response_status}>{formatNumber(count)}</LogsSearchLink>
) : ( ) : (
count count
); );
@@ -77,16 +77,16 @@ const Counters = ({ refreshButton, subtitle }: CountersProps) => {
? t('number_of_dns_query_hours', { count: msToHours(interval) }) ? t('number_of_dns_query_hours', { count: msToHours(interval) })
: t('number_of_dns_query_days', { count: msToDays(interval) }); : t('number_of_dns_query_days', { count: msToDays(interval) });
const rows: RowProps[] = [ const rows = [
{ {
label: 'dns_query', label: 'dns_query',
count: formatNumber(numDnsQueries), count: numDnsQueries.toString(),
tooltipTitle: dnsQueryTooltip, tooltipTitle: dnsQueryTooltip,
response_status: RESPONSE_FILTER.ALL.QUERY, response_status: RESPONSE_FILTER.ALL.QUERY,
}, },
{ {
label: 'blocked_by', label: 'blocked_by',
count: formatNumber(numBlockedFiltering), count: numBlockedFiltering.toString(),
tooltipTitle: 'number_of_dns_query_blocked_24_hours', tooltipTitle: 'number_of_dns_query_blocked_24_hours',
response_status: RESPONSE_FILTER.BLOCKED.QUERY, response_status: RESPONSE_FILTER.BLOCKED.QUERY,
@@ -98,19 +98,19 @@ const Counters = ({ refreshButton, subtitle }: CountersProps) => {
}, },
{ {
label: 'stats_malware_phishing', label: 'stats_malware_phishing',
count: formatNumber(numReplacedSafebrowsing), count: numReplacedSafebrowsing.toString(),
tooltipTitle: 'number_of_dns_query_blocked_24_hours_by_sec', tooltipTitle: 'number_of_dns_query_blocked_24_hours_by_sec',
response_status: RESPONSE_FILTER.BLOCKED_THREATS.QUERY, response_status: RESPONSE_FILTER.BLOCKED_THREATS.QUERY,
}, },
{ {
label: 'stats_adult', label: 'stats_adult',
count: formatNumber(numReplacedParental), count: numReplacedParental.toString(),
tooltipTitle: 'number_of_dns_query_blocked_24_hours_adult', tooltipTitle: 'number_of_dns_query_blocked_24_hours_adult',
response_status: RESPONSE_FILTER.BLOCKED_ADULT_WEBSITES.QUERY, response_status: RESPONSE_FILTER.BLOCKED_ADULT_WEBSITES.QUERY,
}, },
{ {
label: 'enforced_save_search', label: 'enforced_save_search',
count: formatNumber(numReplacedSafesearch), count: numReplacedSafesearch.toString(),
tooltipTitle: 'number_of_dns_query_to_safe_search', tooltipTitle: 'number_of_dns_query_to_safe_search',
response_status: RESPONSE_FILTER.SAFE_SEARCH.QUERY, response_status: RESPONSE_FILTER.SAFE_SEARCH.QUERY,
}, },

View File

@@ -10,7 +10,6 @@ import Card from '../ui/Card';
import DomainCell from './DomainCell'; import DomainCell from './DomainCell';
import { DASHBOARD_TABLES_DEFAULT_PAGE_SIZE, TABLES_MIN_ROWS } from '../../helpers/constants'; import { DASHBOARD_TABLES_DEFAULT_PAGE_SIZE, TABLES_MIN_ROWS } from '../../helpers/constants';
import { formatNumber } from '../../helpers/helpers';
interface TimeCellProps { interface TimeCellProps {
value?: string | number; value?: string | number;
@@ -21,7 +20,7 @@ const TimeCell = ({ value }: TimeCellProps) => {
return ''; return '';
} }
const valueInMilliseconds = formatNumber(round(Number(value) * 1000)); const valueInMilliseconds = round(Number(value) * 1000);
return ( return (
<div className="logs__row o-hidden"> <div className="logs__row o-hidden">

View File

@@ -154,7 +154,7 @@ const Dashboard = ({
}} }}
disabled={processingProtection}> disabled={processingProtection}>
{protectionDisabledDuration {protectionDisabledDuration
? `${t('enable_protection_timer', { time: getRemaningTimeText(protectionDisabledDuration) })}` ? `${t('enable_protection_timer')} ${getRemaningTimeText(protectionDisabledDuration)}`
: getProtectionBtnText(protectionEnabled)} : getProtectionBtnText(protectionEnabled)}
</button> </button>

View File

@@ -1,6 +1,6 @@
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { Field, type InjectedFormProps, reduxForm } from 'redux-form'; import { Field, reduxForm } from 'redux-form';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { shallowEqual, useDispatch, useSelector } from 'react-redux'; import { shallowEqual, useDispatch, useSelector } from 'react-redux';
@@ -104,13 +104,14 @@ const FORM_NAMES = {
response_status: 'response_status', response_status: 'response_status',
}; };
type FiltersFormProps = { interface FiltersFormProps {
className?: string; className?: string;
responseStatusClass?: string; responseStatusClass?: string;
setIsLoading: (...args: unknown[]) => unknown; change: (...args: unknown[]) => unknown;
}; setIsLoading?: (...args: unknown[]) => unknown;
}
const Form = (props: FiltersFormProps & InjectedFormProps) => { const Form = (props: FiltersFormProps) => {
const { className = '', responseStatusClass, setIsLoading, change } = props; const { className = '', responseStatusClass, setIsLoading, change } = props;
const { t } = useTranslation(); const { t } = useTranslation();
@@ -141,6 +142,7 @@ const Form = (props: FiltersFormProps & InjectedFormProps) => {
const onInputClear = async () => { const onInputClear = async () => {
setIsLoading(true); setIsLoading(true);
change(FORM_NAMES.search, DEFAULT_LOGS_FILTER[FORM_NAMES.search]); change(FORM_NAMES.search, DEFAULT_LOGS_FILTER[FORM_NAMES.search]);
setIsLoading(false); setIsLoading(false);
}; };
@@ -193,7 +195,7 @@ const Form = (props: FiltersFormProps & InjectedFormProps) => {
); );
}; };
export const FiltersForm = reduxForm<Record<string, any>, FiltersFormProps>({ export default reduxForm({
form: FORM_NAME.LOGS_FILTER, form: FORM_NAME.LOGS_FILTER,
enableReinitialize: true, enableReinitialize: true,
})(Form); })(Form);

View File

@@ -2,7 +2,7 @@ import React from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { FiltersForm } from './Form'; import Form from './Form';
import { refreshFilteredLogs } from '../../../actions/queryLogs'; import { refreshFilteredLogs } from '../../../actions/queryLogs';
import { addSuccessToast } from '../../../actions/toasts'; import { addSuccessToast } from '../../../actions/toasts';
@@ -38,7 +38,12 @@ const Filters = ({ filter, setIsLoading }: FiltersProps) => {
</svg> </svg>
</button> </button>
</h1> </h1>
<FiltersForm responseStatusClass="d-sm-block" setIsLoading={setIsLoading} initialValues={filter} />
<Form
// responseStatusClass="d-sm-block"
// setIsLoading={setIsLoading}
initialValues={filter}
/>
</div> </div>
); );
}; };

View File

@@ -306,7 +306,7 @@ const ClientsTable = ({
return content; return content;
} }
return <LogsSearchLink search={row.original.name}>{content}</LogsSearchLink>; return <LogsSearchLink search={row.original.ids[0]}>{content}</LogsSearchLink>;
}, },
}, },
{ {

View File

@@ -38,7 +38,7 @@ class Encryption extends Component<EncryptionProps> {
handleFormChange = debounce((values) => { handleFormChange = debounce((values) => {
const submitValues = this.getSubmitValues(values); const submitValues = this.getSubmitValues(values);
if (submitValues.enabled) { if (submitValues.enabled || submitValues.serve_plain_dns) {
this.props.validateTlsConfig(submitValues); this.props.validateTlsConfig(submitValues);
} }
}, DEBOUNCE_TIMEOUT); }, DEBOUNCE_TIMEOUT);

View File

@@ -14,17 +14,6 @@
font-size: 15px; font-size: 15px;
} }
.guide__list {
margin-top: 16px;
padding-left: 0;
}
@media screen and (min-width: 768px) {
.guide__list {
padding-left: 24px;
}
}
.guide__address { .guide__address {
display: block; display: block;
margin-bottom: 7px; margin-bottom: 7px;

View File

@@ -33,13 +33,13 @@ const SetupGuide = ({
<Trans>install_devices_address</Trans>: <Trans>install_devices_address</Trans>:
</div> </div>
<ul className="guide__list"> <div className="mt-3">
{dnsAddresses.map((ip: any) => ( {dnsAddresses.map((ip: any) => (
<li key={ip} className="guide__address"> <li key={ip} className="guide__address">
{ip} {ip}
</li> </li>
))} ))}
</ul> </div>
</div> </div>
<Guide dnsAddresses={dnsAddresses} /> <Guide dnsAddresses={dnsAddresses} />

View File

@@ -12,11 +12,12 @@ const Version = () => {
const dashboard = useSelector((state: RootState) => state.dashboard, shallowEqual); const dashboard = useSelector((state: RootState) => state.dashboard, shallowEqual);
const install = useSelector((state: RootState) => state.install, shallowEqual); const install = useSelector((state: RootState) => state.install, shallowEqual);
if (!dashboard && !install) { if (!dashboard || !install) {
return null; return null;
} }
const version = dashboard?.dnsVersion || install?.dnsVersion; const { dnsVersion, processingVersion, checkUpdateFlag } = dashboard;
const version = dnsVersion || install?.dnsVersion;
const onClick = () => { const onClick = () => {
dispatch(getVersion(true)); dispatch(getVersion(true));
@@ -34,12 +35,12 @@ const Version = () => {
</> </>
)} )}
{dashboard?.checkUpdateFlag && ( {checkUpdateFlag && (
<button <button
type="button" type="button"
className="btn btn-icon btn-icon-sm btn-outline-primary btn-sm ml-2" className="btn btn-icon btn-icon-sm btn-outline-primary btn-sm ml-2"
onClick={onClick} onClick={onClick}
disabled={dashboard?.processingVersion} disabled={processingVersion}
title={t('check_updates_now')}> title={t('check_updates_now')}>
<svg className="icons icon12"> <svg className="icons icon12">
<use xlinkHref="#refresh" /> <use xlinkHref="#refresh" />

View File

@@ -238,12 +238,6 @@ export default {
"homepage": "https://github.com/hagezi/dns-blocklists", "homepage": "https://github.com/hagezi/dns-blocklists",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_51.txt" "source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_51.txt"
}, },
"hagezi_samsung_tracker_blocklist": {
"name": "HaGeZi's Samsung Tracker Blocklist",
"categoryId": "other",
"homepage": "https://github.com/hagezi/dns-blocklists",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_61.txt"
},
"hagezi_the_worlds_most_abused_tlds": { "hagezi_the_worlds_most_abused_tlds": {
"name": "HaGeZi's The World's Most Abused TLDs", "name": "HaGeZi's The World's Most Abused TLDs",
"categoryId": "security", "categoryId": "security",
@@ -262,18 +256,6 @@ export default {
"homepage": "https://github.com/hagezi/dns-blocklists", "homepage": "https://github.com/hagezi/dns-blocklists",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_49.txt" "source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_49.txt"
}, },
"hagezi_windows_office_tracker_blocklist": {
"name": "HaGeZi's Windows/Office Tracker Blocklist",
"categoryId": "other",
"homepage": "https://github.com/hagezi/dns-blocklists",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_63.txt"
},
"hagezi_xiaomi_tracking_blocklist": {
"name": "HaGeZi's Xiaomi Tracker Blocklist",
"categoryId": "other",
"homepage": "https://github.com/hagezi/dns-blocklists",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_60.txt"
},
"no_google": { "no_google": {
"name": "No Google", "name": "No Google",
"categoryId": "other", "categoryId": "other",
@@ -313,7 +295,7 @@ export default {
"phishing_army": { "phishing_army": {
"name": "Phishing Army", "name": "Phishing Army",
"categoryId": "security", "categoryId": "security",
"homepage": "https://phishing.army/", "homepage": "https://gitlab.com/malware-filter/phishing-filter",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_18.txt" "source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_18.txt"
}, },
"scam_blocklist_by_durablenapkin": { "scam_blocklist_by_durablenapkin": {
@@ -358,17 +340,17 @@ export default {
"homepage": "https://github.com/uBlockOrigin/uAssets", "homepage": "https://github.com/uBlockOrigin/uAssets",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_50.txt" "source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_50.txt"
}, },
"ukrainian_security_filter": {
"name": "Ukrainian Security Filter",
"categoryId": "other",
"homepage": "https://github.com/braveinnovators/ukrainian-security-filter",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_62.txt"
},
"urlhaus_filter_online": { "urlhaus_filter_online": {
"name": "Malicious URL Blocklist (URLHaus)", "name": "Malicious URL Blocklist (URLHaus)",
"categoryId": "security", "categoryId": "security",
"homepage": "https://urlhaus.abuse.ch/", "homepage": "https://urlhaus.abuse.ch/",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_11.txt" "source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_11.txt"
},
"windowsspyblocker_hosts_spy_rules": {
"name": "WindowsSpyBlocker - Hosts spy rules",
"categoryId": "other",
"homepage": "https://github.com/crazy-max/WindowsSpyBlocker",
"source": "https://adguardteam.github.io/HostlistsRegistry/assets/filter_23.txt"
} }
} }
} }

View File

@@ -451,10 +451,13 @@ export const getParamsForClientsSearch = (data: any, param: any, additionalParam
clients.add(e[additionalParam]); clients.add(e[additionalParam]);
} }
}); });
const params = {};
const ids = Array.from(clients.values());
ids.forEach((id, i) => {
params[`ip${i}`] = id;
});
return { return params;
clients: Array.from(clients).map((id) => ({ id })),
};
}; };
/** /**
@@ -521,7 +524,7 @@ export const getObjDiff = (initialValues: any, values: any) =>
* @param num {number} to format * @param num {number} to format
* @returns {string} Returns a string with a language-sensitive representation of this number * @returns {string} Returns a string with a language-sensitive representation of this number
*/ */
export const formatNumber = (num: number): string => { export const formatNumber = (num: any) => {
const currentLanguage = i18n.languages[0] || DEFAULT_LANGUAGE; const currentLanguage = i18n.languages[0] || DEFAULT_LANGUAGE;
return num.toLocaleString(currentLanguage); return num.toLocaleString(currentLanguage);
}; };
@@ -670,16 +673,9 @@ export const countClientsStatistics = (ids: any, autoClients: any) => {
* @param {function} t translate * @param {function} t translate
* @returns {string} * @returns {string}
*/ */
export const formatElapsedMs = (elapsedMs: string, t: (key: string) => string) => { export const formatElapsedMs = (elapsedMs: any, t: any) => {
const parsedElapsedMs = parseInt(elapsedMs, 10); const formattedElapsedMs = parseInt(elapsedMs, 10) || parseFloat(elapsedMs).toFixed(2);
return `${formattedElapsedMs} ${t('milliseconds_abbreviation')}`;
if (Number.isNaN(parsedElapsedMs)) {
return elapsedMs;
}
const formattedMs = formatNumber(parsedElapsedMs);
return `${formattedMs} ${t('milliseconds_abbreviation')}`;
}; };
/** /**
@@ -761,9 +757,12 @@ type NestedObject = {
order: number; order: number;
}; };
export const getObjectKeysSorted = <T extends Record<string, NestedObject>, K extends keyof NestedObject>( export const getObjectKeysSorted = <
T extends Record<string, NestedObject>,
K extends keyof NestedObject
>(
object: T, object: T,
sortKey: K, sortKey: K
): string[] => { ): string[] => {
return Object.entries(object) return Object.entries(object)
.sort(([, a], [, b]) => (a[sortKey] as number) - (b[sortKey] as number)) .sort(([, a], [, b]) => (a[sortKey] as number) - (b[sortKey] as number))

View File

@@ -66,7 +66,7 @@ export const renderFormattedClientCell = (value: any, info: any, isDetailed = fa
} }
return ( return (
<div className="logs__text logs__text--client mw-100" title={value}> <div className="logs__text mw-100" title={value}>
<Link to={`logs?search="${encodeURIComponent(value)}"`}>{nameContainer}</Link> <Link to={`logs?search="${encodeURIComponent(value)}"`}>{nameContainer}</Link>
{whoisContainer} {whoisContainer}
</div> </div>

File diff suppressed because it is too large Load Diff

51
go.mod
View File

@@ -1,25 +1,24 @@
module github.com/AdguardTeam/AdGuardHome module github.com/AdguardTeam/AdGuardHome
go 1.23.5 go 1.22.4
require ( require (
github.com/AdguardTeam/dnsproxy v0.73.5 github.com/AdguardTeam/dnsproxy v0.71.2
github.com/AdguardTeam/golibs v0.31.0 github.com/AdguardTeam/golibs v0.23.2
github.com/AdguardTeam/urlfilter v0.20.0 github.com/AdguardTeam/urlfilter v0.18.0
github.com/NYTimes/gziphandler v1.1.1 github.com/NYTimes/gziphandler v1.1.1
github.com/ameshkov/dnscrypt/v2 v2.3.0 github.com/ameshkov/dnscrypt/v2 v2.3.0
github.com/bluele/gcache v0.0.2 github.com/bluele/gcache v0.0.2
github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500 github.com/c2h5oh/datasize v0.0.0-20231215233829-aa82cc1e6500
github.com/digineo/go-ipset/v2 v2.2.1 github.com/digineo/go-ipset/v2 v2.2.1
github.com/fsnotify/fsnotify v1.8.0 github.com/dimfeld/httptreemux/v5 v5.5.0
// TODO(e.burkov): This package is deprecated; find a new one or use our github.com/fsnotify/fsnotify v1.7.0
// own code for that. Perhaps, use gopacket. github.com/go-ping/ping v1.1.0
github.com/go-ping/ping v1.2.0
github.com/google/go-cmp v0.6.0 github.com/google/go-cmp v0.6.0
github.com/google/gopacket v1.1.19 github.com/google/gopacket v1.1.19
github.com/google/renameio/v2 v2.0.0 github.com/google/renameio/v2 v2.0.0
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/insomniacslk/dhcp v0.0.0-20241203100832-a481575ed0ef github.com/insomniacslk/dhcp v0.0.0-20240419123447-f1cffa2c0c49
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86
github.com/kardianos/service v1.2.2 github.com/kardianos/service v1.2.2
github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118 github.com/mdlayher/ethernet v0.0.0-20220221185849-529eae5b6118
@@ -28,15 +27,15 @@ require (
// TODO(a.garipov): This package is deprecated; find a new one or use our // TODO(a.garipov): This package is deprecated; find a new one or use our
// own code for that. Perhaps, use gopacket. // own code for that. Perhaps, use gopacket.
github.com/mdlayher/raw v0.1.0 github.com/mdlayher/raw v0.1.0
github.com/miekg/dns v1.1.62 github.com/miekg/dns v1.1.59
github.com/quic-go/quic-go v0.48.2 github.com/quic-go/quic-go v0.44.0
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.9.0
github.com/ti-mo/netfilter v0.5.2 github.com/ti-mo/netfilter v0.5.2
go.etcd.io/bbolt v1.3.11 go.etcd.io/bbolt v1.3.10
golang.org/x/crypto v0.31.0 golang.org/x/crypto v0.23.0
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
golang.org/x/net v0.33.0 golang.org/x/net v0.25.0
golang.org/x/sys v0.28.0 golang.org/x/sys v0.20.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/natefinch/lumberjack.v2 v2.2.1
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
howett.net/plist v1.0.1 howett.net/plist v1.0.1
@@ -49,19 +48,19 @@ require (
github.com/beefsack/go-rate v0.0.0-20220214233405-116f4ca011a0 // indirect github.com/beefsack/go-rate v0.0.0-20220214233405-116f4ca011a0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect github.com/google/pprof v0.0.0-20240521024322-9665fa269a30 // indirect
github.com/mdlayher/socket v0.5.1 // indirect github.com/mdlayher/socket v0.5.1 // indirect
github.com/onsi/ginkgo/v2 v2.22.1 // indirect github.com/onsi/ginkgo/v2 v2.17.3 // indirect
github.com/patrickmn/go-cache v2.1.0+incompatible // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect
github.com/pierrec/lz4/v4 v4.1.21 // indirect github.com/pierrec/lz4/v4 v4.1.21 // indirect
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/quic-go/qpack v0.5.1 // indirect github.com/quic-go/qpack v0.4.0 // indirect
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect
go.uber.org/mock v0.5.0 // indirect go.uber.org/mock v0.4.0 // indirect
golang.org/x/mod v0.22.0 // indirect golang.org/x/mod v0.17.0 // indirect
golang.org/x/sync v0.10.0 // indirect golang.org/x/sync v0.7.0 // indirect
golang.org/x/text v0.21.0 // indirect golang.org/x/text v0.15.0 // indirect
golang.org/x/tools v0.28.0 // indirect golang.org/x/tools v0.21.0 // indirect
gonum.org/v1/gonum v0.15.1 // indirect gonum.org/v1/gonum v0.15.0 // indirect
) )

122
go.sum
View File

@@ -1,9 +1,9 @@
github.com/AdguardTeam/dnsproxy v0.73.5 h1:3EiVaTfmuW6PcJGbqloR6ZdHACsrYkm9z0eH8ZQTZnQ= github.com/AdguardTeam/dnsproxy v0.71.2 h1:dFG2wga4GDdj1eI3rU2wqjQ6QGQm9MjLRb5ZzyH3Vgg=
github.com/AdguardTeam/dnsproxy v0.73.5/go.mod h1:Oqw+k7LyjDObfYzXYCkpgtirbzbUrmotr92jrb3N09I= github.com/AdguardTeam/dnsproxy v0.71.2/go.mod h1:huI5zyWhlimHBhg0jt2CMinXzsEHymI+WlvxIfmfEGA=
github.com/AdguardTeam/golibs v0.31.0 h1:Z0oPfLTLw6iZmpE58dePy2Bel0MaX+lnDwtFEE5EmIo= github.com/AdguardTeam/golibs v0.23.2 h1:rMjYantwtQ39e8G4zBQ6ZLlm4s3XH30Bc9VxhoOHwao=
github.com/AdguardTeam/golibs v0.31.0/go.mod h1:wIkZ9o2UnppeW6/YD7yJB71dYbMhiuC1Fh/I2ElW7GQ= github.com/AdguardTeam/golibs v0.23.2/go.mod h1:o9i55Sx6v7qogRQeqaBfmLbC/pZqeMBWi015U5PTDY0=
github.com/AdguardTeam/urlfilter v0.20.0 h1:X32qiuVCVd8WDYCEsbdZKfXMzwdVqrdulamtUi4rmzs= github.com/AdguardTeam/urlfilter v0.18.0 h1:ZZzwODC/ADpjJSODxySrrUnt/fvOCfGFaCW6j+wsGfQ=
github.com/AdguardTeam/urlfilter v0.20.0/go.mod h1:gjrywLTxfJh6JOkwi9SU+frhP7kVVEZ5exFGkR99qpk= github.com/AdguardTeam/urlfilter v0.18.0/go.mod h1:IXxBwedLiZA2viyHkaFxY/8mjub0li2PXRg8a3d9Z1s=
github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I=
github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c=
github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY=
@@ -25,14 +25,16 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/digineo/go-ipset/v2 v2.2.1 h1:k6skY+0fMqeUjjeWO/m5OuWPSZUAn7AucHMnQ1MX77g= github.com/digineo/go-ipset/v2 v2.2.1 h1:k6skY+0fMqeUjjeWO/m5OuWPSZUAn7AucHMnQ1MX77g=
github.com/digineo/go-ipset/v2 v2.2.1/go.mod h1:wBsNzJlZlABHUITkesrggFnZQtgW5wkqw1uo8Qxe0VU= github.com/digineo/go-ipset/v2 v2.2.1/go.mod h1:wBsNzJlZlABHUITkesrggFnZQtgW5wkqw1uo8Qxe0VU=
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= github.com/dimfeld/httptreemux/v5 v5.5.0 h1:p8jkiMrCuZ0CmhwYLcbNbl7DDo21fozhKHQ2PccwOFQ=
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/dimfeld/httptreemux/v5 v5.5.0/go.mod h1:QeEylH57C0v3VO0tkKraVz9oD3Uu93CKPnTLbsidvSw=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ping/ping v1.2.0 h1:vsJ8slZBZAXNCK4dPcI2PEE9eM9n9RbXbGouVQ/Y4yQ= github.com/go-ping/ping v1.1.0 h1:3MCGhVX4fyEUuhsfwPrsEdQw6xspHkv5zHsiSoDFZYw=
github.com/go-ping/ping v1.2.0/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk= github.com/go-ping/ping v1.1.0/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk=
github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI=
github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@@ -42,8 +44,8 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg= github.com/google/pprof v0.0.0-20240521024322-9665fa269a30 h1:r6YdmbD41tGHeCWDyHF691LWtL7D1iSTyJaKejTWwVU=
github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= github.com/google/pprof v0.0.0-20240521024322-9665fa269a30/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg= github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg=
github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4= github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@@ -51,8 +53,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 h1:/jC7qQFrv8CrSJVmaolDVOxTfS9kc36uB6H40kdbQq8= github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714 h1:/jC7qQFrv8CrSJVmaolDVOxTfS9kc36uB6H40kdbQq8=
github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis= github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis=
github.com/insomniacslk/dhcp v0.0.0-20241203100832-a481575ed0ef h1:NzQKDfd5ZOPnuZYf9MnRee8x2qecsVqzsnaLjEZiBko= github.com/insomniacslk/dhcp v0.0.0-20240419123447-f1cffa2c0c49 h1:/OuvSMGT9+xnyZ+7MZQ1zdngaCCAdPoSw8B/uurZ7pg=
github.com/insomniacslk/dhcp v0.0.0-20241203100832-a481575ed0ef/go.mod h1:KclMyHxX06VrVr0DJmeFSUb1ankt7xTfoOA35pCkoic= github.com/insomniacslk/dhcp v0.0.0-20240419123447-f1cffa2c0c49/go.mod h1:KclMyHxX06VrVr0DJmeFSUb1ankt7xTfoOA35pCkoic=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 h1:elKwZS1OcdQ0WwEDBeqxKwb7WB62QX8bvZ/FJnVXIfk= github.com/josharian/native v1.1.1-0.20230202152459-5c7d0dd6ab86 h1:elKwZS1OcdQ0WwEDBeqxKwb7WB62QX8bvZ/FJnVXIfk=
@@ -76,14 +78,14 @@ github.com/mdlayher/raw v0.1.0/go.mod h1:yXnxvs6c0XoF/aK52/H5PjsVHmWBCFfZUfoh/Y5
github.com/mdlayher/socket v0.2.1/go.mod h1:QLlNPkFR88mRUNQIzRBMfXxwKal8H7u1h3bL1CV+f0E= github.com/mdlayher/socket v0.2.1/go.mod h1:QLlNPkFR88mRUNQIzRBMfXxwKal8H7u1h3bL1CV+f0E=
github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos= github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ= github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= github.com/miekg/dns v1.1.59 h1:C9EXc/UToRwKLhK5wKU/I4QVsBUc8kE6MkHBkeypWZs=
github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ= github.com/miekg/dns v1.1.59/go.mod h1:nZpewl5p6IvctfgrckopVx2OlSEHPRO/U4SYkRklrEk=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/onsi/ginkgo/v2 v2.22.1 h1:QW7tbJAUDyVDVOM5dFa7qaybo+CRfR7bemlQUN6Z8aM= github.com/onsi/ginkgo/v2 v2.17.3 h1:oJcvKpIb7/8uLpDDtnQuf18xVnwKp8DTD7DQ6gTd/MU=
github.com/onsi/ginkgo/v2 v2.22.1/go.mod h1:S6aTpoRsSq2cZOd+pssHAlKW/Q/jZt6cPrPlnj4a1xM= github.com/onsi/ginkgo/v2 v2.17.3/go.mod h1:nP2DPOQoNsQmsVyv5rDA8JkXQoCs6goXIvr/PRJ1eCc=
github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw= github.com/onsi/gomega v1.33.0 h1:snPCflnZrpMsy94p4lXVEkHo12lmPnc3vY5XBbreexE=
github.com/onsi/gomega v1.36.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog= github.com/onsi/gomega v1.33.0/go.mod h1:+925n5YtiFsLzzafLUHzVMBpvvRAzrydIBiSIxjX3wY=
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
@@ -95,57 +97,57 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= github.com/quic-go/qpack v0.4.0 h1:Cr9BXA1sQS2SmDUWjSofMPNKmvF6IiIfDRmgU0w1ZCo=
github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= github.com/quic-go/qpack v0.4.0/go.mod h1:UZVnYIfi5GRk+zI9UMaCPsmZ2xKJP7XBUvVyT1Knj9A=
github.com/quic-go/quic-go v0.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE= github.com/quic-go/quic-go v0.44.0 h1:So5wOr7jyO4vzL2sd8/pD9Kesciv91zSk8BoFngItQ0=
github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs= github.com/quic-go/quic-go v0.44.0/go.mod h1:z4cx/9Ny9UtGITIPzmPTXh1ULfOyWh4qGQlpnPcWmek=
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= github.com/shirou/gopsutil/v3 v3.23.7 h1:C+fHO8hfIppoJ1WdsVm1RoI0RwXoNdfTK7yWXV0wVj4=
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk= github.com/shirou/gopsutil/v3 v3.23.7/go.mod h1:c4gnmoRC0hQuaLqvxnx1//VXQ0Ms/X9UnJF8pddY5z4=
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM= github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/ti-mo/netfilter v0.2.0/go.mod h1:8GbBGsY/8fxtyIdfwy29JiluNcPK4K7wIT+x42ipqUU= github.com/ti-mo/netfilter v0.2.0/go.mod h1:8GbBGsY/8fxtyIdfwy29JiluNcPK4K7wIT+x42ipqUU=
github.com/ti-mo/netfilter v0.5.2 h1:CTjOwFuNNeZ9QPdRXt1MZFLFUf84cKtiQutNauHWd40= github.com/ti-mo/netfilter v0.5.2 h1:CTjOwFuNNeZ9QPdRXt1MZFLFUf84cKtiQutNauHWd40=
github.com/ti-mo/netfilter v0.5.2/go.mod h1:Btx3AtFiOVdHReTDmP9AE+hlkOcvIy403u7BXXbWZKo= github.com/ti-mo/netfilter v0.5.2/go.mod h1:Btx3AtFiOVdHReTDmP9AE+hlkOcvIy403u7BXXbWZKo=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4=
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM= github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM=
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA= github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.3 h1:E1ctvB7uKFMOJw3fdOW32DwGE9I7t++CRUEMKvFoFiw=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/yusufpapurcu/wmi v1.2.3/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= go.etcd.io/bbolt v1.3.10 h1:+BqfJTcCzTItrop8mq/lbzL8wSGtj94UO/3U31shqG0=
go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= go.etcd.io/bbolt v1.3.10/go.mod h1:bK3UQLPJZly7IlNmV7uVHJDxfe5aK9Ll93e/74Y9oEQ=
go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/gXhegadRdwBIXEFWDo= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -156,25 +158,25 @@ golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.4.1-0.20230131160137-e7d7f63158de/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8= golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.15.1 h1:FNy7N6OUZVUaWG9pTiD+jlhdQ3lMP+/LcTpJ6+a8sQ0= gonum.org/v1/gonum v0.15.0 h1:2lYxjRbTYyxkJxlhC+LvJIx3SsANPdRybu1tGj9/OrQ=
gonum.org/v1/gonum v0.15.1/go.mod h1:eZTZuRFrzu5pcyjN5wJhcIhnUdNijYxX1T2IcrOGY0o= gonum.org/v1/gonum v0.15.0/go.mod h1:xzZVBJBtS+Mz4q0Yl2LJTk+OxOg4jiXZ7qBoM0uISGo=
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

View File

@@ -0,0 +1,94 @@
package aghalg
// RingBuffer is the implementation of ring buffer data structure.
type RingBuffer[T any] struct {
buf []T
cur uint
full bool
}
// NewRingBuffer initializes the new instance of ring buffer. size must be
// greater or equal to zero.
func NewRingBuffer[T any](size uint) (rb *RingBuffer[T]) {
return &RingBuffer[T]{
buf: make([]T, size),
}
}
// Append appends an element to the buffer.
func (rb *RingBuffer[T]) Append(e T) {
if len(rb.buf) == 0 {
return
}
rb.buf[rb.cur] = e
rb.cur = (rb.cur + 1) % uint(cap(rb.buf))
if rb.cur == 0 {
rb.full = true
}
}
// Range calls cb for each element of the buffer. If cb returns false it stops.
func (rb *RingBuffer[T]) Range(cb func(T) (cont bool)) {
before, after := rb.splitCur()
for _, e := range before {
if !cb(e) {
return
}
}
for _, e := range after {
if !cb(e) {
return
}
}
}
// ReverseRange calls cb for each element of the buffer in reverse order. If
// cb returns false it stops.
func (rb *RingBuffer[T]) ReverseRange(cb func(T) (cont bool)) {
before, after := rb.splitCur()
for i := len(after) - 1; i >= 0; i-- {
if !cb(after[i]) {
return
}
}
for i := len(before) - 1; i >= 0; i-- {
if !cb(before[i]) {
return
}
}
}
// splitCur splits the buffer in two, before and after current position in
// chronological order. If buffer is not full, after is nil.
func (rb *RingBuffer[T]) splitCur() (before, after []T) {
if len(rb.buf) == 0 {
return nil, nil
}
cur := rb.cur
if !rb.full {
return rb.buf[:cur], nil
}
return rb.buf[cur:], rb.buf[:cur]
}
// Len returns a length of the buffer.
func (rb *RingBuffer[T]) Len() (l uint) {
if !rb.full {
return rb.cur
}
return uint(cap(rb.buf))
}
// Clear clears the buffer.
func (rb *RingBuffer[T]) Clear() {
rb.full = false
rb.cur = 0
}

View File

@@ -0,0 +1,169 @@
package aghalg_test
import (
"slices"
"testing"
"github.com/AdguardTeam/AdGuardHome/internal/aghalg"
"github.com/stretchr/testify/assert"
)
// elements is a helper function that returns n elements of the buffer.
func elements(b *aghalg.RingBuffer[int], n uint, reverse bool) (es []int) {
fn := b.Range
if reverse {
fn = b.ReverseRange
}
var i uint
fn(func(e int) (cont bool) {
if i >= n {
return false
}
es = append(es, e)
i++
return true
})
return es
}
func TestNewRingBuffer(t *testing.T) {
t.Run("success_and_clear", func(t *testing.T) {
b := aghalg.NewRingBuffer[int](5)
for i := range 10 {
b.Append(i)
}
assert.Equal(t, []int{5, 6, 7, 8, 9}, elements(b, b.Len(), false))
b.Clear()
assert.Zero(t, b.Len())
})
t.Run("zero", func(t *testing.T) {
b := aghalg.NewRingBuffer[int](0)
for i := range 10 {
b.Append(i)
bufLen := b.Len()
assert.EqualValues(t, 0, bufLen)
assert.Empty(t, elements(b, bufLen, false))
assert.Empty(t, elements(b, bufLen, true))
}
})
t.Run("single", func(t *testing.T) {
b := aghalg.NewRingBuffer[int](1)
for i := range 10 {
b.Append(i)
bufLen := b.Len()
assert.EqualValues(t, 1, bufLen)
assert.Equal(t, []int{i}, elements(b, bufLen, false))
assert.Equal(t, []int{i}, elements(b, bufLen, true))
}
})
}
func TestRingBuffer_Range(t *testing.T) {
const size = 5
b := aghalg.NewRingBuffer[int](size)
testCases := []struct {
name string
want []int
count int
length uint
}{{
name: "three",
count: 3,
length: 3,
want: []int{0, 1, 2},
}, {
name: "ten",
count: 10,
length: size,
want: []int{5, 6, 7, 8, 9},
}, {
name: "hundred",
count: 100,
length: size,
want: []int{95, 96, 97, 98, 99},
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
for i := range tc.count {
b.Append(i)
}
bufLen := b.Len()
assert.Equal(t, tc.length, bufLen)
want := tc.want
assert.Equal(t, want, elements(b, bufLen, false))
assert.Equal(t, want[:len(want)-1], elements(b, bufLen-1, false))
assert.Equal(t, want[:len(want)/2], elements(b, bufLen/2, false))
want = want[:cap(want)]
slices.Reverse(want)
assert.Equal(t, want, elements(b, bufLen, true))
assert.Equal(t, want[:len(want)-1], elements(b, bufLen-1, true))
assert.Equal(t, want[:len(want)/2], elements(b, bufLen/2, true))
})
}
}
func TestRingBuffer_Range_increment(t *testing.T) {
const size = 5
b := aghalg.NewRingBuffer[int](size)
testCases := []struct {
name string
want []int
}{{
name: "one",
want: []int{0},
}, {
name: "two",
want: []int{0, 1},
}, {
name: "three",
want: []int{0, 1, 2},
}, {
name: "four",
want: []int{0, 1, 2, 3},
}, {
name: "five",
want: []int{0, 1, 2, 3, 4},
}, {
name: "six",
want: []int{1, 2, 3, 4, 5},
}, {
name: "seven",
want: []int{2, 3, 4, 5, 6},
}, {
name: "eight",
want: []int{3, 4, 5, 6, 7},
}, {
name: "nine",
want: []int{4, 5, 6, 7, 8},
}, {
name: "ten",
want: []int{5, 6, 7, 8, 9},
}}
for i, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
b.Append(i)
bufLen := b.Len()
assert.Equal(t, tc.want, elements(b, bufLen, false))
slices.Reverse(tc.want)
assert.Equal(t, tc.want, elements(b, bufLen, true))
})
}
}

View File

@@ -2,16 +2,19 @@
package aghhttp package aghhttp
import ( import (
"context"
"fmt" "fmt"
"io" "io"
"log/slog"
"net/http" "net/http"
"github.com/AdguardTeam/AdGuardHome/internal/version" "github.com/AdguardTeam/AdGuardHome/internal/version"
"github.com/AdguardTeam/golibs/httphdr" "github.com/AdguardTeam/golibs/httphdr"
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/logutil/slogutil" )
// HTTP scheme constants.
const (
SchemeHTTP = "http"
SchemeHTTPS = "https"
) )
// RegisterFunc is the function that sets the handler to handle the URL for the // RegisterFunc is the function that sets the handler to handle the URL for the
@@ -28,39 +31,12 @@ func OK(w http.ResponseWriter) {
} }
// Error writes formatted message to w and also logs it. // Error writes formatted message to w and also logs it.
//
// TODO(s.chzhen): Remove it.
func Error(r *http.Request, w http.ResponseWriter, code int, format string, args ...any) { func Error(r *http.Request, w http.ResponseWriter, code int, format string, args ...any) {
text := fmt.Sprintf(format, args...) text := fmt.Sprintf(format, args...)
log.Error("%s %s %s: %s", r.Method, r.Host, r.URL, text) log.Error("%s %s %s: %s", r.Method, r.Host, r.URL, text)
http.Error(w, text, code) http.Error(w, text, code)
} }
// ErrorAndLog writes formatted message to w and also logs it with the specified
// logging level.
func ErrorAndLog(
ctx context.Context,
l *slog.Logger,
r *http.Request,
w http.ResponseWriter,
code int,
format string,
args ...any,
) {
text := fmt.Sprintf(format, args...)
l.ErrorContext(
ctx,
"http error",
"host", r.Host,
"method", r.Method,
"raddr", r.RemoteAddr,
"request_uri", r.RequestURI,
slogutil.KeyError, text,
)
http.Error(w, text, code)
}
// UserAgent returns the ID of the service as a User-Agent string. It can also // UserAgent returns the ID of the service as a User-Agent string. It can also
// be used as the value of the Server HTTP header. // be used as the value of the Server HTTP header.
func UserAgent() (ua string) { func UserAgent() (ua string) {

View File

@@ -161,8 +161,7 @@ func (hc *HostsContainer) handleEvents() {
defer close(hc.updates) defer close(hc.updates)
eventsCh := hc.watcher.Events() ok, eventsCh := true, hc.watcher.Events()
ok := eventsCh != nil
for ok { for ok {
select { select {
case _, ok = <-eventsCh: case _, ok = <-eventsCh:

View File

@@ -6,10 +6,10 @@ import (
"bufio" "bufio"
"fmt" "fmt"
"io" "io"
"net"
"strings" "strings"
"github.com/AdguardTeam/AdGuardHome/internal/aghos" "github.com/AdguardTeam/AdGuardHome/internal/aghos"
"github.com/AdguardTeam/golibs/netutil"
) )
func ifaceHasStaticIP(ifaceName string) (ok bool, err error) { func ifaceHasStaticIP(ifaceName string) (ok bool, err error) {
@@ -38,13 +38,9 @@ func (n interfaceName) rcConfStaticConfig(r io.Reader) (_ []string, cont bool, e
// TODO(e.burkov): Expand the check to cover possible // TODO(e.burkov): Expand the check to cover possible
// configurations from man rc.conf(5). // configurations from man rc.conf(5).
fields := strings.Fields(line[cfgLeft:cfgRight]) fields := strings.Fields(line[cfgLeft:cfgRight])
switch { if len(fields) >= 2 &&
case strings.EqualFold(fields[0], "inet") &&
len(fields) < 2, net.ParseIP(fields[1]) != nil {
!strings.EqualFold(fields[0], "inet"),
!netutil.IsValidIPString(fields[1]):
continue
default:
return nil, false, s.Err() return nil, false, s.Err()
} }
} }

View File

@@ -6,10 +6,10 @@ import (
"bufio" "bufio"
"fmt" "fmt"
"io" "io"
"net"
"strings" "strings"
"github.com/AdguardTeam/AdGuardHome/internal/aghos" "github.com/AdguardTeam/AdGuardHome/internal/aghos"
"github.com/AdguardTeam/golibs/netutil"
) )
func ifaceHasStaticIP(ifaceName string) (ok bool, err error) { func ifaceHasStaticIP(ifaceName string) (ok bool, err error) {
@@ -25,13 +25,7 @@ func hostnameIfStaticConfig(r io.Reader) (_ []string, ok bool, err error) {
for s.Scan() { for s.Scan() {
line := strings.TrimSpace(s.Text()) line := strings.TrimSpace(s.Text())
fields := strings.Fields(line) fields := strings.Fields(line)
switch { if len(fields) >= 2 && fields[0] == "inet" && net.ParseIP(fields[1]) != nil {
case
len(fields) < 2,
fields[0] != "inet",
!netutil.IsValidIPString(fields[1]):
continue
default:
return nil, false, s.Err() return nil, false, s.Err()
} }
} }

View File

@@ -160,34 +160,3 @@ func (w *osWatcher) handleErrors() {
log.Error("%s: %s", osWatcherPref, err) log.Error("%s: %s", osWatcherPref, err)
} }
} }
// EmptyFSWatcher is a no-op implementation of the [FSWatcher] interface. It
// may be used on systems not supporting filesystem events.
type EmptyFSWatcher struct{}
// type check
var _ FSWatcher = EmptyFSWatcher{}
// Start implements the [FSWatcher] interface for EmptyFSWatcher. It always
// returns nil error.
func (EmptyFSWatcher) Start() (err error) {
return nil
}
// Close implements the [FSWatcher] interface for EmptyFSWatcher. It always
// returns nil error.
func (EmptyFSWatcher) Close() (err error) {
return nil
}
// Events implements the [FSWatcher] interface for EmptyFSWatcher. It always
// returns nil channel.
func (EmptyFSWatcher) Events() (e <-chan event) {
return nil
}
// Add implements the [FSWatcher] interface for EmptyFSWatcher. It always
// returns nil error.
func (EmptyFSWatcher) Add(_ string) (err error) {
return nil
}

View File

@@ -7,7 +7,6 @@ import (
"bufio" "bufio"
"fmt" "fmt"
"io" "io"
"io/fs"
"os" "os"
"os/exec" "os/exec"
"path" "path"
@@ -20,16 +19,25 @@ import (
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
) )
// Default file, binary, and directory permissions. // UnsupportedError is returned by functions and methods when a particular
const ( // operation Op cannot be performed on the current OS.
DefaultPermDir fs.FileMode = 0o700 type UnsupportedError struct {
DefaultPermExe fs.FileMode = 0o700 Op string
DefaultPermFile fs.FileMode = 0o600 OS string
) }
// Unsupported is a helper that returns a wrapped [errors.ErrUnsupported]. // Error implements the error interface for *UnsupportedError.
func (err *UnsupportedError) Error() (msg string) {
return fmt.Sprintf("%s is unsupported on %s", err.Op, err.OS)
}
// Unsupported is a helper that returns an *UnsupportedError with the Op field
// set to op and the OS field set to the current OS.
func Unsupported(op string) (err error) { func Unsupported(op string) (err error) {
return fmt.Errorf("%s: not supported on %s: %w", op, runtime.GOOS, errors.ErrUnsupported) return &UnsupportedError{
Op: op,
OS: runtime.GOOS,
}
} }
// SetRlimit sets user-specified limit of how many fd's we can use. // SetRlimit sets user-specified limit of how many fd's we can use.
@@ -146,6 +154,16 @@ func IsOpenWrt() (ok bool) {
return isOpenWrt() return isOpenWrt()
} }
// NotifyReconfigureSignal notifies c on receiving reconfigure signals.
func NotifyReconfigureSignal(c chan<- os.Signal) {
notifyReconfigureSignal(c)
}
// IsReconfigureSignal returns true if sig is a reconfigure signal.
func IsReconfigureSignal(sig os.Signal) (ok bool) {
return isReconfigureSignal(sig)
}
// SendShutdownSignal sends the shutdown signal to the channel. // SendShutdownSignal sends the shutdown signal to the channel.
func SendShutdownSignal(c chan<- os.Signal) { func SendShutdownSignal(c chan<- os.Signal) {
sendShutdownSignal(c) sendShutdownSignal(c)

View File

@@ -1,11 +1,22 @@
//go:build unix //go:build darwin || freebsd || linux || openbsd
package aghos package aghos
import ( import (
"os" "os"
"os/signal"
"golang.org/x/sys/unix"
) )
func notifyReconfigureSignal(c chan<- os.Signal) {
signal.Notify(c, unix.SIGHUP)
}
func isReconfigureSignal(sig os.Signal) (ok bool) {
return sig == unix.SIGHUP
}
func sendShutdownSignal(_ chan<- os.Signal) { func sendShutdownSignal(_ chan<- os.Signal) {
// On Unix we are already notified by the system. // On Unix we are already notified by the system.
} }

View File

@@ -4,11 +4,12 @@ package aghos
import ( import (
"os" "os"
"os/signal"
"golang.org/x/sys/windows" "golang.org/x/sys/windows"
) )
func setRlimit(_ uint64) (err error) { func setRlimit(val uint64) (err error) {
return Unsupported("setrlimit") return Unsupported("setrlimit")
} }
@@ -37,6 +38,14 @@ func isOpenWrt() (ok bool) {
return false return false
} }
func notifyReconfigureSignal(c chan<- os.Signal) {
signal.Notify(c, windows.SIGHUP)
}
func isReconfigureSignal(sig os.Signal) (ok bool) {
return sig == windows.SIGHUP
}
func sendShutdownSignal(c chan<- os.Signal) { func sendShutdownSignal(c chan<- os.Signal) {
c <- os.Interrupt c <- os.Interrupt
} }

View File

@@ -1,6 +1,6 @@
package aghos package aghos
// ConfigureSyslog reroutes standard logger output to syslog. // ConfigureSyslog reroutes standard logger output to syslog.
func ConfigureSyslog(serviceName string) (err error) { func ConfigureSyslog(serviceName string) error {
return configureSyslog(serviceName) return configureSyslog(serviceName)
} }

View File

@@ -8,15 +8,11 @@ import (
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
) )
// configureSyslog sets standard log output to syslog. func configureSyslog(serviceName string) error {
func configureSyslog(serviceName string) (err error) {
w, err := syslog.New(syslog.LOG_NOTICE|syslog.LOG_USER, serviceName) w, err := syslog.New(syslog.LOG_NOTICE|syslog.LOG_USER, serviceName)
if err != nil { if err != nil {
// Don't wrap the error, because it's informative enough as is.
return err return err
} }
log.SetOutput(w) log.SetOutput(w)
return nil return nil
} }

View File

@@ -19,30 +19,23 @@ func (w *eventLogWriter) Write(b []byte) (int, error) {
return len(b), w.el.Info(1, string(b)) return len(b), w.el.Info(1, string(b))
} }
// configureSyslog sets standard log output to event log. func configureSyslog(serviceName string) error {
func configureSyslog(serviceName string) (err error) { // Note that the eventlog src is the same as the service name
// Note that the eventlog src is the same as the service name, otherwise we // Otherwise, we will get "the description for event id cannot be found" warning in every log record
// will get "the description for event id cannot be found" warning in every
// log record.
// Continue if we receive "registry key already exists" or if we get // Continue if we receive "registry key already exists" or if we get
// ERROR_ACCESS_DENIED so that we can log without administrative permissions // ERROR_ACCESS_DENIED so that we can log without administrative permissions
// for pre-existing eventlog sources. // for pre-existing eventlog sources.
err = eventlog.InstallAsEventCreate(serviceName, eventlog.Info|eventlog.Warning|eventlog.Error) if err := eventlog.InstallAsEventCreate(serviceName, eventlog.Info|eventlog.Warning|eventlog.Error); err != nil {
if err != nil && if !strings.Contains(err.Error(), "registry key already exists") && err != windows.ERROR_ACCESS_DENIED {
!strings.Contains(err.Error(), "registry key already exists") && return err
err != windows.ERROR_ACCESS_DENIED { }
// Don't wrap the error, because it's informative enough as is.
return err
} }
el, err := eventlog.Open(serviceName) el, err := eventlog.Open(serviceName)
if err != nil { if err != nil {
// Don't wrap the error, because it's informative enough as is.
return err return err
} }
log.SetOutput(&eventLogWriter{el: el}) log.SetOutput(&eventLogWriter{el: el})
return nil return nil
} }

View File

@@ -62,9 +62,7 @@ func newPendingFile(filePath string, mode fs.FileMode) (f PendingFile, err error
return nil, fmt.Errorf("opening pending file: %w", err) return nil, fmt.Errorf("opening pending file: %w", err)
} }
// TODO(e.burkov): The [os.Chmod] implementation is useless on Windows, err = file.Chmod(mode)
// investigate if it can be removed.
err = os.Chmod(file.Name(), mode)
if err != nil { if err != nil {
return nil, fmt.Errorf("preparing pending file: %w", err) return nil, fmt.Errorf("preparing pending file: %w", err)
} }

View File

@@ -58,7 +58,7 @@ func (w *FSWatcher) Add(name string) (err error) {
// ServiceWithConfig is a fake [agh.ServiceWithConfig] implementation for tests. // ServiceWithConfig is a fake [agh.ServiceWithConfig] implementation for tests.
type ServiceWithConfig[ConfigType any] struct { type ServiceWithConfig[ConfigType any] struct {
OnStart func(ctx context.Context) (err error) OnStart func() (err error)
OnShutdown func(ctx context.Context) (err error) OnShutdown func(ctx context.Context) (err error)
OnConfig func() (c ConfigType) OnConfig func() (c ConfigType)
} }
@@ -68,8 +68,8 @@ var _ agh.ServiceWithConfig[struct{}] = (*ServiceWithConfig[struct{}])(nil)
// Start implements the [agh.ServiceWithConfig] interface for // Start implements the [agh.ServiceWithConfig] interface for
// *ServiceWithConfig. // *ServiceWithConfig.
func (s *ServiceWithConfig[_]) Start(ctx context.Context) (err error) { func (s *ServiceWithConfig[_]) Start() (err error) {
return s.OnStart(ctx) return s.OnStart()
} }
// Shutdown implements the [agh.ServiceWithConfig] interface for // Shutdown implements the [agh.ServiceWithConfig] interface for
@@ -89,14 +89,14 @@ func (s *ServiceWithConfig[ConfigType]) Config() (c ConfigType) {
// AddressProcessor is a fake [client.AddressProcessor] implementation for // AddressProcessor is a fake [client.AddressProcessor] implementation for
// tests. // tests.
type AddressProcessor struct { type AddressProcessor struct {
OnProcess func(ctx context.Context, ip netip.Addr) OnProcess func(ip netip.Addr)
OnClose func() (err error) OnClose func() (err error)
} }
// Process implements the [client.AddressProcessor] interface for // Process implements the [client.AddressProcessor] interface for
// *AddressProcessor. // *AddressProcessor.
func (p *AddressProcessor) Process(ctx context.Context, ip netip.Addr) { func (p *AddressProcessor) Process(ip netip.Addr) {
p.OnProcess(ctx, ip) p.OnProcess(ip)
} }
// Close implements the [client.AddressProcessor] interface for // Close implements the [client.AddressProcessor] interface for
@@ -107,18 +107,13 @@ func (p *AddressProcessor) Close() (err error) {
// AddressUpdater is a fake [client.AddressUpdater] implementation for tests. // AddressUpdater is a fake [client.AddressUpdater] implementation for tests.
type AddressUpdater struct { type AddressUpdater struct {
OnUpdateAddress func(ctx context.Context, ip netip.Addr, host string, info *whois.Info) OnUpdateAddress func(ip netip.Addr, host string, info *whois.Info)
} }
// UpdateAddress implements the [client.AddressUpdater] interface for // UpdateAddress implements the [client.AddressUpdater] interface for
// *AddressUpdater. // *AddressUpdater.
func (p *AddressUpdater) UpdateAddress( func (p *AddressUpdater) UpdateAddress(ip netip.Addr, host string, info *whois.Info) {
ctx context.Context, p.OnUpdateAddress(ip, host, info)
ip netip.Addr,
host string,
info *whois.Info,
) {
p.OnUpdateAddress(ctx, ip, host, info)
} }
// Package dnsforward // Package dnsforward

View File

@@ -5,10 +5,9 @@ import (
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"fmt" "fmt"
"slices" "net/netip"
"github.com/AdguardTeam/golibs/log" "github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil"
) )
// init makes sure that the cipher name map is filled. // init makes sure that the cipher name map is filled.
@@ -76,5 +75,15 @@ func SaferCipherSuites() (safe []uint16) {
// CertificateHasIP returns true if cert has at least a single IP address among // CertificateHasIP returns true if cert has at least a single IP address among
// its subjectAltNames. // its subjectAltNames.
func CertificateHasIP(cert *x509.Certificate) (ok bool) { func CertificateHasIP(cert *x509.Certificate) (ok bool) {
return len(cert.IPAddresses) > 0 || slices.ContainsFunc(cert.DNSNames, netutil.IsValidIPString) if len(cert.IPAddresses) > 0 {
return true
}
for _, name := range cert.DNSNames {
if _, err := netip.ParseAddr(name); err == nil {
return true
}
}
return false
} }

View File

@@ -5,7 +5,6 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"fmt" "fmt"
"log/slog"
"net" "net"
"net/netip" "net/netip"
"slices" "slices"
@@ -13,7 +12,7 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/aghos" "github.com/AdguardTeam/AdGuardHome/internal/aghos"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/logutil/slogutil" "github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/osutil" "github.com/AdguardTeam/golibs/osutil"
) )
@@ -39,8 +38,8 @@ type Interface interface {
} }
// New returns the [Interface] properly initialized for the OS. // New returns the [Interface] properly initialized for the OS.
func New(logger *slog.Logger) (arp Interface) { func New() (arp Interface) {
return newARPDB(logger) return newARPDB()
} }
// Empty is the [Interface] implementation that does nothing. // Empty is the [Interface] implementation that does nothing.
@@ -70,30 +69,6 @@ type Neighbor struct {
MAC net.HardwareAddr MAC net.HardwareAddr
} }
// newNeighbor returns the new initialized [Neighbor] by parsing string
// representations of IP and MAC addresses.
func newNeighbor(host, ipStr, macStr string) (n *Neighbor, err error) {
defer func() { err = errors.Annotate(err, "getting arp neighbor: %w") }()
ip, err := netip.ParseAddr(ipStr)
if err != nil {
// Don't wrap the error, as it will get annotated.
return nil, err
}
mac, err := net.ParseMAC(macStr)
if err != nil {
// Don't wrap the error, as it will get annotated.
return nil, err
}
return &Neighbor{
Name: host,
IP: ip,
MAC: mac,
}, nil
}
// Clone returns the deep copy of n. // Clone returns the deep copy of n.
func (n Neighbor) Clone() (clone Neighbor) { func (n Neighbor) Clone() (clone Neighbor) {
return Neighbor{ return Neighbor{
@@ -105,10 +80,10 @@ func (n Neighbor) Clone() (clone Neighbor) {
// validatedHostname returns h if it's a valid hostname, or an empty string // validatedHostname returns h if it's a valid hostname, or an empty string
// otherwise, logging the validation error. // otherwise, logging the validation error.
func validatedHostname(logger *slog.Logger, h string) (host string) { func validatedHostname(h string) (host string) {
err := netutil.ValidateHostname(h) err := netutil.ValidateHostname(h)
if err != nil { if err != nil {
logger.Debug("parsing host of arp output", slogutil.KeyError, err) log.Debug("arpdb: parsing arp output: host: %s", err)
return "" return ""
} }
@@ -157,18 +132,15 @@ func (ns *neighs) reset(with []Neighbor) {
// parseNeighsFunc parses the text from sc as if it'd be an output of some // parseNeighsFunc parses the text from sc as if it'd be an output of some
// ARP-related command. lenHint is a hint for the size of the allocated slice // ARP-related command. lenHint is a hint for the size of the allocated slice
// of Neighbors. // of Neighbors.
// type parseNeighsFunc func(sc *bufio.Scanner, lenHint int) (ns []Neighbor)
// TODO(s.chzhen): Return []*Neighbor instead.
type parseNeighsFunc func(logger *slog.Logger, sc *bufio.Scanner, lenHint int) (ns []Neighbor)
// cmdARPDB is the implementation of the [Interface] that uses command line to // cmdARPDB is the implementation of the [Interface] that uses command line to
// retrieve data. // retrieve data.
type cmdARPDB struct { type cmdARPDB struct {
logger *slog.Logger parse parseNeighsFunc
parse parseNeighsFunc ns *neighs
ns *neighs cmd string
cmd string args []string
args []string
} }
// type check // type check
@@ -186,7 +158,7 @@ func (arp *cmdARPDB) Refresh() (err error) {
} }
sc := bufio.NewScanner(bytes.NewReader(out)) sc := bufio.NewScanner(bytes.NewReader(out))
ns := arp.parse(arp.logger, sc, arp.ns.len()) ns := arp.parse(sc, arp.ns.len())
if err = sc.Err(); err != nil { if err = sc.Err(); err != nil {
// TODO(e.burkov): This error seems unreachable. Investigate. // TODO(e.burkov): This error seems unreachable. Investigate.
return fmt.Errorf("scanning the output: %w", err) return fmt.Errorf("scanning the output: %w", err)

View File

@@ -4,17 +4,17 @@ package arpdb
import ( import (
"bufio" "bufio"
"log/slog" "net"
"net/netip"
"strings" "strings"
"sync" "sync"
"github.com/AdguardTeam/golibs/logutil/slogutil" "github.com/AdguardTeam/golibs/log"
) )
func newARPDB(logger *slog.Logger) (arp *cmdARPDB) { func newARPDB() (arp *cmdARPDB) {
return &cmdARPDB{ return &cmdARPDB{
logger: logger, parse: parseArpA,
parse: parseArpA,
ns: &neighs{ ns: &neighs{
mu: &sync.RWMutex{}, mu: &sync.RWMutex{},
ns: make([]Neighbor, 0), ns: make([]Neighbor, 0),
@@ -33,7 +33,7 @@ func newARPDB(logger *slog.Logger) (arp *cmdARPDB) {
// The expected input format: // The expected input format:
// //
// host.name (192.168.0.1) at ff:ff:ff:ff:ff:ff on en0 ifscope [ethernet] // host.name (192.168.0.1) at ff:ff:ff:ff:ff:ff on en0 ifscope [ethernet]
func parseArpA(logger *slog.Logger, sc *bufio.Scanner, lenHint int) (ns []Neighbor) { func parseArpA(sc *bufio.Scanner, lenHint int) (ns []Neighbor) {
ns = make([]Neighbor, 0, lenHint) ns = make([]Neighbor, 0, lenHint)
for sc.Scan() { for sc.Scan() {
ln := sc.Text() ln := sc.Text()
@@ -48,15 +48,26 @@ func parseArpA(logger *slog.Logger, sc *bufio.Scanner, lenHint int) (ns []Neighb
continue continue
} }
host := validatedHostname(logger, fields[0]) ip, err := netip.ParseAddr(ipStr[1 : len(ipStr)-1])
n, err := newNeighbor(host, ipStr[1:len(ipStr)-1], fields[3])
if err != nil { if err != nil {
logger.Debug("parsing arp output", "line", ln, slogutil.KeyError, err) log.Debug("arpdb: parsing arp output: ip: %s", err)
continue continue
} }
ns = append(ns, *n) hwStr := fields[3]
mac, err := net.ParseMAC(hwStr)
if err != nil {
log.Debug("arpdb: parsing arp output: mac: %s", err)
continue
}
ns = append(ns, Neighbor{
IP: ip,
MAC: mac,
Name: validatedHostname(fields[0]),
})
} }
return ns return ns

View File

@@ -11,7 +11,6 @@ import (
"testing" "testing"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/testutil" "github.com/AdguardTeam/golibs/testutil"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@@ -62,7 +61,7 @@ func (s mapShell) RunCmd(cmd string, args ...string) (code int, out []byte, err
func Test_New(t *testing.T) { func Test_New(t *testing.T) {
var a Interface var a Interface
require.NotPanics(t, func() { a = New(slogutil.NewDiscardLogger()) }) require.NotPanics(t, func() { a = New() })
assert.NotNil(t, a) assert.NotNil(t, a)
} }
@@ -202,9 +201,8 @@ func Test_NewARPDBs(t *testing.T) {
func TestCmdARPDB_arpa(t *testing.T) { func TestCmdARPDB_arpa(t *testing.T) {
a := &cmdARPDB{ a := &cmdARPDB{
logger: slogutil.NewDiscardLogger(), cmd: "cmd",
cmd: "cmd", parse: parseArpA,
parse: parseArpA,
ns: &neighs{ ns: &neighs{
mu: &sync.RWMutex{}, mu: &sync.RWMutex{},
ns: make([]Neighbor, 0), ns: make([]Neighbor, 0),

View File

@@ -6,18 +6,17 @@ import (
"bufio" "bufio"
"fmt" "fmt"
"io/fs" "io/fs"
"log/slog"
"net" "net"
"net/netip" "net/netip"
"strings" "strings"
"sync" "sync"
"github.com/AdguardTeam/AdGuardHome/internal/aghos" "github.com/AdguardTeam/AdGuardHome/internal/aghos"
"github.com/AdguardTeam/golibs/logutil/slogutil" "github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/stringutil" "github.com/AdguardTeam/golibs/stringutil"
) )
func newARPDB(logger *slog.Logger) (arp *arpdbs) { func newARPDB() (arp *arpdbs) {
// Use the common storage among the implementations. // Use the common storage among the implementations.
ns := &neighs{ ns := &neighs{
mu: &sync.RWMutex{}, mu: &sync.RWMutex{},
@@ -40,10 +39,9 @@ func newARPDB(logger *slog.Logger) (arp *arpdbs) {
}, },
// Then, try "arp -a -n". // Then, try "arp -a -n".
&cmdARPDB{ &cmdARPDB{
logger: logger, parse: parseF,
parse: parseF, ns: ns,
ns: ns, cmd: "arp",
cmd: "arp",
// Use -n flag to avoid resolving the hostnames of the neighbors. // Use -n flag to avoid resolving the hostnames of the neighbors.
// By default ARP attempts to resolve the hostnames via DNS. See // By default ARP attempts to resolve the hostnames via DNS. See
// man 8 arp. // man 8 arp.
@@ -53,11 +51,10 @@ func newARPDB(logger *slog.Logger) (arp *arpdbs) {
}, },
// Finally, try "ip neigh". // Finally, try "ip neigh".
&cmdARPDB{ &cmdARPDB{
logger: logger, parse: parseIPNeigh,
parse: parseIPNeigh, ns: ns,
ns: ns, cmd: "ip",
cmd: "ip", args: []string{"neigh"},
args: []string{"neigh"},
}, },
) )
} }
@@ -134,7 +131,7 @@ func (arp *fsysARPDB) Neighbors() (ns []Neighbor) {
// //
// IP address HW type Flags HW address Mask Device // IP address HW type Flags HW address Mask Device
// 192.168.11.98 0x1 0x2 5a:92:df:a9:7e:28 * wan // 192.168.11.98 0x1 0x2 5a:92:df:a9:7e:28 * wan
func parseArpAWrt(logger *slog.Logger, sc *bufio.Scanner, lenHint int) (ns []Neighbor) { func parseArpAWrt(sc *bufio.Scanner, lenHint int) (ns []Neighbor) {
if !sc.Scan() { if !sc.Scan() {
// Skip the header. // Skip the header.
return return
@@ -149,14 +146,25 @@ func parseArpAWrt(logger *slog.Logger, sc *bufio.Scanner, lenHint int) (ns []Nei
continue continue
} }
n, err := newNeighbor("", fields[0], fields[3]) ip, err := netip.ParseAddr(fields[0])
if err != nil { if err != nil {
logger.Debug("parsing arp output", "line", ln, slogutil.KeyError, err) log.Debug("arpdb: parsing arp output: ip: %s", err)
continue continue
} }
ns = append(ns, *n) hwStr := fields[3]
mac, err := net.ParseMAC(hwStr)
if err != nil {
log.Debug("arpdb: parsing arp output: mac: %s", err)
continue
}
ns = append(ns, Neighbor{
IP: ip,
MAC: mac,
})
} }
return ns return ns
@@ -166,7 +174,7 @@ func parseArpAWrt(logger *slog.Logger, sc *bufio.Scanner, lenHint int) (ns []Nei
// expected input format: // expected input format:
// //
// hostname (192.168.1.1) at ab:cd:ef:ab:cd:ef [ether] on enp0s3 // hostname (192.168.1.1) at ab:cd:ef:ab:cd:ef [ether] on enp0s3
func parseArpA(logger *slog.Logger, sc *bufio.Scanner, lenHint int) (ns []Neighbor) { func parseArpA(sc *bufio.Scanner, lenHint int) (ns []Neighbor) {
ns = make([]Neighbor, 0, lenHint) ns = make([]Neighbor, 0, lenHint)
for sc.Scan() { for sc.Scan() {
ln := sc.Text() ln := sc.Text()
@@ -181,15 +189,26 @@ func parseArpA(logger *slog.Logger, sc *bufio.Scanner, lenHint int) (ns []Neighb
continue continue
} }
host := validatedHostname(logger, fields[0]) ip, err := netip.ParseAddr(ipStr[1 : len(ipStr)-1])
n, err := newNeighbor(host, ipStr[1:len(ipStr)-1], fields[3])
if err != nil { if err != nil {
logger.Debug("parsing arp output", "line", ln, slogutil.KeyError, err) log.Debug("arpdb: parsing arp output: ip: %s", err)
continue continue
} }
ns = append(ns, *n) hwStr := fields[3]
mac, err := net.ParseMAC(hwStr)
if err != nil {
log.Debug("arpdb: parsing arp output: mac: %s", err)
continue
}
ns = append(ns, Neighbor{
IP: ip,
MAC: mac,
Name: validatedHostname(fields[0]),
})
} }
return ns return ns
@@ -199,7 +218,7 @@ func parseArpA(logger *slog.Logger, sc *bufio.Scanner, lenHint int) (ns []Neighb
// expected input format: // expected input format:
// //
// 192.168.1.1 dev enp0s3 lladdr ab:cd:ef:ab:cd:ef REACHABLE // 192.168.1.1 dev enp0s3 lladdr ab:cd:ef:ab:cd:ef REACHABLE
func parseIPNeigh(logger *slog.Logger, sc *bufio.Scanner, lenHint int) (ns []Neighbor) { func parseIPNeigh(sc *bufio.Scanner, lenHint int) (ns []Neighbor) {
ns = make([]Neighbor, 0, lenHint) ns = make([]Neighbor, 0, lenHint)
for sc.Scan() { for sc.Scan() {
ln := sc.Text() ln := sc.Text()
@@ -209,14 +228,27 @@ func parseIPNeigh(logger *slog.Logger, sc *bufio.Scanner, lenHint int) (ns []Nei
continue continue
} }
n, err := newNeighbor("", fields[0], fields[4]) n := Neighbor{}
ip, err := netip.ParseAddr(fields[0])
if err != nil { if err != nil {
logger.Debug("parsing arp output", "line", ln, slogutil.KeyError, err) log.Debug("arpdb: parsing arp output: ip: %s", err)
continue continue
} else {
n.IP = ip
} }
ns = append(ns, *n) mac, err := net.ParseMAC(fields[4])
if err != nil {
log.Debug("arpdb: parsing arp output: mac: %s", err)
continue
} else {
n.MAC = mac
}
ns = append(ns, n)
} }
return ns return ns

View File

@@ -9,7 +9,6 @@ import (
"testing" "testing"
"testing/fstest" "testing/fstest"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@@ -70,10 +69,9 @@ func TestCmdARPDB_linux(t *testing.T) {
t.Run("wrt", func(t *testing.T) { t.Run("wrt", func(t *testing.T) {
a := &cmdARPDB{ a := &cmdARPDB{
logger: slogutil.NewDiscardLogger(), parse: parseArpAWrt,
parse: parseArpAWrt, cmd: "arp",
cmd: "arp", args: []string{"-a"},
args: []string{"-a"},
ns: &neighs{ ns: &neighs{
mu: &sync.RWMutex{}, mu: &sync.RWMutex{},
ns: make([]Neighbor, 0), ns: make([]Neighbor, 0),
@@ -88,10 +86,9 @@ func TestCmdARPDB_linux(t *testing.T) {
t.Run("ip_neigh", func(t *testing.T) { t.Run("ip_neigh", func(t *testing.T) {
a := &cmdARPDB{ a := &cmdARPDB{
logger: slogutil.NewDiscardLogger(), parse: parseIPNeigh,
parse: parseIPNeigh, cmd: "ip",
cmd: "ip", args: []string{"neigh"},
args: []string{"neigh"},
ns: &neighs{ ns: &neighs{
mu: &sync.RWMutex{}, mu: &sync.RWMutex{},
ns: make([]Neighbor, 0), ns: make([]Neighbor, 0),

View File

@@ -4,17 +4,17 @@ package arpdb
import ( import (
"bufio" "bufio"
"log/slog" "net"
"net/netip"
"strings" "strings"
"sync" "sync"
"github.com/AdguardTeam/golibs/logutil/slogutil" "github.com/AdguardTeam/golibs/log"
) )
func newARPDB(logger *slog.Logger) (arp *cmdARPDB) { func newARPDB() (arp *cmdARPDB) {
return &cmdARPDB{ return &cmdARPDB{
logger: logger, parse: parseArpA,
parse: parseArpA,
ns: &neighs{ ns: &neighs{
mu: &sync.RWMutex{}, mu: &sync.RWMutex{},
ns: make([]Neighbor, 0), ns: make([]Neighbor, 0),
@@ -34,7 +34,7 @@ func newARPDB(logger *slog.Logger) (arp *cmdARPDB) {
// //
// Host Ethernet Address Netif Expire Flags // Host Ethernet Address Netif Expire Flags
// 192.168.1.1 ab:cd:ef:ab:cd:ef em0 19m59s // 192.168.1.1 ab:cd:ef:ab:cd:ef em0 19m59s
func parseArpA(logger *slog.Logger, sc *bufio.Scanner, lenHint int) (ns []Neighbor) { func parseArpA(sc *bufio.Scanner, lenHint int) (ns []Neighbor) {
// Skip the header. // Skip the header.
if !sc.Scan() { if !sc.Scan() {
return nil return nil
@@ -49,14 +49,27 @@ func parseArpA(logger *slog.Logger, sc *bufio.Scanner, lenHint int) (ns []Neighb
continue continue
} }
n, err := newNeighbor("", fields[0], fields[1]) n := Neighbor{}
ip, err := netip.ParseAddr(fields[0])
if err != nil { if err != nil {
logger.Debug("parsing arp output", "line", ln, slogutil.KeyError, err) log.Debug("arpdb: parsing arp output: ip: %s", err)
continue continue
} else {
n.IP = ip
} }
ns = append(ns, *n) mac, err := net.ParseMAC(fields[1])
if err != nil {
log.Debug("arpdb: parsing arp output: mac: %s", err)
continue
} else {
n.MAC = mac
}
ns = append(ns, n)
} }
return ns return ns

View File

@@ -4,17 +4,17 @@ package arpdb
import ( import (
"bufio" "bufio"
"log/slog" "net"
"net/netip"
"strings" "strings"
"sync" "sync"
"github.com/AdguardTeam/golibs/logutil/slogutil" "github.com/AdguardTeam/golibs/log"
) )
func newARPDB(logger *slog.Logger) (arp *cmdARPDB) { func newARPDB() (arp *cmdARPDB) {
return &cmdARPDB{ return &cmdARPDB{
logger: logger, parse: parseArpA,
parse: parseArpA,
ns: &neighs{ ns: &neighs{
mu: &sync.RWMutex{}, mu: &sync.RWMutex{},
ns: make([]Neighbor, 0), ns: make([]Neighbor, 0),
@@ -31,7 +31,7 @@ func newARPDB(logger *slog.Logger) (arp *cmdARPDB) {
// Internet Address Physical Address Type // Internet Address Physical Address Type
// 192.168.56.1 0a-00-27-00-00-00 dynamic // 192.168.56.1 0a-00-27-00-00-00 dynamic
// 192.168.56.255 ff-ff-ff-ff-ff-ff static // 192.168.56.255 ff-ff-ff-ff-ff-ff static
func parseArpA(logger *slog.Logger, sc *bufio.Scanner, lenHint int) (ns []Neighbor) { func parseArpA(sc *bufio.Scanner, lenHint int) (ns []Neighbor) {
ns = make([]Neighbor, 0, lenHint) ns = make([]Neighbor, 0, lenHint)
for sc.Scan() { for sc.Scan() {
ln := sc.Text() ln := sc.Text()
@@ -44,14 +44,24 @@ func parseArpA(logger *slog.Logger, sc *bufio.Scanner, lenHint int) (ns []Neighb
continue continue
} }
n, err := newNeighbor("", fields[0], fields[1]) ip, err := netip.ParseAddr(fields[0])
if err != nil { if err != nil {
logger.Debug("parsing arp output", "line", ln, slogutil.KeyError, err) log.Debug("arpdb: parsing arp output: ip: %s", err)
continue continue
} }
ns = append(ns, *n) mac, err := net.ParseMAC(fields[1])
if err != nil {
log.Debug("arpdb: parsing arp output: mac: %s", err)
continue
}
ns = append(ns, Neighbor{
IP: ip,
MAC: mac,
})
} }
return ns return ns

View File

@@ -2,7 +2,6 @@ package client
import ( import (
"context" "context"
"log/slog"
"net/netip" "net/netip"
"sync" "sync"
"time" "time"
@@ -11,7 +10,7 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/rdns" "github.com/AdguardTeam/AdGuardHome/internal/rdns"
"github.com/AdguardTeam/AdGuardHome/internal/whois" "github.com/AdguardTeam/AdGuardHome/internal/whois"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/logutil/slogutil" "github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/netutil"
) )
@@ -21,7 +20,7 @@ const ErrClosed errors.Error = "use of closed address processor"
// AddressProcessor is the interface for types that can process clients. // AddressProcessor is the interface for types that can process clients.
type AddressProcessor interface { type AddressProcessor interface {
Process(ctx context.Context, ip netip.Addr) Process(ip netip.Addr)
Close() (err error) Close() (err error)
} }
@@ -32,17 +31,13 @@ type EmptyAddrProc struct{}
var _ AddressProcessor = EmptyAddrProc{} var _ AddressProcessor = EmptyAddrProc{}
// Process implements the [AddressProcessor] interface for EmptyAddrProc. // Process implements the [AddressProcessor] interface for EmptyAddrProc.
func (EmptyAddrProc) Process(_ context.Context, _ netip.Addr) {} func (EmptyAddrProc) Process(_ netip.Addr) {}
// Close implements the [AddressProcessor] interface for EmptyAddrProc. // Close implements the [AddressProcessor] interface for EmptyAddrProc.
func (EmptyAddrProc) Close() (_ error) { return nil } func (EmptyAddrProc) Close() (_ error) { return nil }
// DefaultAddrProcConfig is the configuration structure for address processors. // DefaultAddrProcConfig is the configuration structure for address processors.
type DefaultAddrProcConfig struct { type DefaultAddrProcConfig struct {
// BaseLogger is used to create loggers with custom prefixes for sources of
// information about runtime clients. It must not be nil.
BaseLogger *slog.Logger
// DialContext is used to create TCP connections to WHOIS servers. // DialContext is used to create TCP connections to WHOIS servers.
// DialContext must not be nil if [DefaultAddrProcConfig.UseWHOIS] is true. // DialContext must not be nil if [DefaultAddrProcConfig.UseWHOIS] is true.
DialContext aghnet.DialContextFunc DialContext aghnet.DialContextFunc
@@ -89,15 +84,12 @@ type DefaultAddrProcConfig struct {
type AddressUpdater interface { type AddressUpdater interface {
// UpdateAddress updates information about an IP address, setting host (if // UpdateAddress updates information about an IP address, setting host (if
// not empty) and WHOIS information (if not nil). // not empty) and WHOIS information (if not nil).
UpdateAddress(ctx context.Context, ip netip.Addr, host string, info *whois.Info) UpdateAddress(ip netip.Addr, host string, info *whois.Info)
} }
// DefaultAddrProc processes incoming client addresses with rDNS and WHOIS, if // DefaultAddrProc processes incoming client addresses with rDNS and WHOIS, if
// configured, and updates that information in a client storage. // configured, and updates that information in a client storage.
type DefaultAddrProc struct { type DefaultAddrProc struct {
// logger is used to log the operation of address processor.
logger *slog.Logger
// clientIPsMu serializes closure of clientIPs and access to isClosed. // clientIPsMu serializes closure of clientIPs and access to isClosed.
clientIPsMu *sync.Mutex clientIPsMu *sync.Mutex
@@ -144,7 +136,6 @@ const (
// not be nil. // not be nil.
func NewDefaultAddrProc(c *DefaultAddrProcConfig) (p *DefaultAddrProc) { func NewDefaultAddrProc(c *DefaultAddrProcConfig) (p *DefaultAddrProc) {
p = &DefaultAddrProc{ p = &DefaultAddrProc{
logger: c.BaseLogger.With(slogutil.KeyPrefix, "addrproc"),
clientIPsMu: &sync.Mutex{}, clientIPsMu: &sync.Mutex{},
clientIPs: make(chan netip.Addr, defaultQueueSize), clientIPs: make(chan netip.Addr, defaultQueueSize),
rdns: &rdns.Empty{}, rdns: &rdns.Empty{},
@@ -156,7 +147,6 @@ func NewDefaultAddrProc(c *DefaultAddrProcConfig) (p *DefaultAddrProc) {
if c.UseRDNS { if c.UseRDNS {
p.rdns = rdns.New(&rdns.Config{ p.rdns = rdns.New(&rdns.Config{
Logger: c.BaseLogger.With(slogutil.KeyPrefix, "rdns"),
Exchanger: c.Exchanger, Exchanger: c.Exchanger,
CacheSize: defaultCacheSize, CacheSize: defaultCacheSize,
CacheTTL: defaultIPTTL, CacheTTL: defaultIPTTL,
@@ -164,16 +154,13 @@ func NewDefaultAddrProc(c *DefaultAddrProcConfig) (p *DefaultAddrProc) {
} }
if c.UseWHOIS { if c.UseWHOIS {
p.whois = newWHOIS(c.BaseLogger.With(slogutil.KeyPrefix, "whois"), c.DialContext) p.whois = newWHOIS(c.DialContext)
} }
// TODO(s.chzhen): Pass context. go p.process(c.CatchPanics)
ctx := context.TODO()
go p.process(ctx, c.CatchPanics)
for _, ip := range c.InitialAddresses { for _, ip := range c.InitialAddresses {
p.Process(ctx, ip) p.Process(ip)
} }
return p return p
@@ -181,7 +168,7 @@ func NewDefaultAddrProc(c *DefaultAddrProcConfig) (p *DefaultAddrProc) {
// newWHOIS returns a whois.Interface instance using the given function for // newWHOIS returns a whois.Interface instance using the given function for
// dialing. // dialing.
func newWHOIS(logger *slog.Logger, dialFunc aghnet.DialContextFunc) (w whois.Interface) { func newWHOIS(dialFunc aghnet.DialContextFunc) (w whois.Interface) {
// TODO(s.chzhen): Consider making configurable. // TODO(s.chzhen): Consider making configurable.
const ( const (
// defaultTimeout is the timeout for WHOIS requests. // defaultTimeout is the timeout for WHOIS requests.
@@ -199,7 +186,6 @@ func newWHOIS(logger *slog.Logger, dialFunc aghnet.DialContextFunc) (w whois.Int
) )
return whois.New(&whois.Config{ return whois.New(&whois.Config{
Logger: logger,
DialContext: dialFunc, DialContext: dialFunc,
ServerAddr: whois.DefaultServer, ServerAddr: whois.DefaultServer,
Port: whois.DefaultPort, Port: whois.DefaultPort,
@@ -216,7 +202,7 @@ func newWHOIS(logger *slog.Logger, dialFunc aghnet.DialContextFunc) (w whois.Int
var _ AddressProcessor = (*DefaultAddrProc)(nil) var _ AddressProcessor = (*DefaultAddrProc)(nil)
// Process implements the [AddressProcessor] interface for *DefaultAddrProc. // Process implements the [AddressProcessor] interface for *DefaultAddrProc.
func (p *DefaultAddrProc) Process(ctx context.Context, ip netip.Addr) { func (p *DefaultAddrProc) Process(ip netip.Addr) {
p.clientIPsMu.Lock() p.clientIPsMu.Lock()
defer p.clientIPsMu.Unlock() defer p.clientIPsMu.Unlock()
@@ -228,42 +214,36 @@ func (p *DefaultAddrProc) Process(ctx context.Context, ip netip.Addr) {
case p.clientIPs <- ip: case p.clientIPs <- ip:
// Go on. // Go on.
default: default:
p.logger.DebugContext(ctx, "ip channel is full", "len", len(p.clientIPs)) log.Debug("clients: ip channel is full; len: %d", len(p.clientIPs))
} }
} }
// process processes the incoming client IP-address information. It is intended // process processes the incoming client IP-address information. It is intended
// to be used as a goroutine. Once clientIPs is closed, process exits. // to be used as a goroutine. Once clientIPs is closed, process exits.
func (p *DefaultAddrProc) process(ctx context.Context, catchPanics bool) { func (p *DefaultAddrProc) process(catchPanics bool) {
if catchPanics { if catchPanics {
defer slogutil.RecoverAndLog(ctx, p.logger) defer log.OnPanic("addrProcessor.process")
} }
p.logger.InfoContext(ctx, "processing addresses") log.Info("clients: processing addresses")
for ip := range p.clientIPs { for ip := range p.clientIPs {
host := p.processRDNS(ctx, ip) host := p.processRDNS(ip)
info := p.processWHOIS(ctx, ip) info := p.processWHOIS(ip)
p.addrUpdater.UpdateAddress(ctx, ip, host, info) p.addrUpdater.UpdateAddress(ip, host, info)
} }
p.logger.InfoContext(ctx, "finished processing addresses") log.Info("clients: finished processing addresses")
} }
// processRDNS resolves the clients' IP addresses using reverse DNS. host is // processRDNS resolves the clients' IP addresses using reverse DNS. host is
// empty if there were errors or if the information hasn't changed. // empty if there were errors or if the information hasn't changed.
func (p *DefaultAddrProc) processRDNS(ctx context.Context, ip netip.Addr) (host string) { func (p *DefaultAddrProc) processRDNS(ip netip.Addr) (host string) {
start := time.Now() start := time.Now()
p.logger.DebugContext(ctx, "processing rdns", "ip", ip) log.Debug("clients: processing %s with rdns", ip)
defer func() { defer func() {
p.logger.DebugContext( log.Debug("clients: finished processing %s with rdns in %s", ip, time.Since(start))
ctx,
"finished processing rdns",
"ip", ip,
"host", host,
"elapsed", time.Since(start),
)
}() }()
ok := p.shouldResolve(ip) ok := p.shouldResolve(ip)
@@ -271,7 +251,7 @@ func (p *DefaultAddrProc) processRDNS(ctx context.Context, ip netip.Addr) (host
return return
} }
host, changed := p.rdns.Process(ctx, ip) host, changed := p.rdns.Process(ip)
if !changed { if !changed {
host = "" host = ""
} }
@@ -288,22 +268,16 @@ func (p *DefaultAddrProc) shouldResolve(ip netip.Addr) (ok bool) {
// processWHOIS looks up the information about clients' IP addresses in the // processWHOIS looks up the information about clients' IP addresses in the
// WHOIS databases. info is nil if there were errors or if the information // WHOIS databases. info is nil if there were errors or if the information
// hasn't changed. // hasn't changed.
func (p *DefaultAddrProc) processWHOIS(ctx context.Context, ip netip.Addr) (info *whois.Info) { func (p *DefaultAddrProc) processWHOIS(ip netip.Addr) (info *whois.Info) {
start := time.Now() start := time.Now()
p.logger.DebugContext(ctx, "processing whois", "ip", ip) log.Debug("clients: processing %s with whois", ip)
defer func() { defer func() {
p.logger.DebugContext( log.Debug("clients: finished processing %s with whois in %s", ip, time.Since(start))
ctx,
"finished processing whois",
"ip", ip,
"whois", info,
"elapsed", time.Since(start),
)
}() }()
// TODO(s.chzhen): Move the timeout logic from WHOIS configuration to the // TODO(s.chzhen): Move the timeout logic from WHOIS configuration to the
// context. // context.
info, changed := p.whois.Process(ctx, ip) info, changed := p.whois.Process(context.Background(), ip)
if !changed { if !changed {
info = nil info = nil
} }

View File

@@ -13,7 +13,6 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/client" "github.com/AdguardTeam/AdGuardHome/internal/client"
"github.com/AdguardTeam/AdGuardHome/internal/whois" "github.com/AdguardTeam/AdGuardHome/internal/whois"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/logutil/slogutil"
"github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/netutil"
"github.com/AdguardTeam/golibs/testutil" "github.com/AdguardTeam/golibs/testutil"
"github.com/AdguardTeam/golibs/testutil/fakenet" "github.com/AdguardTeam/golibs/testutil/fakenet"
@@ -26,8 +25,7 @@ func TestEmptyAddrProc(t *testing.T) {
p := client.EmptyAddrProc{} p := client.EmptyAddrProc{}
assert.NotPanics(t, func() { assert.NotPanics(t, func() {
ctx := testutil.ContextWithTimeout(t, testTimeout) p.Process(testIP)
p.Process(ctx, testIP)
}) })
assert.NotPanics(t, func() { assert.NotPanics(t, func() {
@@ -101,7 +99,6 @@ func TestDefaultAddrProc_Process_rDNS(t *testing.T) {
updInfoCh := make(chan *whois.Info, 1) updInfoCh := make(chan *whois.Info, 1)
p := client.NewDefaultAddrProc(&client.DefaultAddrProcConfig{ p := client.NewDefaultAddrProc(&client.DefaultAddrProcConfig{
BaseLogger: slogutil.NewDiscardLogger(),
DialContext: func(_ context.Context, _, _ string) (conn net.Conn, err error) { DialContext: func(_ context.Context, _, _ string) (conn net.Conn, err error) {
panic("not implemented") panic("not implemented")
}, },
@@ -121,8 +118,7 @@ func TestDefaultAddrProc_Process_rDNS(t *testing.T) {
}) })
testutil.CleanupAndRequireSuccess(t, p.Close) testutil.CleanupAndRequireSuccess(t, p.Close)
ctx := testutil.ContextWithTimeout(t, testTimeout) p.Process(tc.ip)
p.Process(ctx, tc.ip)
if !tc.wantUpd { if !tc.wantUpd {
return return
@@ -148,8 +144,8 @@ func newOnUpdateAddress(
ips chan<- netip.Addr, ips chan<- netip.Addr,
hosts chan<- string, hosts chan<- string,
infos chan<- *whois.Info, infos chan<- *whois.Info,
) (f func(ctx context.Context, ip netip.Addr, host string, info *whois.Info)) { ) (f func(ip netip.Addr, host string, info *whois.Info)) {
return func(ctx context.Context, ip netip.Addr, host string, info *whois.Info) { return func(ip netip.Addr, host string, info *whois.Info) {
if !want && (host != "" || info != nil) { if !want && (host != "" || info != nil) {
panic(fmt.Errorf("got unexpected update for %v with %q and %v", ip, host, info)) panic(fmt.Errorf("got unexpected update for %v with %q and %v", ip, host, info))
} }
@@ -212,7 +208,6 @@ func TestDefaultAddrProc_Process_WHOIS(t *testing.T) {
updInfoCh := make(chan *whois.Info, 1) updInfoCh := make(chan *whois.Info, 1)
p := client.NewDefaultAddrProc(&client.DefaultAddrProcConfig{ p := client.NewDefaultAddrProc(&client.DefaultAddrProcConfig{
BaseLogger: slogutil.NewDiscardLogger(),
DialContext: func(_ context.Context, _, _ string) (conn net.Conn, err error) { DialContext: func(_ context.Context, _, _ string) (conn net.Conn, err error) {
return whoisConn, nil return whoisConn, nil
}, },
@@ -232,8 +227,7 @@ func TestDefaultAddrProc_Process_WHOIS(t *testing.T) {
}) })
testutil.CleanupAndRequireSuccess(t, p.Close) testutil.CleanupAndRequireSuccess(t, p.Close)
ctx := testutil.ContextWithTimeout(t, testTimeout) p.Process(testIP)
p.Process(ctx, testIP)
if !tc.wantUpd { if !tc.wantUpd {
return return
@@ -254,9 +248,7 @@ func TestDefaultAddrProc_Process_WHOIS(t *testing.T) {
func TestDefaultAddrProc_Close(t *testing.T) { func TestDefaultAddrProc_Close(t *testing.T) {
t.Parallel() t.Parallel()
p := client.NewDefaultAddrProc(&client.DefaultAddrProcConfig{ p := client.NewDefaultAddrProc(&client.DefaultAddrProcConfig{})
BaseLogger: slogutil.NewDiscardLogger(),
})
err := p.Close() err := p.Close()
assert.NoError(t, err) assert.NoError(t, err)

View File

@@ -8,7 +8,6 @@ import (
"encoding" "encoding"
"fmt" "fmt"
"net/netip" "net/netip"
"slices"
"github.com/AdguardTeam/AdGuardHome/internal/whois" "github.com/AdguardTeam/AdGuardHome/internal/whois"
) )
@@ -119,9 +118,8 @@ func (r *Runtime) Info() (cs Source, host string) {
return cs, info[0] return cs, info[0]
} }
// setInfo sets a host as a client information from the cs. // SetInfo sets a host as a client information from the cs.
func (r *Runtime) setInfo(cs Source, hosts []string) { func (r *Runtime) SetInfo(cs Source, hosts []string) {
// TODO(s.chzhen): Use contract where hosts must contain non-empty host.
if len(hosts) == 1 && hosts[0] == "" { if len(hosts) == 1 && hosts[0] == "" {
hosts = []string{} hosts = []string{}
} }
@@ -138,13 +136,13 @@ func (r *Runtime) setInfo(cs Source, hosts []string) {
} }
} }
// WHOIS returns a copy of WHOIS client information. // WHOIS returns a WHOIS client information.
func (r *Runtime) WHOIS() (info *whois.Info) { func (r *Runtime) WHOIS() (info *whois.Info) {
return r.whois.Clone() return r.whois
} }
// setWHOIS sets a WHOIS client information. info must be non-nil. // SetWHOIS sets a WHOIS client information. info must be non-nil.
func (r *Runtime) setWHOIS(info *whois.Info) { func (r *Runtime) SetWHOIS(info *whois.Info) {
r.whois = info r.whois = info
} }
@@ -177,15 +175,3 @@ func (r *Runtime) isEmpty() (ok bool) {
func (r *Runtime) Addr() (ip netip.Addr) { func (r *Runtime) Addr() (ip netip.Addr) {
return r.ip return r.ip
} }
// clone returns a deep copy of the runtime client.
func (r *Runtime) clone() (c *Runtime) {
return &Runtime{
ip: r.ip,
whois: r.whois.Clone(),
arp: slices.Clone(r.arp),
rdns: slices.Clone(r.rdns),
dhcp: slices.Clone(r.dhcp),
hostsFile: slices.Clone(r.hostsFile),
}
}

View File

@@ -2,7 +2,6 @@ package client
import ( import (
"fmt" "fmt"
"maps"
"net" "net"
"net/netip" "net/netip"
"slices" "slices"
@@ -10,6 +9,7 @@ import (
"github.com/AdguardTeam/AdGuardHome/internal/aghalg" "github.com/AdguardTeam/AdGuardHome/internal/aghalg"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"golang.org/x/exp/maps"
) )
// macKey contains MAC as byte array of 6, 8, or 20 bytes. // macKey contains MAC as byte array of 6, 8, or 20 bytes.
@@ -30,8 +30,8 @@ func macToKey(mac net.HardwareAddr) (key macKey) {
} }
} }
// index stores all information about persistent clients. // Index stores all information about persistent clients.
type index struct { type Index struct {
// nameToUID maps client name to UID. // nameToUID maps client name to UID.
nameToUID map[string]UID nameToUID map[string]UID
@@ -51,9 +51,9 @@ type index struct {
subnetToUID aghalg.SortedMap[netip.Prefix, UID] subnetToUID aghalg.SortedMap[netip.Prefix, UID]
} }
// newIndex initializes the new instance of client index. // NewIndex initializes the new instance of client index.
func newIndex() (ci *index) { func NewIndex() (ci *Index) {
return &index{ return &Index{
nameToUID: map[string]UID{}, nameToUID: map[string]UID{},
clientIDToUID: map[string]UID{}, clientIDToUID: map[string]UID{},
ipToUID: map[netip.Addr]UID{}, ipToUID: map[netip.Addr]UID{},
@@ -63,9 +63,9 @@ func newIndex() (ci *index) {
} }
} }
// add stores information about a persistent client in the index. c must be // Add stores information about a persistent client in the index. c must be
// non-nil, have a UID, and contain at least one identifier. // non-nil and contain UID.
func (ci *index) add(c *Persistent) { func (ci *Index) Add(c *Persistent) {
if (c.UID == UID{}) { if (c.UID == UID{}) {
panic("client must contain uid") panic("client must contain uid")
} }
@@ -92,9 +92,9 @@ func (ci *index) add(c *Persistent) {
ci.uidToClient[c.UID] = c ci.uidToClient[c.UID] = c
} }
// clashesUID returns existing persistent client with the same UID as c. Note // ClashesUID returns existing persistent client with the same UID as c. Note
// that this is only possible when configuration contains duplicate fields. // that this is only possible when configuration contains duplicate fields.
func (ci *index) clashesUID(c *Persistent) (err error) { func (ci *Index) ClashesUID(c *Persistent) (err error) {
p, ok := ci.uidToClient[c.UID] p, ok := ci.uidToClient[c.UID]
if ok { if ok {
return fmt.Errorf("another client %q uses the same uid", p.Name) return fmt.Errorf("another client %q uses the same uid", p.Name)
@@ -103,9 +103,9 @@ func (ci *index) clashesUID(c *Persistent) (err error) {
return nil return nil
} }
// clashes returns an error if the index contains a different persistent client // Clashes returns an error if the index contains a different persistent client
// with at least a single identifier contained by c. c must be non-nil. // with at least a single identifier contained by c. c must be non-nil.
func (ci *index) clashes(c *Persistent) (err error) { func (ci *Index) Clashes(c *Persistent) (err error) {
if p := ci.clashesName(c); p != nil { if p := ci.clashesName(c); p != nil {
return fmt.Errorf("another client uses the same name %q", p.Name) return fmt.Errorf("another client uses the same name %q", p.Name)
} }
@@ -139,8 +139,8 @@ func (ci *index) clashes(c *Persistent) (err error) {
// clashesName returns existing persistent client with the same name as c or // clashesName returns existing persistent client with the same name as c or
// nil. c must be non-nil. // nil. c must be non-nil.
func (ci *index) clashesName(c *Persistent) (existing *Persistent) { func (ci *Index) clashesName(c *Persistent) (existing *Persistent) {
existing, ok := ci.findByName(c.Name) existing, ok := ci.FindByName(c.Name)
if !ok { if !ok {
return nil return nil
} }
@@ -154,7 +154,7 @@ func (ci *index) clashesName(c *Persistent) (existing *Persistent) {
// clashesIP returns a previous client with the same IP address as c. c must be // clashesIP returns a previous client with the same IP address as c. c must be
// non-nil. // non-nil.
func (ci *index) clashesIP(c *Persistent) (p *Persistent, ip netip.Addr) { func (ci *Index) clashesIP(c *Persistent) (p *Persistent, ip netip.Addr) {
for _, ip := range c.IPs { for _, ip := range c.IPs {
existing, ok := ci.ipToUID[ip] existing, ok := ci.ipToUID[ip]
if ok && existing != c.UID { if ok && existing != c.UID {
@@ -167,7 +167,7 @@ func (ci *index) clashesIP(c *Persistent) (p *Persistent, ip netip.Addr) {
// clashesSubnet returns a previous client with the same subnet as c. c must be // clashesSubnet returns a previous client with the same subnet as c. c must be
// non-nil. // non-nil.
func (ci *index) clashesSubnet(c *Persistent) (p *Persistent, s netip.Prefix) { func (ci *Index) clashesSubnet(c *Persistent) (p *Persistent, s netip.Prefix) {
for _, s = range c.Subnets { for _, s = range c.Subnets {
var existing UID var existing UID
var ok bool var ok bool
@@ -193,7 +193,7 @@ func (ci *index) clashesSubnet(c *Persistent) (p *Persistent, s netip.Prefix) {
// clashesMAC returns a previous client with the same MAC address as c. c must // clashesMAC returns a previous client with the same MAC address as c. c must
// be non-nil. // be non-nil.
func (ci *index) clashesMAC(c *Persistent) (p *Persistent, mac net.HardwareAddr) { func (ci *Index) clashesMAC(c *Persistent) (p *Persistent, mac net.HardwareAddr) {
for _, mac = range c.MACs { for _, mac = range c.MACs {
k := macToKey(mac) k := macToKey(mac)
existing, ok := ci.macToUID[k] existing, ok := ci.macToUID[k]
@@ -205,9 +205,9 @@ func (ci *index) clashesMAC(c *Persistent) (p *Persistent, mac net.HardwareAddr)
return nil, nil return nil, nil
} }
// find finds persistent client by string representation of the client ID, IP // Find finds persistent client by string representation of the client ID, IP
// address, or MAC. // address, or MAC.
func (ci *index) find(id string) (c *Persistent, ok bool) { func (ci *Index) Find(id string) (c *Persistent, ok bool) {
uid, found := ci.clientIDToUID[id] uid, found := ci.clientIDToUID[id]
if found { if found {
return ci.uidToClient[uid], true return ci.uidToClient[uid], true
@@ -224,14 +224,14 @@ func (ci *index) find(id string) (c *Persistent, ok bool) {
mac, err := net.ParseMAC(id) mac, err := net.ParseMAC(id)
if err == nil { if err == nil {
return ci.findByMAC(mac) return ci.FindByMAC(mac)
} }
return nil, false return nil, false
} }
// findByName finds persistent client by name. // FindByName finds persistent client by name.
func (ci *index) findByName(name string) (c *Persistent, found bool) { func (ci *Index) FindByName(name string) (c *Persistent, found bool) {
uid, found := ci.nameToUID[name] uid, found := ci.nameToUID[name]
if found { if found {
return ci.uidToClient[uid], true return ci.uidToClient[uid], true
@@ -241,7 +241,7 @@ func (ci *index) findByName(name string) (c *Persistent, found bool) {
} }
// findByIP finds persistent client by IP address. // findByIP finds persistent client by IP address.
func (ci *index) findByIP(ip netip.Addr) (c *Persistent, found bool) { func (ci *Index) findByIP(ip netip.Addr) (c *Persistent, found bool) {
uid, found := ci.ipToUID[ip] uid, found := ci.ipToUID[ip]
if found { if found {
return ci.uidToClient[uid], true return ci.uidToClient[uid], true
@@ -266,8 +266,8 @@ func (ci *index) findByIP(ip netip.Addr) (c *Persistent, found bool) {
return nil, false return nil, false
} }
// findByMAC finds persistent client by MAC. // FindByMAC finds persistent client by MAC.
func (ci *index) findByMAC(mac net.HardwareAddr) (c *Persistent, found bool) { func (ci *Index) FindByMAC(mac net.HardwareAddr) (c *Persistent, found bool) {
k := macToKey(mac) k := macToKey(mac)
uid, found := ci.macToUID[k] uid, found := ci.macToUID[k]
if found { if found {
@@ -277,13 +277,13 @@ func (ci *index) findByMAC(mac net.HardwareAddr) (c *Persistent, found bool) {
return nil, false return nil, false
} }
// findByIPWithoutZone finds a persistent client by IP address without zone. It // FindByIPWithoutZone finds a persistent client by IP address without zone. It
// strips the IPv6 zone index from the stored IP addresses before comparing, // strips the IPv6 zone index from the stored IP addresses before comparing,
// because querylog entries don't have it. See TODO on [querylog.logEntry.IP]. // because querylog entries don't have it. See TODO on [querylog.logEntry.IP].
// //
// Note that multiple clients can have the same IP address with different zones. // Note that multiple clients can have the same IP address with different zones.
// Therefore, the result of this method is indeterminate. // Therefore, the result of this method is indeterminate.
func (ci *index) findByIPWithoutZone(ip netip.Addr) (c *Persistent) { func (ci *Index) FindByIPWithoutZone(ip netip.Addr) (c *Persistent) {
if (ip == netip.Addr{}) { if (ip == netip.Addr{}) {
return nil return nil
} }
@@ -297,9 +297,9 @@ func (ci *index) findByIPWithoutZone(ip netip.Addr) (c *Persistent) {
return nil return nil
} }
// remove removes information about persistent client from the index. c must be // Delete removes information about persistent client from the index. c must be
// non-nil. // non-nil.
func (ci *index) remove(c *Persistent) { func (ci *Index) Delete(c *Persistent) {
delete(ci.nameToUID, c.Name) delete(ci.nameToUID, c.Name)
for _, id := range c.ClientIDs { for _, id := range c.ClientIDs {
@@ -322,32 +322,40 @@ func (ci *index) remove(c *Persistent) {
delete(ci.uidToClient, c.UID) delete(ci.uidToClient, c.UID)
} }
// size returns the number of persistent clients. // Size returns the number of persistent clients.
func (ci *index) size() (n int) { func (ci *Index) Size() (n int) {
return len(ci.uidToClient) return len(ci.uidToClient)
} }
// rangeByName is like [Index.Range] but sorts the persistent clients by name // Range calls f for each persistent client, unless cont is false. The order is
// before iterating ensuring a predictable order. // undefined.
func (ci *index) rangeByName(f func(c *Persistent) (cont bool)) { func (ci *Index) Range(f func(c *Persistent) (cont bool)) {
clients := slices.SortedStableFunc( for _, c := range ci.uidToClient {
maps.Values(ci.uidToClient), if !f(c) {
func(a, b *Persistent) (res int) { return
return strings.Compare(a.Name, b.Name) }
}, }
) }
for _, c := range clients { // RangeByName is like [Index.Range] but sorts the persistent clients by name
// before iterating ensuring a predictable order.
func (ci *Index) RangeByName(f func(c *Persistent) (cont bool)) {
cs := maps.Values(ci.uidToClient)
slices.SortFunc(cs, func(a, b *Persistent) (n int) {
return strings.Compare(a.Name, b.Name)
})
for _, c := range cs {
if !f(c) { if !f(c) {
break break
} }
} }
} }
// closeUpstreams closes upstream configurations of persistent clients. // CloseUpstreams closes upstream configurations of persistent clients.
func (ci *index) closeUpstreams() (err error) { func (ci *Index) CloseUpstreams() (err error) {
var errs []error var errs []error
ci.rangeByName(func(c *Persistent) (cont bool) { ci.RangeByName(func(c *Persistent) (cont bool) {
err = c.CloseUpstreams() err = c.CloseUpstreams()
if err != nil { if err != nil {
errs = append(errs, err) errs = append(errs, err)

View File

@@ -11,18 +11,17 @@ import (
// newIDIndex is a helper function that returns a client index filled with // newIDIndex is a helper function that returns a client index filled with
// persistent clients from the m. It also generates a UID for each client. // persistent clients from the m. It also generates a UID for each client.
func newIDIndex(m []*Persistent) (ci *index) { func newIDIndex(m []*Persistent) (ci *Index) {
ci = newIndex() ci = NewIndex()
for _, c := range m { for _, c := range m {
c.UID = MustNewUID() c.UID = MustNewUID()
ci.add(c) ci.Add(c)
} }
return ci return ci
} }
// TODO(s.chzhen): Remove.
func TestClientIndex_Find(t *testing.T) { func TestClientIndex_Find(t *testing.T) {
const ( const (
cliIPNone = "1.2.3.4" cliIPNone = "1.2.3.4"
@@ -110,7 +109,7 @@ func TestClientIndex_Find(t *testing.T) {
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
for _, id := range tc.ids { for _, id := range tc.ids {
c, ok := ci.find(id) c, ok := ci.Find(id)
require.True(t, ok) require.True(t, ok)
assert.Equal(t, tc.want, c) assert.Equal(t, tc.want, c)
@@ -119,7 +118,7 @@ func TestClientIndex_Find(t *testing.T) {
} }
t.Run("not_found", func(t *testing.T) { t.Run("not_found", func(t *testing.T) {
_, ok := ci.find(cliIPNone) _, ok := ci.Find(cliIPNone)
assert.False(t, ok) assert.False(t, ok)
}) })
} }
@@ -171,11 +170,11 @@ func TestClientIndex_Clashes(t *testing.T) {
clone := tc.client.ShallowClone() clone := tc.client.ShallowClone()
clone.UID = MustNewUID() clone.UID = MustNewUID()
err := ci.clashes(clone) err := ci.Clashes(clone)
require.Error(t, err) require.Error(t, err)
ci.remove(tc.client) ci.Delete(tc.client)
err = ci.clashes(clone) err = ci.Clashes(clone)
require.NoError(t, err) require.NoError(t, err)
}) })
} }
@@ -293,7 +292,7 @@ func TestIndex_FindByIPWithoutZone(t *testing.T) {
for _, tc := range testCases { for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
c := ci.findByIPWithoutZone(tc.ip.WithZone("")) c := ci.FindByIPWithoutZone(tc.ip.WithZone(""))
require.Equal(t, tc.want, c) require.Equal(t, tc.want, c)
}) })
} }
@@ -339,7 +338,7 @@ func TestClientIndex_RangeByName(t *testing.T) {
ci := newIDIndex(tc.want) ci := newIDIndex(tc.want)
var got []*Persistent var got []*Persistent
ci.rangeByName(func(c *Persistent) (cont bool) { ci.RangeByName(func(c *Persistent) (cont bool) {
got = append(got, c) got = append(got, c)
return true return true

View File

@@ -1,20 +1,20 @@
package client package client
import ( import (
"context"
"encoding" "encoding"
"fmt" "fmt"
"log/slog"
"net" "net"
"net/netip" "net/netip"
"slices" "slices"
"strings" "strings"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/filtering" "github.com/AdguardTeam/AdGuardHome/internal/filtering"
"github.com/AdguardTeam/AdGuardHome/internal/filtering/safesearch"
"github.com/AdguardTeam/dnsproxy/proxy" "github.com/AdguardTeam/dnsproxy/proxy"
"github.com/AdguardTeam/dnsproxy/upstream" "github.com/AdguardTeam/golibs/container"
"github.com/AdguardTeam/golibs/errors" "github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/logutil/slogutil" "github.com/AdguardTeam/golibs/log"
"github.com/AdguardTeam/golibs/netutil" "github.com/AdguardTeam/golibs/netutil"
"github.com/google/uuid" "github.com/google/uuid"
) )
@@ -64,109 +64,53 @@ type Persistent struct {
// upstream must be used. // upstream must be used.
UpstreamConfig *proxy.CustomUpstreamConfig UpstreamConfig *proxy.CustomUpstreamConfig
// SafeSearch handles search engine hosts rewrites.
SafeSearch filtering.SafeSearch SafeSearch filtering.SafeSearch
// BlockedServices is the configuration of blocked services of a client. It // BlockedServices is the configuration of blocked services of a client.
// must not be nil after initialization.
BlockedServices *filtering.BlockedServices BlockedServices *filtering.BlockedServices
// Name of the persistent client. Must not be empty.
Name string Name string
// Tags is a list of client tags that categorize the client. Tags []string
Tags []string
// Upstreams is a list of custom upstream DNS servers for the client.
Upstreams []string Upstreams []string
// IPs is a list of IP addresses that identify the client. The client must
// have at least one ID (IP, subnet, MAC, or ClientID).
IPs []netip.Addr IPs []netip.Addr
// Subnets identifying the client. The client must have at least one ID
// (IP, subnet, MAC, or ClientID).
//
// TODO(s.chzhen): Use netutil.Prefix. // TODO(s.chzhen): Use netutil.Prefix.
Subnets []netip.Prefix Subnets []netip.Prefix
MACs []net.HardwareAddr
// MACs identifying the client. The client must have at least one ID (IP,
// subnet, MAC, or ClientID).
MACs []net.HardwareAddr
// ClientIDs identifying the client. The client must have at least one ID
// (IP, subnet, MAC, or ClientID).
ClientIDs []string ClientIDs []string
// UID is the unique identifier of the persistent client. // UID is the unique identifier of the persistent client.
UID UID UID UID
// UpstreamsCacheSize is the cache size for custom upstreams. UpstreamsCacheSize uint32
UpstreamsCacheSize uint32
// UpstreamsCacheEnabled specifies whether custom upstreams are used.
UpstreamsCacheEnabled bool UpstreamsCacheEnabled bool
// UseOwnSettings specifies whether custom filtering settings are used. UseOwnSettings bool
UseOwnSettings bool FilteringEnabled bool
SafeBrowsingEnabled bool
// FilteringEnabled specifies whether filtering is enabled. ParentalEnabled bool
FilteringEnabled bool
// SafeBrowsingEnabled specifies whether safe browsing is enabled.
SafeBrowsingEnabled bool
// ParentalEnabled specifies whether parental control is enabled.
ParentalEnabled bool
// UseOwnBlockedServices specifies whether custom services are blocked.
UseOwnBlockedServices bool UseOwnBlockedServices bool
IgnoreQueryLog bool
IgnoreStatistics bool
// IgnoreQueryLog specifies whether the client requests are logged.
IgnoreQueryLog bool
// IgnoreStatistics specifies whether the client requests are counted.
IgnoreStatistics bool
// SafeSearchConf is the safe search filtering configuration.
//
// TODO(d.kolyshev): Make SafeSearchConf a pointer. // TODO(d.kolyshev): Make SafeSearchConf a pointer.
SafeSearchConf filtering.SafeSearchConfig SafeSearchConf filtering.SafeSearchConfig
} }
// validate returns an error if persistent client information contains errors. // SetTags sets the tags if they are known, otherwise logs an unknown tag.
// allTags must be sorted. func (c *Persistent) SetTags(tags []string, known *container.MapSet[string]) {
func (c *Persistent) validate(ctx context.Context, l *slog.Logger, allTags []string) (err error) { for _, t := range tags {
switch { if !known.Has(t) {
case c.Name == "": log.Info("skipping unknown tag %q", t)
return errors.Error("empty name")
case c.IDsLen() == 0:
return errors.Error("id required")
case c.UID == UID{}:
return errors.Error("uid required")
}
conf, err := proxy.ParseUpstreamsConfig(c.Upstreams, &upstream.Options{}) continue
if err != nil {
return fmt.Errorf("invalid upstream servers: %w", err)
}
err = conf.Close()
if err != nil {
l.ErrorContext(ctx, "client: closing upstream config", slogutil.KeyError, err)
}
for _, t := range c.Tags {
_, ok := slices.BinarySearch(allTags, t)
if !ok {
return fmt.Errorf("invalid tag: %q", t)
} }
c.Tags = append(c.Tags, t)
} }
// TODO(s.chzhen): Move to the constructor.
slices.Sort(c.Tags) slices.Sort(c.Tags)
return nil
} }
// SetIDs parses a list of strings into typed fields and returns an error if // SetIDs parses a list of strings into typed fields and returns an error if
@@ -323,3 +267,20 @@ func (c *Persistent) CloseUpstreams() (err error) {
return nil return nil
} }
// SetSafeSearch initializes and sets the safe search filter for this client.
func (c *Persistent) SetSafeSearch(
conf filtering.SafeSearchConfig,
cacheSize uint,
cacheTTL time.Duration,
) (err error) {
ss, err := safesearch.NewDefault(conf, fmt.Sprintf("client %q", c.Name), cacheSize, cacheTTL)
if err != nil {
// Don't wrap the error, because it's informative enough as is.
return err
}
c.SafeSearch = ss
return nil
}

View File

@@ -7,7 +7,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestPersistent_EqualIDs(t *testing.T) { func TestPersistentClient_EqualIDs(t *testing.T) {
const ( const (
ip = "0.0.0.0" ip = "0.0.0.0"
ip1 = "1.1.1.1" ip1 = "1.1.1.1"

View File

@@ -2,34 +2,39 @@ package client
import "net/netip" import "net/netip"
// runtimeIndex stores information about runtime clients. // RuntimeIndex stores information about runtime clients.
type runtimeIndex struct { type RuntimeIndex struct {
// index maps IP address to runtime client. // index maps IP address to runtime client.
index map[netip.Addr]*Runtime index map[netip.Addr]*Runtime
} }
// newRuntimeIndex returns initialized runtime index. // NewRuntimeIndex returns initialized runtime index.
func newRuntimeIndex() (ri *runtimeIndex) { func NewRuntimeIndex() (ri *RuntimeIndex) {
return &runtimeIndex{ return &RuntimeIndex{
index: map[netip.Addr]*Runtime{}, index: map[netip.Addr]*Runtime{},
} }
} }
// client returns the saved runtime client by ip. If no such client exists, // Client returns the saved runtime client by ip. If no such client exists,
// returns nil. // returns nil.
func (ri *runtimeIndex) client(ip netip.Addr) (rc *Runtime) { func (ri *RuntimeIndex) Client(ip netip.Addr) (rc *Runtime) {
return ri.index[ip] return ri.index[ip]
} }
// add saves the runtime client in the index. IP address of a client must be // Add saves the runtime client in the index. IP address of a client must be
// unique. See [Runtime.Client]. rc must not be nil. // unique. See [Runtime.Client]. rc must not be nil.
func (ri *runtimeIndex) add(rc *Runtime) { func (ri *RuntimeIndex) Add(rc *Runtime) {
ip := rc.Addr() ip := rc.Addr()
ri.index[ip] = rc ri.index[ip] = rc
} }
// rangeClients calls f for each runtime client in an undefined order. // Size returns the number of the runtime clients.
func (ri *runtimeIndex) rangeClients(f func(rc *Runtime) (cont bool)) { func (ri *RuntimeIndex) Size() (n int) {
return len(ri.index)
}
// Range calls f for each runtime client in an undefined order.
func (ri *RuntimeIndex) Range(f func(rc *Runtime) (cont bool)) {
for _, rc := range ri.index { for _, rc := range ri.index {
if !f(rc) { if !f(rc) {
return return
@@ -37,31 +42,17 @@ func (ri *runtimeIndex) rangeClients(f func(rc *Runtime) (cont bool)) {
} }
} }
// setInfo sets the client information from cs for runtime client stored by ip. // Delete removes the runtime client by ip.
// If no such client exists, it creates one. func (ri *RuntimeIndex) Delete(ip netip.Addr) {
func (ri *runtimeIndex) setInfo(ip netip.Addr, cs Source, hosts []string) (rc *Runtime) { delete(ri.index, ip)
rc = ri.index[ip]
if rc == nil {
rc = NewRuntime(ip)
ri.add(rc)
}
rc.setInfo(cs, hosts)
return rc
} }
// clearSource removes information from the specified source from all clients. // DeleteBySource removes all runtime clients that have information only from
func (ri *runtimeIndex) clearSource(src Source) { // the specified source and returns the number of removed clients.
for _, rc := range ri.index { func (ri *RuntimeIndex) DeleteBySource(src Source) (n int) {
rc.unset(src)
}
}
// removeEmpty removes empty runtime clients and returns the number of removed
// clients.
func (ri *runtimeIndex) removeEmpty() (n int) {
for ip, rc := range ri.index { for ip, rc := range ri.index {
rc.unset(src)
if rc.isEmpty() { if rc.isEmpty() {
delete(ri.index, ip) delete(ri.index, ip)
n++ n++

View File

@@ -0,0 +1,85 @@
package client_test
import (
"net/netip"
"testing"
"github.com/AdguardTeam/AdGuardHome/internal/client"
"github.com/stretchr/testify/assert"
)
func TestRuntimeIndex(t *testing.T) {
const cliSrc = client.SourceARP
var (
ip1 = netip.MustParseAddr("1.1.1.1")
ip2 = netip.MustParseAddr("2.2.2.2")
ip3 = netip.MustParseAddr("3.3.3.3")
)
ri := client.NewRuntimeIndex()
currentSize := 0
testCases := []struct {
ip netip.Addr
name string
hosts []string
src client.Source
}{{
src: cliSrc,
ip: ip1,
name: "1",
hosts: []string{"host1"},
}, {
src: cliSrc,
ip: ip2,
name: "2",
hosts: []string{"host2"},
}, {
src: cliSrc,
ip: ip3,
name: "3",
hosts: []string{"host3"},
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
rc := client.NewRuntime(tc.ip)
rc.SetInfo(tc.src, tc.hosts)
ri.Add(rc)
currentSize++
got := ri.Client(tc.ip)
assert.Equal(t, rc, got)
})
}
t.Run("size", func(t *testing.T) {
assert.Equal(t, currentSize, ri.Size())
})
t.Run("range", func(t *testing.T) {
s := 0
ri.Range(func(rc *client.Runtime) (cont bool) {
s++
return true
})
assert.Equal(t, currentSize, s)
})
t.Run("delete", func(t *testing.T) {
ri.Delete(ip1)
currentSize--
assert.Equal(t, currentSize, ri.Size())
})
t.Run("delete_by_src", func(t *testing.T) {
assert.Equal(t, currentSize, ri.DeleteBySource(cliSrc))
assert.Equal(t, 0, ri.Size())
})
}

View File

@@ -1,624 +0,0 @@
package client
import (
"context"
"fmt"
"log/slog"
"net"
"net/netip"
"slices"
"sync"
"time"
"github.com/AdguardTeam/AdGuardHome/internal/arpdb"
"github.com/AdguardTeam/AdGuardHome/internal/dhcpsvc"
"github.com/AdguardTeam/AdGuardHome/internal/whois"
"github.com/AdguardTeam/golibs/errors"
"github.com/AdguardTeam/golibs/hostsfile"
"github.com/AdguardTeam/golibs/logutil/slogutil"
)
// allowedTags is the list of available client tags.
var allowedTags = []string{
"device_audio",
"device_camera",
"device_gameconsole",
"device_laptop",
"device_nas", // Network-attached Storage
"device_other",
"device_pc",
"device_phone",
"device_printer",
"device_securityalarm",
"device_tablet",
"device_tv",
"os_android",
"os_ios",
"os_linux",
"os_macos",
"os_other",
"os_windows",
"user_admin",
"user_child",
"user_regular",
}
// DHCP is an interface for accessing DHCP lease data the [Storage] needs.
type DHCP interface {
// Leases returns all the DHCP leases.
Leases() (leases []*dhcpsvc.Lease)
// HostByIP returns the hostname of the DHCP client with the given IP
// address. host will be empty if there is no such client, due to an
// assumption that a DHCP client must always have a hostname.
HostByIP(ip netip.Addr) (host string)
// MACByIP returns the MAC address for the given IP address leased. It
// returns nil if there is no such client, due to an assumption that a DHCP
// client must always have a MAC address.
MACByIP(ip netip.Addr) (mac net.HardwareAddr)
}
// EmptyDHCP is the empty [DHCP] implementation that does nothing.
type EmptyDHCP struct{}
// type check
var _ DHCP = EmptyDHCP{}
// Leases implements the [DHCP] interface for emptyDHCP.
func (EmptyDHCP) Leases() (leases []*dhcpsvc.Lease) { return nil }
// HostByIP implements the [DHCP] interface for emptyDHCP.
func (EmptyDHCP) HostByIP(_ netip.Addr) (host string) { return "" }
// MACByIP implements the [DHCP] interface for emptyDHCP.
func (EmptyDHCP) MACByIP(_ netip.Addr) (mac net.HardwareAddr) { return nil }
// HostsContainer is an interface for receiving updates to the system hosts
// file.
type HostsContainer interface {
Upd() (updates <-chan *hostsfile.DefaultStorage)
}
// StorageConfig is the client storage configuration structure.
type StorageConfig struct {
// Logger is used for logging the operation of the client storage. It must
// not be nil.
Logger *slog.Logger
// DHCP is used to match IPs against MACs of persistent clients and update
// [SourceDHCP] runtime client information. It must not be nil.
DHCP DHCP
// EtcHosts is used to update [SourceHostsFile] runtime client information.
EtcHosts HostsContainer
// ARPDB is used to update [SourceARP] runtime client information.
ARPDB arpdb.Interface
// InitialClients is a list of persistent clients parsed from the
// configuration file. Each client must not be nil.
InitialClients []*Persistent
// ARPClientsUpdatePeriod defines how often [SourceARP] runtime client
// information is updated.
ARPClientsUpdatePeriod time.Duration
// RuntimeSourceDHCP specifies whether to update [SourceDHCP] information
// of runtime clients.
RuntimeSourceDHCP bool
}
// Storage contains information about persistent and runtime clients.
type Storage struct {
// logger is used for logging the operation of the client storage. It must
// not be nil.
logger *slog.Logger
// mu protects indexes of persistent and runtime clients.
mu *sync.Mutex
// index contains information about persistent clients.
index *index
// runtimeIndex contains information about runtime clients.
runtimeIndex *runtimeIndex
// dhcp is used to update [SourceDHCP] runtime client information.
dhcp DHCP
// etcHosts is used to update [SourceHostsFile] runtime client information.
etcHosts HostsContainer
// arpDB is used to update [SourceARP] runtime client information.
arpDB arpdb.Interface
// done is the shutdown signaling channel.
done chan struct{}
// allowedTags is a sorted list of all allowed tags. It must not be
// modified after initialization.
//
// TODO(s.chzhen): Use custom type.
allowedTags []string
// arpClientsUpdatePeriod defines how often [SourceARP] runtime client
// information is updated. It must be greater than zero.
arpClientsUpdatePeriod time.Duration
// runtimeSourceDHCP specifies whether to update [SourceDHCP] information
// of runtime clients.
runtimeSourceDHCP bool
}
// NewStorage returns initialized client storage. conf must not be nil.
func NewStorage(ctx context.Context, conf *StorageConfig) (s *Storage, err error) {
tags := slices.Clone(allowedTags)
slices.Sort(tags)
s = &Storage{
logger: conf.Logger,
mu: &sync.Mutex{},
index: newIndex(),
runtimeIndex: newRuntimeIndex(),
dhcp: conf.DHCP,
etcHosts: conf.EtcHosts,
arpDB: conf.ARPDB,
done: make(chan struct{}),
allowedTags: tags,
arpClientsUpdatePeriod: conf.ARPClientsUpdatePeriod,
runtimeSourceDHCP: conf.RuntimeSourceDHCP,
}
for i, p := range conf.InitialClients {
err = s.Add(ctx, p)
if err != nil {
return nil, fmt.Errorf("adding client %q at index %d: %w", p.Name, i, err)
}
}
s.ReloadARP(ctx)
return s, nil
}
// Start starts the goroutines for updating the runtime client information.
//
// TODO(s.chzhen): Pass context.
func (s *Storage) Start(ctx context.Context) (err error) {
go s.periodicARPUpdate(ctx)
go s.handleHostsUpdates(ctx)
return nil
}
// Shutdown gracefully stops the client storage.
//
// TODO(s.chzhen): Pass context.
func (s *Storage) Shutdown(_ context.Context) (err error) {
close(s.done)
return s.closeUpstreams()
}
// periodicARPUpdate periodically reloads runtime clients from ARP. It is
// intended to be used as a goroutine.
func (s *Storage) periodicARPUpdate(ctx context.Context) {
defer slogutil.RecoverAndLog(ctx, s.logger)
t := time.NewTicker(s.arpClientsUpdatePeriod)
for {
select {
case <-t.C:
s.ReloadARP(ctx)
case <-s.done:
return
}
}
}
// ReloadARP reloads runtime clients from ARP, if configured.
func (s *Storage) ReloadARP(ctx context.Context) {
if s.arpDB != nil {
s.addFromSystemARP(ctx)
}
}
// addFromSystemARP adds the IP-hostname pairings from the output of the arp -a
// command.
func (s *Storage) addFromSystemARP(ctx context.Context) {
s.mu.Lock()
defer s.mu.Unlock()
if err := s.arpDB.Refresh(); err != nil {
s.arpDB = arpdb.Empty{}
s.logger.ErrorContext(ctx, "refreshing arp container", slogutil.KeyError, err)
return
}
ns := s.arpDB.Neighbors()
if len(ns) == 0 {
s.logger.DebugContext(ctx, "refreshing arp container: the update is empty")
return
}
src := SourceARP
s.runtimeIndex.clearSource(src)
for _, n := range ns {
s.runtimeIndex.setInfo(n.IP, src, []string{n.Name})
}
removed := s.runtimeIndex.removeEmpty()
s.logger.DebugContext(
ctx,
"updating client aliases from arp neighborhood",
"added", len(ns),
"removed", removed,
)
}
// handleHostsUpdates receives the updates from the hosts container and adds
// them to the clients storage. It is intended to be used as a goroutine.
func (s *Storage) handleHostsUpdates(ctx context.Context) {
if s.etcHosts == nil {
return
}
defer slogutil.RecoverAndLog(ctx, s.logger)
for {
select {
case upd, ok := <-s.etcHosts.Upd():
if !ok {
return
}
s.addFromHostsFile(ctx, upd)
case <-s.done:
return
}
}
}
// addFromHostsFile fills the client-hostname pairing index from the system's
// hosts files.
func (s *Storage) addFromHostsFile(ctx context.Context, hosts *hostsfile.DefaultStorage) {
s.mu.Lock()
defer s.mu.Unlock()
src := SourceHostsFile
s.runtimeIndex.clearSource(src)
added := 0
hosts.RangeNames(func(addr netip.Addr, names []string) (cont bool) {
// Only the first name of the first record is considered a canonical
// hostname for the IP address.
//
// TODO(e.burkov): Consider using all the names from all the records.
s.runtimeIndex.setInfo(addr, src, []string{names[0]})
added++
return true
})
removed := s.runtimeIndex.removeEmpty()
s.logger.DebugContext(
ctx,
"updating client aliases from system hosts file",
"added", added,
"removed", removed,
)
}
// type check
var _ AddressUpdater = (*Storage)(nil)
// UpdateAddress implements the [AddressUpdater] interface for *Storage
func (s *Storage) UpdateAddress(ctx context.Context, ip netip.Addr, host string, info *whois.Info) {
// Common fast path optimization.
if host == "" && info == nil {
return
}
s.mu.Lock()
defer s.mu.Unlock()
if host != "" {
s.runtimeIndex.setInfo(ip, SourceRDNS, []string{host})
}
if info != nil {
s.setWHOISInfo(ctx, ip, info)
}
}
// UpdateDHCP updates [SourceDHCP] runtime client information.
func (s *Storage) UpdateDHCP(ctx context.Context) {
if s.dhcp == nil || !s.runtimeSourceDHCP {
return
}
s.mu.Lock()
defer s.mu.Unlock()
src := SourceDHCP
s.runtimeIndex.clearSource(src)
added := 0
for _, l := range s.dhcp.Leases() {
s.runtimeIndex.setInfo(l.IP, src, []string{l.Hostname})
added++
}
removed := s.runtimeIndex.removeEmpty()
s.logger.DebugContext(
ctx,
"updating client aliases from dhcp",
"added", added,
"removed", removed,
)
}
// setWHOISInfo sets the WHOIS information for a runtime client.
func (s *Storage) setWHOISInfo(ctx context.Context, ip netip.Addr, wi *whois.Info) {
_, ok := s.index.findByIP(ip)
if ok {
s.logger.DebugContext(
ctx,
"persistent client is already created, ignore whois info",
"ip", ip,
)
return
}
rc := s.runtimeIndex.client(ip)
if rc == nil {
rc = NewRuntime(ip)
s.runtimeIndex.add(rc)
}
rc.setWHOIS(wi)
s.logger.DebugContext(ctx, "set whois info for runtime client", "ip", ip, "whois", wi)
}
// Add stores persistent client information or returns an error.
func (s *Storage) Add(ctx context.Context, p *Persistent) (err error) {
defer func() { err = errors.Annotate(err, "adding client: %w") }()
err = p.validate(ctx, s.logger, s.allowedTags)
if err != nil {
// Don't wrap the error since there is already an annotation deferred.
return err
}
s.mu.Lock()
defer s.mu.Unlock()
err = s.index.clashesUID(p)
if err != nil {
// Don't wrap the error since there is already an annotation deferred.
return err
}
err = s.index.clashes(p)
if err != nil {
// Don't wrap the error since there is already an annotation deferred.
return err
}
s.index.add(p)
s.logger.DebugContext(
ctx,
"client added",
"name", p.Name,
"ids", p.IDs(),
"clients_count", s.index.size(),
)
return nil
}
// FindByName finds persistent client by name. And returns its shallow copy.
func (s *Storage) FindByName(name string) (p *Persistent, ok bool) {
s.mu.Lock()
defer s.mu.Unlock()
p, ok = s.index.findByName(name)
if ok {
return p.ShallowClone(), ok
}
return nil, false
}
// Find finds persistent client by string representation of the client ID, IP
// address, or MAC. And returns its shallow copy.
//
// TODO(s.chzhen): Accept ClientIDData structure instead, which will contain
// the parsed IP address, if any.
func (s *Storage) Find(id string) (p *Persistent, ok bool) {
s.mu.Lock()
defer s.mu.Unlock()
p, ok = s.index.find(id)
if ok {
return p.ShallowClone(), ok
}
ip, err := netip.ParseAddr(id)
if err != nil {
return nil, false
}
foundMAC := s.dhcp.MACByIP(ip)
if foundMAC != nil {
return s.FindByMAC(foundMAC)
}
return nil, false
}
// FindLoose is like [Storage.Find] but it also tries to find a persistent
// client by IP address without zone. It strips the IPv6 zone index from the
// stored IP addresses before comparing, because querylog entries don't have it.
// See TODO on [querylog.logEntry.IP].
//
// Note that multiple clients can have the same IP address with different zones.
// Therefore, the result of this method is indeterminate.
func (s *Storage) FindLoose(ip netip.Addr, id string) (p *Persistent, ok bool) {
s.mu.Lock()
defer s.mu.Unlock()
p, ok = s.index.find(id)
if ok {
return p.ShallowClone(), ok
}
p = s.index.findByIPWithoutZone(ip)
if p != nil {
return p.ShallowClone(), true
}
return nil, false
}
// FindByMAC finds persistent client by MAC and returns its shallow copy. s.mu
// is expected to be locked.
func (s *Storage) FindByMAC(mac net.HardwareAddr) (p *Persistent, ok bool) {
p, ok = s.index.findByMAC(mac)
if ok {
return p.ShallowClone(), ok
}
return nil, false
}
// RemoveByName removes persistent client information. ok is false if no such
// client exists by that name.
func (s *Storage) RemoveByName(ctx context.Context, name string) (ok bool) {
s.mu.Lock()
defer s.mu.Unlock()
p, ok := s.index.findByName(name)
if !ok {
return false
}
if err := p.CloseUpstreams(); err != nil {
s.logger.ErrorContext(ctx, "removing client", "name", p.Name, slogutil.KeyError, err)
}
s.index.remove(p)
return true
}
// Update finds the stored persistent client by its name and updates its
// information from p.
func (s *Storage) Update(ctx context.Context, name string, p *Persistent) (err error) {
defer func() { err = errors.Annotate(err, "updating client: %w") }()
err = p.validate(ctx, s.logger, s.allowedTags)
if err != nil {
// Don't wrap the error since there is already an annotation deferred.
return err
}
s.mu.Lock()
defer s.mu.Unlock()
stored, ok := s.index.findByName(name)
if !ok {
return fmt.Errorf("client %q is not found", name)
}
// Client p has a newly generated UID, so replace it with the stored one.
//
// TODO(s.chzhen): Remove when frontend starts handling UIDs.
p.UID = stored.UID
err = s.index.clashes(p)
if err != nil {
// Don't wrap the error since there is already an annotation deferred.
return err
}
s.index.remove(stored)
s.index.add(p)
return nil
}
// RangeByName calls f for each persistent client sorted by name, unless cont is
// false.
func (s *Storage) RangeByName(f func(c *Persistent) (cont bool)) {
s.mu.Lock()
defer s.mu.Unlock()
s.index.rangeByName(f)
}
// Size returns the number of persistent clients.
func (s *Storage) Size() (n int) {
s.mu.Lock()
defer s.mu.Unlock()
return s.index.size()
}
// closeUpstreams closes upstream configurations of persistent clients.
func (s *Storage) closeUpstreams() (err error) {
s.mu.Lock()
defer s.mu.Unlock()
return s.index.closeUpstreams()
}
// ClientRuntime returns a copy of the saved runtime client by ip. If no such
// client exists, returns nil.
func (s *Storage) ClientRuntime(ip netip.Addr) (rc *Runtime) {
s.mu.Lock()
defer s.mu.Unlock()
rc = s.runtimeIndex.client(ip)
if rc != nil {
return rc.clone()
}
if !s.runtimeSourceDHCP {
return nil
}
host := s.dhcp.HostByIP(ip)
if host == "" {
return nil
}
rc = s.runtimeIndex.setInfo(ip, SourceDHCP, []string{host})
return rc.clone()
}
// RangeRuntime calls f for each runtime client in an undefined order.
func (s *Storage) RangeRuntime(f func(rc *Runtime) (cont bool)) {
s.mu.Lock()
defer s.mu.Unlock()
s.runtimeIndex.rangeClients(f)
}
// AllowedTags returns the list of available client tags. tags must not be
// modified.
func (s *Storage) AllowedTags() (tags []string) {
return s.allowedTags
}

File diff suppressed because it is too large Load Diff

View File

@@ -2,4 +2,4 @@
package configmigrate package configmigrate
// LastSchemaVersion is the most recent schema version. // LastSchemaVersion is the most recent schema version.
const LastSchemaVersion uint = 29 const LastSchemaVersion uint = 28

View File

@@ -19,7 +19,6 @@ func TestUpgradeSchema1to2(t *testing.T) {
m := New(&Config{ m := New(&Config{
WorkingDir: "", WorkingDir: "",
DataDir: "",
}) })
err := m.migrateTo2(diskConf) err := m.migrateTo2(diskConf)
@@ -521,7 +520,7 @@ func TestUpgradeSchema11to12(t *testing.T) {
name string name string
}{{ }{{
ivl: 1, ivl: 1,
want: timeutil.Duration(timeutil.Day), want: timeutil.Duration{Duration: timeutil.Day},
wantErr: "", wantErr: "",
name: "success", name: "success",
}, { }, {
@@ -604,7 +603,7 @@ func TestUpgradeSchema11to12(t *testing.T) {
ivlVal, ok = ivl.(timeutil.Duration) ivlVal, ok = ivl.(timeutil.Duration)
require.True(t, ok) require.True(t, ok)
assert.Equal(t, 90*24*time.Hour, time.Duration(ivlVal)) assert.Equal(t, 90*24*time.Hour, ivlVal.Duration)
}) })
} }
@@ -1055,12 +1054,12 @@ func TestUpgradeSchema19to20(t *testing.T) {
name string name string
}{{ }{{
ivl: 1, ivl: 1,
want: timeutil.Duration(timeutil.Day), want: timeutil.Duration{Duration: timeutil.Day},
wantErr: "", wantErr: "",
name: "success", name: "success",
}, { }, {
ivl: 0, ivl: 0,
want: timeutil.Duration(timeutil.Day), want: timeutil.Duration{Duration: timeutil.Day},
wantErr: "", wantErr: "",
name: "success", name: "success",
}, { }, {
@@ -1143,7 +1142,7 @@ func TestUpgradeSchema19to20(t *testing.T) {
ivlVal, ok = ivl.(timeutil.Duration) ivlVal, ok = ivl.(timeutil.Duration)
require.True(t, ok) require.True(t, ok)
assert.Equal(t, 24*time.Hour, time.Duration(ivlVal)) assert.Equal(t, 24*time.Hour, ivlVal.Duration)
}) })
} }

View File

@@ -10,24 +10,20 @@ import (
// Config is a the configuration for initializing a [Migrator]. // Config is a the configuration for initializing a [Migrator].
type Config struct { type Config struct {
// WorkingDir is the absolute path to the working directory of AdGuardHome. // WorkingDir is an absolute path to the working directory of AdGuardHome.
WorkingDir string WorkingDir string
// DataDir is the absolute path to the data directory of AdGuardHome.
DataDir string
} }
// Migrator performs the YAML configuration file migrations. // Migrator performs the YAML configuration file migrations.
type Migrator struct { type Migrator struct {
// workingDir is an absolute path to the working directory of AdGuardHome.
workingDir string workingDir string
dataDir string
} }
// New creates a new Migrator. // New creates a new Migrator.
func New(c *Config) (m *Migrator) { func New(cfg *Config) (m *Migrator) {
return &Migrator{ return &Migrator{
workingDir: c.WorkingDir, workingDir: cfg.WorkingDir,
dataDir: c.DataDir,
} }
} }
@@ -124,7 +120,6 @@ func (m *Migrator) upgradeConfigSchema(current, target uint, diskConf yobj) (err
25: migrateTo26, 25: migrateTo26,
26: migrateTo27, 26: migrateTo27,
27: migrateTo28, 27: migrateTo28,
28: m.migrateTo29,
} }
for i, migrate := range upgrades[current:target] { for i, migrate := range upgrades[current:target] {

View File

@@ -1,12 +1,9 @@
package configmigrate_test package configmigrate_test
import ( import (
"bytes"
"io/fs" "io/fs"
"os" "os"
"path" "path"
"path/filepath"
"runtime"
"testing" "testing"
"github.com/AdguardTeam/AdGuardHome/internal/configmigrate" "github.com/AdguardTeam/AdGuardHome/internal/configmigrate"
@@ -205,7 +202,6 @@ func TestMigrateConfig_Migrate(t *testing.T) {
migrator := configmigrate.New(&configmigrate.Config{ migrator := configmigrate.New(&configmigrate.Config{
WorkingDir: t.Name(), WorkingDir: t.Name(),
DataDir: filepath.Join(t.Name(), "data"),
}) })
newBody, upgraded, err := migrator.Migrate(body, tc.targetVersion) newBody, upgraded, err := migrator.Migrate(body, tc.targetVersion)
require.NoError(t, err) require.NoError(t, err)
@@ -215,43 +211,3 @@ func TestMigrateConfig_Migrate(t *testing.T) {
}) })
} }
} }
// TODO(a.garipov): Consider ways of merging into the previous one.
func TestMigrateConfig_Migrate_v29(t *testing.T) {
const (
pathUnix = `/path/to/file.txt`
userDirPatUnix = `TestMigrateConfig_Migrate/v29/data/userfilters/*`
pathWindows = `C:\path\to\file.txt`
userDirPatWindows = `TestMigrateConfig_Migrate\v29\data\userfilters\*`
)
pathToReplace := pathUnix
patternToReplace := userDirPatUnix
if runtime.GOOS == "windows" {
pathToReplace = pathWindows
patternToReplace = userDirPatWindows
}
body, err := fs.ReadFile(testdata, "TestMigrateConfig_Migrate/v29/input.yml")
require.NoError(t, err)
body = bytes.ReplaceAll(body, []byte("FILEPATH"), []byte(pathToReplace))
wantBody, err := fs.ReadFile(testdata, "TestMigrateConfig_Migrate/v29/output.yml")
require.NoError(t, err)
wantBody = bytes.ReplaceAll(wantBody, []byte("FILEPATH"), []byte(pathToReplace))
wantBody = bytes.ReplaceAll(wantBody, []byte("USERFILTERSPATH"), []byte(patternToReplace))
migrator := configmigrate.New(&configmigrate.Config{
WorkingDir: t.Name(),
DataDir: "TestMigrateConfig_Migrate/v29/data",
})
newBody, upgraded, err := migrator.Migrate(body, 29)
require.NoError(t, err)
require.True(t, upgraded)
require.YAMLEq(t, string(wantBody), string(newBody))
}

View File

@@ -1,117 +0,0 @@
http:
address: 127.0.0.1:3000
session_ttl: 3h
pprof:
enabled: true
port: 6060
users:
- name: testuser
password: testpassword
dns:
bind_hosts:
- 127.0.0.1
port: 53
parental_sensitivity: 0
upstream_dns:
- tls://1.1.1.1
- tls://1.0.0.1
- quic://8.8.8.8:784
bootstrap_dns:
- 8.8.8.8:53
edns_client_subnet:
enabled: true
use_custom: false
custom_ip: ""
filtering:
filtering_enabled: true
parental_enabled: false
safebrowsing_enabled: false
safe_search:
enabled: false
bing: true
duckduckgo: true
google: true
pixabay: true
yandex: true
youtube: true
protection_enabled: true
blocked_services:
schedule:
time_zone: Local
ids:
- 500px
blocked_response_ttl: 10
filters:
- url: https://adaway.org/hosts.txt
name: AdAway
enabled: false
- url: FILEPATH
name: Local Filter
enabled: false
clients:
persistent:
- name: localhost
ids:
- 127.0.0.1
- aa:aa:aa:aa:aa:aa
use_global_settings: true
use_global_blocked_services: true
filtering_enabled: false
parental_enabled: false
safebrowsing_enabled: false
safe_search:
enabled: true
bing: true
duckduckgo: true
google: true
pixabay: true
yandex: true
youtube: true
blocked_services:
schedule:
time_zone: Local
ids:
- 500px
runtime_sources:
whois: true
arp: true
rdns: true
dhcp: true
hosts: true
dhcp:
enabled: false
interface_name: vboxnet0
local_domain_name: local
dhcpv4:
gateway_ip: 192.168.0.1
subnet_mask: 255.255.255.0
range_start: 192.168.0.10
range_end: 192.168.0.250
lease_duration: 1234
icmp_timeout_msec: 10
schema_version: 28
user_rules: []
querylog:
enabled: true
file_enabled: true
interval: 720h
size_memory: 1000
ignored:
- '|.^'
statistics:
enabled: true
interval: 240h
ignored:
- '|.^'
os:
group: ''
rlimit_nofile: 123
user: ''
log:
file: ""
max_backups: 0
max_size: 100
max_age: 3
compress: true
local_time: false
verbose: true

View File

@@ -1,120 +0,0 @@
http:
address: 127.0.0.1:3000
session_ttl: 3h
pprof:
enabled: true
port: 6060
users:
- name: testuser
password: testpassword
dns:
bind_hosts:
- 127.0.0.1
port: 53
parental_sensitivity: 0
upstream_dns:
- tls://1.1.1.1
- tls://1.0.0.1
- quic://8.8.8.8:784
bootstrap_dns:
- 8.8.8.8:53
edns_client_subnet:
enabled: true
use_custom: false
custom_ip: ""
filtering:
filtering_enabled: true
parental_enabled: false
safebrowsing_enabled: false
safe_fs_patterns:
- USERFILTERSPATH
- FILEPATH
safe_search:
enabled: false
bing: true
duckduckgo: true
google: true
pixabay: true
yandex: true
youtube: true
protection_enabled: true
blocked_services:
schedule:
time_zone: Local
ids:
- 500px
blocked_response_ttl: 10
filters:
- url: https://adaway.org/hosts.txt
name: AdAway
enabled: false
- url: FILEPATH
name: Local Filter
enabled: false
clients:
persistent:
- name: localhost
ids:
- 127.0.0.1
- aa:aa:aa:aa:aa:aa
use_global_settings: true
use_global_blocked_services: true
filtering_enabled: false
parental_enabled: false
safebrowsing_enabled: false
safe_search:
enabled: true
bing: true
duckduckgo: true
google: true
pixabay: true
yandex: true
youtube: true
blocked_services:
schedule:
time_zone: Local
ids:
- 500px
runtime_sources:
whois: true
arp: true
rdns: true
dhcp: true
hosts: true
dhcp:
enabled: false
interface_name: vboxnet0
local_domain_name: local
dhcpv4:
gateway_ip: 192.168.0.1
subnet_mask: 255.255.255.0
range_start: 192.168.0.10
range_end: 192.168.0.250
lease_duration: 1234
icmp_timeout_msec: 10
schema_version: 29
user_rules: []
querylog:
enabled: true
file_enabled: true
interval: 720h
size_memory: 1000
ignored:
- '|.^'
statistics:
enabled: true
interval: 240h
ignored:
- '|.^'
os:
group: ''
rlimit_nofile: 123
user: ''
log:
file: ""
max_backups: 0
max_size: 100
max_age: 3
compress: true
local_time: false
verbose: true

View File

@@ -37,7 +37,7 @@ func migrateTo12(diskConf yobj) (err error) {
qlogIvl = 90 qlogIvl = 90
} }
dns[field] = timeutil.Duration(time.Duration(qlogIvl) * timeutil.Day) dns[field] = timeutil.Duration{Duration: time.Duration(qlogIvl) * timeutil.Day}
return nil return nil
} }

View File

@@ -38,7 +38,7 @@ func migrateTo20(diskConf yobj) (err error) {
ivl = 1 ivl = 1
} }
stats[field] = timeutil.Duration(time.Duration(ivl) * timeutil.Day) stats[field] = timeutil.Duration{Duration: time.Duration(ivl) * timeutil.Day}
return nil return nil
} }

View File

@@ -48,7 +48,7 @@ func migrateTo23(diskConf yobj) (err error) {
diskConf["http"] = yobj{ diskConf["http"] = yobj{
"address": netip.AddrPortFrom(bindHostAddr, uint16(bindPort)).String(), "address": netip.AddrPortFrom(bindHostAddr, uint16(bindPort)).String(),
"session_ttl": timeutil.Duration(time.Duration(sessionTTL) * time.Hour).String(), "session_ttl": timeutil.Duration{Duration: time.Duration(sessionTTL) * time.Hour}.String(),
} }
delete(diskConf, "bind_host") delete(diskConf, "bind_host")

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