mirror of
https://github.com/AdguardTeam/AdGuardHome.git
synced 2024-11-21 20:45:33 +03:00
all: sync with master; upd chlog
This commit is contained in:
parent
c69639c013
commit
80eb339896
344 changed files with 2793 additions and 23680 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -12,7 +12,6 @@
|
|||
/agh-backup/
|
||||
/bin/
|
||||
/build/*
|
||||
/build2/*
|
||||
/data/
|
||||
/dist/
|
||||
/filtering/tests/filtering.TestLotsOfRules*.pprof
|
||||
|
@ -26,4 +25,3 @@ leases.db
|
|||
node_modules/
|
||||
|
||||
!/build/gitkeep
|
||||
!/build2/gitkeep
|
||||
|
|
53
CHANGELOG.md
53
CHANGELOG.md
|
@ -14,15 +14,52 @@ and this project adheres to
|
|||
<!--
|
||||
## [v0.108.0] - TBA
|
||||
|
||||
## [v0.107.23] - 2023-02-15 (APPROX.)
|
||||
## [v0.107.24] - 2023-02-22 (APPROX.)
|
||||
|
||||
See also the [v0.107.23 GitHub milestone][ms-v0.107.23].
|
||||
See also the [v0.107.24 GitHub milestone][ms-v0.107.24].
|
||||
|
||||
[ms-v0.107.23]: https://github.com/AdguardTeam/AdGuardHome/milestone/59?closed=1
|
||||
[ms-v0.107.24]: https://github.com/AdguardTeam/AdGuardHome/milestone/60?closed=1
|
||||
|
||||
NOTE: Add new changes BELOW THIS COMMENT.
|
||||
-->
|
||||
|
||||
<!--
|
||||
NOTE: Add new changes ABOVE THIS COMMENT.
|
||||
-->
|
||||
|
||||
|
||||
|
||||
## [v0.107.23] - 2023-02-01
|
||||
|
||||
See also the [v0.107.23 GitHub milestone][ms-v0.107.23].
|
||||
|
||||
### Added
|
||||
|
||||
- DNS64 support ([#5117]). The function may be enabled with new `use_dns64`
|
||||
field under `dns` object in the configuration along with `dns64_prefixes`, the
|
||||
set of exclusion prefixes to filter AAAA responses. The Well-Known Prefix
|
||||
(`64:ff9b::/96`) is used if no custom prefixes are specified.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Filtering rules with `*` as the hostname not working properly ([#5245]).
|
||||
- Various dark theme bugs ([#5375]).
|
||||
|
||||
### Removed
|
||||
|
||||
- The “beta frontend” and the corresponding APIs. They never quite worked
|
||||
properly, and the future new version of AdGuard Home API will probably be
|
||||
different.
|
||||
|
||||
Correspondingly, the configuration parameter `beta_bind_port` has been removed
|
||||
as well.
|
||||
|
||||
[#5117]: https://github.com/AdguardTeam/AdGuardHome/issues/5117
|
||||
[#5245]: https://github.com/AdguardTeam/AdGuardHome/issues/5245
|
||||
[#5375]: https://github.com/AdguardTeam/AdGuardHome/issues/5375
|
||||
|
||||
[ms-v0.107.23]: https://github.com/AdguardTeam/AdGuardHome/milestone/59?closed=1
|
||||
|
||||
|
||||
|
||||
## [v0.107.22] - 2023-01-19
|
||||
|
@ -48,7 +85,7 @@ See also the [v0.107.22 GitHub milestone][ms-v0.107.22].
|
|||
running ([#4223], [#5191]).
|
||||
- The `--update` flag performing an update even when there is no version change.
|
||||
- Failing HTTPS redirection on saving the encryption settings ([#4898]).
|
||||
- Zeroing rules counter of erroneusly edited filtering rule lists ([#5290]).
|
||||
- Zeroing rules counter of erroneously edited filtering rule lists ([#5290]).
|
||||
- Filters updating strategy, which could sometimes lead to use of broken or
|
||||
incompletely downloaded lists ([#5258]).
|
||||
|
||||
|
@ -61,7 +98,6 @@ See also the [v0.107.22 GitHub milestone][ms-v0.107.22].
|
|||
|
||||
|
||||
|
||||
|
||||
## [v0.107.21] - 2022-12-15
|
||||
|
||||
See also the [v0.107.21 GitHub milestone][ms-v0.107.21].
|
||||
|
@ -1542,11 +1578,12 @@ See also the [v0.104.2 GitHub milestone][ms-v0.104.2].
|
|||
|
||||
|
||||
<!--
|
||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.23...HEAD
|
||||
[v0.107.23]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.22...v0.107.23
|
||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.24...HEAD
|
||||
[v0.107.24]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.23...v0.107.24
|
||||
-->
|
||||
|
||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.22...HEAD
|
||||
[Unreleased]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.23...HEAD
|
||||
[v0.107.23]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.22...v0.107.23
|
||||
[v0.107.22]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.21...v0.107.22
|
||||
[v0.107.21]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.20...v0.107.21
|
||||
[v0.107.20]: https://github.com/AdguardTeam/AdGuardHome/compare/v0.107.19...v0.107.20
|
||||
|
|
9
Makefile
9
Makefile
|
@ -5,7 +5,6 @@
|
|||
.POSIX:
|
||||
|
||||
CHANNEL = development
|
||||
CLIENT_BETA_DIR = client2
|
||||
CLIENT_DIR = client
|
||||
COMMIT = $$( git rev-parse --short HEAD )
|
||||
DIST_DIR = dist
|
||||
|
@ -29,10 +28,6 @@ SIGN = 1
|
|||
VERBOSE = 0
|
||||
VERSION = v0.0.0
|
||||
YARN = yarn
|
||||
YARN_FLAGS = --cwd $(CLIENT_BETA_DIR)
|
||||
YARN_INSTALL_FLAGS = $(YARN_FLAGS) --network-timeout 120000 --silent\
|
||||
--ignore-engines --ignore-optional --ignore-platform\
|
||||
--ignore-scripts
|
||||
|
||||
# Macros for the build-release target. If FRONTEND_PREBUILT is 0, the
|
||||
# default, the macro $(BUILD_RELEASE_DEPS_$(FRONTEND_PREBUILT)) expands
|
||||
|
@ -90,17 +85,13 @@ init: ; git config core.hooksPath ./scripts/hooks
|
|||
|
||||
js-build:
|
||||
$(NPM) $(NPM_FLAGS) run build-prod
|
||||
$(YARN) $(YARN_FLAGS) build
|
||||
js-deps:
|
||||
$(NPM) $(NPM_INSTALL_FLAGS) ci
|
||||
$(YARN) $(YARN_INSTALL_FLAGS) install
|
||||
|
||||
# TODO(a.garipov): Remove the legacy client tasks support once the new
|
||||
# client is done and the old one is removed.
|
||||
js-lint: ; $(NPM) $(NPM_FLAGS) run lint
|
||||
js-test: ; $(NPM) $(NPM_FLAGS) run test
|
||||
js-beta-lint: ; $(YARN) $(YARN_FLAGS) lint
|
||||
js-beta-test: ; # TODO(v.abdulmyanov): Add tests for the new client.
|
||||
|
||||
go-build: ; $(ENV) "$(SHELL)" ./scripts/make/go-build.sh
|
||||
go-deps: ; $(ENV) "$(SHELL)" ./scripts/make/go-deps.sh
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
Keep this file non-hidden for Go's embedding to work.
|
|
@ -37,8 +37,6 @@
|
|||
"dhcp_ipv6_settings": "DHCP IPv6 إعدادات",
|
||||
"form_error_required": "الحقل مطلوب",
|
||||
"form_error_ip4_format": "عنوان IPv4 غير صالح",
|
||||
"form_error_ip4_range_start_format": "عناوين البداية لـIPv4 غير صالحة للنطاق",
|
||||
"form_error_ip4_range_end_format": "عناوين IPv4 غير صالحة لنطاق النهاية",
|
||||
"form_error_ip4_gateway_format": "عنوان IPv4 غير صالح للبوابة",
|
||||
"form_error_ip6_format": "عنوان IPv6 غير صالح",
|
||||
"form_error_ip_format": "عنوان IP غير صحيح",
|
||||
|
@ -51,7 +49,6 @@
|
|||
"out_of_range_error": "يجب أن يكون خارج النطاق \"{{start}}\" - \"{{end}}\"",
|
||||
"lower_range_start_error": "يجب أن يكون أقل من نطاق البداية",
|
||||
"greater_range_start_error": "يجب أن يكون أكبر من نطاق البداية",
|
||||
"greater_range_end_error": "يجب أن يكون أكبر من نطاق النهاية",
|
||||
"subnet_error": "يجب أن تكون العناوين في شبكة فرعية واحدة",
|
||||
"gateway_or_subnet_invalid": "قناع الشبكة الفرعية غير صالح",
|
||||
"dhcp_form_gateway_input": "IP البوابة",
|
||||
|
@ -300,6 +297,8 @@
|
|||
"blocking_mode_nxdomain": "NXDOMAIN: الرد باستخدام رمز NXDOMAIN",
|
||||
"blocking_mode_null_ip": "IP Null: الاستجابة بعنوان IP صفري (0.0.0.0 لـ A ؛ :: لـ AAAA)",
|
||||
"blocking_mode_custom_ip": "استجابة IP مخصصة بعنوان IP تم تعيينه يدويًا",
|
||||
"theme_light": "فاتح",
|
||||
"theme_dark": "ليلي",
|
||||
"upstream_dns_client_desc": "إذا احتفظت بهذا الحقل فارغًا ، فسيستخدم AdGuard Home الخوادم التي تم تكوينها في<0>DNS إعدادات</0>.",
|
||||
"tracker_source": "مصدر المتعقب",
|
||||
"source_label": "المصدر",
|
||||
|
|
|
@ -298,6 +298,9 @@
|
|||
"blocking_mode_nxdomain": "NXDOMAIN: Адказвае з кодам NXDOMAIN\n",
|
||||
"blocking_mode_null_ip": "Нулёвы IP: Адказвае з нулёвым IP-адрасам (0.0.0.0 для A; :: для AAAA)",
|
||||
"blocking_mode_custom_ip": "Карыстацкі IP: Адказвае з ручна наладжаным IP-адрасам",
|
||||
"theme_auto": "Аўто",
|
||||
"theme_light": "Светлая",
|
||||
"theme_dark": "Цёмная",
|
||||
"upstream_dns_client_desc": "Калі пакінуць поле пустым, AdGuard Home будзе звяртацца да сервераў, паказаных у <0>наладах DNS</0>.",
|
||||
"tracker_source": "Крыніца трэкінгу",
|
||||
"source_label": "Крыніца",
|
||||
|
@ -451,6 +454,7 @@
|
|||
"updates_checked": "Даступная новая версія AdGuard Home",
|
||||
"updates_version_equal": "Версія AdGuard Home актуальная",
|
||||
"check_updates_now": "Праверыць абнаўленні",
|
||||
"version_request_error": "Памылка пры праверцы наяўнасці абнаўленняў. Праверце ваша інтэрнэт-злучэнне.",
|
||||
"dns_privacy": "Зашыфраваны DNS",
|
||||
"setup_dns_privacy_1": "<0>DNS-over-TLS:</0> Ужывайце радок <1>{{address}}</1>.",
|
||||
"setup_dns_privacy_2": "<0>DNS-over-HTTPS:</0> Ужывайце радок <1>{{address}}</1>.",
|
||||
|
|
|
@ -165,8 +165,8 @@
|
|||
"enabled_safe_browsing_toast": "Zapnuté bezpečné prohlížení",
|
||||
"disabled_parental_toast": "Vypnutá Rodičovská kontrola",
|
||||
"enabled_parental_toast": "Zapnutá Rodičovská kontrola",
|
||||
"disabled_safe_search_toast": "Zapnuté bezpečné vyhledávání",
|
||||
"enabled_save_search_toast": "Vypnuté bezpečné vyhledávání",
|
||||
"disabled_safe_search_toast": "Vypnuté bezpečné vyhledávání",
|
||||
"enabled_save_search_toast": "Zapnuté bezpečné vyhledávání",
|
||||
"enabled_table_header": "Zapnuto",
|
||||
"name_table_header": "Název",
|
||||
"list_url_table_header": "Seznam URL",
|
||||
|
@ -298,6 +298,9 @@
|
|||
"blocking_mode_nxdomain": "NXDOMAIN: Odezva s kódem NXDOMAIN",
|
||||
"blocking_mode_null_ip": "Nulová IP: Odezva s nulovou IP adresou (0.0.0.0 pro A; :: pro AAAA)",
|
||||
"blocking_mode_custom_ip": "Vlastní IP. odezva s ručně nastavenou IP adresou",
|
||||
"theme_auto": "Autom.",
|
||||
"theme_light": "Světlý",
|
||||
"theme_dark": "Tmavý",
|
||||
"upstream_dns_client_desc": "Pokud toto pole ponecháte prázdné, AdGuard Home použije servery nakonfigurované v<0>DNS nastavení</0>.",
|
||||
"tracker_source": "Zdroj slídiče",
|
||||
"source_label": "Zdroj",
|
||||
|
@ -451,6 +454,7 @@
|
|||
"updates_checked": "Nová verze AdGuard Home je k dispozici\n",
|
||||
"updates_version_equal": "AdGuard Home je aktuální",
|
||||
"check_updates_now": "Zkontrolovat aktualizace nyní",
|
||||
"version_request_error": "Kontrola aktualizace se nezdařila. Zkontrolujte prosím připojení k Internetu.",
|
||||
"dns_privacy": "Soukromí DNS",
|
||||
"setup_dns_privacy_1": "<0>DNS skrze TLS:</0> Použít <1>{{address}}</1> řetězec.",
|
||||
"setup_dns_privacy_2": "<0>DNS skrze HTTPS:</0> Použít <1>{{address}}</1> řetězec.",
|
||||
|
|
|
@ -298,6 +298,9 @@
|
|||
"blocking_mode_nxdomain": "NXDOMAIN: Svar med NXDOMAIN-kode",
|
||||
"blocking_mode_null_ip": "Null IP: Svar med nul IP-adresse (0.0.0.0 for A; :: for AAAA)",
|
||||
"blocking_mode_custom_ip": "Tilpasset IP: Svar med en manuelt indstillet IP-adresse",
|
||||
"theme_auto": "Auto",
|
||||
"theme_light": "Lyst",
|
||||
"theme_dark": "Mørkt",
|
||||
"upstream_dns_client_desc": "Holdes dette felt tomt, bruger AdGuard Home de i <0>DNS-indstillingerne</0> opsatte servere.",
|
||||
"tracker_source": "Tracker-kilde",
|
||||
"source_label": "Kilde",
|
||||
|
@ -451,6 +454,7 @@
|
|||
"updates_checked": "En ny version af AdGuard Home er tilgængelig\n",
|
||||
"updates_version_equal": "AdGuard Home er opdateret",
|
||||
"check_updates_now": "Søg efter opdateringer nu",
|
||||
"version_request_error": "Opdateringstjek mislykkedes. Tjek internetforbindelsen.",
|
||||
"dns_privacy": "DNS-fortrolighed",
|
||||
"setup_dns_privacy_1": "<0>DNS-over-TLS:</0> Brug <1>{{address}}</1> streng.",
|
||||
"setup_dns_privacy_2": "<0>DNS-over-HTTPS:</0> Brug <1>{{address}}</1> streng.",
|
||||
|
|
|
@ -298,6 +298,9 @@
|
|||
"blocking_mode_nxdomain": "NXDOMAIN: Mit NXDOMAIN-Code antworten",
|
||||
"blocking_mode_null_ip": "Null-IP: Antworten mit Null-IP-Adresse (0.0.0.0.0 für A; :: für AAAA)",
|
||||
"blocking_mode_custom_ip": "Benutzerdefinierte IP: Mit einer manuell eingestellten IP-Adresse antworten",
|
||||
"theme_auto": "Auto",
|
||||
"theme_light": "Hell",
|
||||
"theme_dark": "Dunkel",
|
||||
"upstream_dns_client_desc": "Wenn Sie dieses Feld leer lassen, verwendet AdGuard Home die Server, die in den <0>DNS-Einstellungen</0> konfiguriert sind.",
|
||||
"tracker_source": "Tracker-Quelle",
|
||||
"source_label": "Quelle",
|
||||
|
@ -451,6 +454,7 @@
|
|||
"updates_checked": "Neue Version von AdGuard Home ist jetzt verfügbar",
|
||||
"updates_version_equal": "AdGuard Home ist aktuell",
|
||||
"check_updates_now": "Jetzt nach Aktualisierungen suchen",
|
||||
"version_request_error": "Aktualisierungsprüfung fehlgeschlagen. Bitte überprüfen Sie Ihre Internetverbindung.",
|
||||
"dns_privacy": "DNS-Datenschutz",
|
||||
"setup_dns_privacy_1": "<0>DNS-over-TLS:</0> Zeichenkette <1>{{address}}</1> verwenden.",
|
||||
"setup_dns_privacy_2": "<0>DNS-over-HTTPS:</0> Zeichenkette <1>{{address}}</1> verwenden.",
|
||||
|
|
|
@ -454,6 +454,7 @@
|
|||
"updates_checked": "A new version of AdGuard Home is available",
|
||||
"updates_version_equal": "AdGuard Home is up-to-date",
|
||||
"check_updates_now": "Check for updates now",
|
||||
"version_request_error": "Update check failed. Please check your Internet connection.",
|
||||
"dns_privacy": "DNS Privacy",
|
||||
"setup_dns_privacy_1": "<0>DNS-over-TLS:</0> Use <1>{{address}}</1> string.",
|
||||
"setup_dns_privacy_2": "<0>DNS-over-HTTPS:</0> Use <1>{{address}}</1> string.",
|
||||
|
|
|
@ -298,6 +298,9 @@
|
|||
"blocking_mode_nxdomain": "NXDOMAIN: Responde con el código NXDOMAIN",
|
||||
"blocking_mode_null_ip": "IP nulo: Responde con dirección IP cero (0.0.0.0 para A; :: para AAAA)",
|
||||
"blocking_mode_custom_ip": "IP personalizada: Responde con una dirección IP establecida manualmente",
|
||||
"theme_auto": "Auto",
|
||||
"theme_light": "Claro",
|
||||
"theme_dark": "Oscuro",
|
||||
"upstream_dns_client_desc": "Si se mantiene este campo vacío, AdGuard Home utilizará los servidores configurados en la <0>configuración del DNS</0>.",
|
||||
"tracker_source": "Fuente del rastreador",
|
||||
"source_label": "Fuente",
|
||||
|
@ -451,6 +454,7 @@
|
|||
"updates_checked": "La nueva versión de AdGuard Home está disponible",
|
||||
"updates_version_equal": "AdGuard Home está actualizado",
|
||||
"check_updates_now": "Buscar actualizaciones ahora",
|
||||
"version_request_error": "La búsqueda de actualizaciones falló. Por favor revisa tu conexión a Internet.",
|
||||
"dns_privacy": "DNS cifrado",
|
||||
"setup_dns_privacy_1": "<0>DNS mediante TLS:</0> Utiliza la cadena <1>{{address}}</1>.",
|
||||
"setup_dns_privacy_2": "<0>DNS mediante HTTPS:</0> Utiliza la cadena <1>{{address}}</1>.",
|
||||
|
|
|
@ -32,8 +32,6 @@
|
|||
"dhcp_config_saved": "پیکربندی سرور DHCP ذخیره شده است",
|
||||
"form_error_required": "فیلد مورد نیاز",
|
||||
"form_error_ip4_format": "فرمت نامعتبر IPv4",
|
||||
"form_error_ip4_range_start_format": "قالب IPv4 شروع دامنه نامعتبر است",
|
||||
"form_error_ip4_range_end_format": "قالب IPv4 پایان دامنه نامعتبر است",
|
||||
"form_error_ip4_gateway_format": "قالب IPv4 درگاه نامعتبر است",
|
||||
"form_error_ip6_format": "فرمت نامعتبر IPv6",
|
||||
"form_error_ip_format": "فرمت IPv4 نامعتبر است",
|
||||
|
@ -44,7 +42,6 @@
|
|||
"out_of_range_error": "باید خارج از دامنه باشد\"{{start}}\"-\"{{end}}\"",
|
||||
"lower_range_start_error": "باید کمتر از شروع دامنه باشد",
|
||||
"greater_range_start_error": "باید بیشتر از شروع دامنه باشد",
|
||||
"greater_range_end_error": "باید بیشتر از پایان دامنه باشد",
|
||||
"subnet_error": "آدرس ها باید در یک زیرشبکه باشند",
|
||||
"gateway_or_subnet_invalid": "پوشش زیرشبکه نامعتبر است",
|
||||
"dhcp_form_gateway_input": "آی پی دروازه",
|
||||
|
@ -422,6 +419,7 @@
|
|||
"updates_checked": "نسخه جدیدی از AdGuard Home در دسترس است",
|
||||
"updates_version_equal": "AdGuard Home بروز است",
|
||||
"check_updates_now": "حالا بررسی برای بروز رسانی",
|
||||
"version_request_error": "بررسی بروزرسانی موفق نشد.لطفا ارتباط اینترنتی خود را بررسی کنید",
|
||||
"dns_privacy": "حریم خصوصی DNS",
|
||||
"setup_dns_privacy_1": "<0>DNS-over-TLS:</0> استفاده از<1>{{address}}</1> .",
|
||||
"setup_dns_privacy_2": "<0>DNS-over-HTTPS:</0> استفاده از <1>{{address}}</1> .",
|
||||
|
|
|
@ -272,7 +272,7 @@
|
|||
"nxdomain": "NXDOMAIN",
|
||||
"refused": "REFUSED",
|
||||
"null_ip": "Tyhjä IP",
|
||||
"custom_ip": "Oma IP-osoite",
|
||||
"custom_ip": "Mukautettu IP-osoite",
|
||||
"blocking_ipv4": "IPv4-esto",
|
||||
"blocking_ipv6": "IPv6-esto",
|
||||
"dnscrypt": "DNSCrypt",
|
||||
|
@ -297,7 +297,10 @@
|
|||
"blocking_mode_refused": "REFUSED: Vastaa REFUSED-koodilla",
|
||||
"blocking_mode_nxdomain": "NXDOMAIN: Vastaa NXDOMAIN-koodilla",
|
||||
"blocking_mode_null_ip": "Tyhjä IP: Vastaa IP-nollaosoitteella (0.0.0.0 korvaa A; :: korvaa AAAA)",
|
||||
"blocking_mode_custom_ip": "Oma IP: Vastaa itse määritetyllä IP-osoitteella",
|
||||
"blocking_mode_custom_ip": "Mukautettu IP: Vastaa itse määritetyllä IP-osoitteella",
|
||||
"theme_auto": "Automaattinen",
|
||||
"theme_light": "Vaalea",
|
||||
"theme_dark": "Tumma",
|
||||
"upstream_dns_client_desc": "Jos tämä on tyhjä, käyttää AdGuard Home <0>DNS-asetuksissa</0> määritettyjä palvelimia.",
|
||||
"tracker_source": "Seurannan lähde",
|
||||
"source_label": "Lähde",
|
||||
|
@ -451,6 +454,7 @@
|
|||
"updates_checked": "Uusi versio AdGuard Home -ohjelmasta on saatavana\n",
|
||||
"updates_version_equal": "AdGuard Home on ajan tasalla",
|
||||
"check_updates_now": "Tarkista päivitykset nyt",
|
||||
"version_request_error": "Päivitystarkistus epäonnistui. Tarkista Internet-yhteytesi.",
|
||||
"dns_privacy": "DNS-tietosuoja",
|
||||
"setup_dns_privacy_1": "<0>DNS-over-TLS:</0> Käytä merkkijonoa <1>{{address}}</1>.",
|
||||
"setup_dns_privacy_2": "<0>DNS-over-HTTPS:</0> Käytä merkkijonoa <1>{{address}}</1>.",
|
||||
|
@ -614,7 +618,7 @@
|
|||
"cache_ttl_max_override_desc": "Määritä DNS-välimuistin kohteiden enimmäiselinaika (sekunteina).",
|
||||
"ttl_cache_validation": "Välimuistin vähimmäiselinajan on oltava pienempi tai sama kuin enimmäiselinajan",
|
||||
"cache_optimistic": "Optimistinen välimuisti",
|
||||
"cache_optimistic_desc": "Pakota AdGuard Home vastaamaan välimuistista vaikka sen tiedot olisivat vanhentuneet. Pyri samalla myös päivittämään tiedot.",
|
||||
"cache_optimistic_desc": "Pakota AdGuard Home vastaamaan välimuistista vaikka tiedot olisivat vanhentuneet. Pyri samalla myös päivittämään tiedot.",
|
||||
"filter_category_general": "Yleiset",
|
||||
"filter_category_security": "Turvallisuus",
|
||||
"filter_category_regional": "Alueelliset",
|
||||
|
|
|
@ -222,7 +222,7 @@
|
|||
"updated_upstream_dns_toast": "Serveurs en amont enregistrés",
|
||||
"dns_test_ok_toast": "Les serveurs DNS spécifiés fonctionnent correctement",
|
||||
"dns_test_not_ok_toast": "Impossible d'utiliser le serveur « {{key}} »: veuillez vérifier si le nom saisi est bien correct",
|
||||
"dns_test_warning_toast": "L'amont «{{key}}» ne répond pas aux demandes de test et peut ne pas fonctionner correctement",
|
||||
"dns_test_warning_toast": "L'amont « {{key}} » ne répond pas aux demandes de test et peut ne pas fonctionner correctement",
|
||||
"unblock": "Débloquer",
|
||||
"block": "Bloquer",
|
||||
"disallow_this_client": "Interdire ce client",
|
||||
|
@ -298,6 +298,9 @@
|
|||
"blocking_mode_nxdomain": "NXDOMAIN : Répondre avec le code NXDOMAIN",
|
||||
"blocking_mode_null_ip": "IP nulle : Répondre avec une adresse IP nulle (0.0.0.0 pour A ; :: pour AAAA)",
|
||||
"blocking_mode_custom_ip": "IP personnalisée : Répondre avec une adresse IP définie manuellement",
|
||||
"theme_auto": "Auto",
|
||||
"theme_light": "Thème clair",
|
||||
"theme_dark": "Thème sombre",
|
||||
"upstream_dns_client_desc": "Si vous laissez ce champ vide, AdGuard Home utilisera les serveurs configurés dans les <0>paramètres DNS</0>.",
|
||||
"tracker_source": "Source du traceur",
|
||||
"source_label": "Source",
|
||||
|
@ -451,6 +454,7 @@
|
|||
"updates_checked": "Une nouvelle version de AdGuard Home est disponible",
|
||||
"updates_version_equal": "AdGuard Home est à jour",
|
||||
"check_updates_now": "Vérifier les mises à jour",
|
||||
"version_request_error": "Impossible de vérifier les mises à jour. Veuillez vérifier votre connexion internet.",
|
||||
"dns_privacy": "Confidentialité DNS",
|
||||
"setup_dns_privacy_1": "<0>DNS-over-TLS :</0> Utiliser le string <1>{{address}}</1>.",
|
||||
"setup_dns_privacy_2": "<0>DNS-over-HTTPS :</0> Utiliser le string <1>{{address}}</1>.",
|
||||
|
|
|
@ -298,6 +298,9 @@
|
|||
"blocking_mode_nxdomain": "NXDOMAIN: Odgovor s NXDOMAIN kôdom",
|
||||
"blocking_mode_null_ip": "Nuliran IP: Odgovor s nuliranom IP adresom (0.0.0.0 za A; :: za AAAA)",
|
||||
"blocking_mode_custom_ip": "Prilagođeni IP: Odgovor s ručno postavljenom IP adresom",
|
||||
"theme_auto": "Auto",
|
||||
"theme_light": "Svijetla",
|
||||
"theme_dark": "Tamna",
|
||||
"upstream_dns_client_desc": "Ako ovo polje ostane prazno, AdGuard Home će upotrijebiti poslužitelje postavljene u <0>DNS postavkama</0>.",
|
||||
"tracker_source": "Izvor pratitelja",
|
||||
"source_label": "Izvor",
|
||||
|
@ -451,6 +454,7 @@
|
|||
"updates_checked": "Dostupna je nova verzija AdGuard Home-a",
|
||||
"updates_version_equal": "AdGuard Home je ažuriran",
|
||||
"check_updates_now": "Provjeri ažuriranja sada",
|
||||
"version_request_error": "Ne uspješna provjera ažuriranja. Provjerite vašu Internetsku vezu.",
|
||||
"dns_privacy": "DNS privatnost",
|
||||
"setup_dns_privacy_1": "<0>DNS-over-TLS:</0> Koristite <1>{{address}}</1>.",
|
||||
"setup_dns_privacy_2": "<0>DNS-over-HTTPS:</0> Koristite <1>{{address}}</1>.",
|
||||
|
|
|
@ -298,6 +298,9 @@
|
|||
"blocking_mode_nxdomain": "NXDOMAIN: Az NXDOMAIN kóddal fog válaszolni",
|
||||
"blocking_mode_null_ip": "Null IP: Nullákból álló IP-címmel válaszol (0.0.0.0 for A; :: for AAAA)",
|
||||
"blocking_mode_custom_ip": "Egyedi IP: Válasz egy kézzel beállított IP címmel",
|
||||
"theme_auto": "Auto",
|
||||
"theme_light": "Világos",
|
||||
"theme_dark": "Sötét",
|
||||
"upstream_dns_client_desc": "Ha üresen hagyja ezt a mezőt, az AdGuard Home azokat a szervereket fogja használni, amik a <0>DNS beállításokban</0> vannak beállítva.",
|
||||
"tracker_source": "Követő forrása",
|
||||
"source_label": "Forrás",
|
||||
|
@ -451,6 +454,7 @@
|
|||
"updates_checked": "Elérhető az AdGuard Home új verziója",
|
||||
"updates_version_equal": "Az AdGuard Home naprakész",
|
||||
"check_updates_now": "Frissítések ellenőrzése most",
|
||||
"version_request_error": "A frissítések ellenőrzése sikertelen. Ellenőrizze az internetkapcsolatot.",
|
||||
"dns_privacy": "DNS Adatvédelem",
|
||||
"setup_dns_privacy_1": "<0>DNS-over-TLS:</0> Használja a(z) <1>{{address}}</1> szöveget.",
|
||||
"setup_dns_privacy_2": "<0>DNS-over-HTTPS:</0> Használja a(z) <1>{{address}}</1> szöveget.",
|
||||
|
|
|
@ -298,6 +298,9 @@
|
|||
"blocking_mode_nxdomain": "NXDOMAIN: Respon pakai kode NXDOMAIN",
|
||||
"blocking_mode_null_ip": "Null IP: Respon pakai alamat IP kosong (0.0.0.0 untuk A; :: untuk AAAA)",
|
||||
"blocking_mode_custom_ip": "IP kustom: respon dengan alamat IP yang diset secara manual",
|
||||
"theme_auto": "Auto",
|
||||
"theme_light": "Terang",
|
||||
"theme_dark": "Gelap",
|
||||
"upstream_dns_client_desc": "Jika Anda biarkan bidang ini kosong, AdGuard Home akan memakai server yang dikonfigurasi di<0>Pengaturan DNS</0>.",
|
||||
"tracker_source": "Sumber pelacak",
|
||||
"source_label": "Sumber",
|
||||
|
|
|
@ -298,6 +298,9 @@
|
|||
"blocking_mode_nxdomain": "NXDOMAIN: Rispondi con il codice NXDOMAIN",
|
||||
"blocking_mode_null_ip": "IP nullo: Rispondi con indirizzo IP zero (0.0.0.0 per A; :: per AAAA)",
|
||||
"blocking_mode_custom_ip": "IP personalizzato: Rispondi con un indirizzo IP impostato manualmente",
|
||||
"theme_auto": "Auto",
|
||||
"theme_light": "Chiaro",
|
||||
"theme_dark": "Scuro",
|
||||
"upstream_dns_client_desc": "Se lasci questo spazio vuoto, AdGuard Home utilizzerà i server configurati nelle <0>impostazioni DNS</0>.",
|
||||
"tracker_source": "Origine del tracciatore",
|
||||
"source_label": "Fonte",
|
||||
|
@ -451,6 +454,7 @@
|
|||
"updates_checked": "Nuova versione di AdGuard Home è disponibile",
|
||||
"updates_version_equal": "AdGuard Home è aggiornato",
|
||||
"check_updates_now": "Ricerca aggiornamenti ora",
|
||||
"version_request_error": "Ricerca aggiornamenti non riuscita. Per favore controlla la tua connessione internet.",
|
||||
"dns_privacy": "Privacy DNS",
|
||||
"setup_dns_privacy_1": "<0>DNS su TLS:</0> Utilizza la stringa <1>{{address}}</1>.",
|
||||
"setup_dns_privacy_2": "<0>DNS su HTTPS:</0> Utilizza la stringa <1>{{address}}</1>.",
|
||||
|
|
|
@ -298,6 +298,9 @@
|
|||
"blocking_mode_nxdomain": "NXDOMAIN:NXDOMAINコードで応答します",
|
||||
"blocking_mode_null_ip": "Null IP:ゼロのIPアドレスで応答します(Aの場合は0.0.0.0; AAAAの場合は::)",
|
||||
"blocking_mode_custom_ip": "カスタムIP:手動で設定されたIPアドレスで応答します",
|
||||
"theme_auto": "自動",
|
||||
"theme_light": "ライト",
|
||||
"theme_dark": "ダーク",
|
||||
"upstream_dns_client_desc": "このフィールドを未入力のままにすると、AdGuard Homeは<0>DNS設定</0>で構成されたサーバを使用します。",
|
||||
"tracker_source": "追跡元",
|
||||
"source_label": "ソース",
|
||||
|
@ -451,6 +454,7 @@
|
|||
"updates_checked": "AdGuard Homeの新バージョンが利用可能です。",
|
||||
"updates_version_equal": "AdGuard Homeは既に最新です",
|
||||
"check_updates_now": "今すぐアップデートを確認する",
|
||||
"version_request_error": "アップデート確認に失敗しました。インターネット接続を確認してください。",
|
||||
"dns_privacy": "DNSプライバシー",
|
||||
"setup_dns_privacy_1": "<0>DNS-over-TLS:</0> <1>{{address}}</1>という文字列を使用してください。",
|
||||
"setup_dns_privacy_2": "<0>DNS-over-HTTPS:</0> <1>{{address}}</1>という文字列を使用してください。",
|
||||
|
|
|
@ -298,6 +298,9 @@
|
|||
"blocking_mode_nxdomain": "NXDOMAIN: NXDOMAIN 코드로 응답",
|
||||
"blocking_mode_null_ip": "Null IP: 제로 IP 주소 (A는 0.0.0.0; AAAA는 ::) 로 응답합니다",
|
||||
"blocking_mode_custom_ip": "커스텀 IP: 직접 설정한 IP 주소로 응답합니다",
|
||||
"theme_auto": "자동",
|
||||
"theme_light": "라이트 테마",
|
||||
"theme_dark": "다크 테마",
|
||||
"upstream_dns_client_desc": "이 값을 비워둔다면 AdGuard Home은 <0>DNS 설정</0>에 설정되어 있는 값을 사용합니다.",
|
||||
"tracker_source": "추적기 소스",
|
||||
"source_label": "소스",
|
||||
|
@ -451,6 +454,7 @@
|
|||
"updates_checked": "AdGuard Home의 새 버전을 사용할 수 있습니다",
|
||||
"updates_version_equal": "AdGuard Home 최신 상태입니다.",
|
||||
"check_updates_now": "지금 업데이트 확인",
|
||||
"version_request_error": "업데이트 체크에 실패했습니다. 인터넷 연결 상태를 확인해주세요.",
|
||||
"dns_privacy": "DNS 프라이버시",
|
||||
"setup_dns_privacy_1": "<0>DNS-over-TLS:</0> <1>{{address}}</1> 사용하세요.",
|
||||
"setup_dns_privacy_2": "<0>DNS-over-HTTPS:</0> <1>{{address}}</1> 사용하세요.",
|
||||
|
|
|
@ -298,6 +298,9 @@
|
|||
"blocking_mode_nxdomain": "NXDOMAIN: Reageer met NXDOMAIN code",
|
||||
"blocking_mode_null_ip": "Nul IP: Reageer met een nul IP address (0.0.0.0 voor A; :: voor AAAA)",
|
||||
"blocking_mode_custom_ip": "Aangepast IP: Reageer met een handmatige ingesteld IP adres",
|
||||
"theme_auto": "Automatisch",
|
||||
"theme_light": "Licht",
|
||||
"theme_dark": "Donker",
|
||||
"upstream_dns_client_desc": "Indien je dit veld leeglaat zal AdGuard Home de servers welke zijn ingesteld in de <0>DNS instellingen</0> gebruiken.",
|
||||
"tracker_source": "Bron volger",
|
||||
"source_label": "Bron",
|
||||
|
@ -451,6 +454,7 @@
|
|||
"updates_checked": "Een nieuwe versie van AdGuard Home is beschikbaar\n",
|
||||
"updates_version_equal": "AdGuard Home is actueel",
|
||||
"check_updates_now": "Controleer op updates",
|
||||
"version_request_error": "Updatecontrole mislukt. Controleer je internetverbinding.",
|
||||
"dns_privacy": "DNS Privacy",
|
||||
"setup_dns_privacy_1": "<0>DNS-via-TLS:</0> Gebruik <1>{{address}}</1> string.",
|
||||
"setup_dns_privacy_2": "<0>DNS-via-HTTPS:</0> Gebruik <1>{{address}}</1> string.",
|
||||
|
|
|
@ -433,6 +433,7 @@
|
|||
"updates_checked": "En ny versjon av AdGuard Home er tilgjengelig",
|
||||
"updates_version_equal": "AdGuard Home er fullt oppdatert",
|
||||
"check_updates_now": "Se etter oppdateringer nå",
|
||||
"version_request_error": "Oppdateringssjekken mislyktes. Vennligst sjekk internettforbindelsen din.",
|
||||
"dns_privacy": "DNS-privatliv",
|
||||
"setup_dns_privacy_1": "<0>DNS-over-TLS:</0> Benytt <1>{{address}}</1>-strengen.",
|
||||
"setup_dns_privacy_2": "<0>DNS-over-HTTPS:</0> Benytt <1>{{address}}</1>-strengen.",
|
||||
|
|
|
@ -298,6 +298,9 @@
|
|||
"blocking_mode_nxdomain": "NXDOMAIN: Odpowiedz kodem NXDOMAIN",
|
||||
"blocking_mode_null_ip": "Null IP: Odpowiedz z zerowym adresem IP (0.0.0.0 dla A; :: dla AAAA)",
|
||||
"blocking_mode_custom_ip": "Niestandardowy adres IP: Odpowiedz ręcznie ustawionym adresem IP",
|
||||
"theme_auto": "Auto",
|
||||
"theme_light": "Jasny",
|
||||
"theme_dark": "Ciemny",
|
||||
"upstream_dns_client_desc": "Jeśli to pole pozostanie puste, AdGuard Home użyje serwerów skonfigurowanych w <0>Ustawieniach DNS</0>.",
|
||||
"tracker_source": "Źródło skryptu śledzącego",
|
||||
"source_label": "Źródło",
|
||||
|
@ -451,6 +454,7 @@
|
|||
"updates_checked": "Dostępna jest nowa wersja programu AdGuard Home\n",
|
||||
"updates_version_equal": "AdGuard Home jest aktualny",
|
||||
"check_updates_now": "Sprawdź aktualizacje teraz",
|
||||
"version_request_error": "Sprawdzanie aktualizacji zakończone niepowodzeniem. Sprawdź swoje połączenie z internetem.",
|
||||
"dns_privacy": "Prywatny DNS",
|
||||
"setup_dns_privacy_1": "<0>DNS-over-TLS:</0> Skorzystaj z adresu <1>{{address}}</1>.",
|
||||
"setup_dns_privacy_2": "<0>DNS-over-HTTPS:</0> Skorzystaj z adresu <1>{{address}}</1>.",
|
||||
|
|
|
@ -298,6 +298,9 @@
|
|||
"blocking_mode_nxdomain": "NXDOMAIN: Responder com o código NXDOMAIN",
|
||||
"blocking_mode_null_ip": "IP nulo: Responder com endereço IP zero (0.0.0.0 para A; :: para AAAA)",
|
||||
"blocking_mode_custom_ip": "IP personalizado: Responder com um endereço IP definido manualmente",
|
||||
"theme_auto": "Automático",
|
||||
"theme_light": "Claro",
|
||||
"theme_dark": "Escuro",
|
||||
"upstream_dns_client_desc": "Se você mantiver este campo vazio, o AdGuard Home usará os servidores configurados nas configurações <0>DNS</0>.",
|
||||
"tracker_source": "Fonte do rastreador",
|
||||
"source_label": "Fonte",
|
||||
|
@ -451,6 +454,7 @@
|
|||
"updates_checked": "Uma nova versão do AdGuard Home está disponível\n",
|
||||
"updates_version_equal": "O AdGuard Home está atualizado.",
|
||||
"check_updates_now": "Verificar atualizações",
|
||||
"version_request_error": "A verificação de atualização falhou. Por favor, verifique sua conexão com a internet.",
|
||||
"dns_privacy": "Privacidade de DNS",
|
||||
"setup_dns_privacy_1": "<0>DNS-sobre-TLS:</0> Use <1>{{address}}</1> string.",
|
||||
"setup_dns_privacy_2": "<0>DNS-sobre-HTTPS:</0> Use <1>{{address}}</1> string.",
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
"out_of_range_error": "Deve estar fora do intervalo \"{{start}}\"-\"{{end}}\"",
|
||||
"lower_range_start_error": "Deve ser inferior ao início do intervalo",
|
||||
"greater_range_start_error": "Deve ser maior que o início do intervalo",
|
||||
"subnet_error": "Os endereços devem estar em uma sub-rede",
|
||||
"subnet_error": "Os endereços devem estar numa sub-rede",
|
||||
"gateway_or_subnet_invalid": "Máscara de sub-rede inválida",
|
||||
"dhcp_form_gateway_input": "IP do gateway",
|
||||
"dhcp_form_subnet_input": "Máscara de sub-rede",
|
||||
|
@ -298,6 +298,9 @@
|
|||
"blocking_mode_nxdomain": "NXDOMAIN: Responder com o código NXDOMAIN",
|
||||
"blocking_mode_null_ip": "IP nulo: Responder com endereço IP zero (0.0.0.0 para A; :: para AAAA)",
|
||||
"blocking_mode_custom_ip": "IP personalizado: Responder com um endereço IP definido manualmente",
|
||||
"theme_auto": "Automático",
|
||||
"theme_light": "Claro",
|
||||
"theme_dark": "Escuro",
|
||||
"upstream_dns_client_desc": "Se mantiver esse campo vazio, o AdGuard Home usará os servidores configurados nas <0>Definições de DNS</0>.",
|
||||
"tracker_source": "Fonte do rastreador",
|
||||
"source_label": "Fonte",
|
||||
|
@ -332,10 +335,10 @@
|
|||
"install_devices_router": "Router",
|
||||
"install_devices_router_desc": "Esta configuração cobre automaticamente todos os dispositivos conectados ao seu router doméstico, sem a necessidade de configurar cada um deles manualmente.",
|
||||
"install_devices_address": "O servidor de DNS do AdGuard Home está a capturar os seguintes endereços",
|
||||
"install_devices_router_list_1": "Abra as preferências do seu roteador. Normalmente, tu podes acessá-lo de teu navegador por meio de um URL, como http://192.168.0.1/ ou http://192.168.1.1/. Tu podes ser solicitado a inserir uma palavra-passe. Se tu não se lembrar, muitas vezes tu podes repor a palavra-passe pressionando um botão no próprio roteador, mas esteja ciente de que se esse procedimento for escolhido, tu provavelmente perderás toda a definição do roteador. Se o teu roteador requer uma aplicação para configurá-lo, instale a aplicação no seu telefone ou PC e use-o para acessar as definições do roteador.",
|
||||
"install_devices_router_list_1": "Abra as preferências do seu router. Normalmente, tu podes acessá-lo de teu navegador por meio de um URL, como http://192.168.0.1/ ou http://192.168.1.1/. Tu podes ser solicitado a inserir uma palavra-passe. Se tu não se lembrar, muitas vezes tu podes repor a palavra-passe pressionando um botão no próprio roteador, mas esteja ciente de que se esse procedimento for escolhido, tu provavelmente perderás toda a definição do router. Se o teu router requer uma aplicação para configurá-lo, instale a aplicação no seu telefone ou PC e use-o para acessar as definições do router.",
|
||||
"install_devices_router_list_2": "Encontre as configurações de DNS. Procure as letras DNS ao lado de um campo que permite dois ou três conjuntos de números, cada um dividido em quatro grupos de um a três números.",
|
||||
"install_devices_router_list_3": "Insira aqui seu servidor do AdGuard Home.",
|
||||
"install_devices_router_list_4": "Em alguns tipos de roteador, um servidor DNS personalizado não pode ser configurado. Nesse caso, configurar o AdGuard Home como um <0>Servidor DHCP</0> pode ajudar. Caso contrário, tu deve verificar o manual do router sobre como personalizar os servidores DNS em seu modelo de router específico.",
|
||||
"install_devices_router_list_4": "Em alguns tipos de router, um servidor DNS personalizado não pode ser configurado. Nesse caso, configurar o AdGuard Home como um <0>Servidor DHCP</0> pode ajudar. Caso contrário, tu deves verificar o manual do router sobre como personalizar os servidores DNS no seu modelo de router específico.",
|
||||
"install_devices_windows_list_1": "Abra o Painel de Controlo através do Menu Iniciar ou pela Pesquisa do Windows.",
|
||||
"install_devices_windows_list_2": "Entre na categoria Rede e Internet e depois clique em Central de Rede e Partilha.",
|
||||
"install_devices_windows_list_3": "No painel esquerdo, clique em \"Alterar configurações do adaptador\".",
|
||||
|
@ -451,6 +454,7 @@
|
|||
"updates_checked": "Uma nova versão do AdGuard Home está disponível\n",
|
||||
"updates_version_equal": "O AdGuard Home está atualizado",
|
||||
"check_updates_now": "Verificar atualizações",
|
||||
"version_request_error": "A verificação de atualização falhou. Verifique a sua ligação à internet.",
|
||||
"dns_privacy": "Privacidade de DNS",
|
||||
"setup_dns_privacy_1": "<0>DNS-sobre-TLS:</0> Use <1>{{address}}</1> string.",
|
||||
"setup_dns_privacy_2": "<0>DNS-sobre-HTTPS:</0> Use <1>{{address}}</1> string.",
|
||||
|
@ -579,7 +583,7 @@
|
|||
"client_blocked": "Cliente \"{{ip}}\" foi bloqueado com sucesso",
|
||||
"client_unblocked": "Cliente \"{{ip}}\" foi desbloqueado com sucesso",
|
||||
"static_ip": "Endereço de IP estático",
|
||||
"static_ip_desc": "O AdGuard Home é um servidor, portanto, ele precisa de um endereço de IP estático para funcionar corretamente. Caso contrário, em algum momento, seu roteador poderá atribuir um novo endereço de IP neste dispositivo.",
|
||||
"static_ip_desc": "O AdGuard Home é um servidor, portanto, ele precisa de um endereço de IP estático para funcionar corretamente. Caso contrário, em algum momento, seu router poderá atribuir um novo endereço de IP neste dispositivo.",
|
||||
"set_static_ip": "Definir um endereço de IP estático",
|
||||
"install_static_ok": "Boas notícias! O endereço de IP estático já está configurado",
|
||||
"install_static_error": "O AdGuard Home não pode configurar automaticamente para esta interface de rede. Por favor, procure uma instrução sobre como fazer isso manualmente.",
|
||||
|
|
|
@ -298,6 +298,9 @@
|
|||
"blocking_mode_nxdomain": "NXDOMAIN: Răspunde cu codul NXDOMAIN",
|
||||
"blocking_mode_null_ip": "IP nul: răspunde cu o adresă IP zero (0.0.0.0 pentru A; :: pentru AAAA)",
|
||||
"blocking_mode_custom_ip": "IP personalizat: răspunde cu o adresă IP setată manual",
|
||||
"theme_auto": "Auto",
|
||||
"theme_light": "Luminoasă",
|
||||
"theme_dark": "Sombră",
|
||||
"upstream_dns_client_desc": "Dacă mențineți acest câmp gol, AdGuard Home va folosi serverele configurate în <0>setările DNS</0>.",
|
||||
"tracker_source": "Sursă tracker",
|
||||
"source_label": "Sursă",
|
||||
|
@ -451,6 +454,7 @@
|
|||
"updates_checked": "Este disponibilă o nouă versiune de AdGuard Home\n",
|
||||
"updates_version_equal": "AdGuard Home este la zi",
|
||||
"check_updates_now": "Verificați actualizările acum",
|
||||
"version_request_error": "Verificarea actualizării nu a reușit. Verificați conexiunea internet.",
|
||||
"dns_privacy": "Confidențialitate DNS",
|
||||
"setup_dns_privacy_1": "<0>DNS-over-TLS:</0> Folosiți stringul <1>{{address}}</1>.",
|
||||
"setup_dns_privacy_2": "<0>DNS-over-HTTPS:</0> Folosiți stringul <1>{{address}}</1>.",
|
||||
|
|
|
@ -298,6 +298,9 @@
|
|||
"blocking_mode_nxdomain": "NXDOMAIN: Отвечает с кодом NXDOMAIN\n",
|
||||
"blocking_mode_null_ip": "Нулевой IP: Отвечает с нулевым IP-адресом (0.0.0.0 для A; :: для AAAA)",
|
||||
"blocking_mode_custom_ip": "Пользовательский IP: Отвечает с вручную настроенным IP-адресом",
|
||||
"theme_auto": "Авто",
|
||||
"theme_light": "Светлая",
|
||||
"theme_dark": "Тёмная",
|
||||
"upstream_dns_client_desc": "Если оставить поле пустым, AdGuard Home будет обращаться к серверам, указанным в <0>настройках DNS</0>.",
|
||||
"tracker_source": "Источник трекинга",
|
||||
"source_label": "Источник",
|
||||
|
@ -451,6 +454,7 @@
|
|||
"updates_checked": "Доступна новая версия AdGuard Home",
|
||||
"updates_version_equal": "Версия AdGuard Home актуальна",
|
||||
"check_updates_now": "Проверить обновления",
|
||||
"version_request_error": "Ошибка при проверке наличия обновлений. Проверьте ваше интернет-соединение.",
|
||||
"dns_privacy": "Зашифрованный DNS",
|
||||
"setup_dns_privacy_1": "<0>DNS-over-TLS:</0> Используйте строку <1>{{address}}</1>.",
|
||||
"setup_dns_privacy_2": "<0>DNS-over-HTTPS:</0> Используйте строку <1>{{address}}</1>.",
|
||||
|
|
|
@ -30,8 +30,6 @@
|
|||
"dhcp_ipv6_settings": "ග.ධා.වි.කෙ. අ.ජා.කෙ. 6 සැකසුම්",
|
||||
"form_error_required": "ඇවැසි ක්ෂේත්රයකි",
|
||||
"form_error_ip4_format": "IPv4 ලිපිනය වලංගු නොවේ",
|
||||
"form_error_ip4_range_start_format": "පරාසය ආරම්භයේ වලංගු නොවන අ.ජා.කෙ.4 ලිපිනයකි",
|
||||
"form_error_ip4_range_end_format": "පරාසය අවසානයේ වලංගු නොවන අ.ජා.කෙ.4 ලිපිනයකි",
|
||||
"form_error_ip6_format": "වලංගු නොවන අ.ජා.කෙ.6 ලිපිනයකි",
|
||||
"form_error_ip_format": "අ.ජා.කෙ. (IP) ලිපිනය වලංගු නොවේ",
|
||||
"form_error_mac_format": "මා.ප්ර.පා. ලිපිනය වලංගු නොවේ",
|
||||
|
@ -42,7 +40,6 @@
|
|||
"out_of_range_error": "\"{{start}}\"-\"{{end}}\" පරාසයෙන් පිට විය යුතුය",
|
||||
"lower_range_start_error": "පරාසය ආරම්භයට වඩා අඩු විය යුතුය",
|
||||
"greater_range_start_error": "පරාසය ආරම්භයට වඩා වැඩි විය යුතුය",
|
||||
"greater_range_end_error": "පරාසය අවසානයට වඩා වැඩි විය යුතුය",
|
||||
"subnet_error": "ලිපින එක් අනුජාලයක තිබිය යුතුය",
|
||||
"dhcp_form_range_title": "අ.ජා. කෙ. (IP) ලිපින පරාසය",
|
||||
"dhcp_form_range_start": "පරාසය ආරම්භය",
|
||||
|
|
|
@ -298,6 +298,9 @@
|
|||
"blocking_mode_nxdomain": "NXDOMAIN: Odpovedať kódom NXDOMAIN",
|
||||
"blocking_mode_null_ip": "Null IP: Odpoveď s nulovou IP adresou (0.0.0.0 pre A; :: pre AAAA)",
|
||||
"blocking_mode_custom_ip": "Vlastná IP adresa: Odpovedzte s manuálne nastavenou IP adresou",
|
||||
"theme_auto": "Auto",
|
||||
"theme_light": "Svetlá",
|
||||
"theme_dark": "Tmavá",
|
||||
"upstream_dns_client_desc": "Ak ponecháte toto pole prázdne, AdGuard Home použije servery nakonfigurované v <0>nastaveniach DNS</0>.",
|
||||
"tracker_source": "Zdroj sledovania",
|
||||
"source_label": "Zdroj",
|
||||
|
@ -451,6 +454,7 @@
|
|||
"updates_checked": "K dispozícii je nová verzia aplikácie AdGuard Home\n",
|
||||
"updates_version_equal": "AdGuard Home je aktuálny",
|
||||
"check_updates_now": "Skontrolovať aktualizácie teraz",
|
||||
"version_request_error": "Kontrola aktualizácie zlyhala. Skontrolujte svoje internetové pripojenie.",
|
||||
"dns_privacy": "DNS súkromie",
|
||||
"setup_dns_privacy_1": "<0>DNS-over-TLS:</0> Použiť <1>{{address}}</1> reťazec.",
|
||||
"setup_dns_privacy_2": "<0>DNS-over-HTTPS:</0> Použiť <1>{{address}}</1> reťazec.",
|
||||
|
|
|
@ -298,6 +298,9 @@
|
|||
"blocking_mode_nxdomain": "NXDOMAIN: Odziv s kodo NXDOMAIN",
|
||||
"blocking_mode_null_ip": "Prazen IP: Odziv z ničelnim naslovom IP (0.0.0.0 za A; :: za AAAA)",
|
||||
"blocking_mode_custom_ip": "IP po meri: Odziv z ročno nastavljenim naslovom IP",
|
||||
"theme_auto": "Auto",
|
||||
"theme_light": "Svetla tema",
|
||||
"theme_dark": "Temna tema",
|
||||
"upstream_dns_client_desc": "Če pustite to polje prazno, bo AdGuard Home uporabil strežnike, konfigurirane v <0>nastavitvah DNS</0>.",
|
||||
"tracker_source": "Vir sledilca",
|
||||
"source_label": "Vir",
|
||||
|
@ -451,6 +454,7 @@
|
|||
"updates_checked": "Na voljo je nova različica programa AdGuard Home\n",
|
||||
"updates_version_equal": "AdGuard Home je posodobljen",
|
||||
"check_updates_now": "Preveri obstoj posodobitev zdaj",
|
||||
"version_request_error": "Posodobitev ni uspela. Preverite vašo internetno povezavo.",
|
||||
"dns_privacy": "Zasebnost DNS",
|
||||
"setup_dns_privacy_1": "<0>DNS-prek-TLS:</0> Uporabite niz <1>{{address}}</1>.",
|
||||
"setup_dns_privacy_2": "<0>DNS-prek-HTTPS:</0> Uporabite niz <1>{{address}}</1>.",
|
||||
|
|
|
@ -298,6 +298,9 @@
|
|||
"blocking_mode_nxdomain": "NXDOMAIN: Odgovara sa NXDOMAIN kodom",
|
||||
"blocking_mode_null_ip": "Null IP: Odgovara sa zero IP adresom (0.0.0.0 za A; :: za AAAA)",
|
||||
"blocking_mode_custom_ip": "Prilagođeni IP: Odgovara sa ručno podešenom IP adresom",
|
||||
"theme_auto": "Auto",
|
||||
"theme_light": "Svetla tema",
|
||||
"theme_dark": "Tamna tema",
|
||||
"upstream_dns_client_desc": "AKo ovo polje ostavite prazno, AdGuard Home će koristiti servere konfigurisane u <0>DNS postavkama</0>.",
|
||||
"tracker_source": "Izvor praćenja",
|
||||
"source_label": "Izvor",
|
||||
|
@ -451,6 +454,7 @@
|
|||
"updates_checked": "Dostupna je nova verzija AdGuard Home-a",
|
||||
"updates_version_equal": "AdGuard Home je ažuriran na najnoviju verziju",
|
||||
"check_updates_now": "Proveri da li postoje ispravke",
|
||||
"version_request_error": "Provera ažuriranja nije uspela. Proverite svoju vezu sa internetom.",
|
||||
"dns_privacy": "DNS privatnost",
|
||||
"setup_dns_privacy_1": "<0>DNS-over-TLS:</0> koristi <1>{{address}}</1> string.",
|
||||
"setup_dns_privacy_2": "<0>DNS-over-HTTPS:</0> koristi <1>{{address}}</1> string.",
|
||||
|
|
|
@ -298,6 +298,9 @@
|
|||
"blocking_mode_nxdomain": "NXDOMAIN: Svara med NXDOMAIN kod",
|
||||
"blocking_mode_null_ip": "Null IP: Svara med noll IP adress (0.0.0.0 för A; :: för AAAA)",
|
||||
"blocking_mode_custom_ip": "Anpassad IP: Svara med en manuellt inställd IP adress",
|
||||
"theme_auto": "Auto",
|
||||
"theme_light": "Ljust",
|
||||
"theme_dark": "Mörkt",
|
||||
"upstream_dns_client_desc": "Om detta fält är tomt kommer AdGuard Home att använda de servrar som konfigurerats i <0>DNS inställningarna</0>.",
|
||||
"tracker_source": "Spårningskälla",
|
||||
"source_label": "Källa",
|
||||
|
@ -451,6 +454,7 @@
|
|||
"updates_checked": "En ny version av AdGuard Home är tillgänglig\n",
|
||||
"updates_version_equal": "AdGuard Home är uppdaterat",
|
||||
"check_updates_now": "Sök efter uppdateringar nu",
|
||||
"version_request_error": "Uppdateringskontroll misslyckades. Kontrollera din internetanslutning.",
|
||||
"dns_privacy": "DNS-Integritet",
|
||||
"setup_dns_privacy_1": "<0>DNS-över-TLS:</0> Använd: <1>{{address}}</1>",
|
||||
"setup_dns_privacy_2": "<0>DNS-över-HTTPS:</0> Använd: <1>{{address}}</1>",
|
||||
|
|
|
@ -128,14 +128,14 @@
|
|||
"number_of_dns_query_days": "Son {{count}} gün boyunca işlenen DNS sorgularının sayısı",
|
||||
"number_of_dns_query_days_plural": "Son {{count}} gün boyunca işlenen DNS sorgularının sayısı",
|
||||
"number_of_dns_query_24_hours": "Son 24 saat içinde işlenen DNS sorgularının sayısı",
|
||||
"number_of_dns_query_blocked_24_hours": "Reklam engelleme filtreleri ve ana makine engel listeleri tarafından engellenen DNS isteklerinin sayısı",
|
||||
"number_of_dns_query_blocked_24_hours": "Reklam engelleme filtreleri ve hosts engel listeleri tarafından engellenen DNS isteklerinin sayısı",
|
||||
"number_of_dns_query_blocked_24_hours_by_sec": "AdGuard gezinti koruması modülü tarafından engellenen DNS isteklerinin sayısı",
|
||||
"number_of_dns_query_blocked_24_hours_adult": "Engellenen yetişkin içerikli sitelerin sayısı",
|
||||
"enforced_save_search": "Uygulanan güvenli arama",
|
||||
"number_of_dns_query_to_safe_search": "Güvenli Aramanın uygulandığı arama motorlarına gönderilen DNS isteklerinin sayısı",
|
||||
"average_processing_time": "Ortalama işlem süresi",
|
||||
"average_processing_time_hint": "Bir DNS isteğinin milisaniye cinsinden ortalama işlem süresi",
|
||||
"block_domain_use_filters_and_hosts": "Filtre ve ana makine listelerini kullanarak alan adlarını engelle",
|
||||
"block_domain_use_filters_and_hosts": "Filtre ve hosts dosyalarını kullanarak alan adlarını engelle",
|
||||
"filters_block_toggle_hint": "<a>Filtreler</a> ayarlarında engelleme kuralları oluşturabilirsiniz.",
|
||||
"use_adguard_browsing_sec": "AdGuard gezinti koruması web hizmetini kullan",
|
||||
"use_adguard_browsing_sec_hint": "AdGuard Home, alan adının gezinti koruması web hizmeti tarafından engellenip engellenmediğini kontrol eder. Kontrolü gerçekleştirmek için gizlilik dostu arama API'sini kullanır: sunucuya yalnızca SHA256 karma alan adının kısa bir ön eki gönderilir.",
|
||||
|
@ -177,7 +177,7 @@
|
|||
"edit_table_action": "Düzenle",
|
||||
"delete_table_action": "Sil",
|
||||
"elapsed": "Geçen süre",
|
||||
"filters_and_hosts_hint": "AdGuard Home, temel reklam engelleme kurallarını ve ana makine dosyalarının söz dizimini anlar.",
|
||||
"filters_and_hosts_hint": "AdGuard Home, temel reklam engelleme kurallarını ve hosts dosyalarının söz dizimini anlar.",
|
||||
"no_blocklist_added": "Engel listesi eklenmedi",
|
||||
"no_whitelist_added": "İzin listesi eklenmedi",
|
||||
"add_blocklist": "Engel listesi ekle",
|
||||
|
@ -197,8 +197,8 @@
|
|||
"form_error_url_format": "Geçersiz URL biçimi",
|
||||
"form_error_url_or_path_format": "Geçersiz URL adresi veya dosya yolu",
|
||||
"custom_filter_rules": "Özel filtreleme kuralları",
|
||||
"custom_filter_rules_hint": "Her satıra bir kural girin. Reklam engelleme kuralı veya ana makine dosyası söz dizimi kullanabilirsiniz.",
|
||||
"system_host_files": "Sistem ana makine dosyaları",
|
||||
"custom_filter_rules_hint": "Her satıra bir kural girin. Reklam engelleme kuralı veya hosts dosyası söz dizimi kullanabilirsiniz.",
|
||||
"system_host_files": "Sistem hosts dosyaları",
|
||||
"examples_title": "Örnekler",
|
||||
"example_meaning_filter_block": "example.org'a ve tüm alt alanlarına erişimi engeller;",
|
||||
"example_meaning_filter_whitelist": "example.org'a ve tüm alt alanlarına erişimin engelini kaldırır;",
|
||||
|
@ -298,6 +298,9 @@
|
|||
"blocking_mode_nxdomain": "NXDOMAIN: NXDOMAIN koduyla yanıt verin",
|
||||
"blocking_mode_null_ip": "Boş IP: Sıfır IP adresiyle yanıt verin (A için 0.0.0.0; :: AAAA için)",
|
||||
"blocking_mode_custom_ip": "Özel IP: El ile ayarlanmış bir IP adresiyle yanıt verin",
|
||||
"theme_auto": "Otomatik",
|
||||
"theme_light": "Açık",
|
||||
"theme_dark": "Koyu",
|
||||
"upstream_dns_client_desc": "Bu alanı boş bırakırsanız, AdGuard Home, <0>DNS ayarlarında</0> yapılandırılan sunucuları kullanır.",
|
||||
"tracker_source": "İzleyici kaynağı",
|
||||
"source_label": "Kaynak",
|
||||
|
@ -347,12 +350,12 @@
|
|||
"install_devices_macos_list_3": "Listedeki ilk bağlantıyı seçin ve Gelişmiş öğesine tıklayın.",
|
||||
"install_devices_macos_list_4": "DNS sekmesini seçin ve AdGuard Home sunucunuzun adreslerini girin.",
|
||||
"install_devices_android_list_1": "Android Menüsü ana ekranından Ayarlar'a dokunun.",
|
||||
"install_devices_android_list_2": "Menüde bulunan Wi-Fi öğesine dokunun. Mevcut tüm ağlar listelenecektir (mobil ağlar için özel DNS sunucusu ayarlanamaz).",
|
||||
"install_devices_android_list_2": "Menüde bulunan Wi-Fi öğesine dokunun. Mevcut tüm ağlar listelenecektir (telefon ağlar için özel DNS sunucusu ayarlanamaz).",
|
||||
"install_devices_android_list_3": "Bağlı olduğunuz ağın üzerine basılı tutun ve Ağı Değiştir'e dokunun.",
|
||||
"install_devices_android_list_4": "Bazı cihazlarda, diğer ayarları görmek için \"Gelişmiş\" seçeneğini seçmeniz gerekebilir. Android DNS ayarlarınızı yapmak için IP ayarlarını DHCP modundan Statik moda değiştirmeniz gerekir.",
|
||||
"install_devices_android_list_5": "DNS 1 ve DNS 2 değerlerini AdGuard Home sunucunuzun adresleriyle değiştirin.",
|
||||
"install_devices_ios_list_1": "Ana ekrandan Ayarlar'a dokunun.",
|
||||
"install_devices_ios_list_2": "Sol menüde bulunan Wi-Fi bölümüne girin (mobil ağlar için özel DNS sunucusu ayarlanamaz).",
|
||||
"install_devices_ios_list_2": "Sol menüde bulunan Wi-Fi bölümüne girin (telefon ağlar için özel DNS sunucusu ayarlanamaz).",
|
||||
"install_devices_ios_list_3": "O anda aktif olan ağın adına dokunun.",
|
||||
"install_devices_ios_list_4": "DNS alanına AdGuard Home sunucunuzun adreslerini girin.",
|
||||
"get_started": "Başlayın",
|
||||
|
@ -451,6 +454,7 @@
|
|||
"updates_checked": "AdGuard Home'un yeni bir sürümü mevcut",
|
||||
"updates_version_equal": "AdGuard Home yazılımı güncel durumda",
|
||||
"check_updates_now": "Güncellemeleri şimdi denetle",
|
||||
"version_request_error": "Güncelleme denetimi başarısız. Lütfen internet bağlantınızı kontrol edin.",
|
||||
"dns_privacy": "DNS Gizliliği",
|
||||
"setup_dns_privacy_1": "<0>DNS-over-TLS:</0> <1>{{address}}</1> dizesini kullan.",
|
||||
"setup_dns_privacy_2": "<0>DNS-over-HTTPS:</0> <1>{{address}}</1> dizesini kullan.",
|
||||
|
@ -476,7 +480,7 @@
|
|||
"rewrite_confirm_delete": "\"{{key}}\" için DNS yeniden yazımını silmek istediğinize emin misiniz?",
|
||||
"rewrite_desc": "Belirli bir alan adı için özel DNS yanıtını kolayca yapılandırmanızı sağlar.",
|
||||
"rewrite_applied": "Yeniden yazım kuralı uygulandı",
|
||||
"rewrite_hosts_applied": "Ana makine dosyası kuralı tarafından yeniden yazıldı",
|
||||
"rewrite_hosts_applied": "Hosts dosyası kuralı tarafından yeniden yazıldı",
|
||||
"dns_rewrites": "DNS yeniden yazımları",
|
||||
"form_domain": "Alan adı veya joker karakter girin",
|
||||
"form_answer": "IP adresi veya alan adı girin",
|
||||
|
@ -539,7 +543,7 @@
|
|||
"network": "Ağ",
|
||||
"descr": "Açıklama",
|
||||
"whois": "WHOIS",
|
||||
"filtering_rules_learn_more": "Kendi ana makine listelerinizi oluşturma hakkında <0>daha fazla bilgi edinin</0>.",
|
||||
"filtering_rules_learn_more": "Kendi hosts listelerinizi oluşturma hakkında <0>daha fazla bilgi edinin</0>.",
|
||||
"blocked_by_response": "Yanıt olarak CNAME veya IP tarafından engellendi",
|
||||
"blocked_by_cname_or_ip": "CNAME veya IP tarafından engellendi",
|
||||
"try_again": "Tekrar dene",
|
||||
|
|
|
@ -298,6 +298,9 @@
|
|||
"blocking_mode_nxdomain": "NXDOMAIN: Відповісти з кодом NXDOMAIN",
|
||||
"blocking_mode_null_ip": "Нульовий IP: Відповісти з нульовою IP-адресою (0.0.0.0 для A; :: для AAAA)",
|
||||
"blocking_mode_custom_ip": "Спеціальна IP-адреса: Відповісти із вручну встановленою IP-адресою",
|
||||
"theme_auto": "Авто",
|
||||
"theme_light": "Світла",
|
||||
"theme_dark": "Темна",
|
||||
"upstream_dns_client_desc": "Якщо це поле залишатиметься порожнім, AdGuard Home використовуватиме сервери, вказані в <0>налаштуваннях DNS</0>.",
|
||||
"tracker_source": "Джерело відстежувача",
|
||||
"source_label": "Джерело",
|
||||
|
@ -451,6 +454,7 @@
|
|||
"updates_checked": "Доступна нова версія AdGuard Home",
|
||||
"updates_version_equal": "AdGuard Home останньої версії",
|
||||
"check_updates_now": "Перевірити наявність оновлень",
|
||||
"version_request_error": "Не вдалося перевірити оновлення. Будь ласка, перевірте з'єднання з інтернетом.",
|
||||
"dns_privacy": "Конфіденційність DNS",
|
||||
"setup_dns_privacy_1": "<0>DNS-over-TLS: </0>Використайте рядок <1>{{address}}</1>.",
|
||||
"setup_dns_privacy_2": "<0>DNS-over-HTTPS:</0> Використайте рядок <1>{{address}}</1>.",
|
||||
|
|
|
@ -298,6 +298,9 @@
|
|||
"blocking_mode_nxdomain": "NXDOMAIN: Phản hổi với mã NXDOMAIN",
|
||||
"blocking_mode_null_ip": "Null IP: Trả lời bằng không địa chỉ IP (0.0.0.0 cho A; :: cho AAAA)",
|
||||
"blocking_mode_custom_ip": "IP tùy chỉnh: Phản hồi với địa chỉ IP đã được tiết lập",
|
||||
"theme_auto": "Tự động",
|
||||
"theme_light": "Light theme",
|
||||
"theme_dark": "Dark theme",
|
||||
"upstream_dns_client_desc": "Nếu để trống trường này, AdGuardHome sẽ sử dụng nhũng máy chủ được cấu hình ở <0>Cấu hình DNS</0>.",
|
||||
"tracker_source": "Nguồn theo dõi",
|
||||
"source_label": "Nguồn",
|
||||
|
@ -451,6 +454,7 @@
|
|||
"updates_checked": "Phiên bản mới của AdGuard Home có sẵn",
|
||||
"updates_version_equal": "AdGuard Home đã được cập nhật",
|
||||
"check_updates_now": "Kiểm tra cập nhật ngay bây giờ",
|
||||
"version_request_error": "Cập nhật không thành công. Hãy kiểm tra kết nối internet của bạn.",
|
||||
"dns_privacy": "DNS Riêng Tư",
|
||||
"setup_dns_privacy_1": "<0>DNS-over-TLS:</0> Sử dụng chuỗi <1>{{address}}</1>.",
|
||||
"setup_dns_privacy_2": "<0>DNS-over-HTTPS:</0> Sử dụng chuỗi <1>{{address}}</1>.",
|
||||
|
|
|
@ -298,6 +298,9 @@
|
|||
"blocking_mode_nxdomain": "NXDOMAIN:以NXDOMAIN码响应",
|
||||
"blocking_mode_null_ip": "空IP:以零IP地址响应(A记录 0.0.0.0;AAAA记录 ::)",
|
||||
"blocking_mode_custom_ip": "自定IP:以手动设置的IP地址响应",
|
||||
"theme_auto": "自动",
|
||||
"theme_light": "浅色主题",
|
||||
"theme_dark": "深色主题",
|
||||
"upstream_dns_client_desc": "如果将此字段留空,AdGuard Home 将使用在<0>DNS设置</0>中配置的服务器。",
|
||||
"tracker_source": "追踪器来源",
|
||||
"source_label": "源",
|
||||
|
@ -451,6 +454,7 @@
|
|||
"updates_checked": "AdGuard Home 的新版本现在可用",
|
||||
"updates_version_equal": "AdGuard Home已经是最新版本",
|
||||
"check_updates_now": "立即检查更新",
|
||||
"version_request_error": "检查更新失败。请检查您的因特网连接。",
|
||||
"dns_privacy": "DNS 隐私",
|
||||
"setup_dns_privacy_1": "<0>DNS-over-TLS:</0> 使用 <1>{{address}}</1> 字符串。",
|
||||
"setup_dns_privacy_2": "<0>DNS-over-HTTPS:</0> 使用 <1>{{address}}</1> 字符串。",
|
||||
|
|
|
@ -38,8 +38,6 @@
|
|||
"form_error_required": "必要欄位",
|
||||
"form_error_ip4_format": "無效的 IPv4 格式",
|
||||
"form_error_ip6_format": "無效的 IPv6 格式",
|
||||
"form_error_ip4_range_start_format": "無效的 IPv4 範圍起始位址",
|
||||
"form_error_ip4_range_end_format": "無效的 IPv4 範圍結束位址",
|
||||
"form_error_ip4_gateway_format": "閘道的 IPv4 位址無效",
|
||||
"form_error_ip_format": "無效的 IP 位址",
|
||||
"form_error_mac_format": "無效的 「MAC 位址」格式",
|
||||
|
|
|
@ -298,6 +298,9 @@
|
|||
"blocking_mode_nxdomain": "不存在的網域(NXDOMAIN):以 NXDOMAIN 碼回覆",
|
||||
"blocking_mode_null_ip": "無效的 IP:以零值 IP 位址(0.0.0.0 供 A;:: 供 AAAA)回覆",
|
||||
"blocking_mode_custom_ip": "自訂的 IP:以一組手動地被設定的 IP 位址回覆",
|
||||
"theme_auto": "自動",
|
||||
"theme_light": "淺色",
|
||||
"theme_dark": "深色",
|
||||
"upstream_dns_client_desc": "如果您將此欄位留空,AdGuard Home 將使用在 <0>DNS 設定</0>中被配置的伺服器。",
|
||||
"tracker_source": "追蹤器來源",
|
||||
"source_label": "來源",
|
||||
|
@ -451,6 +454,7 @@
|
|||
"updates_checked": "AdGuard Home 的新版本為可用的",
|
||||
"updates_version_equal": "AdGuard Home 為最新的",
|
||||
"check_updates_now": "立即檢查更新",
|
||||
"version_request_error": "更新檢查已失敗。請檢查您的網際網路連線。",
|
||||
"dns_privacy": "DNS 隱私",
|
||||
"setup_dns_privacy_1": "<0>DNS-over-TLS:</0>使用 <1>{{address}}</1> 字串。",
|
||||
"setup_dns_privacy_2": "<0>DNS-over-HTTPS:</0>使用 <1>{{address}}</1> 字串。",
|
||||
|
|
|
@ -141,7 +141,7 @@ export const getVersion = (recheck = false) => async (dispatch, getState) => {
|
|||
}
|
||||
}
|
||||
} catch (error) {
|
||||
dispatch(addErrorToast({ error }));
|
||||
dispatch(addErrorToast({ error: 'version_request_error' }));
|
||||
dispatch(getVersionFailure());
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
:root {
|
||||
--black: #131313;
|
||||
--bgcolor: #f5f7fb;
|
||||
--mcolor: #495057;
|
||||
--scolor: rgba(74, 74, 74, 0.7);
|
||||
|
@ -26,11 +27,13 @@
|
|||
--gray-a5: #a5a5a5;
|
||||
--gray-d8: #d8d8d8;
|
||||
--gray-f3: #f3f3f3;
|
||||
--loading-bg: rgba(255, 255, 255, 0.48);
|
||||
--font-family-monospace: Monaco, Menlo, "Ubuntu Mono", Consolas, source-code-pro, monospace;
|
||||
--font-size-disable-autozoom: 1rem;
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
--black: #ffffff;
|
||||
--bgcolor: #131313;
|
||||
--mcolor: #e6e6e6;
|
||||
--scolor: #a5a5a5;
|
||||
|
@ -44,16 +47,18 @@
|
|||
--ctrl-dropdown-bgcolor-focus: #000;
|
||||
--ctrl-dropdown-color-focus: #fff;
|
||||
--btn-success-bgcolor: #67b279;
|
||||
--form-disabled-bgcolor: #3d3d3d;
|
||||
--form-disabled-bgcolor: #2d2d2d;
|
||||
--form-disabled-color: #a5a5a5;
|
||||
--logs__text-color: #f3f3f3;
|
||||
--rt-nodata-bgcolor: #1c1c1c;
|
||||
--rt-nodata-color: #fff;
|
||||
--modal-overlay-bgcolor: #1c1c1c;
|
||||
--modal-overlay-bgcolor: rgba(19, 19, 19, 0.75);
|
||||
--logs__table-bgcolor: #3d3d3d;
|
||||
--logs__row--blue-bgcolor: #467fcf;
|
||||
--logs__row--white-bgcolor: #1c1c1c;
|
||||
--detailed-info-color: #fff;
|
||||
--gray300: #f3f3f3;
|
||||
--loading-bg: #131313;
|
||||
}
|
||||
|
||||
body {
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-top: 1px solid #dee2e6;
|
||||
border-top: 1px solid var(--card-border-color);
|
||||
padding: 0.75rem 1.5rem;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
.nav-tabs .nav-link.active {
|
||||
border-color: var(--green-74);
|
||||
color: var(--green-74);
|
||||
border-color: var(--btn-success-bgcolor);
|
||||
color: var(--btn-success-bgcolor);
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.nav-tabs .nav-link.active:hover {
|
||||
border-color: #58a273;
|
||||
color: #58a273;
|
||||
border-color: #4b9400;
|
||||
color: #4b9400;
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
border-radius: 4px !important;
|
||||
pointer-events: auto !important;
|
||||
background-color: var(--ctrl-bgcolor);
|
||||
color: var(--scolor);
|
||||
color: var(--mcolor);
|
||||
z-index: 102;
|
||||
overflow-y: auto;
|
||||
max-height: 100%;
|
||||
|
|
|
@ -24,6 +24,12 @@
|
|||
--option-border-radius: 4px;
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
--red: rgba(223, 56, 18, 0.25);
|
||||
--green-pale: rgba(103, 178, 121, 0.25);
|
||||
--yellow: rgba(247, 181, 0, 0.2);
|
||||
}
|
||||
|
||||
.logs__text {
|
||||
padding: 0 1px;
|
||||
text-overflow: ellipsis;
|
||||
|
@ -36,6 +42,14 @@
|
|||
line-height: 1.5rem;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .logs__text a {
|
||||
color: var(--gray-f3);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .logs__text a:hover {
|
||||
color: var(--gray-f3);
|
||||
}
|
||||
|
||||
.logs__text--bold {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
@ -66,6 +80,10 @@
|
|||
border-radius: 4px;
|
||||
}
|
||||
|
||||
[data-theme=dark] .icon--selected {
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.text-pre {
|
||||
white-space: pre-wrap !important;
|
||||
overflow-wrap: break-word;
|
||||
|
@ -205,6 +223,12 @@
|
|||
position: relative;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 1025px) {
|
||||
.logs__cell--client {
|
||||
width: 13rem;
|
||||
}
|
||||
}
|
||||
|
||||
.logs__cell--header__container > .logs__cell--header__item {
|
||||
border-right: 0;
|
||||
font-size: 1rem;
|
||||
|
@ -340,6 +364,10 @@
|
|||
overflow: hidden;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .tooltip-custom__container .button-action--arrow-option:not(:disabled):hover {
|
||||
background: var(--ctrl-dropdown-bgcolor-focus);
|
||||
}
|
||||
|
||||
.button-action--arrow-option-container {
|
||||
overflow: visible;
|
||||
transform-origin: left;
|
||||
|
@ -482,6 +510,10 @@
|
|||
color: var(--green79);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .logs__question.icon--lightgray {
|
||||
color: var(--gray-f3);
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.logs__question {
|
||||
display: none;
|
||||
|
@ -491,3 +523,8 @@
|
|||
.logs__modal {
|
||||
max-width: 720px;
|
||||
}
|
||||
|
||||
.logs__modal-wrap {
|
||||
padding: 1rem 1.5rem;
|
||||
background-color: var(--card-bgcolor);
|
||||
}
|
||||
|
|
|
@ -195,11 +195,11 @@ const Logs = () => {
|
|||
onRequestClose={closeModal}
|
||||
style={{
|
||||
content: {
|
||||
width: '100%',
|
||||
width: 'calc(100% - 32px)',
|
||||
height: 'fit-content',
|
||||
left: '50%',
|
||||
top: 47,
|
||||
padding: '1rem 1.5rem 1rem',
|
||||
padding: '0',
|
||||
maxWidth: '720px',
|
||||
transform: 'translateX(-50%)',
|
||||
},
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
padding: 10px 15px;
|
||||
border: 1px solid #eee;
|
||||
border: 1px solid var(--card-border-color);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
.dropdown-item.active,
|
||||
.dropdown-item:active {
|
||||
background-color: var(--green-74);
|
||||
background-color: var(--btn-success-bgcolor);
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import classNames from 'classnames';
|
||||
|
@ -10,7 +10,7 @@ import i18n from '../../i18n';
|
|||
import Version from './Version';
|
||||
import './Footer.css';
|
||||
import './Select.css';
|
||||
import { setHtmlLangAttr } from '../../helpers/helpers';
|
||||
import { setHtmlLangAttr, setUITheme } from '../../helpers/helpers';
|
||||
import { changeTheme } from '../../actions';
|
||||
|
||||
const linksData = [
|
||||
|
@ -36,6 +36,13 @@ const Footer = () => {
|
|||
const currentTheme = useSelector((state) => (state.dashboard ? state.dashboard.theme : 'auto'));
|
||||
const profileName = useSelector((state) => (state.dashboard ? state.dashboard.name : ''));
|
||||
const isLoggedIn = profileName !== '';
|
||||
const [currentThemeLocal, setCurrentThemeLocal] = useState('auto');
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoggedIn) {
|
||||
setUITheme(window.matchMedia('(prefers-color-scheme: dark)').matches ? THEMES.dark : THEMES.light);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const getYear = () => {
|
||||
const today = new Date();
|
||||
|
@ -53,6 +60,12 @@ const Footer = () => {
|
|||
dispatch(changeTheme(value));
|
||||
};
|
||||
|
||||
const onThemeChangedLocal = (event) => {
|
||||
const { value } = event.target;
|
||||
setUITheme(value);
|
||||
setCurrentThemeLocal(value);
|
||||
};
|
||||
|
||||
const renderCopyright = () => <div className="footer__column">
|
||||
<div className="footer__copyright">
|
||||
{t('copyright')} © {getYear()}{' '}
|
||||
|
@ -70,24 +83,34 @@ const Footer = () => {
|
|||
{t(name)}
|
||||
</a>);
|
||||
|
||||
const renderThemeSelect = (currentTheme, isLoggedIn) => {
|
||||
if (!isLoggedIn) {
|
||||
return '';
|
||||
}
|
||||
const themeSelectOptions = () => (
|
||||
Object.values(THEMES)
|
||||
.map((theme) => (
|
||||
<option key={theme} value={theme}>
|
||||
{t(`theme_${theme}`)}
|
||||
</option>
|
||||
))
|
||||
);
|
||||
|
||||
return <select
|
||||
const renderThemeSelect = () => (
|
||||
<select
|
||||
className="form-control select select--theme"
|
||||
value={currentTheme}
|
||||
onChange={onThemeChanged}
|
||||
>
|
||||
{Object.values(THEMES)
|
||||
.map((theme) => (
|
||||
<option key={theme} value={theme}>
|
||||
{t(`theme_${theme}`)}
|
||||
</option>
|
||||
))}
|
||||
</select>;
|
||||
};
|
||||
{themeSelectOptions()}
|
||||
</select>
|
||||
);
|
||||
|
||||
const renderThemeSelectLocal = () => (
|
||||
<select
|
||||
className="form-control select select--theme"
|
||||
value={currentThemeLocal}
|
||||
onChange={onThemeChangedLocal}
|
||||
>
|
||||
{themeSelectOptions()}
|
||||
</select>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -98,7 +121,7 @@ const Footer = () => {
|
|||
{renderLinks(linksData)}
|
||||
</div>
|
||||
<div className="footer__column footer__column--theme">
|
||||
{renderThemeSelect(currentTheme, isLoggedIn)}
|
||||
{isLoggedIn ? renderThemeSelect() : renderThemeSelectLocal()}
|
||||
</div>
|
||||
<div className="footer__column footer__column--language">
|
||||
<select
|
||||
|
|
|
@ -11,6 +11,10 @@
|
|||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.card-chart-bg {
|
||||
color: var(--black);
|
||||
}
|
||||
|
||||
.card-chart-bg path[d^="M0,32"] {
|
||||
transform: translateY(32px);
|
||||
}
|
||||
|
|
|
@ -25,9 +25,9 @@ const Line = ({
|
|||
theme={{
|
||||
crosshair: {
|
||||
line: {
|
||||
stroke: 'black',
|
||||
stroke: 'currentColor',
|
||||
strokeWidth: 1,
|
||||
strokeOpacity: 0.35,
|
||||
strokeOpacity: 0.5,
|
||||
},
|
||||
},
|
||||
}}
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
z-index: 100;
|
||||
width: 100%;
|
||||
min-height: 100vh;
|
||||
background-color: rgba(255, 255, 255, 0.48);
|
||||
background-color: var(--loading-bg);
|
||||
}
|
||||
|
||||
.loading:after {
|
||||
|
|
|
@ -23,14 +23,29 @@
|
|||
background-color: var(--rt-nodata-bgcolor);
|
||||
}
|
||||
|
||||
.ReactTable .-loading .-loading-inner {
|
||||
color: var(--gray300);
|
||||
}
|
||||
|
||||
.ReactTable .-pagination input, .ReactTable .-pagination select {
|
||||
color: var(--rt-nodata-color);
|
||||
background-color: var(--rt-nodata-bgcolor);
|
||||
}
|
||||
|
||||
[data-theme=dark] .ReactTable .rt-table::-webkit-scrollbar-track {
|
||||
background-color: var(--card-bgcolor);
|
||||
}
|
||||
|
||||
[data-theme=dark] .ReactTable .rt-table::-webkit-scrollbar-thumb {
|
||||
background-color: #888888;
|
||||
}
|
||||
|
||||
[data-theme=dark] .ReactTable .-pagination .-btn {
|
||||
color: var(--scolor);
|
||||
background-color: var(--ctrl-bgcolor);
|
||||
filter: invert(1);
|
||||
}
|
||||
|
||||
[data-theme=dark] .ReactTable .-pagination .-btn:disabled {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.rt-tr-group.logs__row--red {
|
||||
|
|
|
@ -2579,16 +2579,58 @@ fieldset:disabled a.btn {
|
|||
box-shadow: 0 0 0 2px rgba(134, 142, 150, 0.5);
|
||||
}
|
||||
|
||||
[data-theme=dark] .btn-secondary {
|
||||
color: #868e96;
|
||||
background-color: transparent;
|
||||
background-image: none;
|
||||
border-color: #868e96;
|
||||
}
|
||||
|
||||
[data-theme=dark] .btn-secondary:hover {
|
||||
color: #fff;
|
||||
background-color: #868e96;
|
||||
border-color: #868e96;
|
||||
}
|
||||
|
||||
[data-theme=dark] .btn-secondary:focus,
|
||||
[data-theme=dark] .btn-secondary.focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
[data-theme=dark] .btn-secondary:focus-visible,
|
||||
[data-theme=dark] .btn-secondary.focus {
|
||||
box-shadow: 0 0 0 2px rgba(134, 142, 150, 0.5);
|
||||
}
|
||||
|
||||
[data-theme=dark] .btn-secondary.disabled,
|
||||
[data-theme=dark] .btn-secondary:disabled {
|
||||
color: #868e96;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
[data-theme=dark] .btn-secondary:not(:disabled):not(.disabled):active,
|
||||
[data-theme=dark] .btn-secondary:not(:disabled):not(.disabled).active {
|
||||
color: #fff;
|
||||
background-color: #868e96;
|
||||
border-color: #868e96;
|
||||
}
|
||||
|
||||
[data-theme=dark] .btn-secondary:not(:disabled):not(.disabled):active:focus,
|
||||
[data-theme=dark] .btn-secondary:not(:disabled):not(.disabled).active:focus {
|
||||
box-shadow: 0 0 0 2px rgba(134, 142, 150, 0.5);
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
color: #fff;
|
||||
background-color: var(--btn-success-bgcolor);
|
||||
border-color: #5eba00;
|
||||
border-color: var(--btn-success-bgcolor);
|
||||
}
|
||||
|
||||
.btn-success:hover {
|
||||
color: #fff;
|
||||
background-color: #4b9400;
|
||||
border-color: #448700;
|
||||
border-color: #4b9400;
|
||||
}
|
||||
|
||||
.btn-success:focus,
|
||||
|
@ -2608,7 +2650,7 @@ fieldset:disabled a.btn {
|
|||
.show>.btn-success.dropdown-toggle {
|
||||
color: #fff;
|
||||
background-color: #448700;
|
||||
border-color: #3e7a00;
|
||||
border-color: #448700;
|
||||
}
|
||||
|
||||
.btn-success:not(:disabled):not(.disabled):active:focus,
|
||||
|
@ -3251,6 +3293,10 @@ tbody.collapse.show {
|
|||
border-radius: 3px;
|
||||
}
|
||||
|
||||
[data-theme=dark] .dropdown-menu {
|
||||
border: 1px solid var(--card-border-color);
|
||||
}
|
||||
|
||||
.dropup .dropdown-menu {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.125rem;
|
||||
|
@ -5509,7 +5555,7 @@ button.close {
|
|||
-ms-flex-pack: justify;
|
||||
justify-content: space-between;
|
||||
padding: 1rem;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
border-bottom: 1px solid var(--card-border-color);
|
||||
border-top-left-radius: 3px;
|
||||
border-top-right-radius: 3px;
|
||||
}
|
||||
|
@ -5539,7 +5585,7 @@ button.close {
|
|||
-ms-flex-pack: end;
|
||||
justify-content: flex-end;
|
||||
padding: 1rem;
|
||||
border-top: 1px solid #e9ecef;
|
||||
border-top: 1px solid var(--card-border-color);
|
||||
}
|
||||
|
||||
.modal-footer> :not(:first-child) {
|
||||
|
@ -13679,6 +13725,10 @@ Card alert
|
|||
color: #6e7687;
|
||||
}
|
||||
|
||||
[data-theme=dark] .dropdown-item {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.dropdown-menu-arrow:before {
|
||||
position: absolute;
|
||||
top: -6px;
|
||||
|
@ -13706,6 +13756,10 @@ Card alert
|
|||
content: "";
|
||||
}
|
||||
|
||||
[data-theme=dark] .dropdown-menu-arrow:after {
|
||||
border-bottom: 5px solid var(--card-border-color);
|
||||
}
|
||||
|
||||
.dropdown-menu-arrow.dropdown-menu-right:before,
|
||||
.dropdown-menu-arrow.dropdown-menu-right:after {
|
||||
left: auto;
|
||||
|
@ -15473,6 +15527,10 @@ a.tag-addon:hover {
|
|||
transition: 0.3s border-color, 0.3s background-color;
|
||||
}
|
||||
|
||||
[data-theme=dark] .custom-switch-indicator {
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.custom-switch-indicator:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
justify-content: space-between;
|
||||
margin-bottom: 15px;
|
||||
padding: 10px 0;
|
||||
border-bottom: 1px solid #e8e8e8;
|
||||
border-bottom: 1px solid var(--card-border-color);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
|
@ -40,6 +40,10 @@
|
|||
opacity: 0.6;
|
||||
}
|
||||
|
||||
[data-theme=dark] .tab__control {
|
||||
filter: invert(1);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.tab__control {
|
||||
white-space: normal;
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
"11": "unknown",
|
||||
"12": "extensions",
|
||||
"13": "email",
|
||||
"14": "consent",
|
||||
"15": "telemetry",
|
||||
"101": "mobile_analytics"
|
||||
},
|
||||
"trackers": {
|
||||
|
@ -149,6 +151,7 @@
|
|||
"akadns.net": "akamai_technologies",
|
||||
"akamaiedge.net": "akamai_technologies",
|
||||
"apple.com": "apple",
|
||||
"apple.news": "apple",
|
||||
"apple-dns.net": "apple",
|
||||
"aaplimg.com": "apple",
|
||||
"icloud.com": "apple",
|
||||
|
|
|
@ -21,11 +21,16 @@
|
|||
margin: 0 auto;
|
||||
padding: 30px 20px;
|
||||
line-height: 1.6;
|
||||
background-color: #fff;
|
||||
background-color: var(--card-bgcolor);
|
||||
box-shadow: 0 1px 4px rgba(74, 74, 74, 0.36);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
[data-theme=dark] .setup__container {
|
||||
box-shadow: none;
|
||||
border: 1px solid var(--card-border-color);
|
||||
}
|
||||
|
||||
@media screen and (min-width: 768px) {
|
||||
.setup__container {
|
||||
width: 650px;
|
||||
|
@ -39,6 +44,10 @@
|
|||
max-width: 140px;
|
||||
}
|
||||
|
||||
[data-theme=dark] .setup__logo {
|
||||
filter: invert(1);
|
||||
}
|
||||
|
||||
.setup__nav {
|
||||
text-align: center;
|
||||
}
|
||||
|
|
|
@ -13,6 +13,10 @@
|
|||
min-height: 100vh;
|
||||
}
|
||||
|
||||
[data-theme=dark] .login__logo {
|
||||
filter: invert(1);
|
||||
}
|
||||
|
||||
.login__form {
|
||||
margin: auto;
|
||||
padding: 40px 15px 100px;
|
||||
|
|
|
@ -36,7 +36,7 @@ class Login extends Component {
|
|||
<div className="login">
|
||||
<div className="login__form">
|
||||
<div className="text-center mb-6">
|
||||
<img src={logo} className="h-6" alt="logo" />
|
||||
<img src={logo} className="h-6 login__logo" alt="logo" />
|
||||
</div>
|
||||
<Form onSubmit={this.handleSubmit} processing={processingLogin} />
|
||||
<div className="login__info">
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
scripts
|
||||
node_modules
|
||||
postcss.config.js
|
||||
src/lib/entities
|
||||
src/lib/apis
|
||||
openApi
|
|
@ -1,5 +0,0 @@
|
|||
{
|
||||
"extends": [
|
||||
"./scripts/lint/dev.js"
|
||||
]
|
||||
}
|
18
client2/declaration.d.ts
vendored
18
client2/declaration.d.ts
vendored
|
@ -1,18 +0,0 @@
|
|||
declare module '*.pcss' {
|
||||
const content: {[className: string]: string};
|
||||
export default content;
|
||||
}
|
||||
declare module '*.css' {
|
||||
const content: {[className: string]: string};
|
||||
export default content;
|
||||
}
|
||||
declare module '*.png'
|
||||
declare module '*.jpg'
|
||||
declare let AUTH_TOKEN: string;
|
||||
declare let MAIN_TOKEN: string | undefined;
|
||||
declare let NO_CAPTCHA: boolean | undefined;
|
||||
declare module 'dygraphs';
|
||||
declare module '@novnc/novnc/core/rfb';
|
||||
// cp - CloudPayments script
|
||||
declare let cp: any;
|
||||
declare const DEV: any;
|
|
@ -1,89 +0,0 @@
|
|||
{
|
||||
"author": "Performix",
|
||||
"private": true,
|
||||
"name": "adguard-home",
|
||||
"version": "0.1.0",
|
||||
"scripts": {
|
||||
"build": "webpack --config ./scripts/webpack/webpack.config.prod.js",
|
||||
"start": "webpack serve --config ./scripts/webpack/webpack.config.dev.js",
|
||||
"generate": "rm -rf ./src/lib/entities ./src/lib/apis && ts-node --compiler-options '{ \"module\": \"CommonJS\" }' ./scripts/generator/index.ts",
|
||||
"translations:check": "ts-node --compiler-options '{ \"module\": \"CommonJS\" }' ./scripts/plugins/checkTranslations.ts",
|
||||
"lint": "eslint -c ./scripts/lint/prod.js --ext .tsx --ext .ts ./",
|
||||
"go:build": "cd .. && make REBUILD_CLIENT=0 build",
|
||||
"go:run": "sudo ../AdguardHome"
|
||||
},
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@adguard/translate": "^0.2.0",
|
||||
"@ant-design/icons": "^4.4.0",
|
||||
"@sentry/react": "^5.27.0",
|
||||
"antd": "^4.7.2",
|
||||
"classnames": "^2.2.6",
|
||||
"dayjs": "^1.9.3",
|
||||
"formik": "^2.2.0",
|
||||
"mobx": "^6.0.1",
|
||||
"mobx-react-lite": "^3.0.1",
|
||||
"qs": "^6.9.4",
|
||||
"react": "^17.0.0",
|
||||
"react-dom": "^17.0.0",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"recharts": "^2.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/classnames": "^2.2.10",
|
||||
"@types/qs": "^6.9.5",
|
||||
"@types/react": "^16.9.53",
|
||||
"@types/react-dom": "^16.9.8",
|
||||
"@types/react-redux": "^7.1.9",
|
||||
"@types/react-router-dom": "^5.1.6",
|
||||
"@typescript-eslint/eslint-plugin": "^4.5.0",
|
||||
"@typescript-eslint/parser": "^4.5.0",
|
||||
"antd-dayjs-webpack-plugin": "^1.0.1",
|
||||
"autoprefixer": "^10.0.1",
|
||||
"connect-history-api-fallback": "^1.6.0",
|
||||
"copy-webpack-plugin": "^6.2.1",
|
||||
"css-loader": "^5.0.0",
|
||||
"eslint": "^7.11.0",
|
||||
"eslint-config-airbnb-base": "^14.2.0",
|
||||
"eslint-config-airbnb-typescript": "^12.0.0",
|
||||
"eslint-import-resolver-typescript": "^2.3.0",
|
||||
"eslint-loader": "^4.0.2",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"eslint-plugin-react": "^7.21.5",
|
||||
"eslint-plugin-react-hooks": "^4.2.0",
|
||||
"file-loader": "^6.1.1",
|
||||
"html-webpack-plugin": "^4.5.0",
|
||||
"http-proxy-middleware": "^1.0.6",
|
||||
"less": "^3.12.2",
|
||||
"less-loader": "^5.0.0",
|
||||
"mini-css-extract-plugin": "^1.1.1",
|
||||
"optimize-css-assets-webpack-plugin": "^5.0.4",
|
||||
"postcss": "^8.1.2",
|
||||
"postcss-calc": "^7.0.5",
|
||||
"postcss-css-variables": "^0.17.0",
|
||||
"postcss-custom-media": "^7.0.8",
|
||||
"postcss-import": "^13.0.0",
|
||||
"postcss-inline-svg": "^4.1.0",
|
||||
"postcss-loader": "^4.0.4",
|
||||
"postcss-mixins": "^7.0.1",
|
||||
"postcss-modules": "^3.2.2",
|
||||
"postcss-nested": "^5.0.1",
|
||||
"postcss-preset-env": "^6.7.0",
|
||||
"postcss-reporter": "^7.0.1",
|
||||
"postcss-variables": "^1.1.1",
|
||||
"style-loader": "^2.0.0",
|
||||
"stylelint": "^13.7.2",
|
||||
"stylelint-webpack-plugin": "^2.1.1",
|
||||
"terser-webpack-plugin": "^5.0.0",
|
||||
"ts-loader": "^8.0.6",
|
||||
"ts-morph": "^8.1.2",
|
||||
"ts-node": "^9.0.0",
|
||||
"typescript": "^4.0.3",
|
||||
"url-loader": "^4.1.1",
|
||||
"webpack": "^5.10.0",
|
||||
"webpack-cli": "^4.2.0",
|
||||
"webpack-dev-server": "^3.11.0",
|
||||
"webpack-merge": "^5.2.0",
|
||||
"yaml": "^1.10.0"
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
module.exports = {
|
||||
plugins: [
|
||||
['postcss-import', {}],
|
||||
['postcss-nested', {}],
|
||||
['postcss-custom-media', {}],
|
||||
['postcss-variables', {}],
|
||||
['postcss-calc', {}],
|
||||
['postcss-mixins', {}],
|
||||
['postcss-preset-env', { stage: 3, features: { 'nesting-rules': true } }],
|
||||
['postcss-reporter', { clearMessages: true }],
|
||||
['postcss-inline-svg', {
|
||||
paths: ['frontend/icons', 'vendor/adguard/utils-bundle/src/Resources/frontend/icons'],
|
||||
svgo: { plugins: [{ cleanupAttrs: true }] }
|
||||
}],
|
||||
['autoprefixer'],
|
||||
]
|
||||
};
|
Binary file not shown.
Before Width: | Height: | Size: 2.1 KiB |
Binary file not shown.
Before Width: | Height: | Size: 1.3 KiB |
|
@ -1,8 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16pt" height="16pt"
|
||||
viewBox="0 0 16 16" version="1.1">
|
||||
<g id="surface1">
|
||||
<path style=" stroke:none;fill-rule:evenodd;fill:rgb(0%,0%,0%);fill-opacity:1;"
|
||||
d="M 8 0 C 10.5 0 13.515625 0.574219 16 1.835938 L 15.996094 2.542969 C 15.957031 5.605469 15.410156 11.71875 8 16 C 0.5 11.667969 0.03125 5.460938 0.00390625 2.433594 L 0 1.835938 C 2.484375 0.574219 5.5 0 8 0 Z M 11.769531 4.203125 L 11.761719 4.203125 L 7.890625 8.160156 L 6.433594 6.4375 C 5.738281 5.644531 4.792969 6.25 4.570312 6.40625 L 7.929688 10.285156 L 12.570312 4.136719 C 12.230469 3.867188 11.933594 4.054688 11.769531 4.203125 Z M 11.769531 4.203125 "/>
|
||||
</g>
|
||||
</svg>
|
Before Width: | Height: | Size: 801 B |
|
@ -1,23 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, shrink-to-fit=no">
|
||||
<meta name="theme-color" content="#000000">
|
||||
<meta name="google" content="notranslate">
|
||||
<meta http-equiv="x-dns-prefetch-control" content="off">
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="assets/apple-touch-icon-180x180.png" />
|
||||
<link rel="mask-icon" href="assets/safari-pinned-tab.svg" color="#67B279">
|
||||
<link rel="icon" type="image/png" href="assets/favicon.png" sizes="48x48">
|
||||
<title>AdGuard Home</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
You need to enable JavaScript to run this app.
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
</html>
|
|
@ -1,22 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, shrink-to-fit=no">
|
||||
<meta name="theme-color" content="#000000">
|
||||
<meta name="google" content="notranslate">
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="assets/apple-touch-icon-180x180.png" />
|
||||
<link rel="mask-icon" href="assets/safari-pinned-tab.svg" color="#67B279">
|
||||
<link rel="icon" type="image/png" href="assets/favicon.png" sizes="48x48">
|
||||
<title>Setup AdGuard Home</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
You need to enable JavaScript to run this app.
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
</html>
|
|
@ -1,22 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, shrink-to-fit=no">
|
||||
<meta name="theme-color" content="#000000">
|
||||
<meta name="google" content="notranslate">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="assets/apple-touch-icon-180x180.png" />
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||||
<link rel="mask-icon" href="assets/safari-pinned-tab.svg" color="#67B279">
|
||||
<link rel="icon" type="image/png" href="assets/favicon.png" sizes="48x48">
|
||||
<title>Login</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>
|
||||
You need to enable JavaScript to run this app.
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
</body>
|
||||
</html>
|
|
@ -1,12 +0,0 @@
|
|||
export const OPEN_API_PATH = '../openapi/openapi.yaml';
|
||||
export const ENT_DIR = './src/lib/entities';
|
||||
export const API_DIR = './src/lib/apis';
|
||||
export const LOCALE_FOLDER_PATH = './src/lib/intl/__locales';
|
||||
export const TRANSLATOR_CLASS_NAME = 'Translator';
|
||||
export const USE_INTL_NAME = 'useIntl';
|
||||
|
||||
export const trimQuotes = (str: string) => {
|
||||
return str.replace(/\'|\"/g, '');
|
||||
};
|
||||
|
||||
export const GENERATOR_ENTITY_ALLIAS = 'Entities/';
|
|
@ -1,18 +0,0 @@
|
|||
import * as fs from 'fs';
|
||||
import * as YAML from 'yaml';
|
||||
import { OPEN_API_PATH } from '../consts';
|
||||
|
||||
import EntitiesGenerator from './src/generateEntities';
|
||||
import ApisGenerator from './src/generateApis';
|
||||
|
||||
|
||||
const generateApi = (openApi: Record<string, any>) => {
|
||||
const ent = new EntitiesGenerator(openApi);
|
||||
ent.save();
|
||||
|
||||
const api = new ApisGenerator(openApi);
|
||||
api.save();
|
||||
}
|
||||
|
||||
const openApiFile = fs.readFileSync(OPEN_API_PATH, 'utf8');
|
||||
generateApi(YAML.parse(openApiFile));
|
|
@ -1,317 +0,0 @@
|
|||
/* eslint-disable no-template-curly-in-string */
|
||||
/* eslint-disable @typescript-eslint/no-unused-expressions */
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { stringify } from 'qs';
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import * as morph from 'ts-morph';
|
||||
|
||||
import {
|
||||
API_DIR as API_DIR_CONST,
|
||||
GENERATOR_ENTITY_ALLIAS,
|
||||
} from '../../consts';
|
||||
import { toCamel, capitalize, schemaParamParser } from './utils';
|
||||
|
||||
|
||||
const API_DIR = path.resolve(API_DIR_CONST);
|
||||
if (!fs.existsSync(API_DIR)) {
|
||||
fs.mkdirSync(API_DIR);
|
||||
}
|
||||
|
||||
const { Project, QuoteKind } = morph;
|
||||
|
||||
|
||||
class ApiGenerator {
|
||||
project = new Project({
|
||||
tsConfigFilePath: './tsconfig.json',
|
||||
addFilesFromTsConfig: false,
|
||||
manipulationSettings: {
|
||||
quoteKind: QuoteKind.Single,
|
||||
usePrefixAndSuffixTextForRename: false,
|
||||
useTrailingCommas: true,
|
||||
},
|
||||
});
|
||||
|
||||
openapi: Record<string, any>;
|
||||
|
||||
serverUrl: string;
|
||||
|
||||
paths: any;
|
||||
|
||||
/* interface Controllers {
|
||||
[controller: string]: {
|
||||
[operationId: string]: { parameters - from opneApi, responses - from opneApi, method }
|
||||
}
|
||||
} */
|
||||
controllers: Record<string, any> = {};
|
||||
|
||||
apis: morph.SourceFile[] = [];
|
||||
|
||||
constructor(openapi: Record<string, any>) {
|
||||
this.openapi = openapi;
|
||||
this.paths = openapi.paths;
|
||||
this.serverUrl = openapi.servers[0].url;
|
||||
|
||||
Object.keys(this.paths).forEach((pathKey) => {
|
||||
Object.keys(this.paths[pathKey]).forEach((method) => {
|
||||
const {
|
||||
tags, operationId, parameters, responses, requestBody, security,
|
||||
} = this.paths[pathKey][method];
|
||||
const controller = toCamel((tags ? tags[0] : pathKey.split('/')[1]).replace('-controller', ''));
|
||||
|
||||
if (this.controllers[controller]) {
|
||||
this.controllers[controller][operationId] = {
|
||||
parameters,
|
||||
responses,
|
||||
method,
|
||||
requestBody,
|
||||
security,
|
||||
pathKey: pathKey.replace(/{/g, '${'),
|
||||
};
|
||||
} else {
|
||||
this.controllers[controller] = { [operationId]: {
|
||||
parameters,
|
||||
responses,
|
||||
method,
|
||||
requestBody,
|
||||
security,
|
||||
pathKey: pathKey.replace(/{/g, '${'),
|
||||
} };
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this.generateApiFiles();
|
||||
}
|
||||
|
||||
generateApiFiles = () => {
|
||||
Object.keys(this.controllers).forEach(this.generateApiFile);
|
||||
};
|
||||
|
||||
generateApiFile = (cName: string) => {
|
||||
const apiFile = this.project.createSourceFile(`${API_DIR}/${cName}.ts`);
|
||||
apiFile.addStatements([
|
||||
'// This file was autogenerated. Please do not change.',
|
||||
'// All changes will be overwrited on commit.',
|
||||
'',
|
||||
]);
|
||||
|
||||
// const schemaProperties = schemas[schemaName].properties;
|
||||
const importEntities: any[] = [];
|
||||
|
||||
// add api class to file
|
||||
const apiClass = apiFile.addClass({
|
||||
name: `${capitalize(cName)}Api`,
|
||||
isDefaultExport: true,
|
||||
});
|
||||
|
||||
// get operations of controller
|
||||
const controllerOperations = this.controllers[cName];
|
||||
const operationList = Object.keys(controllerOperations).sort();
|
||||
// for each operation add fetcher
|
||||
operationList.forEach((operation) => {
|
||||
const {
|
||||
requestBody, responses, parameters, method, pathKey, security,
|
||||
} = controllerOperations[operation];
|
||||
|
||||
const queryParams: any[] = []; // { name, type }
|
||||
const bodyParam: any[] = []; // { name, type }
|
||||
|
||||
let hasResponseBodyType: /* boolean | ReturnType<schemaParamParser> */ false | [string, boolean, boolean, boolean, boolean] = false;
|
||||
let contentType = '';
|
||||
if (parameters) {
|
||||
parameters.forEach((p: any) => {
|
||||
const [
|
||||
pType, isArray, isClass, isImport,
|
||||
] = schemaParamParser(p.schema, this.openapi);
|
||||
|
||||
if (isImport) {
|
||||
importEntities.push({ type: pType, isClass });
|
||||
}
|
||||
if (p.in === 'query') {
|
||||
queryParams.push({
|
||||
name: p.name, type: `${pType}${isArray ? '[]' : ''}`, hasQuestionToken: !p.required });
|
||||
}
|
||||
});
|
||||
}
|
||||
if (queryParams.length > 0) {
|
||||
const imp = apiFile.getImportDeclaration((i) => {
|
||||
return i.getModuleSpecifierValue() === 'qs';
|
||||
}); if (!imp) {
|
||||
apiFile.addImportDeclaration({
|
||||
moduleSpecifier: 'qs',
|
||||
defaultImport: 'qs',
|
||||
});
|
||||
}
|
||||
}
|
||||
if (requestBody) {
|
||||
let content = requestBody.content;
|
||||
const { $ref }: { $ref: string } = requestBody;
|
||||
|
||||
if (!content && $ref) {
|
||||
const name = $ref.split('/').pop() as string;
|
||||
content = this.openapi.components.requestBodies[name].content;
|
||||
}
|
||||
|
||||
[contentType] = Object.keys(content);
|
||||
const data = content[contentType];
|
||||
|
||||
const [
|
||||
pType, isArray, isClass, isImport,
|
||||
] = schemaParamParser(data.schema, this.openapi);
|
||||
|
||||
if (isImport) {
|
||||
importEntities.push({ type: pType, isClass });
|
||||
bodyParam.push({ name: pType.toLowerCase(), type: `${isClass ? 'I' : ''}${pType}${isArray ? '[]' : ''}`, isClass, pType });
|
||||
} else {
|
||||
bodyParam.push({ name: 'data', type: `${pType}${isArray ? '[]' : ''}` });
|
||||
|
||||
}
|
||||
}
|
||||
if (responses['200']) {
|
||||
const { content, headers } = responses['200'];
|
||||
if (content && (content['*/*'] || content['application/json'])) {
|
||||
const { schema, examples } = content['*/*'] || content['application/json'];
|
||||
|
||||
if (!schema) {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const propType = schemaParamParser(schema, this.openapi);
|
||||
const [pType, , isClass, isImport] = propType;
|
||||
|
||||
if (isImport) {
|
||||
importEntities.push({ type: pType, isClass });
|
||||
}
|
||||
hasResponseBodyType = propType;
|
||||
}
|
||||
}
|
||||
let returnType = '';
|
||||
if (hasResponseBodyType) {
|
||||
const [pType, isArray, isClass] = hasResponseBodyType as any;
|
||||
let data = `Promise<${isClass ? 'I' : ''}${pType}${isArray ? '[]' : ''}`;
|
||||
returnType = data;
|
||||
} else {
|
||||
returnType = 'Promise<number';
|
||||
}
|
||||
const shouldValidate = bodyParam.filter(b => b.isClass);
|
||||
if (shouldValidate.length > 0) {
|
||||
returnType += ' | string[]';
|
||||
}
|
||||
// append Error to default type return;
|
||||
returnType += ' | Error>';
|
||||
|
||||
const fetcher = apiClass.addMethod({
|
||||
isAsync: true,
|
||||
isStatic: true,
|
||||
name: operation,
|
||||
returnType,
|
||||
});
|
||||
const params = [...queryParams, ...bodyParam].sort((a, b) => (Number(!!a.hasQuestionToken) - Number(!!b.hasQuestionToken)));
|
||||
fetcher.addParameters(params);
|
||||
|
||||
fetcher.setBodyText((w) => {
|
||||
// Add data to URLSearchParams
|
||||
if (contentType === 'text/plain') {
|
||||
bodyParam.forEach((b) => {
|
||||
w.writeLine(`const params = String(${b.name});`);
|
||||
});
|
||||
} else {
|
||||
if (shouldValidate.length > 0) {
|
||||
w.writeLine(`const haveError: string[] = [];`);
|
||||
shouldValidate.forEach((b) => {
|
||||
w.writeLine(`const ${b.name}Valid = new ${b.pType}(${b.name});`);
|
||||
w.writeLine(`haveError.push(...${b.name}Valid.validate());`);
|
||||
});
|
||||
w.writeLine(`if (haveError.length > 0) {`);
|
||||
w.writeLine(` return Promise.resolve(haveError);`)
|
||||
w.writeLine(`}`);
|
||||
}
|
||||
}
|
||||
// Switch return of fetch in case on queryParams
|
||||
if (queryParams.length > 0) {
|
||||
w.writeLine('const queryParams = {');
|
||||
queryParams.forEach((q) => {
|
||||
w.writeLine(` ${q.name}: ${q.name},`);
|
||||
});
|
||||
w.writeLine('}');
|
||||
w.writeLine(`return await fetch(\`${this.serverUrl}${pathKey}?\${qs.stringify(queryParams, { arrayFormat: 'comma' })}\`, {`);
|
||||
} else {
|
||||
w.writeLine(`return await fetch(\`${this.serverUrl}${pathKey}\`, {`);
|
||||
}
|
||||
// Add method
|
||||
w.writeLine(` method: '${method.toUpperCase()}',`);
|
||||
|
||||
// add Fetch options
|
||||
if (contentType && contentType !== 'multipart/form-data') {
|
||||
w.writeLine(' headers: {');
|
||||
w.writeLine(` 'Content-Type': '${contentType}',`);
|
||||
w.writeLine(' },');
|
||||
}
|
||||
if (contentType) {
|
||||
switch (contentType) {
|
||||
case 'text/plain':
|
||||
w.writeLine(' body: params,');
|
||||
break;
|
||||
default:
|
||||
w.writeLine(` body: JSON.stringify(${bodyParam.map((b) => b.isClass ? `${b.name}Valid.serialize()` : b.name).join(', ')}),`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle response
|
||||
if (hasResponseBodyType) {
|
||||
w.writeLine('}).then(async (res) => {');
|
||||
w.writeLine(' if (res.status === 200) {');
|
||||
w.writeLine(' return res.json();');
|
||||
} else {
|
||||
w.writeLine('}).then(async (res) => {');
|
||||
w.writeLine(' if (res.status === 200) {');
|
||||
w.writeLine(' return res.status;');
|
||||
}
|
||||
|
||||
// Handle Error
|
||||
w.writeLine(' } else {');
|
||||
w.writeLine(' return new Error(String(res.status));');
|
||||
w.writeLine(' }');
|
||||
w.writeLine('})');
|
||||
});
|
||||
});
|
||||
|
||||
const imports: any[] = [];
|
||||
const types: string[] = [];
|
||||
importEntities.forEach((i) => {
|
||||
const { type } = i;
|
||||
if (!types.includes(type)) {
|
||||
imports.push(i);
|
||||
types.push(type);
|
||||
}
|
||||
});
|
||||
imports.sort((a,b) => a.type > b.type ? 1 : -1).forEach((ie) => {
|
||||
const { type: pType, isClass } = ie;
|
||||
if (isClass) {
|
||||
apiFile.addImportDeclaration({
|
||||
moduleSpecifier: `${GENERATOR_ENTITY_ALLIAS}${pType}`,
|
||||
defaultImport: pType,
|
||||
namedImports: [`I${pType}`],
|
||||
});
|
||||
} else {
|
||||
apiFile.addImportDeclaration({
|
||||
moduleSpecifier: `${GENERATOR_ENTITY_ALLIAS}${pType}`,
|
||||
namedImports: [pType],
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.apis.push(apiFile);
|
||||
};
|
||||
|
||||
save = () => {
|
||||
this.apis.forEach(async (e) => {
|
||||
await e.saveSync();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
export default ApiGenerator;
|
|
@ -1,603 +0,0 @@
|
|||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import * as morph from 'ts-morph';
|
||||
|
||||
import { ENT_DIR } from '../../consts';
|
||||
import { TYPES, toCamel, schemaParamParser, uncapitalize } from './utils';
|
||||
|
||||
const { Project, QuoteKind } = morph;
|
||||
|
||||
|
||||
const EntDir = path.resolve(ENT_DIR);
|
||||
if (!fs.existsSync(EntDir)) {
|
||||
fs.mkdirSync(EntDir);
|
||||
}
|
||||
|
||||
class EntitiesGenerator {
|
||||
project = new Project({
|
||||
tsConfigFilePath: './tsconfig.json',
|
||||
addFilesFromTsConfig: false,
|
||||
manipulationSettings: {
|
||||
quoteKind: QuoteKind.Single,
|
||||
usePrefixAndSuffixTextForRename: false,
|
||||
useTrailingCommas: true,
|
||||
},
|
||||
});
|
||||
|
||||
openapi: Record<string, any>;
|
||||
|
||||
schemas: Record<string, any>;
|
||||
|
||||
schemaNames: string[];
|
||||
|
||||
entities: morph.SourceFile[] = [];
|
||||
|
||||
constructor(openapi: Record<string, any>) {
|
||||
this.openapi = openapi;
|
||||
this.schemas = openapi.components.schemas;
|
||||
this.schemaNames = Object.keys(this.schemas);
|
||||
this.generateEntities();
|
||||
}
|
||||
|
||||
generateEntities = () => {
|
||||
this.schemaNames.forEach(this.generateEntity);
|
||||
};
|
||||
|
||||
generateEntity = (sName: string) => {
|
||||
const { properties, type, oneOf } = this.schemas[sName];
|
||||
const notAClass = !properties && TYPES[type as keyof typeof TYPES];
|
||||
|
||||
if (oneOf) {
|
||||
this.generateOneOf(sName);
|
||||
return;
|
||||
}
|
||||
|
||||
if (notAClass) {
|
||||
this.generateEnum(sName);
|
||||
} else {
|
||||
this.generateClass(sName);
|
||||
}
|
||||
};
|
||||
|
||||
generateEnum = (sName: string) => {
|
||||
const entityFile = this.project.createSourceFile(`${EntDir}/${sName}.ts`);
|
||||
entityFile.addStatements([
|
||||
'// This file was autogenerated. Please do not change.',
|
||||
'// All changes will be overwrited on commit.',
|
||||
'',
|
||||
]);
|
||||
|
||||
const { enum: enumMembers } = this.schemas[sName];
|
||||
entityFile.addEnum({
|
||||
name: sName,
|
||||
members: enumMembers.map((e: string) => ({ name: e.toUpperCase(), value: e })),
|
||||
isExported: true,
|
||||
});
|
||||
|
||||
this.entities.push(entityFile);
|
||||
};
|
||||
|
||||
generateOneOf = (sName: string) => {
|
||||
const entityFile = this.project.createSourceFile(`${EntDir}/${sName}.ts`);
|
||||
entityFile.addStatements([
|
||||
'// This file was autogenerated. Please do not change.',
|
||||
'// All changes will be overwrited on commit.',
|
||||
'',
|
||||
]);
|
||||
const importEntities: { type: string, isClass: boolean }[] = [];
|
||||
const entities = this.schemas[sName].oneOf.map((elem: any) => {
|
||||
const [
|
||||
pType, isArray, isClass, isImport,
|
||||
] = schemaParamParser(elem, this.openapi);
|
||||
importEntities.push({ type: pType, isClass });
|
||||
return { type: pType, isArray };
|
||||
});
|
||||
entityFile.addTypeAlias({
|
||||
name: sName,
|
||||
isExported: true,
|
||||
type: entities.map((e: any) => e.isArray ? `I${e.type}[]` : `I${e.type}`).join(' | '),
|
||||
})
|
||||
|
||||
// add import
|
||||
importEntities.sort((a, b) => a.type > b.type ? 1 : -1).forEach((ie) => {
|
||||
const { type: pType, isClass } = ie;
|
||||
if (isClass) {
|
||||
entityFile.addImportDeclaration({
|
||||
moduleSpecifier: `./${pType}`,
|
||||
namedImports: [`I${pType}`],
|
||||
});
|
||||
} else {
|
||||
entityFile.addImportDeclaration({
|
||||
moduleSpecifier: `./${pType}`,
|
||||
namedImports: [pType],
|
||||
});
|
||||
}
|
||||
});
|
||||
this.entities.push(entityFile);
|
||||
}
|
||||
|
||||
generateClass = (sName: string) => {
|
||||
const entityFile = this.project.createSourceFile(`${EntDir}/${sName}.ts`);
|
||||
entityFile.addStatements([
|
||||
'// This file was autogenerated. Please do not change.',
|
||||
'// All changes will be overwrited on commit.',
|
||||
'',
|
||||
]);
|
||||
|
||||
|
||||
const { properties: sProps, required, $ref, additionalProperties } = this.schemas[sName];
|
||||
if ($ref) {
|
||||
const temp = $ref.split('/');
|
||||
const importSchemaName = `${temp[temp.length - 1]}`;
|
||||
entityFile.addImportDeclaration({
|
||||
defaultImport: importSchemaName,
|
||||
moduleSpecifier: `./${importSchemaName}`,
|
||||
namedImports: [`I${importSchemaName}`],
|
||||
});
|
||||
|
||||
entityFile.addTypeAlias({
|
||||
name: `I${sName}`,
|
||||
type: `I${importSchemaName}`,
|
||||
isExported: true,
|
||||
})
|
||||
|
||||
entityFile.addStatements(`export default ${importSchemaName};`);
|
||||
this.entities.push(entityFile);
|
||||
return;
|
||||
}
|
||||
|
||||
const importEntities: { type: string, isClass: boolean }[] = [];
|
||||
const entityInterface = entityFile.addInterface({
|
||||
name: `I${sName}`,
|
||||
isExported: true,
|
||||
});
|
||||
|
||||
const sortedSProps = Object.keys(sProps || {}).sort();
|
||||
const additionalPropsOnly = additionalProperties && sortedSProps.length === 0;
|
||||
|
||||
// add server response interface to entityFile
|
||||
sortedSProps.forEach((sPropName) => {
|
||||
const [
|
||||
pType, isArray, isClass, isImport, isAdditional
|
||||
] = schemaParamParser(sProps[sPropName], this.openapi);
|
||||
|
||||
if (isImport) {
|
||||
importEntities.push({ type: pType, isClass });
|
||||
}
|
||||
const propertyType = isAdditional
|
||||
? `{ [key: string]: ${isClass ? 'I' : ''}${pType}${isArray ? '[]' : ''} }`
|
||||
: `${isClass ? 'I' : ''}${pType}${isArray ? '[]' : ''}`;
|
||||
entityInterface.addProperty({
|
||||
name: sPropName,
|
||||
type: propertyType,
|
||||
hasQuestionToken: !(
|
||||
(required && required.includes(sPropName)) || sProps[sPropName].required
|
||||
),
|
||||
});
|
||||
});
|
||||
if (additionalProperties) {
|
||||
const [
|
||||
pType, isArray, isClass, isImport, isAdditional
|
||||
] = schemaParamParser(additionalProperties, this.openapi);
|
||||
|
||||
if (isImport) {
|
||||
importEntities.push({ type: pType, isClass });
|
||||
}
|
||||
const type = isAdditional
|
||||
? `{ [key: string]: ${isClass ? 'I' : ''}${pType}${isArray ? '[]' : ''} }`
|
||||
: `${isClass ? 'I' : ''}${pType}${isArray ? '[]' : ''}`;
|
||||
entityInterface.addIndexSignature({
|
||||
keyName: 'key',
|
||||
keyType: 'string',
|
||||
returnType: additionalPropsOnly ? type : `${type} | undefined`,
|
||||
});
|
||||
}
|
||||
|
||||
// add import
|
||||
const imports: { type: string, isClass: boolean }[] = [];
|
||||
const types: string[] = [];
|
||||
importEntities.forEach((i) => {
|
||||
const { type } = i;
|
||||
if (!types.includes(type)) {
|
||||
imports.push(i);
|
||||
types.push(type);
|
||||
}
|
||||
});
|
||||
imports.sort((a, b) => a.type > b.type ? 1 : -1).forEach((ie) => {
|
||||
const { type: pType, isClass } = ie;
|
||||
if (isClass) {
|
||||
entityFile.addImportDeclaration({
|
||||
defaultImport: pType,
|
||||
moduleSpecifier: `./${pType}`,
|
||||
namedImports: [`I${pType}`],
|
||||
});
|
||||
} else {
|
||||
entityFile.addImportDeclaration({
|
||||
moduleSpecifier: `./${pType}`,
|
||||
namedImports: [pType],
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const entityClass = entityFile.addClass({
|
||||
name: sName,
|
||||
isDefaultExport: true,
|
||||
});
|
||||
|
||||
// addProperties to class;
|
||||
sortedSProps.forEach((sPropName) => {
|
||||
const [pType, isArray, isClass, isImport, isAdditional] = schemaParamParser(sProps[sPropName], this.openapi);
|
||||
|
||||
const isRequred = (required && required.includes(sPropName))
|
||||
|| sProps[sPropName].required;
|
||||
|
||||
const propertyType = isAdditional
|
||||
? `{ [key: string]: ${pType}${isArray ? '[]' : ''}${isRequred ? '' : ' | undefined'} }`
|
||||
: `${pType}${isArray ? '[]' : ''}${isRequred ? '' : ' | undefined'}`;
|
||||
|
||||
entityClass.addProperty({
|
||||
name: `_${sPropName}`,
|
||||
isReadonly: true,
|
||||
type: propertyType,
|
||||
});
|
||||
const getter = entityClass.addGetAccessor({
|
||||
name: toCamel(sPropName),
|
||||
returnType: propertyType,
|
||||
statements: [`return this._${sPropName};`],
|
||||
});
|
||||
const { description, example, minItems, maxItems, maxLength, minLength, maximum, minimum } = sProps[sPropName];
|
||||
if (description || example) {
|
||||
getter.addJsDoc(`${example ? `Description: ${description}` : ''}${example ? `\nExample: ${example}` : ''}`);
|
||||
}
|
||||
if (minItems) {
|
||||
entityClass.addGetAccessor({
|
||||
isStatic: true,
|
||||
name: `${toCamel(sPropName)}MinItems`,
|
||||
statements: [`return ${minItems};`],
|
||||
});
|
||||
}
|
||||
if (maxItems) {
|
||||
entityClass.addGetAccessor({
|
||||
isStatic: true,
|
||||
name: `${toCamel(sPropName)}MaxItems`,
|
||||
statements: [`return ${maxItems};`],
|
||||
});
|
||||
}
|
||||
if (typeof minLength === 'number') {
|
||||
entityClass.addGetAccessor({
|
||||
isStatic: true,
|
||||
name: `${toCamel(sPropName)}MinLength`,
|
||||
statements: [`return ${minLength};`],
|
||||
});
|
||||
}
|
||||
if (maxLength) {
|
||||
entityClass.addGetAccessor({
|
||||
isStatic: true,
|
||||
name: `${toCamel(sPropName)}MaxLength`,
|
||||
statements: [`return ${maxLength};`],
|
||||
});
|
||||
}
|
||||
if (typeof minimum === 'number') {
|
||||
entityClass.addGetAccessor({
|
||||
isStatic: true,
|
||||
name: `${toCamel(sPropName)}MinValue`,
|
||||
statements: [`return ${minimum};`],
|
||||
});
|
||||
}
|
||||
if (maximum) {
|
||||
entityClass.addGetAccessor({
|
||||
isStatic: true,
|
||||
name: `${toCamel(sPropName)}MaxValue`,
|
||||
statements: [`return ${maximum};`],
|
||||
});
|
||||
}
|
||||
|
||||
if (!(isArray && isClass) && !isClass) {
|
||||
const isEnum = !isClass && isImport;
|
||||
const isRequired = (required && required.includes(sPropName)) || sProps[sPropName].required;
|
||||
const { maxLength, minLength, maximum, minimum } = sProps[sPropName];
|
||||
const haveValidationFields = maxLength || typeof minLength === 'number' || maximum || typeof minimum === 'number';
|
||||
if (isRequired || haveValidationFields) {
|
||||
const prop = toCamel(sPropName);
|
||||
const validateField = entityClass.addMethod({
|
||||
isStatic: true,
|
||||
name: `${prop}Validate`,
|
||||
returnType: `boolean`,
|
||||
parameters: [{
|
||||
name: prop,
|
||||
type: `${pType}${isArray ? '[]' : ''}${isRequred ? '' : ' | undefined'}`,
|
||||
}],
|
||||
})
|
||||
|
||||
validateField.setBodyText((w) => {
|
||||
w.write('return ');
|
||||
const nonRequiredCall = isRequired ? prop : `!${prop} ? true : ${prop}`;
|
||||
if (pType === 'string') {
|
||||
if (isArray) {
|
||||
w.write(`${nonRequiredCall}.reduce<boolean>((result, p) => result && (typeof p === 'string' && !!p.trim()), true)`);
|
||||
} else {
|
||||
if (typeof minLength === 'number' && maxLength) {
|
||||
w.write(`(${nonRequiredCall}.length >${minLength > 0 ? '=' : ''} ${minLength}) && (${nonRequiredCall}.length <= ${maxLength})`);
|
||||
}
|
||||
if (typeof minLength !== 'number' || !maxLength) {
|
||||
w.write(`${isRequired ? `typeof ${prop} === 'string'` : `!${prop} ? true : typeof ${prop} === 'string'`} && !!${nonRequiredCall}.trim()`);
|
||||
}
|
||||
}
|
||||
} else if (pType === 'number') {
|
||||
if (isArray) {
|
||||
w.write(`${nonRequiredCall}.reduce<boolean>((result, p) => result && typeof p === 'number', true)`);
|
||||
} else {
|
||||
if (typeof minimum === 'number' && maximum) {
|
||||
w.write(`${isRequired ? `${prop} >= ${minimum} && ${prop} <= ${maximum}` : `!${prop} ? true : ((${prop} >= ${minimum}) && (${prop} <= ${maximum}))`}`);
|
||||
}
|
||||
if (typeof minimum !== 'number' || !maximum) {
|
||||
w.write(`${isRequired ? `typeof ${prop} === 'number'` : `!${prop} ? true : typeof ${prop} === 'number'`}`);
|
||||
}
|
||||
}
|
||||
} else if (pType === 'boolean') {
|
||||
w.write(`${isRequired ? `typeof ${prop} === 'boolean'` : `!${prop} ? true : typeof ${prop} === 'boolean'`}`);
|
||||
} else if (isEnum) {
|
||||
if (isArray){
|
||||
w.write(`${nonRequiredCall}.reduce<boolean>((result, p) => result && Object.keys(${pType}).includes(${prop}), true)`);
|
||||
} else {
|
||||
w.write(`${isRequired ? `Object.keys(${pType}).includes(${prop})` : `!${prop} ? true : typeof ${prop} === 'boolean'`}`);
|
||||
}
|
||||
}
|
||||
|
||||
w.write(';');
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
if (additionalProperties) {
|
||||
const [
|
||||
pType, isArray, isClass, isImport, isAdditional
|
||||
] = schemaParamParser(additionalProperties, this.openapi);
|
||||
const type = `Record<string, ${pType}${isArray ? '[]' : ''}>`;
|
||||
|
||||
entityClass.addProperty({
|
||||
name: additionalPropsOnly ? 'data' : `${uncapitalize(pType)}Data`,
|
||||
isReadonly: true,
|
||||
type: type,
|
||||
});
|
||||
}
|
||||
// add constructor;
|
||||
const ctor = entityClass.addConstructor({
|
||||
parameters: [{
|
||||
name: 'props',
|
||||
type: `I${sName}`,
|
||||
}],
|
||||
});
|
||||
ctor.setBodyText((w) => {
|
||||
if (additionalProperties) {
|
||||
const [
|
||||
pType, isArray, isClass, isImport, isAdditional
|
||||
] = schemaParamParser(additionalProperties, this.openapi);
|
||||
w.writeLine(`this.${additionalPropsOnly ? 'data' : `${uncapitalize(pType)}Data`} = Object.entries(props).reduce<Record<string, ${pType}>>((prev, [key, value]) => {`);
|
||||
if (isClass) {
|
||||
w.writeLine(` prev[key] = new ${pType}(value!);`);
|
||||
} else {
|
||||
w.writeLine(' prev[key] = value!;')
|
||||
}
|
||||
w.writeLine(' return prev;');
|
||||
w.writeLine('}, {})');
|
||||
return;
|
||||
}
|
||||
sortedSProps.forEach((sPropName) => {
|
||||
const [
|
||||
pType, isArray, isClass, , isAdditional
|
||||
] = schemaParamParser(sProps[sPropName], this.openapi);
|
||||
const req = (required && required.includes(sPropName))
|
||||
|| sProps[sPropName].required;
|
||||
if (!req) {
|
||||
if ((pType === 'boolean' || pType === 'number' || pType ==='string') && !isClass && !isArray) {
|
||||
w.writeLine(`if (typeof props.${sPropName} === '${pType}') {`);
|
||||
} else {
|
||||
w.writeLine(`if (props.${sPropName}) {`);
|
||||
}
|
||||
}
|
||||
if (isAdditional) {
|
||||
if (isArray && isClass) {
|
||||
w.writeLine(`${!req ? ' ' : ''}this._${sPropName} = props.${sPropName}.map((p) => Object.keys(p).reduce((prev, key) => {
|
||||
return { ...prev, [key]: new ${pType}(p[key])};
|
||||
},{}))`);
|
||||
} else if (isClass) {
|
||||
w.writeLine(`${!req ? ' ' : ''}this._${sPropName} = Object.keys(props.${sPropName}).reduce((prev, key) => {
|
||||
return { ...prev, [key]: new ${pType}(props.${sPropName}[key])};
|
||||
},{})`);
|
||||
} else {
|
||||
if (pType === 'string' && !isArray) {
|
||||
w.writeLine(`${!req ? ' ' : ''}this._${sPropName} = Object.keys(props.${sPropName}).reduce((prev, key) => {
|
||||
return { ...prev, [key]: props.${sPropName}[key].trim()};
|
||||
},{})`);
|
||||
} else {
|
||||
w.writeLine(`${!req ? ' ' : ''}this._${sPropName} = Object.keys(props.${sPropName}).reduce((prev, key) => {
|
||||
return { ...prev, [key]: props.${sPropName}[key]};
|
||||
},{})`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (isArray && isClass) {
|
||||
w.writeLine(`${!req ? ' ' : ''}this._${sPropName} = props.${sPropName}.map((p) => new ${pType}(p));`);
|
||||
} else if (isClass) {
|
||||
w.writeLine(`${!req ? ' ' : ''}this._${sPropName} = new ${pType}(props.${sPropName});`);
|
||||
} else {
|
||||
if (pType === 'string' && !isArray) {
|
||||
w.writeLine(`${!req ? ' ' : ''}this._${sPropName} = props.${sPropName}.trim();`);
|
||||
} else {
|
||||
w.writeLine(`${!req ? ' ' : ''}this._${sPropName} = props.${sPropName};`);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!req) {
|
||||
w.writeLine('}');
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// add serialize method;
|
||||
const serialize = entityClass.addMethod({
|
||||
isStatic: false,
|
||||
name: 'serialize',
|
||||
returnType: `I${sName}`,
|
||||
});
|
||||
serialize.setBodyText((w) => {
|
||||
if (additionalProperties) {
|
||||
const [
|
||||
pType, isArray, isClass, isImport, isAdditional
|
||||
] = schemaParamParser(additionalProperties, this.openapi);
|
||||
w.writeLine(`return Object.entries(this.${additionalPropsOnly ? 'data' : `${uncapitalize(pType)}Data`}).reduce<Record<string, ${isClass ? 'I' : ''}${pType}>>((prev, [key, value]) => {`);
|
||||
if (isClass) {
|
||||
w.writeLine(` prev[key] = value.serialize();`);
|
||||
} else {
|
||||
w.writeLine(' prev[key] = value;')
|
||||
}
|
||||
w.writeLine(' return prev;');
|
||||
w.writeLine('}, {})');
|
||||
return;
|
||||
}
|
||||
w.writeLine(`const data: I${sName} = {`);
|
||||
const unReqFields: string[] = [];
|
||||
sortedSProps.forEach((sPropName) => {
|
||||
const req = (required && required.includes(sPropName))
|
||||
|| sProps[sPropName].required;
|
||||
const [, isArray, isClass, , isAdditional] = schemaParamParser(sProps[sPropName], this.openapi);
|
||||
if (!req) {
|
||||
unReqFields.push(sPropName);
|
||||
return;
|
||||
}
|
||||
if (isAdditional) {
|
||||
if (isArray && isClass) {
|
||||
w.writeLine(` ${sPropName}: this._${sPropName}.map((p) => Object.keys(p).reduce((prev, key) => ({ ...prev, [key]: p[key].serialize() }))),`);
|
||||
} else if (isClass) {
|
||||
w.writeLine(` ${sPropName}: Object.keys(this._${sPropName}).reduce<Record<string, any>>((prev, key) => ({ ...prev, [key]: this._${sPropName}[key].serialize() }), {}),`);
|
||||
} else {
|
||||
w.writeLine(` ${sPropName}: Object.keys(this._${sPropName}).reduce((prev, key) => ({ ...prev, [key]: this._${sPropName}[key] })),`);
|
||||
}
|
||||
} else {
|
||||
if (isArray && isClass) {
|
||||
w.writeLine(` ${sPropName}: this._${sPropName}.map((p) => p.serialize()),`);
|
||||
} else if (isClass) {
|
||||
w.writeLine(` ${sPropName}: this._${sPropName}.serialize(),`);
|
||||
} else {
|
||||
w.writeLine(` ${sPropName}: this._${sPropName},`);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
w.writeLine('};');
|
||||
unReqFields.forEach((sPropName) => {
|
||||
const [, isArray, isClass, , isAdditional] = schemaParamParser(sProps[sPropName], this.openapi);
|
||||
w.writeLine(`if (typeof this._${sPropName} !== 'undefined') {`);
|
||||
if (isAdditional) {
|
||||
if (isArray && isClass) {
|
||||
w.writeLine(` data.${sPropName} = this._${sPropName}.map((p) => Object.keys(p).reduce((prev, key) => ({ ...prev, [key]: p[key].serialize() }), {}));`);
|
||||
} else if (isClass) {
|
||||
w.writeLine(` data.${sPropName} = Object.keys(this._${sPropName}).reduce((prev, key) => ({ ...prev, [key]: this._${sPropName}[key].serialize() }), {});`);
|
||||
} else {
|
||||
w.writeLine(` data.${sPropName} = Object.keys(this._${sPropName}).reduce((prev, key) => ({ ...prev, [key]: this._${sPropName}[key] }), {});`);
|
||||
}
|
||||
} else {
|
||||
if (isArray && isClass) {
|
||||
w.writeLine(` data.${sPropName} = this._${sPropName}.map((p) => p.serialize());`);
|
||||
} else if (isClass) {
|
||||
w.writeLine(` data.${sPropName} = this._${sPropName}.serialize();`);
|
||||
} else {
|
||||
w.writeLine(` data.${sPropName} = this._${sPropName};`);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
w.writeLine(`}`);
|
||||
});
|
||||
w.writeLine('return data;');
|
||||
});
|
||||
|
||||
// add validate method
|
||||
const validate = entityClass.addMethod({
|
||||
isStatic: false,
|
||||
name: 'validate',
|
||||
returnType: `string[]`,
|
||||
})
|
||||
validate.setBodyText((w) => {
|
||||
if (additionalPropsOnly) {
|
||||
w.writeLine('return []')
|
||||
return;
|
||||
}
|
||||
w.writeLine('const validate = {');
|
||||
Object.keys(sProps || {}).forEach((sPropName) => {
|
||||
const [pType, isArray, isClass, , isAdditional] = schemaParamParser(sProps[sPropName], this.openapi);
|
||||
|
||||
const { maxLength, minLength, maximum, minimum } = sProps[sPropName];
|
||||
|
||||
const isRequired = (required && required.includes(sPropName)) || sProps[sPropName].required;
|
||||
const nonRequiredCall = isRequired ? `this._${sPropName}` : `!this._${sPropName} ? true : this._${sPropName}`;
|
||||
|
||||
if (isArray && isClass) {
|
||||
w.writeLine(` ${sPropName}: ${nonRequiredCall}.reduce((result, p) => result && p.validate().length === 0, true),`);
|
||||
} else if (isClass && !isAdditional) {
|
||||
w.writeLine(` ${sPropName}: ${nonRequiredCall}.validate().length === 0,`);
|
||||
} else {
|
||||
if (pType === 'string') {
|
||||
if (isArray) {
|
||||
w.writeLine(` ${sPropName}: ${nonRequiredCall}.reduce((result, p) => result && typeof p === 'string', true),`);
|
||||
} else {
|
||||
if (typeof minLength === 'number' && maxLength) {
|
||||
w.writeLine(` ${sPropName}: (${nonRequiredCall}.length >${minLength > 0 ? '=' : ''} ${minLength}) && (${nonRequiredCall}.length <= ${maxLength}),`);
|
||||
}
|
||||
if (typeof minLength !== 'number' || !maxLength) {
|
||||
w.writeLine(` ${sPropName}: ${isRequired ? `typeof this._${sPropName} === 'string'` : `!this._${sPropName} ? true : typeof this._${sPropName} === 'string'`} && !this._${sPropName} ? true : this._${sPropName},`);
|
||||
}
|
||||
}
|
||||
} else if (pType === 'number') {
|
||||
if (isArray) {
|
||||
w.writeLine(` ${sPropName}: ${nonRequiredCall}.reduce((result, p) => result && typeof p === 'number', true),`);
|
||||
} else {
|
||||
if (typeof minimum === 'number' && maximum) {
|
||||
w.writeLine(` ${sPropName}: ${isRequired ? `this._${sPropName} >= ${minimum} && this._${sPropName} <= ${maximum}` : `!this._${sPropName} ? true : ((this._${sPropName} >= ${minimum}) && (this._${sPropName} <= ${maximum}))`},`);
|
||||
}
|
||||
if (typeof minimum !== 'number' || !maximum) {
|
||||
w.writeLine(` ${sPropName}: ${isRequired ? `typeof this._${sPropName} === 'number'` : `!this._${sPropName} ? true : typeof this._${sPropName} === 'number'`},`);
|
||||
}
|
||||
}
|
||||
} else if (pType === 'boolean') {
|
||||
w.writeLine(` ${sPropName}: ${isRequired ? `typeof this._${sPropName} === 'boolean'` : `!this._${sPropName} ? true : typeof this._${sPropName} === 'boolean'`},`);
|
||||
}
|
||||
}
|
||||
});
|
||||
w.writeLine('};');
|
||||
w.writeLine('const isError: string[] = [];')
|
||||
w.writeLine('Object.keys(validate).forEach((key) => {');
|
||||
w.writeLine(' if (!(validate as any)[key]) {');
|
||||
w.writeLine(' isError.push(key);');
|
||||
w.writeLine(' }');
|
||||
w.writeLine('});');
|
||||
w.writeLine('return isError;');
|
||||
|
||||
});
|
||||
|
||||
// add update method;
|
||||
const update = entityClass.addMethod({
|
||||
isStatic: false,
|
||||
name: 'update',
|
||||
returnType: `${sName}`,
|
||||
});
|
||||
update.addParameter({
|
||||
name: 'props',
|
||||
type: additionalPropsOnly ? `I${sName}` : `Partial<I${sName}>`,
|
||||
});
|
||||
update.setBodyText((w) => { w.writeLine(`return new ${sName}({ ...this.serialize(), ...props });`); });
|
||||
|
||||
this.entities.push(entityFile);
|
||||
};
|
||||
|
||||
save = () => {
|
||||
this.entities.forEach(async (e) => {
|
||||
await e.saveSync();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export default EntitiesGenerator;
|
|
@ -1,83 +0,0 @@
|
|||
const toCamel = (s: string) => {
|
||||
return s.replace(/([-_][a-z])/ig, ($1) => {
|
||||
return $1.toUpperCase()
|
||||
.replace('-', '')
|
||||
.replace('_', '');
|
||||
});
|
||||
};
|
||||
const capitalize = (s: string) => {
|
||||
return s[0].toUpperCase() + s.slice(1);
|
||||
};
|
||||
const uncapitalize = (s: string) => {
|
||||
return s[0].toLowerCase() + s.slice(1);
|
||||
};
|
||||
const TYPES = {
|
||||
integer: 'number',
|
||||
float: 'number',
|
||||
number: 'number',
|
||||
string: 'string',
|
||||
boolean: 'boolean',
|
||||
};
|
||||
|
||||
/**
|
||||
* @param schemaProp: valueof shema.properties[key]
|
||||
* @param openApi: openapi object
|
||||
* @returns [propType - basicType or import one, isArray, isClass, isImport]
|
||||
*/
|
||||
const schemaParamParser = (schemaProp: any, openApi: any): [string, boolean, boolean, boolean, boolean] => {
|
||||
let type = '';
|
||||
let isImport = false;
|
||||
let isClass = false;
|
||||
let isArray = false;
|
||||
let isAdditional = false;
|
||||
|
||||
if (schemaProp.$ref || schemaProp.additionalProperties?.$ref) {
|
||||
const temp = (schemaProp.$ref || schemaProp.additionalProperties?.$ref).split('/');
|
||||
|
||||
if (schemaProp.additionalProperties) {
|
||||
isAdditional = true;
|
||||
}
|
||||
|
||||
type = `${temp[temp.length - 1]}`;
|
||||
|
||||
const cl = openApi ? openApi.components.schemas[type] : {};
|
||||
|
||||
if (cl.$ref) {
|
||||
const link = schemaParamParser(cl, openApi);
|
||||
link.shift();
|
||||
return [type, ...link] as any;
|
||||
}
|
||||
|
||||
if (cl.type === 'string' && cl.enum) {
|
||||
isImport = true;
|
||||
}
|
||||
|
||||
if (cl.type === 'object' && !cl.oneOf) {
|
||||
isClass = true;
|
||||
isImport = true;
|
||||
} else if (cl.type === 'array') {
|
||||
const temp: any = schemaParamParser(cl.items, openApi);
|
||||
type = `${temp[0]}`;
|
||||
isArray = true;
|
||||
isClass = isClass || temp[2];
|
||||
isImport = isImport || temp[3];
|
||||
}
|
||||
} else if (schemaProp.type === 'array') {
|
||||
const temp: any = schemaParamParser(schemaProp.items, openApi);
|
||||
type = `${temp[0]}`;
|
||||
isArray = true;
|
||||
isClass = isClass || temp[2];
|
||||
isImport = isImport || temp[3];
|
||||
} else {
|
||||
type = (TYPES as Record<any, string>)[schemaProp.type];
|
||||
}
|
||||
if (!type) {
|
||||
// TODO: Fix bug with Error fields.
|
||||
type = 'any';
|
||||
// throw new Error('Failed to find entity type');
|
||||
}
|
||||
|
||||
return [type, isArray, isClass, isImport, isAdditional];
|
||||
};
|
||||
|
||||
export { TYPES, toCamel, capitalize, uncapitalize, schemaParamParser };
|
|
@ -1,226 +0,0 @@
|
|||
import * as fs from 'fs';
|
||||
import {
|
||||
Project,
|
||||
VariableStatement,
|
||||
SyntaxKind,
|
||||
Node,
|
||||
Statement,
|
||||
ts,
|
||||
Identifier,
|
||||
SourceFile,
|
||||
} from 'ts-morph';
|
||||
import {
|
||||
LOCALE_FOLDER_PATH,
|
||||
TRANSLATOR_CLASS_NAME,
|
||||
USE_INTL_NAME,
|
||||
trimQuotes,
|
||||
} from '../consts';
|
||||
import { checkForms, AvailableLocales } from '../../src/localization/Translator';
|
||||
|
||||
const project = new Project({
|
||||
tsConfigFilePath: './tsconfig.json',
|
||||
});
|
||||
|
||||
let lang = 'ru';
|
||||
let option = '';
|
||||
|
||||
if (process.argv.length > 2) {
|
||||
lang = process.argv[2];
|
||||
option = process.argv[3];
|
||||
}
|
||||
|
||||
const usedTranslations: string[] = [];
|
||||
const usedPluralTranslations: string[] = [];
|
||||
|
||||
const problemFiles: string[] = [];
|
||||
const sourceFiles = project.getSourceFiles();
|
||||
const sourceFilesWithIntl = sourceFiles.filter((sf) => {
|
||||
return !!sf.getImportDeclarations().find((id) => {
|
||||
return !!id.getNamedImports().find((ni) => ni.getName() === USE_INTL_NAME)
|
||||
})
|
||||
});
|
||||
const getFileUsedIntl = (statements: Statement<ts.Statement>[]) => {
|
||||
statements.forEach((s) => {
|
||||
if (s instanceof VariableStatement) {
|
||||
s.forEachDescendant((node) => {
|
||||
let intVariableDeclaration: Identifier = null;
|
||||
switch (node.getKind()) {
|
||||
case SyntaxKind.VariableDeclaration:
|
||||
if (node.getSymbol()) {
|
||||
const name = node.getSymbol().getName();
|
||||
const callExp = node.getChildren().find((n) => n.getKind() === SyntaxKind.CallExpression);
|
||||
if (callExp) {
|
||||
const callExpIden = callExp.getChildren().find(n => n.getKind() === SyntaxKind.Identifier);
|
||||
if (callExpIden && callExpIden.getSymbol().getName() === USE_INTL_NAME) {
|
||||
intVariableDeclaration = node as Identifier;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (intVariableDeclaration) {
|
||||
intVariableDeclaration.findReferencesAsNodes().forEach((fr) => {
|
||||
if (fr instanceof Node) {
|
||||
const parent = fr.getParentIfKind(SyntaxKind.PropertyAccessExpression);
|
||||
if (parent && (parent.getName() === 'getMessage' || parent.getName() === 'getPlural')) {
|
||||
const syntaxList = parent.getNextSiblings().find((n) => n.getKind() === SyntaxKind.SyntaxList);
|
||||
if (syntaxList) {
|
||||
const id = syntaxList.getChildren()[0];
|
||||
if (id && id.getKind() !== SyntaxKind.StringLiteral) {
|
||||
problemFiles.push(fr.getSourceFile().getFilePath());
|
||||
}
|
||||
if (id) {
|
||||
usedTranslations.push(trimQuotes(id.getText()));
|
||||
if (parent.getName() === 'getPlural') {
|
||||
usedPluralTranslations.push(trimQuotes(id.getText()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const getFileUsedTranslations = (file: SourceFile) => {
|
||||
const namedImport = file.getImportDeclarations().find((id) => !!id.getNamedImports().find((ni) => ni.getName() === TRANSLATOR_CLASS_NAME));
|
||||
if (namedImport) {
|
||||
const identifier = namedImport.getImportClause().getNamedImports().find((iden) => iden.getName() === TRANSLATOR_CLASS_NAME);
|
||||
const translateReferences = identifier.getNodeProperty('name').findReferencesAsNodes();
|
||||
if (translateReferences.length > 0) {
|
||||
translateReferences.forEach((identifierNode) => {
|
||||
if (identifierNode.getParentIfKind(SyntaxKind.TypeReference)) {
|
||||
const translatorVariable = identifierNode.getParent().getPreviousSibling().getPreviousSiblingIfKind(SyntaxKind.Identifier);
|
||||
if (translatorVariable) {
|
||||
translatorVariable.findReferencesAsNodes().forEach((node) => {
|
||||
const parent = node.getParentIfKind(SyntaxKind.PropertyAccessExpression);
|
||||
if (parent && (parent.getName() === 'getMessage' || parent.getName() === 'getPlural')) {
|
||||
|
||||
const syntaxList = parent.getNextSiblings().find((n) => n.getKind() === SyntaxKind.SyntaxList);
|
||||
if (syntaxList) {
|
||||
const id = syntaxList.getChildren()[0];
|
||||
if (id && id.getKind() !== SyntaxKind.StringLiteral) {
|
||||
problemFiles.push(parent.getSourceFile().getFilePath());
|
||||
}
|
||||
if (id) {
|
||||
usedTranslations.push(trimQuotes(id.getText()));
|
||||
if (parent.getName() === 'getPlural') {
|
||||
usedPluralTranslations.push(trimQuotes(id.getText()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
sourceFilesWithIntl.forEach((file) => {
|
||||
getFileUsedIntl(file.getStatements());
|
||||
})
|
||||
|
||||
const sourceFilesWithTranslator = project.getSourceFiles().filter((sf) => {
|
||||
return !!sf.getImportDeclarations().find((id) => {
|
||||
return !!id.getNamedImports().find((ni) => ni.getName() === TRANSLATOR_CLASS_NAME)
|
||||
})
|
||||
});
|
||||
sourceFilesWithTranslator.forEach((file) => {
|
||||
getFileUsedTranslations(file);
|
||||
})
|
||||
const filteredUsedTranslations = Array.from(new Set(usedTranslations));
|
||||
const filteredUsedPluralTranslations = Array.from(new Set(usedPluralTranslations));
|
||||
|
||||
if (problemFiles.length) {
|
||||
console.warn(`\n============== Files where translation id provided not as string ==============\n`);
|
||||
console.log(problemFiles.join('\n'));
|
||||
process.exit(255);
|
||||
}
|
||||
|
||||
const allFiles = fs.readdirSync(LOCALE_FOLDER_PATH);
|
||||
// Use ru or needed language
|
||||
const translationFile = allFiles.find((file) => file.includes(`${lang}.json`));
|
||||
|
||||
if (!translationFile) {
|
||||
console.error('File not found');
|
||||
process.exit(255);
|
||||
}
|
||||
|
||||
const translationsObject = JSON.parse(fs.readFileSync(`./src/lib/intl/__locales/${translationFile}`, { flag: 'r+' }) as unknown as string);
|
||||
const translations = {
|
||||
locale: translationFile,
|
||||
messages: Object.keys(translationsObject),
|
||||
};
|
||||
|
||||
const someMessagesNotFound: string[] = [];
|
||||
const notUsed: string[] = [];
|
||||
const notFound: string[] = [];
|
||||
const checkLocaleMessages = (locale: string, messages: string[]) => {
|
||||
filteredUsedTranslations.forEach(f => {
|
||||
if (!messages.includes(f)) {
|
||||
notFound.push(f);
|
||||
}
|
||||
});
|
||||
messages.forEach(t => {
|
||||
if (!filteredUsedTranslations.includes(t)) {
|
||||
notUsed.push(t);
|
||||
}
|
||||
});
|
||||
if (notFound.length > 0) {
|
||||
someMessagesNotFound.push(locale);
|
||||
}
|
||||
}
|
||||
|
||||
const render = (data: string[], title: string) => {
|
||||
console.log(`============ ${title} ============`);
|
||||
console.table(data);
|
||||
console.log(`============ ${title} ============`);
|
||||
}
|
||||
|
||||
checkLocaleMessages(translations.locale, translations.messages);
|
||||
|
||||
const checkPluralForm = () => {
|
||||
const pluralFormWrong: string[] = [];
|
||||
filteredUsedPluralTranslations.forEach((id) => {
|
||||
const message = translationsObject[id];
|
||||
if (!checkForms(message, lang as AvailableLocales, id)) {
|
||||
pluralFormWrong.push(id)
|
||||
}
|
||||
});
|
||||
return pluralFormWrong;
|
||||
}
|
||||
|
||||
const plural = checkPluralForm();
|
||||
if (!option && (someMessagesNotFound.length || plural.length > 0 )) {
|
||||
someMessagesNotFound.forEach(locale => console.error(`\nSome translatins for ${locale} was not found!\n`));
|
||||
plural.forEach(id => console.error(`\nTranslation with id: "${id}" - have wrong number of plural forms!\n`));
|
||||
process.exit(255);
|
||||
}
|
||||
if (option) {
|
||||
switch (option) {
|
||||
case '--show-missing': {
|
||||
render(notFound, 'NotFound')
|
||||
break;
|
||||
}
|
||||
case '--show-unused': {
|
||||
render(notUsed, 'notUsed')
|
||||
break;
|
||||
}
|
||||
case '--check-plurals': {
|
||||
render(plural, 'Wrong Plural Form')
|
||||
}
|
||||
default: {
|
||||
if (someMessagesNotFound.length) {
|
||||
someMessagesNotFound.forEach(locale => console.error(`\nSome translatins for ${locale} was not found!\n\n`));
|
||||
process.exit(255);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,79 +0,0 @@
|
|||
module.exports = {
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
project: './tsconfig.json',
|
||||
ecmaFeatures: {
|
||||
jsx: true
|
||||
},
|
||||
extraFileExtensions: ['mjs', 'tsx', 'ts'],
|
||||
ecmaVersion: 2020,
|
||||
sourceType: 'module'
|
||||
},
|
||||
plugins: ['react', '@typescript-eslint', 'import'],
|
||||
env: {
|
||||
browser: true,
|
||||
commonjs: true,
|
||||
es6: true,
|
||||
es2020: true,
|
||||
jest: true,
|
||||
},
|
||||
settings: {
|
||||
react: {
|
||||
pragma: 'React',
|
||||
version: 'detect',
|
||||
},
|
||||
'import/resolver': {
|
||||
typescript: {
|
||||
alwaysTryTypes: true
|
||||
}
|
||||
},
|
||||
'import/parsers': {
|
||||
'@typescript-eslint/parser': ['.ts', '.tsx'],
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
'@typescript-eslint/explicit-module-boundary-types': 0,
|
||||
'@typescript-eslint/explicit-function-return-type': [0, { allowExpressions: true }],
|
||||
'@typescript-eslint/indent': ['error', 4],
|
||||
'@typescript-eslint/interface-name-prefix': [0, { prefixWithI: 'never' }],
|
||||
'@typescript-eslint/no-explicit-any': [0],
|
||||
'@typescript-eslint/naming-convention': [2, {
|
||||
selector: 'enum', format: ['UPPER_CASE', 'PascalCase'],
|
||||
}],
|
||||
'@typescript-eslint/no-non-null-assertion': 0,
|
||||
'arrow-body-style': 'off',
|
||||
'consistent-return': 0,
|
||||
curly: [2, 'all'],
|
||||
'default-case': 0,
|
||||
'import/no-cycle': 0,
|
||||
'import/prefer-default-export': 'off',
|
||||
'import/no-named-as-default': 0,
|
||||
indent: [0, 4],
|
||||
'no-alert': 2,
|
||||
'no-console': 2,
|
||||
'no-debugger': 2,
|
||||
'no-underscore-dangle': 'off',
|
||||
'no-useless-escape': 'off',
|
||||
'object-curly-newline': 'off',
|
||||
'react-hooks/exhaustive-deps': 0,
|
||||
'react/display-name': 0,
|
||||
'react/jsx-indent-props': ['error', 4],
|
||||
'react/jsx-indent': ['error', 4],
|
||||
'react/jsx-one-expression-per-line': 'off',
|
||||
'react/jsx-props-no-spreading': 0,
|
||||
'react/prop-types': 'off',
|
||||
'react/state-in-constructor': 'off',
|
||||
},
|
||||
extends: [
|
||||
'airbnb-base',
|
||||
'airbnb-typescript/base',
|
||||
'airbnb/hooks',
|
||||
'plugin:react/recommended',
|
||||
'plugin:@typescript-eslint/eslint-recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'plugin:import/errors',
|
||||
'plugin:import/warnings',
|
||||
'plugin:import/typescript',
|
||||
],
|
||||
globals: {},
|
||||
};
|
|
@ -1,10 +0,0 @@
|
|||
module.exports = {
|
||||
rules: {
|
||||
'no-alert': 0,
|
||||
'no-debugger': 0,
|
||||
'no-console': 0,
|
||||
},
|
||||
extends: [
|
||||
'./common',
|
||||
],
|
||||
};
|
|
@ -1,5 +0,0 @@
|
|||
module.exports = {
|
||||
extends: [
|
||||
'./common.js',
|
||||
],
|
||||
};
|
|
@ -1,40 +0,0 @@
|
|||
const yaml = require('yaml');
|
||||
const fs = require('fs');
|
||||
|
||||
const ZERO_HOST = '0.0.0.0';
|
||||
const LOCALHOST = '127.0.0.1';
|
||||
const DEFAULT_PORT = 80;
|
||||
|
||||
const importConfig = () => {
|
||||
try {
|
||||
const doc = yaml.parse(fs.readFileSync('../AdguardHome.yaml', 'utf8'));
|
||||
const { bind_host, bind_port } = doc;
|
||||
return {
|
||||
bind_host,
|
||||
bind_port,
|
||||
};
|
||||
} catch (e) {
|
||||
return {
|
||||
bind_host: ZERO_HOST,
|
||||
bind_port: DEFAULT_PORT,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const getDevServerConfig = () => {
|
||||
const { bind_host: host, bind_port: port } = importConfig();
|
||||
const { DEV_SERVER_PORT } = process.env;
|
||||
|
||||
const devServerHost = host === ZERO_HOST ? LOCALHOST : host;
|
||||
const devServerPort = 3000 || port + 8000;
|
||||
|
||||
return {
|
||||
host: devServerHost,
|
||||
port: devServerPort
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
importConfig,
|
||||
getDevServerConfig
|
||||
};
|
|
@ -1,74 +0,0 @@
|
|||
const path = require('path');
|
||||
const AntdDayjsWebpackPlugin = require('antd-dayjs-webpack-plugin');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const tsconfig = require('../../tsconfig.json');
|
||||
|
||||
const RESOURCES_PATH = path.resolve(__dirname, '../../');
|
||||
const HTML_PATH = path.resolve(RESOURCES_PATH, 'public/index.html');
|
||||
const HTML_INSTALL_PATH = path.resolve(RESOURCES_PATH, 'public/install.html');
|
||||
|
||||
module.exports = {
|
||||
entry: {
|
||||
install: './src/Install.tsx',
|
||||
main: './src/App.tsx'
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.tsx', '.ts', '.js', '.pcss'],
|
||||
alias: Object.keys(tsconfig.compilerOptions.paths).reduce((aliases, key) => {
|
||||
// Reduce to load aliases from ./tsconfig.json in appropriate for webpack form
|
||||
const paths = tsconfig.compilerOptions.paths[key].map(p => p.replace('/*', ''));
|
||||
aliases[key.replace('/*', '')] = path.resolve(
|
||||
__dirname,
|
||||
'../../',
|
||||
tsconfig.compilerOptions.baseUrl,
|
||||
...paths,
|
||||
);
|
||||
return aliases;
|
||||
}, {}),
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: 'ts-loader',
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
{
|
||||
test: /\.(woff|woff2)$/,
|
||||
use: [{
|
||||
loader: 'file-loader',
|
||||
options:{
|
||||
outputPath:'./',
|
||||
}
|
||||
}],
|
||||
},
|
||||
{
|
||||
test:/\.(png|jpe?g|gif)$/,
|
||||
exclude: /(node_modules)/,
|
||||
use:[{
|
||||
loader:'file-loader',
|
||||
options:{
|
||||
outputPath:'./images',
|
||||
}
|
||||
}]
|
||||
}
|
||||
],
|
||||
},
|
||||
|
||||
plugins: [
|
||||
// new AntdDayjsWebpackPlugin()
|
||||
new HtmlWebpackPlugin({
|
||||
inject: true,
|
||||
cache: false,
|
||||
chunks: ['main'],
|
||||
template: HTML_PATH,
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
inject: true,
|
||||
cache: false,
|
||||
chunks: ['install'],
|
||||
filename: 'install.html',
|
||||
template: HTML_INSTALL_PATH,
|
||||
}),
|
||||
],
|
||||
};
|
|
@ -1,114 +0,0 @@
|
|||
const history = require('connect-history-api-fallback');
|
||||
const { merge } = require('webpack-merge');
|
||||
const path = require('path');
|
||||
const proxy = require('http-proxy-middleware');
|
||||
const Webpack = require('webpack');
|
||||
|
||||
const { getDevServerConfig } = require('./helpers');
|
||||
const baseConfig = require('./webpack.config.base');
|
||||
|
||||
const devHost = process.env.DEV_HOST
|
||||
const target = getDevServerConfig();
|
||||
|
||||
const options = {
|
||||
target: devHost || `http://${target.host}:${target.port}`, // target host
|
||||
changeOrigin: true, // needed for virtual hosted sites
|
||||
};
|
||||
const apiProxy = proxy.createProxyMiddleware(options);
|
||||
|
||||
module.exports = merge(baseConfig, {
|
||||
mode: 'development',
|
||||
output: {
|
||||
path: path.resolve(__dirname, '../../build2'),
|
||||
filename: '[name].bundle.js',
|
||||
},
|
||||
optimization: {
|
||||
noEmitOnErrors: true,
|
||||
},
|
||||
devServer: {
|
||||
port: 4000,
|
||||
historyApiFallback: true,
|
||||
before: (app) => {
|
||||
app.use('/control', apiProxy);
|
||||
app.use(history({
|
||||
rewrites: [
|
||||
{
|
||||
from: /\.(png|jpe?g|gif)$/,
|
||||
to: (context) => {
|
||||
const name = context.parsedUrl.pathname.split('/');
|
||||
return `/images/${name[name.length - 1]}`
|
||||
}
|
||||
}, {
|
||||
from: /\.(woff|woff2)$/,
|
||||
to: (context) => {
|
||||
const name = context.parsedUrl.pathname.split('/');
|
||||
return `/${name[name.length - 1]}`
|
||||
}
|
||||
}, {
|
||||
from: /\.(js|css)$/,
|
||||
to: (context) => {
|
||||
const name = context.parsedUrl.pathname.split('/');
|
||||
return `/${name[name.length - 1]}`
|
||||
}
|
||||
}
|
||||
],
|
||||
}));
|
||||
}
|
||||
},
|
||||
devtool: 'eval-source-map',
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
enforce: 'pre',
|
||||
test: /\.tsx?$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'eslint-loader',
|
||||
options: {
|
||||
configFile: path.resolve(__dirname, '../lint/dev.js'),
|
||||
}
|
||||
},
|
||||
{
|
||||
test: (resource) => {
|
||||
return (
|
||||
resource.indexOf('.pcss')+1
|
||||
|| resource.indexOf('.css')+1
|
||||
|| resource.indexOf('.less')+1
|
||||
) && !(resource.indexOf('.module.')+1);
|
||||
},
|
||||
use: ['style-loader', 'css-loader', 'postcss-loader', {
|
||||
loader: 'less-loader',
|
||||
options: {
|
||||
javascriptEnabled: true,
|
||||
},
|
||||
}],
|
||||
},
|
||||
{
|
||||
test: /\.module\.p?css$/,
|
||||
use: [
|
||||
'style-loader',
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
modules: true,
|
||||
sourceMap: true,
|
||||
importLoaders: 1,
|
||||
modules: {
|
||||
localIdentName: "[name]__[local]___[hash:base64:5]",
|
||||
}
|
||||
},
|
||||
},
|
||||
'postcss-loader',
|
||||
],
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new Webpack.DefinePlugin({
|
||||
DEV: true,
|
||||
'process.env.DEV_SERVER_PORT': JSON.stringify(3000),
|
||||
}),
|
||||
new Webpack.HotModuleReplacementPlugin(),
|
||||
new Webpack.ProgressPlugin(),
|
||||
],
|
||||
});
|
|
@ -1,89 +0,0 @@
|
|||
const path = require('path');
|
||||
const { merge } = require('webpack-merge');
|
||||
const baseConfig = require('./webpack.config.base');
|
||||
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
||||
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
|
||||
const TerserJSPlugin = require('terser-webpack-plugin');
|
||||
const Webpack = require('webpack');
|
||||
const CopyPlugin = require('copy-webpack-plugin');
|
||||
|
||||
module.exports = merge(baseConfig, {
|
||||
mode: 'production',
|
||||
devtool: 'source-map',
|
||||
stats: 'minimal',
|
||||
performance : {
|
||||
hints : false
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, '../../../build2/static'),
|
||||
filename: '[name].bundle.[hash:5].js',
|
||||
publicPath: '/'
|
||||
},
|
||||
optimization: {
|
||||
minimizer: [new TerserJSPlugin({terserOptions: {
|
||||
output: {
|
||||
comments: false,
|
||||
},
|
||||
},
|
||||
extractComments: false,
|
||||
}), new OptimizeCSSAssetsPlugin({})],
|
||||
splitChunks: {
|
||||
cacheGroups: {
|
||||
styles: {
|
||||
name: 'styles',
|
||||
test: /\.css$/,
|
||||
chunks: 'all',
|
||||
enforce: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: (resource) => {
|
||||
return (
|
||||
resource.indexOf('.pcss')+1
|
||||
|| resource.indexOf('.css')+1
|
||||
|| resource.indexOf('.less')+1
|
||||
) && !(resource.indexOf('.module.')+1);
|
||||
},
|
||||
use: [{
|
||||
loader: MiniCssExtractPlugin.loader,
|
||||
}, 'css-loader', 'postcss-loader', {
|
||||
loader: 'less-loader',
|
||||
options: {
|
||||
javascriptEnabled: true,
|
||||
},
|
||||
}],
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
{
|
||||
test: /\.module\.p?css$/,
|
||||
use: [
|
||||
{
|
||||
loader: MiniCssExtractPlugin.loader,
|
||||
},
|
||||
{
|
||||
loader: 'css-loader',
|
||||
options: {
|
||||
modules: true,
|
||||
sourceMap: true,
|
||||
importLoaders: 1,
|
||||
},
|
||||
},
|
||||
'postcss-loader',
|
||||
],
|
||||
exclude: /node_modules/,
|
||||
}
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new Webpack.DefinePlugin({
|
||||
DEV: false,
|
||||
}),
|
||||
new MiniCssExtractPlugin({
|
||||
filename: '[name].[hash:5].css',
|
||||
}),
|
||||
]
|
||||
});
|
|
@ -1,18 +0,0 @@
|
|||
import './main.pcss';
|
||||
import './lib/ant/ant.less';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import Store, { storeValue } from 'Store';
|
||||
import './lib/ant';
|
||||
|
||||
import App from './components/App';
|
||||
|
||||
const Container = () => {
|
||||
return (
|
||||
<Store.Provider value={storeValue}>
|
||||
<App/>
|
||||
</Store.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
ReactDOM.render(<Container />, document.getElementById('app'));
|
|
@ -1,18 +0,0 @@
|
|||
import './main.pcss';
|
||||
import './lib/ant/ant.less';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import Store, { storeValue } from 'Store/installStore';
|
||||
import './lib/ant';
|
||||
|
||||
import Install from './components/Install';
|
||||
|
||||
const Container = () => {
|
||||
return (
|
||||
<Store.Provider value={storeValue}>
|
||||
<Install/>
|
||||
</Store.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
ReactDOM.render(<Container />, document.getElementById('app'));
|
Binary file not shown.
Before Width: | Height: | Size: 24 KiB |
|
@ -1,20 +0,0 @@
|
|||
import React, { FC } from 'react';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
|
||||
import Icons from 'Common/ui/Icons';
|
||||
import Routes from './Routes';
|
||||
|
||||
import { ErrorBoundary } from './Errors';
|
||||
|
||||
const App: FC = () => {
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
<BrowserRouter>
|
||||
<Routes />
|
||||
<Icons />
|
||||
</BrowserRouter>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
|
@ -1,136 +0,0 @@
|
|||
import React, { FC, useContext } from 'react';
|
||||
import { Row, Col } from 'antd';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
import Store from 'Store';
|
||||
import { InnerLayout } from 'Common/ui/layouts';
|
||||
import theme from 'Lib/theme';
|
||||
import { BlockCard, TopDomains, BlockedQueries, TopClients, ServerStatistics } from './components';
|
||||
|
||||
const Dashboard:FC = observer(() => {
|
||||
const store = useContext(Store);
|
||||
const {
|
||||
dashboard: { stats, filteringConfig },
|
||||
system: { status },
|
||||
ui: { intl },
|
||||
} = store;
|
||||
|
||||
if (!stats || !filteringConfig) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const {
|
||||
numBlockedFiltering,
|
||||
numReplacedParental,
|
||||
numReplacedSafebrowsing,
|
||||
replacedParental,
|
||||
replacedSafebrowsing,
|
||||
avgProcessingTime,
|
||||
blockedFiltering,
|
||||
|
||||
topBlockedDomains,
|
||||
topQueriedDomains,
|
||||
dnsQueries,
|
||||
numDnsQueries,
|
||||
|
||||
} = stats;
|
||||
|
||||
const { filters } = filteringConfig!;
|
||||
const allFilters = filters?.length;
|
||||
const allRules = filters?.reduce((prev, e) => prev + (e.rulesCount || 0), 0);
|
||||
const enabled = filters?.filter((e) => e.enabled).length;
|
||||
|
||||
return (
|
||||
<InnerLayout title={`AdGuard Home ${status?.version}`}>
|
||||
<div className={theme.content.container}>
|
||||
<Row gutter={[24, 24]}>
|
||||
<Col span={24} md={12}>
|
||||
<TopDomains
|
||||
title={intl.getMessage('stats_query_domain')}
|
||||
overal={numDnsQueries!}
|
||||
chartData={dnsQueries!}
|
||||
tableData={topQueriedDomains!}
|
||||
color={theme.chartColors.green}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={24} md={12}>
|
||||
<TopDomains
|
||||
useValueColor
|
||||
title={intl.getMessage('top_blocked_domains')}
|
||||
overal={numBlockedFiltering!}
|
||||
chartData={blockedFiltering!}
|
||||
tableData={topBlockedDomains!}
|
||||
color={theme.chartColors.red}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<Row gutter={[24, 24]}>
|
||||
<Col span={24} md={18}>
|
||||
<Row gutter={[24, 24]}>
|
||||
<Col span={24} md={8}>
|
||||
<BlockCard
|
||||
title={intl.getMessage('dashboard_blocked_ads')}
|
||||
overal={numBlockedFiltering!}
|
||||
data={blockedFiltering!}
|
||||
color={theme.chartColors.red}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={24} md={8}>
|
||||
<BlockCard
|
||||
title={intl.getMessage('dashboard_blocked_trackers')}
|
||||
overal={numBlockedFiltering!}
|
||||
data={blockedFiltering!}
|
||||
color={theme.chartColors.orange}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={24} md={8}>
|
||||
<BlockCard
|
||||
title={intl.getMessage('stats_adult')}
|
||||
overal={numReplacedParental!}
|
||||
data={replacedParental!}
|
||||
color={theme.chartColors.purple}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={24} md={8}>
|
||||
<BlockCard
|
||||
title={intl.getMessage('stats_malware_phishing')}
|
||||
overal={numReplacedSafebrowsing!}
|
||||
data={replacedSafebrowsing!}
|
||||
color={theme.chartColors.red}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={24} md={8}>
|
||||
<BlockCard
|
||||
title={intl.getMessage('average_processing_time')}
|
||||
overal={`${Math.round(avgProcessingTime! * 100)} ${intl.getMessage('milliseconds_abbreviation')}`}
|
||||
data={blockedFiltering!}
|
||||
color={theme.chartColors.green}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={24} md={8}>
|
||||
<BlockCard
|
||||
title={intl.getMessage('dashboard_filter_rules')}
|
||||
overal={allRules!}
|
||||
text={intl.getMessage('dashboard_filter_rules_count', { enabled, all: allFilters })}
|
||||
color={theme.chartColors.green}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
<Col span={24} md={6}>
|
||||
{/* TODO: fix chart */}
|
||||
<BlockedQueries
|
||||
other={numBlockedFiltering! / 3}
|
||||
ads={numBlockedFiltering!}
|
||||
trackers={numBlockedFiltering!}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
<TopClients />
|
||||
<ServerStatistics />
|
||||
</div>
|
||||
</InnerLayout>
|
||||
);
|
||||
});
|
||||
|
||||
export default Dashboard;
|
|
@ -1,20 +0,0 @@
|
|||
.container {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
padding: 24px;
|
||||
background-color: var(--white);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
margin-bottom: 4px;
|
||||
color: var(--gray700);
|
||||
}
|
||||
|
||||
.overal {
|
||||
font-size: 30px;
|
||||
line-height: 38px;
|
||||
margin-bottom: 18px;
|
||||
color: var(--gray900);
|
||||
}
|
|
@ -1,35 +0,0 @@
|
|||
import React, { FC } from 'react';
|
||||
import { AreaChart, Area, ResponsiveContainer } from 'recharts';
|
||||
|
||||
import s from './BlockCard.module.pcss';
|
||||
|
||||
interface BlockCardProps {
|
||||
overal: number | string;
|
||||
data?: number[];
|
||||
text?: string;
|
||||
color?: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
const BlockCard: FC<BlockCardProps> = ({ overal, data, color, title, text }) => {
|
||||
return (
|
||||
<div className={s.container}>
|
||||
<div className={s.title}>{title}</div>
|
||||
<div className={s.overal}>{overal}</div>
|
||||
{data && (
|
||||
<ResponsiveContainer width="100%" height={25}>
|
||||
<AreaChart data={data.map((n) => ({ name: 'data', value: n }))}>
|
||||
<Area dataKey="value" stroke={color} fill={color} dot={false} />
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
)}
|
||||
{text && (
|
||||
<div>
|
||||
{text}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BlockCard;
|
|
@ -1 +0,0 @@
|
|||
export { default as BlockCard } from './BlockCard';
|
|
@ -1,16 +0,0 @@
|
|||
.container {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
padding: 24px;
|
||||
background-color: var(--white);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
margin-bottom: 4px;
|
||||
color: var(--gray700);
|
||||
}
|
||||
.pie {
|
||||
padding: 34px 0px;
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
import theme from 'Lib/theme';
|
||||
import React, { FC, useContext, useState } from 'react';
|
||||
import { PieChart, Pie, ResponsiveContainer, Sector, Cell } from 'recharts';
|
||||
|
||||
import Store from 'Store';
|
||||
|
||||
import s from './BlockedQueries.module.pcss';
|
||||
|
||||
interface BlockCardProps {
|
||||
ads: number;
|
||||
trackers: number;
|
||||
other: number;
|
||||
}
|
||||
|
||||
const renderActiveShape = (props: any): any => {
|
||||
const {
|
||||
cx, cy, innerRadius, outerRadius, startAngle, endAngle,
|
||||
fill, payload, percent,
|
||||
} = props;
|
||||
return (
|
||||
<g>
|
||||
<text x={cx} y={cy - 11} dy={8} textAnchor="middle" fill={fill}>{payload.name}</text>
|
||||
<text x={cx} y={cy + 18} dy={8} fontSize={24} textAnchor="middle" >{Math.round(percent * 100)}%</text>
|
||||
<Sector
|
||||
cx={cx}
|
||||
cy={cy}
|
||||
innerRadius={innerRadius + 5}
|
||||
outerRadius={outerRadius + 5}
|
||||
startAngle={startAngle + 1}
|
||||
endAngle={endAngle - 1}
|
||||
fill={fill}
|
||||
/>
|
||||
</g>
|
||||
);
|
||||
};
|
||||
|
||||
const BlockedQueries: FC<BlockCardProps> = ({ ads, trackers, other }) => {
|
||||
const store = useContext(Store);
|
||||
const [activeIndex, setActiveIndex] = useState(0);
|
||||
const { ui: { intl } } = store;
|
||||
const data = [
|
||||
{ name: intl.getMessage('other'), value: other, color: theme.chartColors.gray700 },
|
||||
{ name: intl.getMessage('ads'), value: ads, color: theme.chartColors.red },
|
||||
{ name: intl.getMessage('trackers'), value: trackers, color: theme.chartColors.orange },
|
||||
];
|
||||
const onChart: any = (_: any, index: number) => {
|
||||
setActiveIndex(index);
|
||||
};
|
||||
return (
|
||||
<div className={s.container}>
|
||||
<div className={s.title}>{intl.getMessage('dashboard_blocked_queries')}</div>
|
||||
<div className={s.pie}>
|
||||
<ResponsiveContainer width="100%" height={190}>
|
||||
<PieChart>
|
||||
<Pie
|
||||
activeIndex={activeIndex}
|
||||
data={data}
|
||||
dataKey="value"
|
||||
nameKey="name"
|
||||
innerRadius={60}
|
||||
outerRadius={80}
|
||||
activeShape={renderActiveShape}
|
||||
onClick={onChart}
|
||||
>
|
||||
{data.map((entry, index) => (
|
||||
<Cell key={`cell-${index}`} fill={entry.color} />
|
||||
))}
|
||||
</Pie>
|
||||
</PieChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default BlockedQueries;
|
|
@ -1 +0,0 @@
|
|||
export { default as BlockedQueries } from './BlockedQueries';
|
|
@ -1,46 +0,0 @@
|
|||
.container {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
background-color: var(--white);
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.title {
|
||||
padding: 24px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
line-height: 22px;
|
||||
border-bottom: 1px solid var(--gray300);
|
||||
color: var(--gray900);
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 24px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.cardBorder {
|
||||
border-right: 1px solid var(--gray300);
|
||||
|
||||
&:last-of-type {
|
||||
border-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.cardTitle {
|
||||
font-weight: 500;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.cardDesc {
|
||||
color: var(--gray700);
|
||||
}
|
||||
|
||||
.cardValue {
|
||||
color: var(--gray900);
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
.chart {
|
||||
margin-top: 24px;
|
||||
}
|
|
@ -1,89 +0,0 @@
|
|||
import React, { FC, useContext } from 'react';
|
||||
import { Row, Col } from 'antd';
|
||||
import { AreaChart, Area, ResponsiveContainer } from 'recharts';
|
||||
|
||||
import Store from 'Store';
|
||||
import theme from 'Lib/theme';
|
||||
|
||||
import s from './ServerStatistics.module.pcss';
|
||||
|
||||
const ServerStatistics: FC = () => {
|
||||
const store = useContext(Store);
|
||||
const { ui: { intl } } = store;
|
||||
|
||||
const data = [0, 10, 2, 14, 12, 24, 5, 8, 10, 0, 3, 5, 7, 8, 3];
|
||||
return (
|
||||
<div className={s.container}>
|
||||
<div className={s.title}>{intl.getMessage('dashboard_server_statistics')}</div>
|
||||
<Row>
|
||||
<Col span={24} md={6} className={s.cardBorder}>
|
||||
<div className={s.card}>
|
||||
<div className={s.cardTitle}>
|
||||
Average server load
|
||||
</div>
|
||||
<div className={s.cardDesc}>
|
||||
<div>
|
||||
Processes: 213
|
||||
</div>
|
||||
<div>
|
||||
Cores: 2
|
||||
</div>
|
||||
</div>
|
||||
<ResponsiveContainer width="100%" height={25} className={s.chart}>
|
||||
<AreaChart data={data.map((n) => ({ name: 'data', value: n }))}>
|
||||
<Area dataKey="value" stroke={theme.chartColors.green} fill={theme.chartColors.green} dot={false} />
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={24} md={6} className={s.cardBorder}>
|
||||
<div className={s.card}>
|
||||
<div className={s.cardTitle}>
|
||||
Memory usage
|
||||
</div>
|
||||
<div className={s.cardValue}>
|
||||
236 Mb
|
||||
</div>
|
||||
<ResponsiveContainer width="100%" height={25} className={s.chart}>
|
||||
<AreaChart data={data.map((n) => ({ name: 'data', value: n }))}>
|
||||
<Area dataKey="value" stroke={theme.chartColors.orange} fill={theme.chartColors.orange} dot={false} />
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={24} md={6} className={s.cardBorder}>
|
||||
<div className={s.card}>
|
||||
<div className={s.cardTitle}>
|
||||
DNS cashe size
|
||||
</div>
|
||||
<div className={s.cardValue}>
|
||||
2 363 records
|
||||
</div>
|
||||
<div className={s.cardDesc}>
|
||||
<div>
|
||||
32 Mb
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
<Col span={24} md={6} className={s.cardBorder}>
|
||||
<div className={s.card}>
|
||||
<div className={s.cardTitle}>
|
||||
Upstream servers data
|
||||
</div>
|
||||
<div className={s.cardDesc}>
|
||||
<div>
|
||||
Processes: 213
|
||||
</div>
|
||||
<div>
|
||||
Cores: 2
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ServerStatistics;
|
|
@ -1 +0,0 @@
|
|||
export { default as ServerStatistics } from './ServerStatistics';
|
|
@ -1,43 +0,0 @@
|
|||
.container {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
background-color: var(--white);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
margin-bottom: 4px;
|
||||
padding: 24px;
|
||||
color: var(--gray900);
|
||||
}
|
||||
|
||||
.table {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tableTitle {
|
||||
color: var(--gray700);
|
||||
background-color: #fafafa;
|
||||
padding: 24px;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.tableGrid {
|
||||
display: grid;
|
||||
grid-template-columns: 4fr 1fr 1fr 1.5fr 1fr .5fr;
|
||||
padding: 16px 24px;
|
||||
border-bottom: 1px solid var(--gray300);
|
||||
|
||||
&:last-of-type {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
> div {
|
||||
align-self: center;
|
||||
}
|
||||
}
|
||||
.ids {
|
||||
color: var(--gray700)
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
import React, { FC, useContext } from 'react';
|
||||
import { Button } from 'antd';
|
||||
import cn from 'classnames';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
import Store from 'Store';
|
||||
|
||||
import s from './TopClients.module.pcss';
|
||||
|
||||
const TopClients: FC = observer(() => {
|
||||
const store = useContext(Store);
|
||||
const { ui: { intl }, dashboard } = store;
|
||||
const { clientsInfo, stats } = dashboard;
|
||||
const topClients = new Map();
|
||||
stats?.topClients?.forEach((client) => {
|
||||
const [id, requests] = Object.entries(client.numberData);
|
||||
topClients.set(id, requests);
|
||||
});
|
||||
const clients = Array.from(clientsInfo.entries());
|
||||
|
||||
return (
|
||||
<div className={s.container}>
|
||||
<div className={s.title}>{intl.getMessage('Top Clients')}</div>
|
||||
<div className={s.table}>
|
||||
<div className={cn(s.tableTitle, s.tableGrid)}>
|
||||
<div>{intl.getMessage('client_table_header')}</div>
|
||||
<div>{intl.getMessage('requests')}</div>
|
||||
<div>{intl.getMessage('show_blocked_responses')}</div>
|
||||
<div>%</div>
|
||||
<div/>
|
||||
<div/>
|
||||
</div>
|
||||
{clients.map(([id, c]) => {
|
||||
const request = topClients.get(id);
|
||||
return (
|
||||
<div className={s.tableGrid} key={id}>
|
||||
<div>
|
||||
{c.name}
|
||||
<div className={s.ids}>
|
||||
{c.ids?.map((cid) => (
|
||||
<div key={cid}>{cid}</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
{request}
|
||||
</div>
|
||||
<div>
|
||||
API
|
||||
{/* TODO: api */}
|
||||
</div>
|
||||
<div>
|
||||
API / {request}
|
||||
</div>
|
||||
<div>
|
||||
<Button>
|
||||
{intl.getMessage('Block')}
|
||||
</Button>
|
||||
</div>
|
||||
<div>
|
||||
...
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default TopClients;
|
|
@ -1 +0,0 @@
|
|||
export { default as TopClients } from './TopClients';
|
|
@ -1,62 +0,0 @@
|
|||
.container {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
background-color: var(--white);
|
||||
}
|
||||
|
||||
.title {
|
||||
padding: 24px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
line-height: 22px;
|
||||
border-bottom: 1px solid var(--gray300);
|
||||
margin-bottom: 16px;
|
||||
color: var(--gray900);
|
||||
}
|
||||
|
||||
.content {
|
||||
padding: 24px;
|
||||
|
||||
}
|
||||
|
||||
.overal {
|
||||
font-size: 24px;
|
||||
line-height: 32px;
|
||||
margin-bottom: 24px;
|
||||
color: var(--gray900);
|
||||
}
|
||||
|
||||
.table {
|
||||
position: relative;
|
||||
overflow-y: auto;
|
||||
max-height: 280px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tableHeader {
|
||||
/* TODO: color */
|
||||
position: sticky;
|
||||
top: 0;
|
||||
width: inherit;
|
||||
background-color: #fafafa;
|
||||
font-weight: 500;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.tableRow {
|
||||
display: grid;
|
||||
grid-template-columns: 3fr 1fr 1.5fr;
|
||||
grid-column-gap: 10px;
|
||||
padding: 8px 16px;
|
||||
border-bottom: 1px solid var(--gray300);
|
||||
}
|
||||
|
||||
.domain {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.progress {
|
||||
display: flex;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue