+ client: Refactor DHCP settings

This commit is contained in:
Artem Baskal 2020-08-19 18:23:05 +03:00 committed by Simon Zolin
parent c9f58ce4a7
commit 1d35d73fc5
49 changed files with 2953 additions and 1660 deletions

View File

@ -13,6 +13,7 @@
"hr": "Hrvatski",
"id": "Indonesian",
"it": "Italiano",
"hu": "Magyar",
"no": "Norsk",
"pl": "Polski",
"pt-br": "Português (BR)",
@ -32,7 +33,8 @@
"zh-tw": "正體中文",
"zh-cn": "简体中文",
"ko": "한국어",
"th": "ภาษาไทย"
"th": "ภาษาไทย",
"si-lk": "සිංහල"
}
}
]

View File

@ -217,7 +217,7 @@ You may need to prepare before using these builds:
You are welcome to fork this repository, make your changes and submit a pull request — https://github.com/AdguardTeam/AdGuardHome/pulls
Please note, that we don't expect people to contribute to both UI and golang parts of the program simultaneously. Ideally, the golang part is implemented first, i.e. configuration, API, and the functionality itself. The UI part can be implemented later in a different pull request by a different person.
Please note that we don't expect people to contribute to both UI and golang parts of the program simultaneously. Ideally, the golang part is implemented first, i.e. configuration, API, and the functionality itself. The UI part can be implemented later in a different pull request by a different person.
<a id="test-unstable-versions"></a>
### Test unstable versions
@ -235,7 +235,7 @@ There are three options how you can install an unstable version:
There are three options how you can install an unstable version.
1. You can either install a beta or edge version of AdGuard Home which we update periodically. If you're already using stable version of AdGuard Home, just replace the executable with a new one.
1. You can either install AdGuard Home from "beta" or "edge" distribution channel which we update periodically. If you're already using stable version of AdGuard Home, just replace the executable file with a new one.
2. You can use the Docker image from the `edge` tag, which is synced with the repo master branch.
3. You can install AdGuard Home from `beta` or `edge` channels on the Snap Store.

83
client/.eslintrc.json vendored
View File

@ -1,18 +1,15 @@
{
"parser": "babel-eslint",
"extends": [
"plugin:react/recommended",
"airbnb-base"
],
"env": {
"jest": true,
"node": true,
"browser": true,
"commonjs": true
},
"settings": {
"react": {
"pragma": "React",
@ -24,35 +21,65 @@
}
}
},
"rules": {
"indent": ["error", 4, {
"SwitchCase": 1,
"VariableDeclarator": 1,
"outerIIFEBody": 1,
"FunctionDeclaration": {
"parameters": 1,
"body": 1
},
"FunctionExpression": {
"parameters": 1,
"body": 1
},
"CallExpression": {
"arguments": 1
},
"ArrayExpression": 1,
"ObjectExpression": 1,
"ImportDeclaration": 1,
"flatTernaryExpressions": false,
"ignoredNodes": ["JSXElement", "JSXElement > *", "JSXAttribute", "JSXIdentifier", "JSXNamespacedName", "JSXMemberExpression", "JSXSpreadAttribute", "JSXExpressionContainer", "JSXOpeningElement", "JSXClosingElement", "JSXText", "JSXEmptyExpression", "JSXSpreadChild"],
"ignoreComments": false
}],
"indent": [
"error",
4,
{
"SwitchCase": 1,
"VariableDeclarator": 1,
"outerIIFEBody": 1,
"FunctionDeclaration": {
"parameters": 1,
"body": 1
},
"FunctionExpression": {
"parameters": 1,
"body": 1
},
"CallExpression": {
"arguments": 1
},
"ArrayExpression": 1,
"ObjectExpression": 1,
"ImportDeclaration": 1,
"flatTernaryExpressions": false,
"ignoredNodes": [
"JSXElement",
"JSXElement > *",
"JSXAttribute",
"JSXIdentifier",
"JSXNamespacedName",
"JSXMemberExpression",
"JSXSpreadAttribute",
"JSXExpressionContainer",
"JSXOpeningElement",
"JSXClosingElement",
"JSXText",
"JSXEmptyExpression",
"JSXSpreadChild"
],
"ignoreComments": false
}
],
"class-methods-use-this": "off",
"no-shadow": "off",
"camelcase": "off",
"no-console": ["warn", { "allow": ["warn", "error"] }],
"import/no-extraneous-dependencies": ["error", { "devDependencies": true }],
"no-console": [
"warn",
{
"allow": [
"warn",
"error"
]
}
],
"import/no-extraneous-dependencies": [
"error",
{
"devDependencies": true
}
],
"import/prefer-default-export": "off",
"no-alert": "off"
}

18
client/package-lock.json generated vendored
View File

@ -7412,11 +7412,21 @@
"dev": true
},
"i18next": {
"version": "19.4.4",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-19.4.4.tgz",
"integrity": "sha512-ofaHtdsDdX3A5nYur1HWblB7J4hIcjr2ACdnwTAJgc8hTfPbyzZfGX0hVkKpI3vzDIgO6Uzc4v1ffW2W6gG6zw==",
"version": "19.6.2",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-19.6.2.tgz",
"integrity": "sha512-Zyd/Z32FY+sD+Eg6sLj5DeDSlrIN3WZ4onuOBRGcjDx/rvodsyUZ9TJ2Y+3aD9Vu8MPbiMU2WesIER/rs1ioyw==",
"requires": {
"@babel/runtime": "^7.3.1"
"@babel/runtime": "^7.10.1"
},
"dependencies": {
"@babel/runtime": {
"version": "7.10.5",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.5.tgz",
"integrity": "sha512-otddXKhdNn7d0ptoFRHtMLa8LqDxLYwTjB4nYgM1yy5N6gU/MUf8zqyyLltCH3yAVitBzmwK4us+DD0l/MauAg==",
"requires": {
"regenerator-runtime": "^0.13.4"
}
}
}
},
"i18next-browser-languagedetector": {

2
client/package.json vendored
View File

@ -18,7 +18,7 @@
"axios": "^0.19.2",
"classnames": "^2.2.6",
"date-fns": "^1.29.0",
"i18next": "^19.4.4",
"i18next": "^19.6.2",
"i18next-browser-languagedetector": "^4.2.0",
"ipaddr.js": "^1.9.1",
"js-yaml": "^3.14.0",

View File

@ -11,6 +11,8 @@
"save_config": "Save config",
"enabled_dhcp": "DHCP server enabled",
"disabled_dhcp": "DHCP server disabled",
"unavailable_dhcp": "DHCP is unavailable",
"unavailable_dhcp_desc": "AdGuard Home cannot run a DHCP server on your OS",
"dhcp_title": "DHCP server (experimental!)",
"dhcp_description": "If your router does not provide DHCP settings, you can use AdGuard's own built-in DHCP server.",
"dhcp_enable": "Enable DHCP server",
@ -21,6 +23,8 @@
"dhcp_static_leases": "DHCP static leases",
"dhcp_leases_not_found": "No DHCP leases found",
"dhcp_config_saved": "DHCP config successfully saved",
"dhcp_ipv4_settings": "DHCP IPv4 Settings",
"dhcp_ipv6_settings": "DHCP IPv6 Settings",
"form_error_required": "Required field",
"form_error_ip4_format": "Invalid IPv4 format",
"form_error_ip6_format": "Invalid IPv6 format",
@ -29,6 +33,7 @@
"form_error_client_id_format": "Invalid client ID format",
"form_error_positive": "Must be greater than 0",
"form_error_negative": "Must be equal to 0 or greater",
"range_end_error": "Must be greater than range start",
"dhcp_form_gateway_input": "Gateway IP",
"dhcp_form_subnet_input": "Subnet mask",
"dhcp_form_range_title": "Range of IP addresses",
@ -561,7 +566,8 @@
"filter_category_security_desc": "Lists that specialize on blocking malware, phishing or scam domains",
"filter_category_regional_desc": "Lists that focus on regional ads and tracking servers",
"filter_category_other_desc": "Other blocklists",
"setup_config_to_enable_dhcp_server": "Setup config to enable DHCP server",
"original_response": "Original response",
"click_to_view_queries": "Click to view queries",
"port_53_faq_link": "Port 53 is often occupied by \"DNSStubListener\" or \"systemd-resolved\" services. Please read <0>this instruction</0> on how to resolve this."
}
}

View File

@ -0,0 +1,565 @@
{
"client_settings": "Kliens beállítások",
"example_upstream_reserved": "megadhat egy DNS-t <0> felfelé egy adott tartományhoz </0>",
"upstream_parallel": "A párhuzamos lekérdezések segítségével felgyorsíthatja a megoldást az összes upstream kiszolgáló egyidejű lekérdezésével",
"parallel_requests": "Párhuzamos kérelmek",
"load_balancing": "Terheléselosztás",
"load_balancing_desc": "Egyszerre csak egy szerverről történjen lekérdezés. Az AdGuard Home egy súlyozott, véletlenszerű algoritmust fog használni a megfelelő szerver kiválasztására, így a leggyorsabb szervert gyakrabban fogja használni.",
"bootstrap_dns": "Bootstrap DNS kiszolgálók",
"bootstrap_dns_desc": "A Bootstrap DNS-kiszolgálók a DoH / DoT-megoldók IP-címeinek feloldására szolgálnak",
"check_dhcp_servers": "Ellenőrizze a DHCP-kiszolgálókat",
"save_config": "Konfiguráció mentése",
"enabled_dhcp": "A DHCP-kiszolgáló engedélyezve van",
"disabled_dhcp": "A DHCP-kiszolgáló le van tiltva",
"dhcp_title": "DHCP-kiszolgáló (kísérleti!)",
"dhcp_description": "Ha az útválasztó nem nyújt DHCP-beállításokat, akkor az AdGuard saját beépített DHCP-kiszolgálóját használhatod",
"dhcp_enable": "A DHCP-kiszolgáló engedélyezése",
"dhcp_disable": "A DHCP-kiszolgáló letiltása",
"dhcp_not_found": "Biztonságos a beépített DHCP-kiszolgáló engedélyezése - nem találtunk aktív DHCP-kiszolgálókat a hálózaton. Javasoljuk azonban, hogy kézzel ellenőrizze, hogy az automatikus tesztünk jelenleg nem ad 100% -os garanciát.",
"dhcp_found": "Aktív DHCP-kiszolgáló található a hálózaton. Nem biztonságos a beépített DHCP-kiszolgáló engedélyezése.",
"dhcp_leases": "DHCP bérlés",
"dhcp_static_leases": "Statikus DHCP",
"dhcp_leases_not_found": "Nem találhatóak DHCP kliensek",
"dhcp_config_saved": "DHCP beállítások elmentve",
"form_error_required": "Kötelező mező",
"form_error_ip4_format": "Helytelen IPv4 formátum",
"form_error_ip6_format": "Érvénytelen IPv6 formátum",
"form_error_ip_format": "Érvénytelen IPv4 formátum",
"form_error_mac_format": "Érvénytelen MAC formátum",
"form_error_client_id_format": "Érvénytelen kliens ID formátum",
"form_error_positive": "Legfeljebb nulla legyen",
"form_error_negative": "Legalább 0-nak kell lennie",
"dhcp_form_gateway_input": "Átjáró IP",
"dhcp_form_subnet_input": "Alhálózati maszk",
"dhcp_form_range_title": "IP címtartomány",
"dhcp_form_range_start": "Tartomány kezdete",
"dhcp_form_range_end": "Tartomány vége",
"dhcp_form_lease_title": "DHCP bérlési ideje (másodpercben)",
"dhcp_form_lease_input": "Bérlési idő",
"dhcp_interface_select": "Válaszd ki a DHCP interface-t",
"dhcp_hardware_address": "Hardvercím",
"dhcp_ip_addresses": "Ip címek",
"ip": "IP",
"dhcp_table_hostname": "Host név",
"dhcp_table_expires": "Lejár",
"dhcp_warning": "Ha egyébként engedélyezni szeretné a DHCP-kiszolgálót, győződjön meg arról, hogy nincs-e más aktív DHCP-kiszolgáló a hálózaton. Ellenkező esetben a csatlakoztatott eszközöket megszakíthatja az interneten!",
"dhcp_error": "Nem tudtuk meghatározni, hogy van-e másik DHCP-kiszolgáló a hálózaton.",
"dhcp_static_ip_error": "A DHCP-kiszolgáló használatához statikus IP-címet kell beállítani. Nem sikerült meghatározni, hogy ez a hálózati interfész statikus IP-cím használatával van-e beállítva. Állítsa be kézzel egy statikus IP-címet.",
"dhcp_dynamic_ip_found": "A rendszer dinamikus IP-címkonfigurációt használ az <0> {{interfaceName}} </0> interfészhez. A DHCP-kiszolgáló használatához statikus IP-címet kell beállítani. Jelenlegi IP-címe <0> {{ipAddress}} </0>. Ha automatikusan megnyomja az Enable DHCP gombot, automatikusan beállítjuk ezt az IP-címet statikusnak.",
"dhcp_lease_added": "Statikus bérlet \"{{key}}\" sikeresen hozzáadva",
"dhcp_lease_deleted": "Statikus bérlet \"{{key}}\" sikeresen törölve",
"dhcp_new_static_lease": "Új statikus bérlet",
"dhcp_static_leases_not_found": "Nincs DHCP statikus bérlet",
"dhcp_add_static_lease": "Statikus bérlet hozzáadása",
"dhcp_reset": "Biztosan visszaállítod a DHCP beállításokat?",
"country": "Ország",
"city": "Város",
"delete_confirm": "Biztosan törli a \"{{key}}\" -t?",
"form_enter_hostname": "Adja meg a hosztnevet",
"error_details": "Hiba részletei",
"response_details": "Válasz adatai",
"request_details": "Kérés adatai",
"client_details": "Kliens részletei",
"details": "Részletek",
"back": "Vissza",
"dashboard": "Irányítópult",
"settings": "Beállítások",
"filters": "Szűrők",
"filter": "Szűrő",
"query_log": "Lekérdezési napló",
"compact": "Kompakt",
"nothing_found": "Nincs találat",
"faq": "GYIK",
"version": "verzió",
"address": "Cím",
"protocol": "Protokoll",
"on": "Be",
"off": "Ki",
"copyright": "Szerzői jog",
"homepage": "Honlap",
"report_an_issue": "Probléma bejelentése",
"privacy_policy": "Adatvédelmi irányelvek",
"enable_protection": "Védelem engedélyezése",
"enabled_protection": "Engedélyezett védelem",
"disable_protection": "Védelem letiltása",
"disabled_protection": "Letiltott védelem",
"refresh_statics": "Statisztikák frissítése",
"dns_query": "DNS lekérdezések",
"blocked_by": "<0>Szűrők által blokkolt</0>",
"stats_malware_phishing": "Blokkolt vírusok/adathalászat",
"stats_adult": "Blokkolt felnőtt tartalmak",
"stats_query_domain": "A legjobban lekérdezett területek",
"for_last_24_hours": "Utolsó 24 órában",
"for_last_days": "a legutolsó {{count}} napra",
"for_last_days_plural": "a legutolsó {{count}} napra",
"no_domains_found": "Nem található domain",
"requests_count": "Kérések száma",
"top_blocked_domains": "A legjobban blokkolt tartományok",
"top_clients": "Legaktívabb kliensek",
"no_clients_found": "Nem található kliens",
"general_statistics": "Általános statisztikák",
"number_of_dns_query_days": "Lekérdezések száma az utolsó {{count}} napban",
"number_of_dns_query_days_plural": "Feldolgozott lekérdezések száma az utolsó {{count}} napban",
"number_of_dns_query_24_hours": "Számos DNS lekérdezések feldolgozása az elmúlt 24 órában",
"number_of_dns_query_blocked_24_hours": "Számos DNS-kérés blokkolva van az adblock-szűrők által, és blokklistákat tartalmaz",
"number_of_dns_query_blocked_24_hours_by_sec": "Számos DNS-kérés blokkolva van az AdGuard böngésző biztonsági moduljában",
"number_of_dns_query_blocked_24_hours_adult": "Blokkolt felnőtt tartalmak száma",
"enforced_save_search": "Erősített biztonságos keresés",
"number_of_dns_query_to_safe_search": "Számos DNS-kérelem olyan keresőmotorokhoz, amelyekre a Biztonságos keresés érvényesült",
"average_processing_time": "Átlagos feldolgozási idő",
"average_processing_time_hint": "Átlagos idő milliszekundumban a DNS-kérelem feldolgozásakor",
"block_domain_use_filters_and_hosts": "A tartományok blokkolása szűrőkkel és házigazdákkal",
"filters_block_toggle_hint": "A <a href='#filters'> Szűrők </a> beállításaiban beállíthatja a blokkolási szabályokat.",
"use_adguard_browsing_sec": "Használja az AdGuard böngészés biztonsági webszolgáltatását",
"use_adguard_browsing_sec_hint": "Az AdGuard Home ellenőrzi, hogy a böngésző biztonsági webszolgáltatás a tartományt feketelistára tette-e. Az ellenőrzés elvégzéséhez adatvédelmi keresési API-t fog használni: az SHA256-os hash-név csak egy rövid előtagot küld a kiszolgálónak.",
"use_adguard_parental": "Használja az AdGuard szülői felügyelet webszolgáltatását",
"use_adguard_parental_hint": "Az AdGuard Home ellenőrzi, hogy a domain tartalmaz-e felnőtt anyagokat. Ugyanazokat az adatvédelmi API-kat használja, mint a böngésző biztonsági webszolgáltatás.",
"enforce_safe_search": "A biztonságos keresés végrehajtása",
"enforce_save_search_hint": "Az AdGuard Home a következő keresőmotorokban biztosíthatja a biztonságos keresést: Google, Youtube, Bing, DuckDuckGo és Yandex.",
"no_servers_specified": "Nincsenek megadott kiszolgálók",
"general_settings": "Általános beállítások",
"dns_settings": "DNS beállítások",
"dns_blocklists": "DNS blokkolási listák",
"dns_allowlists": "DNS engedélyezési listák",
"dns_blocklists_desc": "Az AdGuard Home blokkolni fogja azokat a domaineket, amik szerepelnek a blokkolási listán.",
"dns_allowlists_desc": "A DNS engedélyezési listán szereplő domainek engedélyezve lesznek, akkor is, ha szerepelnek bármelyik blokkolási listán.",
"custom_filtering_rules": "Egyéni szűrési szabályok",
"encryption_settings": "Titkosítási beállítások",
"dhcp_settings": "DHCP beállítások",
"upstream_dns": "Upstream DNS-kiszolgálók",
"upstream_dns_hint": "Ha üresen hagyod ezt a mezőt, az AdGuard a(z) <a href='https://www.quad9.net/' target='_blank'>Quad9</a>-t fogja használni.",
"test_upstream_btn": "Upstreamek tesztelése",
"upstreams": "Feltöltés",
"apply_btn": "Alkalmaz",
"disabled_filtering_toast": "Letiltott szűrés",
"enabled_filtering_toast": "Engedélyezett szűrés",
"disabled_safe_browsing_toast": "Letiltott biztonságos böngészés",
"enabled_safe_browsing_toast": "Engedélyezett biztonságos böngészés",
"disabled_parental_toast": "Letiltott szülői felügyelet",
"enabled_parental_toast": "Engedélyezett szülői felügyelet",
"disabled_safe_search_toast": "Letiltott biztonságos keresés",
"enabled_save_search_toast": "Engedélyezett biztonságos keresés",
"enabled_table_header": "Engedélyezve",
"name_table_header": "Név",
"list_url_table_header": "Lista URL-je",
"rules_count_table_header": "Szabályok száma",
"last_time_updated_table_header": "Utoljára frissítve",
"actions_table_header": "Akciók",
"request_table_header": "Kérelem",
"edit_table_action": "Szerkesztés",
"delete_table_action": "Törlés",
"elapsed": "Eltelt időtartam",
"filters_and_hosts_hint": "Az AdGuard Home megérti az alapvető adblock szabályokat és a fájlokat tartalmazó szintaxist.",
"no_blocklist_added": "Nincsnek blokkolási listák hozzáadva",
"no_whitelist_added": "Nincsenek engedélyezési listák hozzáadva",
"add_blocklist": "Blokkolási lista hozzáadása",
"add_allowlist": "Engedélyezési lista hozzáadása",
"cancel_btn": "Megszünteti",
"enter_name_hint": "Adja meg a nevet",
"enter_url_or_path_hint": "Írjon be egy URL-t vagy egy útvonalat a listához",
"check_updates_btn": "Frissítések keresése",
"new_blocklist": "Új blokkolási lista",
"new_allowlist": "Új engedélyezési lista",
"edit_blocklist": "Blokkolási lista módosítása",
"edit_allowlist": "Engedélyezési lista módosítása",
"choose_blocklist": "Blokkolási lista választás",
"choose_allowlist": "Engedélyezési lista választás",
"enter_valid_blocklist": "Adjon meg egy érvényes URL-t a blokkolási listához.",
"enter_valid_allowlist": "Adjon meg egy érvényes URL-t az engedélyezési listához.",
"form_error_url_format": "Egyedi URL formátum",
"form_error_url_or_path_format": "Helytelen URL vagy elérési út a listához",
"custom_filter_rules": "Egyéni szűrési szabályok",
"custom_filter_rules_hint": "Adjon meg egy szabályt egy sorban. Használhatja az adblock szabályokat vagy a fájlokat tartalmazó szintaxist.",
"examples_title": "Példák",
"example_meaning_filter_block": "blokkolja a example.org domain és az összes aldomain hozzáférését",
"example_meaning_filter_whitelist": "example.org tartomány és az összes aldomain hozzáférésének feloldása",
"example_meaning_host_block": "Az AdGuard Home most visszatér a 127.0.0.1 címre a example.org domainhez (de nem az aldomainjeihez).",
"example_comment": "! Ide írhat egy megjegyzést",
"example_comment_meaning": "Csak egy megjegyzés",
"example_comment_hash": "# Megjegyzés is",
"example_regex_meaning": "megakadályozza a hozzáférést a reguláris kifejezéssel egyező domainek-nél",
"example_upstream_regular": "rendszeres DNS (UDP felett)",
"example_upstream_dot": "titkosított <0>DNS-over-TLS</0>",
"example_upstream_doh": "titkosított <0>DNS-over-HTTPS</0>",
"example_upstream_sdns": "a <0> DNS bélyegek </0> használatával <1> DNSCrypt </1> vagy <2> DNS-over-HTTPS </2>",
"example_upstream_tcp": "hagyományos DNS (TCP felett)",
"all_lists_up_to_date_toast": "Már minden lista naprakész",
"updated_upstream_dns_toast": "Frissítette az upstream DNS-kiszolgálókat",
"dns_test_ok_toast": "A megadott DNS-kiszolgálók megfelelően működnek",
"dns_test_not_ok_toast": "Szerver \"{{key}}\": nem használható, ellenőrizze, hogy helyesen írta-e be",
"unblock": "Blokkolás feloldása",
"block": "Blokkolás",
"time_table_header": "Idő",
"date": "Dátum",
"domain_name_table_header": "Domain név",
"domain_or_client": "Webcím vagy kliens",
"type_table_header": "Típus",
"response_table_header": "Válasz",
"response_code": "Válaszkód",
"client_table_header": "Kliens",
"empty_response_status": "Üres",
"show_all_filter_type": "Mutasd az összeset",
"show_filtered_type": "Szűrés megjelenítése",
"no_logs_found": "Nem található napló",
"refresh_btn": "Frissítés",
"previous_btn": "Előző",
"next_btn": "Következő",
"loading_table_status": "Töltés...",
"page_table_footer_text": "Oldal",
"rows_table_footer_text": "sor",
"updated_custom_filtering_toast": "Egyéni szűrési módok frissítése",
"rule_removed_from_custom_filtering_toast": "Szabály eltávolítva az egyedi szűrési módok közül",
"rule_added_to_custom_filtering_toast": "Szabály hozzáadva az egyedi szűrési módokhoz",
"query_log_response_status": "Státusz: {{value}}",
"query_log_filtered": "{{filter}} által szűrve",
"query_log_confirm_clear": "Biztosan törlöd a lekérdezési naplót?",
"query_log_cleared": "A lekérdezési napló sikeresen törölve",
"query_log_updated": "A lekérdezési napló sikeresen frissítve lett",
"query_log_clear": "Lekérdezési napló törlése",
"query_log_retention": "Lekérdezési naplók megtartása",
"query_log_enable": "Naplózás engedélyezése",
"query_log_configuration": "Naplózás beállítása",
"query_log_disabled": "Lekérdezési napló kikapcsolva. Bekapcsolható a <0>Beállítások</0>ban",
"query_log_strict_search": "Használj \"dupla idézőjelet\" a pontos kereséshez",
"query_log_retention_confirm": "Biztos benne, hogy megváltoztatja a kérések naplójának megőrzési idejét? Ha csökkentette az értéket, a megadottnál korábbi adatok elvesznek",
"anonymize_client_ip": "Kliens IP-címének anonimizálása",
"anonymize_client_ip_desc": "Ne mentse el a kliens teljes IP-címét a naplókban és a statisztikákban",
"dns_config": "DNS szerver beállításai",
"dns_cache_config": "DNS gyorsítótár beállításai",
"dns_cache_config_desc": "Itt tudja konfigurálni a DNS gyorsítótárat",
"blocking_mode": "Blokkolás módja",
"default": "Alapértelmezett",
"nxdomain": "NXDOMAIN",
"null_ip": "Null IP-cím",
"custom_ip": "Egyedi IP",
"blocking_ipv4": "IPv4 blokkolása",
"blocking_ipv6": "IPv6 blokkolása",
"dns_over_https": "DNS-over-HTTPS",
"dns_over_tls": "DNS-over-TLS",
"plain_dns": "Egyszerű DNS",
"form_enter_rate_limit": "Adja meg a kérések maximális számát",
"rate_limit": "Kérések korlátozása",
"edns_enable": "EDNS kliens alhálózat engedélyezése",
"edns_cs_desc": "Ha engedélyezve van, az AdGuard Home a kliensek alhálózatait küldi el a DNS-kiszolgálóknak.",
"rate_limit_desc": "Maximálisan hány kérést küldhet egy kliens másodpercenként (0: korlátlan)",
"blocking_ipv4_desc": "A blokkolt A kéréshez visszaadandó IP-cím",
"blocking_ipv6_desc": "A blokkolt AAAA kéréshez visszaadandó IP-cím",
"blocking_mode_default": "Alapértelmezés: Adblock-stílusú szabály esetén NXDOMAIN válasz küldése, /etc/hosts-stílusú szabály esetén pedig a szabályban meghatározott IP-címmel való válasz küldése",
"blocking_mode_nxdomain": "NXDOMAIN: Az NXDOMAIN kóddal fog válaszolni",
"blocking_mode_null_ip": "Null IP: Nullákból álló IP-címmel válaszol (0.0.0.0 for A; :: for AAAA)",
"blocking_mode_custom_ip": "Egyedi IP: Válasz egy kézzel beállított IP címmel",
"upstream_dns_client_desc": "Ha üresen hagyja ezt a mezőt, az AdGuard Home azokat a szervereket fogja használni, amik a <0>DNS beállításokban</0> vannak beállítva.",
"tracker_source": "Követő forrása",
"source_label": "Forrás",
"found_in_known_domain_db": "Benne van az ismert domainek listájában.",
"category_label": "Kategória",
"rule_label": "Szabály",
"list_label": "Lista",
"unknown_filter": "Ismeretlen szűrő: {{filterId}}",
"known_tracker": "Ismert követő",
"install_welcome_title": "Üdvözli az AdGuard Home!",
"install_welcome_desc": "Az AdGuard Home egy, a teljes hálózatot lefedő hirdetés és követő blokkoló DNS szerver. Az a célja, hogy lehetővé tegye a teljes hálózat és az összes eszköz vezérlését, és nem igényel kliensoldali programot.",
"install_settings_title": "Webes admin felület",
"install_settings_listen": "Figyelő felület",
"install_settings_port": "Port",
"install_settings_interface_link": "Az AdGuard Home webes admin felülete elérhető a következő címe(ke)n:",
"form_error_port": "Adjon meg egy érvényes portot",
"install_settings_dns": "DNS szerver",
"install_settings_dns_desc": "Be kell állítania az eszközeit vagy a routerét, hogy használni tudja a DNS szervert a következő címeken:",
"install_settings_all_interfaces": "Minden felület",
"install_auth_title": "Hitelesítés",
"install_auth_desc": "Erősen ajánlott a jelszavas hitelesítés beállítása az AdGuard Home webes admin felületéhez. Még akkor is, ha csak a helyi hálózaton érhető el, óvja meg az illetéktelen hozzáférésektől.",
"install_auth_username": "Felhasználónév",
"install_auth_password": "Jelszó",
"install_auth_confirm": "Jelszó megerősítése",
"install_auth_username_enter": "Felhasználónév megadása",
"install_auth_password_enter": "Jelszó megadása",
"install_step": "Lépés",
"install_devices_title": "Állítsa be az eszközeit",
"install_devices_desc": "Az AdGuard Home használatának megkezdéséhez be kell állítania az eszközeit, hogy azok használni tudják.",
"install_submit_title": "Gratulálunk!",
"install_submit_desc": "A telepítési folyamat befejeződött, minden készen áll az AdGuard Home használatára.",
"install_devices_router": "Router",
"install_devices_router_desc": "Ez a beállítás lefed minden eszközt, amik az Ön routeréhez csatlakoznak, így azokat nem kell külön, kézzel beállítania.",
"install_devices_address": "Az AdGuard DNS szerver a következő címeket figyeli",
"install_devices_router_list_1": "Nyissa meg a router beállításait. Ez általában a böngészőn keresztül történik egy URL megadásával (pl. http://192.168.0.1/ vagy http://192.168.1.1/). Ez az oldal valószínűleg felhasználónevet és jelszót fog kérni. Ha nem tudja a belépési adatokat, nézze meg a router dobozát, a router alján levő fehér címkét vagy a technikai dokumentációt az interneten, végső esetben pedig visszaállíthatja a routert. Néhány router speciális alkalmazást igényel, amik lehetséges, hogy már telepítve vannak a számítógépére vagy a mobil eszközére.",
"install_devices_router_list_2": "Keresse meg a DHCP/DNS beállításokat. Keresse a DNS szót egy olyan mező mellett, amely egy 4 csoportból álló, 1-3 számjegyű számsort vár.",
"install_devices_router_list_3": "Adja meg az AdGuard Home szerver címét itt.",
"install_devices_router_list_4": "Bizonyos típusú routereknél nem állíthat be egyéni DNS-kiszolgálót. Ebben az esetben segíthet, ha az AdGuard Home-t DHCP-szerverként állítja be. Ellenkező esetben keresse meg az adott router kézikönyvében a DNS-kiszolgálók testreszabását.",
"install_devices_windows_list_1": "Nyissa meg a Vezérlőpultot a Start menün vagy a Windows keresőn keresztül.",
"install_devices_windows_list_2": "Válassza a Hálózat és internet kategóriát, majd pedig a Hálózati és megosztási központot.",
"install_devices_windows_list_3": "A képernyő bal oldalán keresse meg az Adapterbeállítások módosítása lehetőséget és kattintson rá.",
"install_devices_windows_list_4": "Válassza ki a jelenleg is használt kapcsolatot, majd jobb egérgombbal kattintson rá és a megjelenő menüből válassza a Tulajdonságok elemet.",
"install_devices_windows_list_5": "Keresse meg az Internet Protocol Version 4 (TCP/IP) elemet a listában, válassza ki, majd ismét kattintson a Tulajdonságokra.",
"install_devices_windows_list_6": "Válassza a Következő DNS címek használata lehetőséget és adja meg az AdGuard Home szerver címeit.",
"install_devices_macos_list_1": "Kattintson az Apple ikonra és válassza a Rendszerbeállításokat.",
"install_devices_macos_list_2": "Kattintson a Hálózat lehetőségre.",
"install_devices_macos_list_3": "Válassza ki az első kapcsolatot a listából és kattintson a Haladó beállításokra.",
"install_devices_macos_list_4": "Válassza ki a DNS fület és adja meg az AdGuard Home szerver címeit.",
"install_devices_android_list_1": "Az Android kezdőképernyőjén érintse meg a Beállítások gombot.",
"install_devices_android_list_2": "Érintse meg a Wi-Fi gombot a menüben. Ekkor a képernyőre kerül az összes elérhető hálózat (mobilinternethez nem lehet egyedi DNS-t megadni).",
"install_devices_android_list_3": "Nyomjon hosszan arra a hálózatra a listából, amelyikre éppen csatlakozva van, majd válassza a Hálózat módosítása lehetőséget.",
"install_devices_android_list_4": "Egyes eszközökön előfordulhat, hogy a további beállítások megtekintéséhez a Speciális/haladó beállítások részt kell megnyitni. Az Android DNS-beállításainak módosításához ekkor az IP-beállításokat DHCP-ről statikusra kell váltania.",
"install_devices_android_list_5": "Változtassa meg a DNS 1 és a DNS 2 értékét az AdGuard Home szerver címeire.",
"install_devices_ios_list_1": "A kezdőképernyőn érintse meg a Beállítások gombot.",
"install_devices_ios_list_2": "Válassza ki a Wi-Fi-t a bal oldali menüből (mobilinternetnél nem lehetséges a DNS beállítása).",
"install_devices_ios_list_3": "Érintse meg a jelenleg használt hálózat nevét.",
"install_devices_ios_list_4": "A DNS mezőbe adja meg az AdGuard Home szerver címeit.",
"get_started": "Kezdés",
"next": "Következő",
"open_dashboard": "Irányítópult megnyitása",
"install_saved": "Sikeres mentés",
"encryption_title": "Titkosítás",
"encryption_desc": "Titkosítás (HTTPS/TLS) támogatása mind a DNS, mind pedig a webes admin felület számára",
"encryption_config_saved": "Titkosítási beállítások mentve",
"encryption_server": "Szerver neve",
"encryption_server_enter": "Adja meg az Ön domain címét",
"encryption_server_desc": "A HTTPS használatához be kell írnia egy, az SSL-tanúsítvánnyal megegyező kiszolgálónevet.",
"encryption_redirect": "Automatikus átirányítás HTTPS kapcsolatra",
"encryption_redirect_desc": "Ha be van jelölve, az AdGuard Home automatikusan átirányítja a HTTP kapcsolatokat a biztonságos HTTPS protokollra.",
"encryption_https": "HTTPS port",
"encryption_https_desc": "Ha a HTTPS port konfigurálva van, akkor az AdGuard Home admin felülete elérhető lesz a HTTPS-en keresztül, és ezenkívül DNS-over-HTTPS-t is biztosít a '/dns-query' helyen.",
"encryption_dot": "DNS-over-TLS port",
"encryption_dot_desc": "Ha ez a port be van állítva, az AdGuard Home DNS-over-TLS szerverként tud futni ezen a porton.",
"encryption_certificates": "Tanúsítványok",
"encryption_certificates_desc": "A titkosítás használatához érvényes SSL tanúsítványláncot kell megadnia a domainjéhez. Ingyenes tanúsítványt kaphat a <0>{{link}}</0> webhelyen, vagy megvásárolhatja az egyik megbízható tanúsítványkibocsátó hatóságtól.",
"encryption_certificates_input": "Másolja be ide a PEM-kódolt tanúsítványt.",
"encryption_status": "Állapot",
"encryption_expire": "Lejár",
"encryption_key": "Privát kulcs",
"encryption_key_input": "Másolja ki és illessze be ide a tanúsítványa PEM-kódolt privát kulcsát.",
"encryption_enable": "Titkosítás engedélyezése (HTTPS, DNS-over-HTTPS, és DNS-over-TLS)",
"encryption_enable_desc": "Ha a titkosítás engedélyezve van, az AdGuard Home admin felülete működik HTTPS-en keresztül, és a DNS szerver is várja a kéréseket DNS-over-HTTPS-en, valamint DNS-over-TLS-en keresztül.",
"encryption_chain_valid": "A tanúsítványlánc érvényes",
"encryption_chain_invalid": "A tanúsítványlánc érvénytelen",
"encryption_key_valid": "Ez egy érvényes {{type}} privát kulcs",
"encryption_key_invalid": "Ez egy érvénytelen {{type}} privát kulcs",
"encryption_subject": "Tárgy",
"encryption_issuer": "Kibocsátó",
"encryption_hostnames": "Hosztnevek",
"encryption_reset": "Biztosan visszaállítja a titkosítási beállításokat?",
"topline_expiring_certificate": "Az SSL-tanúsítványa hamarosan lejár. Frissítse a <0>Titkosítási beállításokat</0>.",
"topline_expired_certificate": "Az SSL-tanúsítványa lejárt. Frissítse a <0>Titkosítási beállításokat</0>.",
"form_error_port_range": "A port értékét a 80-65535 tartományban adja meg",
"form_error_port_unsafe": "Ez a port nem biztonságos",
"form_error_equal": "Nem egyezhetnek",
"form_error_password": "A jelszavak nem egyeznek",
"reset_settings": "Beállítások visszaállítása",
"update_announcement": "Az AdGuard Home {{version}} verziója elérhető! <0>Kattintson ide</0> további információkért.",
"setup_guide": "Beállítási útmutató",
"dns_addresses": "DNS címek",
"dns_start": "A DNS szerver indul",
"dns_status_error": "Hiba történt a DNS szerver állapotának ellenőrzésekor",
"down": "Nem elérhető",
"fix": "Állandó",
"dns_providers": "Itt van az <0>ismert DNS szolgáltatók listája</0>, amelyekből választhat.",
"update_now": "Frissítés most",
"update_failed": "Az automatikus frissítés nem sikerült. Kérjük, hogy <a href='https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started#update'>kövesse ezeket a lépéseket</a> a manuális frissítéshez.",
"processing_update": "Kérjük várjon, az AdGuard Home frissítése folyamatban van",
"clients_title": "Kliensek",
"clients_desc": "Az AdGuard Home-hoz csatlakozó eszközök kezelése",
"settings_global": "Globális",
"settings_custom": "Egyéni",
"table_client": "Kliens",
"table_name": "Név",
"save_btn": "Mentés",
"client_add": "Kliens hozzáadása",
"client_new": "Új kliens",
"client_edit": "Kliens módosítása",
"client_identifier": "Azonosító",
"ip_address": "IP cím",
"client_identifier_desc": "A klienseket be lehet azonosítani IP-cím, CIDR, valamint MAC-cím alapján. Kérjük, vegye figyelembe, hogy a MAC-cím alapján történő azonosítás csak akkor működik, ha az AdGuard Home egyben <0>DHCP szerverként</0> is funkcionál",
"form_enter_ip": "IP-cím megadása",
"form_enter_mac": "MAC-cím megadása",
"form_enter_id": "Azonosító megadása",
"form_add_id": "Azonosító hozzáadása",
"form_client_name": "Adja meg a kliens nevét",
"name": "Név",
"client_global_settings": "Globális beállítások használata",
"client_deleted": "A(z) \"{{key}}\" kliens sikeresen el lett távolítva",
"client_added": "A(z) \"{{key}}\" kliens sikeresen hozzá lett adva",
"client_updated": "A(z) \"{{key}}\" kliens sikeresen frissítve lett",
"clients_not_found": "Nem található kliens",
"client_confirm_delete": "Biztosan törölni szeretné az \"{{key}}\" klienst?",
"list_confirm_delete": "Biztosan törölni kívánja ezt a listát?",
"auto_clients_title": "Kliensek (futási idő)",
"auto_clients_desc": "Az AdGuard Home-ot használó, de a konfigurációban nem tárolt ügyfelek adatai",
"access_title": "Hozzáférési beállítások",
"access_desc": "Itt konfigurálhatja az AdGuard Home DNS-kiszolgáló hozzáférési szabályait.",
"access_allowed_title": "Engedélyezett kliensek",
"access_allowed_desc": "A CIDR vagy IP címek listája. Ha konfigurálva van, az AdGuard Home csak az alábbi IP-címekről fogadja el a kéréseket.",
"access_disallowed_title": "Nem engedélyezett kliensek",
"access_disallowed_desc": "A CIDR vagy IP címek listája. Ha konfigurálva van, az AdGuard Home ebből az IP-címből lekéri a kéréseket.",
"access_blocked_title": "Blokkolt tartományok",
"access_blocked_desc": "Ne keverje össze ezt a szűrőkkel. Az AdGuard Home a DNS-lekérdezéseket a lekérdezés kérdésében el fogja hagyni ezeken a tartományokon",
"access_settings_saved": "A hozzáférési beállítások sikeresen mentésre kerültek",
"updates_checked": "A frissítések sikeresen ellenőrizve lettek",
"updates_version_equal": "Az AdGuard Home naprakész",
"check_updates_now": "Frissítések ellenőrzése most",
"dns_privacy": "DNS Adatvédelem",
"setup_dns_privacy_1": "<0>DNS-over-TLS:</0> Használja a(z) <1>{{address}}</1> szöveget.",
"setup_dns_privacy_2": "<0>DNS-over-HTTPS:</0> Használja a(z) <1>{{address}}</1> szöveget.",
"setup_dns_privacy_3": "<0>Kérjük, vegye figyelembe, hogy a titkosított DNS protokollok csak Android 9-től vannak támogatva. Tehát további szoftvert kell telepítenie más operációs rendszerekhez.</0><0>Itt megtalálja azon szoftverek listáját, amelyeket használhat.</0>",
"setup_dns_privacy_android_1": "Az Android 9 natív módon támogatja a DNS-over-TLS-t. A beállításához menjen a Beállítások → Hálózat & internet → Speciális → Privát DNS menübe, és adja meg itt a domaint.",
"setup_dns_privacy_android_2": "Az <0>AdGuard for Android</0> támogatja a <1>DNS-over-HTTPS</1>-t és a <1>DNS-over-TLS</1>-t.",
"setup_dns_privacy_android_3": "Az <0>Intra</0> hozzáadja a <1>DNS-over-HTTPS</1> támogatást az Androidhoz.",
"setup_dns_privacy_ios_1": "A <0>DNSCloak</0> támogatja a <1>DNS-over-HTTPS</1>-t, de ahhoz, hogy a saját szerverhez konfigurálhassa, létre kell hoznia egy <2>DNS bélyeget</2> hozzá.",
"setup_dns_privacy_ios_2": "Az <0>AdGuard for iOS</0> támogatja a <1>DNS-over-HTTPS</1> és a <1>DNS-over-TLS</1> beállításokat.",
"setup_dns_privacy_other_title": "Egyéb megvalósítások",
"setup_dns_privacy_other_1": "Maga az AdGuard Home bármilyen platformon biztonságos DNS-kliens lehet.",
"setup_dns_privacy_other_2": "A <0>dnsproxy</0> támogatja az összes ismert biztonságos DNS protokollt.",
"setup_dns_privacy_other_3": "A <0>dnscrypt-proxy</0> támogatja a <1>DNS-over-HTTPS</1>-t.",
"setup_dns_privacy_other_4": "A <0>Mozilla Firefox</0> támogatja a <1>DNS-over-HTTPS</1>-t.",
"setup_dns_privacy_other_5": "További megvalósításokat találhat <0>ide</0> és <1>ide</1> kattintva.",
"setup_dns_notice": "Ahhoz, hogy a <1>DNS-over-HTTPS</1> vagy a <1>DNS-over-TLS</1> valamelyikét használja, muszáj <0>beállítania a titkosítást</0> az AdGuard Home beállításaiban.",
"rewrite_added": "DNS átírás a(z) \"{{key}}\" kulcshoz sikeresen hozzáadva",
"rewrite_deleted": "DNS átírás a(z) \"{{key}}\" kulcshoz sikeresen törölve",
"rewrite_add": "DNS átírás hozzáadása",
"rewrite_not_found": "Nem találhatók DNS átírások",
"rewrite_confirm_delete": "Biztosan törölni szeretné a DNS átírást ehhez: \"{{key}}\"?",
"rewrite_desc": "Lehetővé teszi, hogy egyszerűen beállítson egyéni DNS választ egy adott domain névhez.",
"rewrite_applied": "Alkalmazott átírási szabály",
"rewrite_hosts_applied": "Átírt a gazda fájl szabálya által",
"dns_rewrites": "DNS átírások",
"form_domain": "Adja meg a domain nevet vagy a helyettesítő karaktert",
"form_answer": "Adjon meg egy IP-címet vagy egy domain nevet",
"form_error_domain_format": "Érvénytelen domain formátum",
"form_error_answer_format": "Érvénytelen válasz formátum",
"configure": "Beállítás",
"main_settings": "Fő beállítások",
"block_services": "Speciális szolgáltatások blokkolása",
"blocked_services": "Blokkolt szolgáltatások",
"blocked_services_desc": "Lehetővé teszi a népszerű oldalak és szolgáltatások blokkolását.",
"blocked_services_saved": "Blokkolt szolgáltatások sikeresen mentve",
"blocked_services_global": "A globálisan tiltott szolgáltatások használata",
"blocked_service": "Blokkolt szolgáltatás",
"block_all": "Összes blokkolása",
"unblock_all": "Összes feloldása",
"encryption_certificate_path": "Tanúsítvány útvonala",
"encryption_private_key_path": "Privát kulcs útvonala",
"encryption_certificates_source_path": "Tanúsítványfájl útvonalának megadása",
"encryption_certificates_source_content": "Tanúsítvány tartalmának megadása",
"encryption_key_source_path": "Privát kulcsfájl beállítása",
"encryption_key_source_content": "Privát kulcs tartalmának megadása",
"stats_params": "Statisztikai beállítások",
"config_successfully_saved": "A beállítások sikeresen el lettek mentve",
"interval_24_hour": "24 óra",
"interval_days": "{{count}} nap",
"interval_days_plural": "{{count}} nap",
"domain": "Domain",
"answer": "Válasz",
"filter_added_successfully": "A lista sikeresen hozzá lett adva",
"filter_removed_successfully": "A lista sikeresen el lett távolítva",
"filter_updated": "A lista sikeresen frissítve lett",
"statistics_configuration": "Statisztikai beállítások",
"statistics_retention": "Statisztika megőrzése",
"statistics_retention_desc": "Ha csökkenti az intervallum értékét, az előtte levő adatok elvesznek",
"statistics_clear": " Statisztikák visszaállítása",
"statistics_clear_confirm": "Biztosan vissza akarja állítani a statisztikákat?",
"statistics_retention_confirm": "Biztos benne, hogy megváltoztatja a statisztika megőrzési idejét? Ha csökkentette az értéket, a megadottnál korábbi adatok elvesznek",
"statistics_cleared": "A statisztikák sikeresen vissza lettek állítva",
"interval_hours": "{{count}} óra",
"interval_hours_plural": "{{count}} óra",
"filters_configuration": "Szűrők beállításai",
"filters_enable": "Szűrők engedélyezése",
"filters_interval": "Szűrőfrissítési gyakoriság",
"disabled": "Kikapcsolva",
"username_label": "Felhasználónév",
"username_placeholder": "Felhasználónév megadása",
"password_label": "Jelszó",
"password_placeholder": "Jelszó megadása",
"sign_in": "Bejelentkezés",
"sign_out": "Kijelentkezés",
"forgot_password": "Elfelejtette a jelszót?",
"forgot_password_desc": "Kérjük, hogy kövesse <0>ezeket a lépéseket</0> a jelszó visszaállításához.",
"location": "Helyzet",
"orgname": "Szervezet neve",
"netname": "Hálózat neve",
"network": "Hálózat",
"descr": "Leírás",
"whois": "Whois",
"filtering_rules_learn_more": "<0>Tudjon meg többet</0> a saját hosztlisták létrehozásáról.",
"blocked_by_response": "Blokkolva a CNAME vagy a válasz IP-címe alapján",
"blocked_by_cname_or_ip": "CNAME vagy IP által blokkolva",
"try_again": "Próbálja újra",
"domain_desc": "Adja meg a domain nevet vagy a helyettesítő karaktert ahhoz a címhez, amit át kíván íratni.",
"example_rewrite_domain": "csak ehhez a domainhez írja át a válaszokat.",
"example_rewrite_wildcard": "az <0>example.org</0> összes aldomainjéhez átírja a válaszokat.",
"rewrite_ip_address": "IP-cím: használja ezt az IP-t A vagy AAAA válaszban",
"rewrite_domain_name": "Domain név: CNAME rekord hozzáadása",
"rewrite_A": "<0>A</0>: speciális érték, megtartja az upstream felől érkező <0>A</0> rekordokat",
"rewrite_AAAA": "<0>AAAA</0>: speciális érték, megtartja az upstream felől érkező <0>AAAA</0> rekordokat",
"disable_ipv6": "IPv6 letiltása",
"disable_ipv6_desc": "Ha ez a szolgáltatás engedélyezve van, akkor az összes IPv6-cím (AAAA típus) DNS-lekérdezése elveszik.",
"fastest_addr": "Leggyorsabb IP-cím",
"fastest_addr_desc": "Kérdezze le az összes DNS szervert és küldje vissza a leggyorsabb IP-címet az összes válasz alapján",
"autofix_warning_text": "Ha a \"Javítás\" lehetőségre kattint, az AdGuard Home megpróbálja beállítani a rendszerét, hogy használja az AdGuard Home DNS szervert.",
"autofix_warning_list": "A következő feladatokat hajtja végre: <0>A DNSStubListener rendszer kikapcsolása</0><0>Beállítja a DNS-kiszolgáló címét 127.0.0.1-re.</0><0>Lecseréli az /etc/resolv.conf szimbolikus útvonalat erre: /run/systemd/resolve/resolv.conf</0><0>A DNSStubListener leállítása (a rendszer által feloldott szolgáltatás újratöltése)</0>",
"autofix_warning_result": "Mindennek eredményeként az Ön rendszeréből származó összes DNS-kérést alapértelmezés szerint az AdGuard Home dolgozza fel.",
"tags_title": "Címkék",
"tags_desc": "Kiválaszthatja a klienseknek megfelelő címkéket. A címkék beilleszthetők a szűrési szabályokba, és lehetővé teszik azok pontosabb alkalmazását. <0>További információ</0>",
"form_select_tags": "Válasszon kliens címkéket",
"check_title": "Szűrés ellenőrzése",
"check_desc": "Ellenőrzi, hogy a hosztnév szűrve van-e",
"check": "Ellenőrzés",
"form_enter_host": "Adja meg a hosztnevet",
"filtered_custom_rules": "Szűrve van az egyéni szűrési szabályok alapján",
"choose_from_list": "Választás a listából",
"add_custom_list": "Egyedi lista hozzáadása",
"host_whitelisted": "Ez a hoszt a kivételek között szerepel",
"check_ip": "IP-címek: {{ip}}",
"check_cname": "CNAME: {{cname}}",
"check_reason": "Indok: {{reason}}",
"check_rule": "Szabály: {{rule}}",
"check_service": "Szolgáltatás neve: {{service}}",
"check_not_found": "Nem található az Ön szűrőlistái között",
"client_confirm_block": "Biztosan blokkolni szeretné a(z) \"{{ip}}\" klienst?",
"client_confirm_unblock": "Biztosan fel szeretné oldani a(z) \"{{ip}}\" kliens blokkolását?",
"client_blocked": "A(z) \"{{ip}}\" kliens sikeresen blokkolva",
"client_unblocked": "A(z) \"{{ip}}\" kliens blokkolása sikeresen feloldva",
"static_ip": "Statikus IP-cím",
"static_ip_desc": "Az AdGuard Home egy szerver, tehát statikus IP-címre van szüksége a megfelelő működéshez. Ellenkező esetben a router valamikor más IP-címet rendelhet ehhez az eszközhöz.",
"set_static_ip": "Statikus IP-cím beállítása",
"install_static_ok": "Jó hír! A statikus IP-cím már be van állítva",
"install_static_error": "Az AdGuard Home nem tudja automatikusan konfigurálni ezt a hálózati felületet. Kérjük, nézzen utána, hogyan kell ezt manuálisan elvégezni.",
"install_static_configure": "Úgy észleltük, hogy dinamikus IP-cím van használatban — <0>{{ip}}</0>. Szeretné ezt statikus IP-címként használni?",
"confirm_static_ip": "Az AdGuard Home beállítja az {{ip}} IP-címet az Ön statikus IP-címének. Biztosan folytatni kívánja?",
"list_updated": "{{count}} lista frissítve lett",
"list_updated_plural": "{{count}} lista frissítve lett",
"dnssec_enable": "DNSSEC engedélyezése",
"dnssec_enable_desc": "Állítsa be a DNSSEC jelzőt a kimenő DNS-lekérdezésekbe, és ellenőrizze az eredményt (szükséges a DNSSEC-kompatibilis feloldás)",
"validated_with_dnssec": "DNSSEC-kel ellenőrizve",
"all_queries": "Minden kérés",
"show_blocked_responses": "Tiltva",
"show_whitelisted_responses": "Kivételezett",
"show_processed_responses": "Feldolgozott",
"blocked_safebrowsing": "Blokkolva a biztonságos böngészés által",
"blocked_adult_websites": "Blokkolva a felnőtt tartalmak által",
"blocked_threats": "Blokkolt fenyegetések",
"allowed": "Engedélyezve",
"filtered": "Megszűrt",
"rewritten": "Átírt",
"safe_search": "Biztonságos keresés",
"blocklist": "Tiltási lista",
"milliseconds_abbreviation": "ms",
"cache_size": "Gyorsítótár mérete",
"cache_size_desc": "DNS gyorsítótár mérete (bájtokban)",
"enter_cache_size": "Adja meg a gyorsítótár méretét",
"enter_cache_ttl_min_override": "Adja meg a minimális TTL-t",
"enter_cache_ttl_max_override": "Adja meg a maximális TTL-t",
"cache_ttl_min_override_desc": "Felülbírálja az upstream kiszolgálótól kapott (minimális) TTL értéket. Ez az érték nem lehet nagyobb, mint 3600 másodperc (vagyis 1 óra)",
"cache_ttl_max_override_desc": "Felülbírálja az upstream kiszolgálótól kapott (maximális) TTL értéket",
"min_exceeds_max_value": "A minimális érték meghaladja a maximális értéket",
"value_not_larger_than": "Az érték nem lehet nagyobb, mint {{maximum}}",
"filter_category_general": "Általános",
"filter_category_security": "Biztonság",
"filter_category_regional": "Regionális",
"filter_category_other": "Egyéb",
"filter_category_general_desc": "Olyan listák, amelyek blokkolják a nyomkövetést és a hirdetéseket a legtöbb eszközön",
"filter_category_security_desc": "Olyan listák, amelyek a kártékony, adathalász vagy átverős oldalak tiltására vannak kifejlesztve",
"filter_category_regional_desc": "Olyan listák, amelyek a regionális hirdetések, valamint a nyomkövető szerverek ellen vannak kifejlesztve",
"filter_category_other_desc": "További tiltólisták",
"original_response": "Eredeti válasz",
"click_to_view_queries": "Kattintson a lekérésekért",
"port_53_faq_link": "Az 53-as portot gyakran a \"DNSStubListener\" vagy a \"systemd-resolved\" (rendszer által feloldott) szolgáltatások használják. Kérjük, olvassa el <0>ezt az útmutatót</0> a probléma megoldásához."
}

View File

@ -0,0 +1,443 @@
{
"client_settings": "අනුග්‍රාහක සැකසුම්",
"check_dhcp_servers": "DHCP සේවාදායකයන් සඳහා පරීක්ෂා කරන්න",
"save_config": "වින්‍යාසය සුරකින්න",
"enabled_dhcp": "DHCP සේවාදායකය සබල කර ඇත",
"disabled_dhcp": "DHCP සේවාදායකය අබල කර ඇත",
"dhcp_title": "ග.ධා.වි.කෙ. සේවාදායකය (පර්යේෂණාත්මක!)",
"dhcp_description": "ඔබගේ මාර්ගකාරකය ග.ධා.වි.කෙ. (DHCP) සැකසුම් ලබා නොදෙන්නේ නම්, ඔබට AdGuardHome හි ඇති ග.ධා.වි.කෙ. සේවාදායකය භාවිතා කළ හැකිය.",
"dhcp_enable": "DHCP සේවාදායකය සබල කරන්න",
"dhcp_disable": "DHCP සේවාදායකය අබල කරන්න",
"dhcp_config_saved": "DHCP වින්‍යාසය සාර්ථකව සුරකින ලදි",
"form_error_required": "අවශ්‍ය ක්ෂේත්‍රයකි",
"form_error_ip4_format": "වලංගු නොවන IPv4 ආකෘතියකි",
"form_error_ip6_format": "වලංගු නොවන IPv6 ආකෘතියකි",
"form_error_ip_format": "වලංගු නොවන අ.ජා. කෙ. (IP) ආකෘතියකි",
"form_error_mac_format": "වලංගු නොවන MAC ආකෘතියකි",
"form_error_client_id_format": "වලංගු නොවන අනුග්‍රාහක හැඳුනුම් ආකෘතියකි",
"form_error_positive": "0 ට වඩා වැඩි විය යුතුය",
"form_error_negative": "0 හෝ ඊට වැඩි විය යුතුය",
"dhcp_form_range_title": "අ.ජා. කෙ. (IP) ලිපින පරාසය",
"dhcp_form_range_start": "පරාසය ආරම්භය",
"dhcp_form_range_end": "පරාසය අවසානය",
"dhcp_interface_select": "ග.ධා.වි.කෙ. (DHCP) අතුරුමුහුණත තෝරන්න",
"dhcp_hardware_address": "දෘඩාංග ලිපිනය",
"dhcp_ip_addresses": "අ.ජා. කෙ. (IP) ලිපින",
"ip": "අ.ජා. කෙ. (IP)",
"dhcp_table_hostname": "ධාරක නාමය",
"dhcp_table_expires": "කල් ඉකුත් වීම",
"dhcp_warning": "ඔබට කෙසේ හෝ ග.ධා.වි.කෙ. (DHCP) සේවාදායකය සබල කිරීමට අවශ්‍ය නම්, ඔබේ ජාලයේ වෙනත් ක්‍රියාකාරී ග.ධා.වි.කෙ. සේවාදායකයක් නොමැති බව තහවුරු කරගන්න. එසේ නොමැති නම්, එය සම්බන්ධිත උපාංග සඳහා අන්තර්ජාලය බිඳ දැමිය හැකිය!",
"dhcp_error": "ජාලයේ තවත් ග.ධා.වි.කෙ. (DHCP) සේවාදායකයක් තිබේද යන්න අපට තීරණය කළ නොහැකි විය.",
"dhcp_static_ip_error": "ග.ධා.වි.කෙ. (DHCP) සේවාදායකය භාවිතා කිරීම සඳහා ස්ථිතික අන්තර්ජාල කෙටුම්පත් (IP) ලිපිනයක් සැකසිය යුතුය. මෙම ජාල අතුරුමුහුණත ස්ථිතික අ.ජා. කෙ. ලිපිනයක් භාවිතයෙන් වින්‍යාසගත කර තිබේද යන්න තීරණය කිරීමට අප අසමත් විය. කරුණාකර ස්ථිතික අ.ජා. කෙ. ලිපිනයක් අතින් සකසන්න.",
"dhcp_dynamic_ip_found": "ඔබේ පද්ධතිය <0>{{interfaceName}}</0> අතුරු මුහුණත සඳහා ගතික අන්තර්ජාල කෙටුම්පත් (IP) ලිපින වින්‍යාසය භාවිතා කරයි. ග.ධා.වි.කෙ. (DHCP) සේවාදායකය භාවිතා කිරීම සඳහා ස්ථිතික අ.ජා. කෙ. ලිපිනයක් සැකසිය යුතුය. ඔබගේ වර්තමාන අ.ජා. කෙ. ලිපිනය <0>{{ipAddress}}</0> වේ. ඔබ ග.ධා.වි.කෙ. සබල කරන්න බොත්තම එබුවහොත් අපි ස්වයංක්‍රීයව මෙම අ.ජා. කෙ. ලිපිනය ස්ථිතික ලෙස සකසන්නෙමු.",
"dhcp_reset": "ග.ධා.වි.කෙ. (DHCP) වින්‍යාසය යළි පිහිටුවීමට අවශ්‍ය බව ඔබට විශ්වාස ද?",
"country": "රට",
"city": "නගරය",
"delete_confirm": "\"{{key}}\" මකා දැමීමට අවශ්‍ය බව ඔබට විශ්වාසද?",
"form_enter_hostname": "ධාරක නාමය ඇතුළත් කරන්න",
"error_details": "දෝෂ විස්තර",
"response_details": "ප්‍රතිචාරය‌ෙහි විස්තර",
"request_details": "ඉල්ලීමෙහි විස්තර",
"client_details": "අනුග්‍රාහකයේ විස්තර",
"details": "විස්තර",
"back": "ආපසු",
"dashboard": "උපකරණ පුවරුව",
"settings": "සැකසුම්",
"filters": "පෙරහන්",
"filter": "පෙරහන",
"query_log": "විමසුම් ලොගය",
"nothing_found": "කිසිවක් සොයාගත නොහැක",
"faq": "නිති අසන පැණ",
"version": "අනුවාදය",
"address": "ලිපිනය",
"protocol": "කෙටුම්පත",
"on": "සක්‍රියයි",
"off": "අක්‍රියයි",
"copyright": "ප්‍රකාශන හිමිකම",
"homepage": "මුල් පිටුව",
"report_an_issue": "ගැටලුවක් වාර්තා කරන්න",
"privacy_policy": "රහස්‍යතා ප්‍රතිපත්තිය",
"enable_protection": "ආරක්ෂණය සබල කරන්න",
"enabled_protection": "ආරක්ෂණය සබල කර ඇත",
"disable_protection": "ආරක්ෂණය අබල කරන්න",
"disabled_protection": "ආරක්ෂණය අබල කර ඇත",
"refresh_statics": "සංඛ්‍යාලේඛන නැවුම් කරන්න",
"dns_query": "ව.නා.ප. (DNS) විමසුම්",
"blocked_by": "<0>පෙරහන් මගින් අවහිර කරන ලද</0>",
"stats_malware_phishing": "අවහිර කළ ද්වේශාංග/තතුබෑම්",
"stats_adult": "අවහිර කළ වැඩිහිටි වෙබ් අඩවි",
"stats_query_domain": "ජනප්‍රිය විමසන ලද වසම්",
"for_last_24_hours": "පසුගිය පැය 24 සඳහා",
"for_last_days": "පසුගිය දින {{count}} සඳහා",
"for_last_days_plural": "පසුගිය දින {{count}} සඳහා",
"no_domains_found": "වසම් කිසිවක් සොයා ගත නොහැකි විය",
"requests_count": "ඉල්ලීම් ගණන",
"top_blocked_domains": "ජනප්‍රිය අවහිර කළ වසම්",
"top_clients": "ජනප්‍රිය අනුග්‍රාහකයන්",
"general_statistics": "පොදු සංඛ්යාලේඛන",
"number_of_dns_query_blocked_24_hours": "දැන්වීම් වාරණ පෙරහන් සහ ධාරක වාරණ ලැයිස්තු මගින් අවහිර කරන ලද DNS ඉල්ලීම් ගණන",
"number_of_dns_query_blocked_24_hours_by_sec": "AdGuard browsing security ඒකකය මගින් අවහිර කරන ලද DNS ඉල්ලීම් ගණන",
"number_of_dns_query_blocked_24_hours_adult": "අවහිර කළ වැඩිහිටි වෙබ් අඩවි ගණන",
"enforced_save_search": "ආරක්ෂිත සෙවීම බලාත්මක කරන ලද",
"number_of_dns_query_to_safe_search": "ආරක්ෂිත සෙවීම බලාත්මක කළ සෙවුම් යන්ත්‍ර සඳහා DNS ඉල්ලීම් ගණන",
"average_processing_time": "සාමාන්‍ය සැකසුම් කාලය",
"average_processing_time_hint": "DNS ඉල්ලීමක් සැකසීමේ සාමාන්‍ය කාලය මිලි තත්පර වලින්",
"block_domain_use_filters_and_hosts": "පෙරහන් සහ ධාරක ගොනු භාවිතා කරමින් වසම් අවහිර කරන්න",
"filters_block_toggle_hint": "ඔබට අවහිර කිරීමේ නීති <a href='#filters'>පෙරහන්</a> තුළ පිහිටුවිය හැකිය.",
"use_adguard_browsing_sec": "AdGuard browsing security web service භාවිතා කරන්න",
"use_adguard_parental": "AdGuard parental control වෙබ් සේවාව භාවිතා කරන්න",
"use_adguard_parental_hint": "වසමේ වැඩිහිටියන්ට අදාල කරුණු අඩංගු දැයි AdGuard Home විසින් පරීක්ෂා කරනු ඇත. එය browsing security වෙබ් සේවාව මෙන් රහස්‍යතා හිතකාමී API භාවිතා කරයි.",
"enforce_safe_search": "ආරක්ෂිත සෙවීම බලාත්මක කරන්න",
"enforce_save_search_hint": "AdGuard Home හට පහත සෙවුම් යන්ත්‍ර තුළ ආරක්ෂිත සෙවීම බලාත්මක කළ හැකිය: Google, Youtube, Bing, DuckDuckGo සහ Yandex.",
"no_servers_specified": "සේවාදායක කිසිවක් නිශ්චිතව දක්වා නැත",
"general_settings": "පොදු සැකසුම්",
"dns_settings": "DNS සැකසුම්",
"dns_blocklists": "DNS අවහිර කිරී‌‌‌‌‌මේ ලැයිස්තු",
"dns_allowlists": "DNS අවසර දීමේ ලැයිස්තු",
"dns_allowlists_desc": "DNS අවසර දීමේ ලැයිස්තුවල වසම් කිසියම් අවහිර කිරී‌‌‌‌‌මේ ලැයිස්තුව අඩංගු වුවද එය න‌ොසලකා හැර අවසර දෙනු ලැබේ.",
"custom_filtering_rules": "අභිරුචි පෙරීමේ නීති",
"encryption_settings": "සංකේතාංකන සැකසුම්",
"dhcp_settings": "DHCP සැකසුම්",
"disabled_filtering_toast": "පෙරීම අබල කර ඇත",
"enabled_filtering_toast": "පෙරීම සබල කර ඇත",
"disabled_parental_toast": "Parental control අබල කර ඇත",
"enabled_parental_toast": "Parental control සබල කර ඇත",
"disabled_safe_search_toast": "ආරක්ෂිත සෙවීම අබල කර ඇත",
"enabled_save_search_toast": "ආරක්ෂිත සෙවීම සබල කර ඇත",
"enabled_table_header": "සබල කර ඇත",
"name_table_header": "නම",
"list_url_table_header": "URL ලැයිස්තුව",
"rules_count_table_header": "නීති ගණන",
"last_time_updated_table_header": "අවසන් වරට යාවත්කාලීන කරන ලද",
"actions_table_header": "ක්‍රියාමාර්ග",
"request_table_header": "ඉල්ලීම",
"edit_table_action": "සංස්කරණය කරන්න",
"delete_table_action": "මකන්න",
"no_blocklist_added": "අවහිර කිරීමේ ලැයිස්තු එකතු කර නැත",
"no_whitelist_added": "අවසර දීමේ ලැයිස්තු එකතු කර නැත",
"add_blocklist": "අවහිර කිරී‌‌‌‌‌මේ ලැයිස්තුවක් එකතු කරන්න",
"add_allowlist": "අවසර දීමේ ලැයිස්තුවක් එකතු කරන්න",
"cancel_btn": "අහෝසි කරන්න",
"enter_name_hint": "නම ඇතුළත් කරන්න",
"enter_url_or_path_hint": "ලැයිස්තුවක URL හෝ ස්ථීර මාර්ගයක් ඇතුළත් කරන්න",
"check_updates_btn": "යාවත්කාල පරීක්ෂා කරන්න",
"new_blocklist": "නව අවහිර කිරී‌‌‌‌‌මේ ලැයිස්තුව",
"new_allowlist": "නව අවසර දීමේ ලැයිස්තුව",
"edit_blocklist": "අවහිර කිරී‌‌‌‌‌මේ ලැයිස්තුව සංස්කරණය කරන්න",
"edit_allowlist": "අවසර දීමේ ලැයිස්තුව සංස්කරණය කරන්න",
"enter_valid_blocklist": "අවහිර කිරී‌‌‌‌‌මේ ලැයිස්තුවට වලංගු URL ලිපිනයක් ඇතුළත් කරන්න.",
"enter_valid_allowlist": "අවසර දීමේ ලැයිස්තුවට වලංගු URL ලිපිනයක් ඇතුළත් කරන්න.",
"form_error_url_format": "වලංගු නොවන URL ආකෘතියකි",
"form_error_url_or_path_format": "ලැයිස්තුවක වලංගු නොවන URL හෝ ස්ථීර මාර්ගයකි",
"custom_filter_rules": "අභිරුචි පෙරීමේ නීති",
"examples_title": "උදාහරණ",
"example_meaning_filter_block": "example.org වසමට සහ එහි සියලුම උප වසම් වලට පිවිසීම අවහිර කරන්න",
"example_meaning_filter_whitelist": "example.org වසමට සහ එහි සියලුම උප වසම් වලට ප්‍රවේශය අවහිර නොකරන්න",
"example_meaning_host_block": "AdGuard Home දැන් example.org වසම සඳහා 127.0.0.1 ලිපිනය ලබා දෙනු ඇත (නමුත් එහි උප ලිපින නොවේ).",
"example_comment": "! මෙතැන අදහස් දැක්වීමක්",
"example_comment_meaning": "විස්තර කිිරීමක්",
"example_comment_hash": "# එසේම අදහස් දැක්වීමක්",
"example_regex_meaning": "නිශ්චිතව දක්වා ඇති නිත්‍ය වාක්‍යවිධියට ගැලපෙන වසම් වෙත පිවිසීම අවහිර කරන්න",
"example_upstream_regular": "සාමාන්‍ය DNS (UDP හරහා)",
"example_upstream_dot": "සංකේතාංකනය කළ <0>DNS-over-TLS</0>",
"example_upstream_doh": "සංකේතාංකනය කළ <0>DNS-over-HTTPS</0>",
"example_upstream_tcp": "සාමාන්‍ය DNS (TCP හරහා)",
"all_lists_up_to_date_toast": "සියලුම ලැයිස්තු දැනටමත් යාවත්කාලීනයි",
"dns_test_ok_toast": "සඳහන් කළ DNS සේවාදායකයන් නිවැරදිව ක්‍රියා කරයි",
"dns_test_not_ok_toast": "සේවාදායකය \"{{key}}\": භාවිතා කළ නොහැකි විය, කරුණාකර ඔබ එය නිවැරදිව ලියා ඇත්දැයි පරීක්ෂා කරන්න",
"unblock": "අනවහිර කරන්න",
"block": "අවහිර කරන්න",
"time_table_header": "වේලාව",
"date": "දිනය",
"domain_name_table_header": "වසම් නාමය",
"domain_or_client": "වසම හෝ අනුග්‍රාහකය",
"type_table_header": "වර්ගය",
"response_table_header": "ප්‍රතිචාරය",
"response_code": "ප්‍රතිචාර කේතය",
"client_table_header": "අනුග්‍රාහකය",
"empty_response_status": "හිස්",
"show_all_filter_type": "සියල්ල පෙන්වන්න",
"refresh_btn": "නැවුම් කරන්න",
"previous_btn": "පෙර",
"next_btn": "ඊළඟ",
"loading_table_status": "පූරණය ‌වෙමින්...",
"page_table_footer_text": "පිටුව",
"rows_table_footer_text": "පේළි",
"updated_custom_filtering_toast": "අභිරුචි පෙරීමේ නීති යාවත්කාල කරන ලදි",
"rule_removed_from_custom_filtering_toast": "අභිරුචි පෙරීමේ නීති තුළින් නීතියක් ඉවත් කරන ලදි",
"rule_added_to_custom_filtering_toast": "අභිරුචි පෙරීමේ නීති තුළට මෙම නීතිය එකතු කරන ලදි",
"query_log_response_status": "තත්ත්වය: {{value}}",
"query_log_filtered": "{{filter}} මගින් පෙරහන් කරන ලදි",
"query_log_confirm_clear": "සම්පූර්ණ විමසුම් ලොගය ඉවත් කිරීමට අවශ්‍ය යැයි ඔබට විශ්වාසද?",
"query_log_clear": "විමසුම් ලොග ඉවත් කරන්න",
"query_log_retention": "විමසුම් ලොග රඳවා තබා ගැනීම",
"query_log_enable": "ලොගය සබල කරන්න",
"query_log_configuration": "ලොග වින්‍යාසය",
"query_log_disabled": "විමසුම් ලොගය අබල කර ඇති අතර එය <0>සැකසුම්</0> තුළ වින්‍යාසගත කළ හැකිය",
"query_log_strict_search": "ඉතා නිවැරදිව සෙවීම සඳහා ද්විත්ව උද්ධෘතය භාවිතා කරන්න",
"query_log_retention_confirm": "විමසුම් ලොගය රඳවා තබා ගැනීම වෙනස් කිරීමට අවශ්‍ය බව ඔබට විශ්වාසද? ඔබ කාල පරතරයෙහි අගය අඩු කළහොත් සමහර දත්ත නැති වී යනු ඇත",
"anonymize_client_ip": "අනුග්‍රාහකයෙහි අ.ජා. කෙ. (IP) නිර්නාමික කරන්න",
"dns_config": "DNS සේවාදායක වින්‍යාසය",
"default": "සුපුරුදු",
"nxdomain": "නොපවතින වසම",
"null_ip": "අභිශූන්‍යය අ.ජා. කෙ. (IP)",
"custom_ip": "අභිරුචි අ.ජා. කෙ. (IP)",
"dns_over_https": "DNS-over-HTTPS",
"dns_over_tls": "DNS-over-TLS",
"form_enter_rate_limit": "අනුපාත සීමාව ඇතුළත් කරන්න",
"rate_limit": "අනුපාත සීමාව",
"edns_enable": "EDNS අනුග්‍රාහක අනුජාලය සබල කරන්න",
"blocking_ipv4_desc": "අවහිර කළ A ඉල්ලීමක් සඳහා ආපසු එවිය යුතු අ.ජා. කෙ. (IP) ලිපිනය",
"blocking_ipv6_desc": "අවහිර කළ AAAA ඉල්ලීමක් සඳහා ආපසු එවිය යුතු අ.ජා. කෙ. (IP) ලිපිනය",
"blocking_mode_nxdomain": "නොපවතින වසම (NXDOMAIN): NXDOMAIN කේතය සමඟ ප්‍රතිචාර දක්වයි",
"blocking_mode_null_ip": "අභිශූන්‍යය අන්තර්ජාල කෙටුම්පත: ශුන්‍ය අ.ජා. කෙ. (IP) ලිපිනය සමඟ ප්‍රතිචාර දක්වයි (A සඳහා 0.0.0.0; AAAA සඳහා ::)",
"blocking_mode_custom_ip": "අභිරුචි අන්තර්ජාල කෙටුම්පත: අතින් සැකසූ අ.ජා. කෙ. (IP) ලිපිනයක් සමඟ ප්‍රතිචාර දක්වයි",
"upstream_dns_client_desc": "ඔබ මෙම ක්ෂේත්‍රය හිස්ව තබා ගන්නේ නම්, AdGuard Home විසින් <0>DNS සැකසුම්</0> හි වින්‍යාසගත කර ඇති සේවාදායකයන් භාවිතා කරනු ඇත.",
"source_label": "මූලාශ්‍රය",
"category_label": "ප්‍රවර්ගය",
"rule_label": "නීතිය",
"list_label": "ලැයිස්තුව",
"unknown_filter": "{{filterId}} නොදන්නා පෙරහනකි",
"known_tracker": "දන්නා ලුහුබැඳීමක්",
"install_welcome_title": "AdGuard Home වෙත සාදරයෙන් පිළිගනිමු!",
"install_welcome_desc": "AdGuard Home යනු ජාලය පුරා ඇති දැන්වීම් සහ ලුහුබැඳීම අවහිර කරන DNS සේවාදායකි. ඔබගේ මුළු ජාලය සහ සියලුම උපාංග පාලනය කිරීමට ඉඩ සලසා දීම එහි පරමාර්ථය යි, එයට අනුග්‍රාහක පාර්ශවීය වැඩසටහනක් භාවිතා කිරීම අවශ්‍ය නොවේ.",
"install_settings_title": "පරිපාලක වෙබ් අතුරු මුහුණත",
"install_settings_listen": "සවන් දෙන අතුරු මුහුණත",
"install_settings_port": "කවුළුව",
"form_error_port": "වලංගු කවුළුවක අගයක් ඇතුළත් කරන්න",
"install_settings_dns": "DNS සේවාදායකය",
"install_settings_all_interfaces": "සියලුම අතුරුමුහුණත්",
"install_auth_title": "සත්‍යාපනය",
"install_auth_desc": "ඔබගේ AdGuard Home පරිපාලක වෙබ් අතුරු මුහුණතට මුරපද සත්‍යාපනය වින්‍යාසගත කිරීම අතිශයින් නිර්දේශ කෙරේ. එය ඔබගේ ස්ථානීය ජාල‌ය‌ෙ‌න් පමණක් ප්‍රවේශ විය හැකි වුවද, එය තව දුරටත් සීමා රහිත ප්‍රවේශයකින් ආරක්ෂා කර ගැනීම වැදගත් ය.",
"install_auth_username": "පරිශීලක නාමය",
"install_auth_password": "මුරපදය",
"install_auth_confirm": "මුරපදය තහවුරු කරන්න",
"install_auth_username_enter": "පරිශීලක නාමය ඇතුළත් කරන්න",
"install_auth_password_enter": "මුරපදය ඇතුළත් කරන්න",
"install_step": "පියවර",
"install_devices_title": "ඔබගේ උපාංග වින්‍යාසගත කරන්න",
"install_devices_desc": "AdGuard Home භාවිතා කිරීම ආරම්භයට, ඔබගේ උපාංග එය පරිශ්‍රීලනයට වින්‍යාසගත කිරීම අවශ්‍ය වේ.",
"install_submit_title": "සුභ පැතුම්!",
"install_submit_desc": "පිහිටුවීමේ ක්‍රියා පටිපාටිය අවසන් වී ඇති අතර ඔබ AdGuard Home භාවිතය ආරම්භ කිරීමට සූදානම්ය.",
"install_devices_router": "මාර්ගකාරකය",
"install_devices_router_desc": "මෙම පිහිටුම ඔබගේ නිවසේ මාර්ගකාරකයට සම්බන්ධ සියලුම උපාංග ස්වයංක්‍රීයව ආවරණය කරන අතර ඔබට ඒවා අතින් වින්‍යාසගත කිරීමට අවශ්‍ය නොවනු ඇත.",
"install_devices_router_list_3": "ඔබගේ AdGuard Home සේවාදායක ලිපින එහි ඇතුළත් කරන්න.",
"install_devices_router_list_4": "ඔබට සමහර වර්ගයේ රවුටර වල අභිරුචි ව.නා.ප. (DNS) සේවාදායකයක් සැකසිය නොහැක. මෙම අවස්ථාවේදී AdGuard Home <0>ග.ධා.වි.කෙ. (DHCP) සේවාදායකයක්</0> ලෙස පිහිටුවන්නේ නම් එය උපකාර වනු ඇත. එසේ නොමැතිනම්, ඔබග‌ේ විශේෂිත මාර්ගකාරක මාදිළිය සඳහා වූ ව.නා.ප. සේවාදායකයන් රිසිකරණය කරන්නේ කෙසේද යන්න පිළිබඳ අත්පොත සෙවිය යුතුය.",
"install_devices_windows_list_1": "ආරම්භක මෙනුව හෝ වින්ඩෝස් සෙවුම හරහා පාලක පැනලය විවෘත කරන්න.",
"install_devices_windows_list_2": "ජාල සහ අන්තර්ජාල ප්‍රවර්ගයට ගොස් පසුව ජාල සහ බෙදාගැනීමේ මධ්‍යස්ථානය වෙත යන්න.",
"install_devices_windows_list_3": "උපයුක්තක‌‌‌යෙහි සැකසුම් වෙනස් කිරීම තිරයේ වම් පසින් සොයාගෙන එය මත ක්ලික් කරන්න.",
"install_devices_windows_list_4": "ඔබගේ ක්‍රියාකාරී සම්බන්ධතාවය තෝරන්න, එය මත දකුණු-ක්ලික් කර ගුණාංග තෝරන්න.",
"install_devices_windows_list_5": "ලැයිස්තුවේ ඇති අන්තර්ජාල කෙටුම්පත් අනුවාදය 4 (TCP/IP) සොයාගෙන එය තෝරා ඉන්පසු ගුණාංග මත නැවත ක්ලික් කරන්න.",
"install_devices_windows_list_6": "'පහත දැක්වෙන DNS සේවාදායක ලිපින භාවිතා කරන්න' යන්න තෝරා ඔබගේ AdGuard Home සේවාදායක ලිපින ඇතුළත් කරන්න.",
"install_devices_macos_list_1": "ඇපල් අයිකනය මත ක්ලික් කර පද්ධති මනාපයන් වෙත යන්න.",
"install_devices_macos_list_2": "ජාලය මත ක්ලික් කරන්න.",
"install_devices_macos_list_3": "ඔබගේ ලැයිස්තුවේ පළමු සම්බන්ධතාවය තෝරා උසස් මත ක්ලික් කරන්න.",
"install_devices_macos_list_4": "DNS තීරුව තෝරා ඔබගේ AdGuard Home සේවාදායක ලිපින ඇතුළත් කරන්න.",
"install_devices_android_list_1": "ඇන්ඩ්‍රොයිඩ් මෙනුවෙහි මුල් තිරයෙන්, සැකසීම් මත තට්ටු කරන්න.",
"install_devices_android_list_2": "මෙනුවේ Wi-Fi මත තට්ටු කරන්න. පවතින සියලුම ජාල ලැයිස්තුගත කර ඇති තිරය පෙන්වනු ඇත (ජංගම සම්බන්ධතාවය සඳහා අභිරුචි DNS සැකසිය නොහැක).",
"install_devices_android_list_3": "ඔබ සම්බන්ධ වී ඇති ජාලය මත දිගු වේලාවක් ඔබන්න, ඉන්පසුව ජාලය වෙනස් කිරීම මත තට්ටු කරන්න.",
"install_devices_android_list_4": "ඔබට සමහර උපාංගවල වැඩිදුර සැකසුම් බැලීමට \"උසස්\" සඳහා වූ කොටුව සලකුණු කිරීමට අවශ්‍ය විය හැකිය. එමෙන්ම ඔබගේ ඇන්ඩ්‍රොයිඩ් DNS සැකසුම් වෙනස් කිරීමට, අ.ජා. කෙ. (IP) සැකසුම් ග.ධා.වි.කෙ. (DHCP) සිට ස්ථිතික වෙත මාරු කළ යුතුය.",
"install_devices_android_list_5": "DNS 1 සහ DNS 2 පිහිටුවීම් අගයන් ඔබගේ AdGuard Home සේවාදායක ලිපින වලට වෙනස් කරන්න.",
"install_devices_ios_list_1": "මුල් තිරයේ සිට, සැකසුම් මත තට්ටු කරන්න.",
"install_devices_ios_list_2": "වම්පස මෙනුවෙහි Wi-Fi තෝරන්න (ජංගම දුරකථන සඳහා DNS වින්‍යාසගත කිරීමට නොහැකිය).",
"install_devices_ios_list_3": "දැනට ක්‍රියාකාරී ජාලයයහෙි නම මත තට්ටු කරන්න.",
"install_devices_ios_list_4": "DNS ක්ෂේත්‍රය තුළ ඔබගේ AdGuard Home සේවාදායක ලිපින ඇතුළත් කරන්න.",
"get_started": "ආරම්භ කර ගන්න",
"next": "ඊළඟ",
"open_dashboard": "උපකරණ පුවරුව විවෘත කරන්න",
"install_saved": "සාර්ථකව සුරකින ලදි",
"encryption_title": "සංකේතාංකනය",
"encryption_desc": "ගුප්තකේතනය (HTTPS/TLS) සඳහා DNS සහ පරිපාලක වෙබ් අතුරු මුහුණත සහය දක්වයි",
"encryption_config_saved": "සංකේතාංකන වින්‍යාසය සුරකින ලදි",
"encryption_server": "සේවාදායක‌‌‌‌යේ නම",
"encryption_server_enter": "ඔබගේ වසම් නාමය ඇතුළත් කරන්න",
"encryption_redirect": "ස්වයංක්‍රීයව HTTPS වෙත හරවා යවන්න",
"encryption_redirect_desc": "සබල කර ඇත්නම්, AdGuard Home ඔබව ස්වයංක්‍රීයව HTTP සිට HTTPS ලිපින වෙත හරවා යවනු ඇත.",
"encryption_https": "HTTPS කවුළුව",
"encryption_https_desc": "HTTPS කවුළුව වින්‍යාසගත කර ඇත්නම්, AdGuard Home පරිපාලක අතුරුමුහුණත HTTPS හරහා ප්‍රවේශ විය හැකි අතර එය '/ dns-query' ස්ථානයේ DNS-over-HTTPS ද ලබා දෙනු ඇත.",
"encryption_dot": "DNS-over-TLS කවුළුව",
"encryption_dot_desc": "මෙම කවුළුව වින්‍යාසගත කර ඇත්නම්, AdGuard Home විසින් මෙම කවුළුව හරහා DNS-over-TLS සේවාදායකයක් ක්‍රියාත්මක කරනු ඇත.",
"encryption_certificates": "සහතික",
"encryption_certificates_input": "ඔබගේ PEM-කේතාංකනය කළ සහතික පිටපත් කර මෙහි අලවන්න.",
"encryption_status": "තත්ත්වය",
"encryption_expire": "කල් ඉකුත් වීම",
"encryption_key": "පුද්ගලික යතුර",
"encryption_key_input": "ඔබගේ සහතිකය සඳහා PEM-කේතාංකනය කළ පුද්ගලික යතුර පිටපත් කර මෙහි අලවන්න.",
"encryption_enable": "සංකේතාංකනය සබල කරන්න (HTTPS, DNS-over-HTTPS සහ DNS-over-TLS)",
"encryption_enable_desc": "සංකේතාංකනය සබල කර ඇත්නම්, AdGuard Home පරිපාලක අතුරුමුහුණත HTTPS හරහා ක්‍රියා කරනු ඇති අතර DNS සේවාදායකය DNS-over-HTTPS සහ DNS-over-TLS හරහා ලැබෙන ඉල්ලීම් සඳහා සවන් දෙනු ඇත.",
"encryption_key_valid": "මෙය වලංගු {{type}} පුද්ගලික යතුරකි",
"encryption_key_invalid": "මෙය වලංගු නොවන {{type}} පුද්ගලික යතුරකි",
"encryption_subject": "මාතෘකාව",
"encryption_issuer": "නිකුත් කරන්නා",
"encryption_hostnames": "ධාරක නාම",
"encryption_reset": "සංකේතාංකන සැකසුම් යළි පිහිටුවීමට අවශ්‍ය බව ඔබට විශ්වාස ද?",
"topline_expiring_certificate": "ඔබගේ SSL සහතිකය කල් ඉකුත්වීමට ආසන්න වී ඇත. <0>සංකේතාංකන සැකසුම්</0> යාවත්කාල කරන්න.",
"topline_expired_certificate": "ඔබගේ SSL සහතිකය කල් ඉකුත් වී ඇත. <0>සංකේතාංකන සැකසුම්</0> යාවත්කාල කරන්න.",
"form_error_port_range": "80-65535 පරාසය‌ෙ‌හි කවුළුවක අගයක් ඇතුළත් කරන්න",
"form_error_port_unsafe": "මෙය අනාරක්ෂිත කවුළුවකි",
"form_error_equal": "සමාන නොවිය යුතුය",
"form_error_password": "මුරපදය නොගැලපුුුුුුණි",
"reset_settings": "සැකසුම් යළි පිහිටුවන්න",
"update_announcement": "AdGuard Home {{version}} දැන් ලබා ගත හැකිය! වැඩි විස්තර සඳහා <0>මෙහි ක්ලික් කරන්න</0>.",
"setup_guide": "පිහිටුවීමේ මාර්ගෝපදේශය",
"dns_addresses": "DNS ලිපින",
"dns_start": "DNS සේවාදායකය ආරම්භ වෙමින් පවතී",
"down": "පහත",
"fix": "නිරාකරණය කරන්න",
"dns_providers": "මෙහි තෝරා ගැනීමට <0>දන්නා DNS සපයන්නන්ගේ ලැයිස්තුවක්</0> ඇත.",
"update_now": "දැන් \tයාවත්කාල කරන්න",
"update_failed": "ස්වයංක්‍රීය යාවත්කාල කිරීම අසාර්ථක විය. අතින් යාවත්කාල කිරීමට කරුණාකර <a href='https://github.com/AdguardTeam/AdGuardHome/wiki/Getting-Started#update'>පියවර අනුගමනය කරන්න</a>.",
"processing_update": "කරුණාකර රැඳී සිටින්න, AdGuard Home යාවත්කාලීන වෙමින් පවතී",
"clients_title": "අනුග්‍රාහකයන්",
"clients_desc": "AdGuard Home වෙත සම්බන්ධ කර ඇති උපාංග වින්‍යාසගත කරන්න",
"settings_global": "ගෝලීය",
"settings_custom": "අභිරුචි",
"table_client": "අනුග්‍රාහකය",
"table_name": "නම",
"save_btn": "සුරකින්න",
"client_add": "අනුග්‍රාහකයක් එකතු කරන්න",
"client_new": "නව අනුග්‍රාහකය",
"client_edit": "අනුග්‍රාහකය සංස්කරණය කරන්න",
"client_identifier": "හඳුන්වනය",
"ip_address": "අ.ජා. කෙ. (IP) ලිපිනය",
"client_identifier_desc": "අනුග්‍රාහකයන් අ.ජා. කෙ. (IP) ලිපිනයක් හෝ මා.ප්‍ර.පා. (MAC) ලිපිනයක් මගින් හඳුනාගත හැකිය. මා.ප්‍ර.පා. හඳුන්වනයක් ලෙස භාවිතා කළ හැක්කේ AdGuard Home ද <0>DHCP සේවාදායකයක්</0> නම් පමණක් බව කරුණාවෙන් සලකන්න. ",
"form_enter_ip": "අ.ජා. කෙ. (IP) ඇතුළත් කරන්න",
"form_enter_mac": "MAC ඇතුළත් කරන්න",
"form_enter_id": "හඳුන්වනය ඇතුළත් කරන්න",
"form_add_id": "හඳුන්වනයක් එක් කරන්න",
"form_client_name": "අනුග්‍රාහකයේ නම ඇතුළත් කරන්න",
"name": "නම",
"client_global_settings": "ගෝලීය සැකසුම් භාවිතා කරන්න",
"client_deleted": "\"{{key}}\" අනුග්‍රාහකය සාර්ථකව ඉවත් කරන ලදි",
"client_added": "\"{{key}}\" අනුග්‍රාහකය සාර්ථකව එකතු කරන ලදි",
"client_updated": "\"{{key}}\" අනුග්‍රාහකය සාර්ථකව යාවත්කාල කරන ලදි",
"client_confirm_delete": "\"{{key}}\" අනුග්‍රාහකය ඉවත් කිරීමට අවශ්‍ය බව ඔබට විශ්වාසද?",
"list_confirm_delete": "මෙම ලැයිස්තුව ඉවත් කිරීමට අවශ්‍ය බව ඔබට විශ්වාස ද?",
"auto_clients_desc": "AdGuard Home භාවිතා කරන අනුග්‍රාහකයන්‌ග‌ේ දත්ත, නමුත් වින්‍යාසය තුළ ගබඩා කර නොමැති",
"access_title": "ප්‍රවේශවීමට සැකසුම්",
"access_desc": "මෙහිදී ඔබට AdGuard Home DNS සේවාදායකය සඳහා ප්‍රවේශ වී‌‌‌‌මේ නීති වින්‍යාසගත කළ හැකිය.",
"access_allowed_title": "අවසර ලත් අනුග්‍රාහකයන්",
"access_allowed_desc": "CIDR හෝ අ.ජා. කෙ. (IP) ලිපින ලැයිස්තුවක් වින්‍යාසගත කර ඇත්නම්, AdGuard Home විසින් එම අ.ජා. කෙ. ලිපින වලින් පමණක් ඉල්ලීම් පිළිගනු ඇත.",
"access_disallowed_title": "අවසර නොලත් අනුග්‍රාහකයන්",
"access_disallowed_desc": "CIDR හෝ අ.ජා. කෙ. (IP) ලිපින ලැයිස්තුවක් වින්‍යාසගත කර ඇත්නම්, AdGuard Home විසින් එම අ.ජා. කෙ. ලිපින වලින් ඉල්ලීම් අත්හරිනු ඇත.",
"access_blocked_title": "අවහිර කළ වසම්",
"access_settings_saved": "ප්‍රවේශ වීමේ සැකසුම් සාර්ථකව සුරකින ලදි",
"updates_checked": "යාවත්කාලීන කිරීම් සාර්ථකව පරික්ෂා කර ඇත",
"updates_version_equal": "AdGuard Home යාවත්කාලීනයි",
"check_updates_now": "යාවත්කාල කිරීම සඳහා දැන් පරීක්ෂා කරන්න",
"dns_privacy": "DNS රහස්‍යතා",
"setup_dns_privacy_1": "<0>DNS-over-TLS:</0> <1>{{address}}</1> තන්තුව භාවිතා කරයි.",
"setup_dns_privacy_2": "<0>DNS-over-HTTPS:</0> <1>{{address}}</1> තන්තුව භාවිතා කරයි.",
"setup_dns_privacy_3": "<0>සංකේතාංකන ව.නා.ප. (DNS) කෙටුම්පත් සඳහා සහය දක්වන්නේ ඇන්ඩ්‍රොයිඩ් 9 පමණක් බව කරුණාවෙන් සලකන්න. එබැවින් ඔබ වෙනත් මෙහෙයුම් පද්ධති සඳහා අතිරේක මෘදුකාංග ස්ථාපනය කළ යුතුය.</0><0> ඔබට භාවිතා කළ හැකි මෘදුකාංග ලැයිස්තුවක් පහත දැක්‌වේ.</0>",
"setup_dns_privacy_android_2": "<1>DNS-over-HTTPS</1> සහ <1>DNS-over-TLS</1> සඳහා <0>AdGuard for Android</0> සහය දක්වයි.",
"setup_dns_privacy_ios_2": "<1>DNS-over-HTTPS</1> සහ <1>DNS-over-TLS</1> පිහිටුවීම් සඳහා <0>AdGuard for iOS</0> සහය දක්වයි.",
"setup_dns_privacy_other_2": "<0>dnsproxy</0> දන්නා සියලුම ආරක්ෂිත DNS කෙටුම්පත් සඳහා සහය දක්වයි.",
"setup_dns_privacy_other_3": "<1>DNS-over-HTTPS</1> සඳහා <0>dnscrypt-පෙරකලාසිය</0> සහය දක්වයි.",
"setup_dns_privacy_other_4": "<1>DNS-over-HTTPS</1> සඳහා <0>මොසිල්ලා ෆයර්ෆොක්ස්</0> සහය දක්වයි.",
"setup_dns_notice": "ඔබට <1>DNS-over-HTTPS</1> හෝ <1>DNS-over-TLS</1> භාවිතා කිරීම සඳහා AdGuard Home සැකසුම් තුළ <0>සංකේතාංකනය වින්‍යාසගත</0> කිරීමට අවශ්‍ය වේ.",
"rewrite_add": "DNS නැවත ලිවීමක් එකතු කරන්න",
"rewrite_confirm_delete": "\"{{key}}\" සඳහා DNS නැවත ලිවීම ඉවත් කිරීමට අවශ්‍ය බව ඔබට විශ්වාසද?",
"dns_rewrites": "DNS නැවත ලිවීම්",
"form_domain": "වසම ඇතුළත් කරන්න",
"form_answer": "අ.ජා. කෙ. (IP) ලිපිනය ‌හෝ වසම ඇතුළත් කරන්න ",
"form_error_domain_format": "වලංගු නොවන වසම් ආකෘතියකි",
"form_error_answer_format": "වලංගු නොවන පිළිතුරු ආකෘතියකි",
"configure": "වින්‍යාසගත කරන්න",
"main_settings": "ප්‍රධාන සැකසුම්",
"block_services": "විශේෂිත සේවාවන් අවහිර කරන්න",
"blocked_services": "අවහිර කළ සේවාවන්",
"blocked_services_desc": "ජනප්‍රිය අඩවි සහ සේවාවන් ඉක්මනින් අවහිර කිරීමට ඉඩ දෙයි.",
"blocked_services_saved": "අවහිර කළ සේවාවන් සාර්ථකව සුරකින ලදි",
"blocked_services_global": "ගෝලීය අවහිර කළ සේවාවන් භාවිතා කරන්න",
"blocked_service": "අවහිර කළ සේවාව",
"block_all": "සියල්ල අවහිර කරන්න",
"unblock_all": "සියල්ල අනවහිර කරන්න",
"encryption_certificate_path": "සහතිකයේ මාර්ගය",
"encryption_private_key_path": "පුද්ගලික යතුරෙහි මාර්ගය",
"encryption_certificates_source_path": "සහතික ගොනු‌ව‌ෙහි මාර්ගය සකසන්න",
"encryption_certificates_source_content": "සහතිකවල අන්තර්ගත අලවන්න",
"encryption_key_source_path": "පුද්ගලික යතුරක ගොනුවක් සකසන්න",
"encryption_key_source_content": "පුද්ගලික යතු‌රෙහි අන්තර්ගත අලවන්න",
"stats_params": "සංඛ්‍යාලේඛන වින්‍යාසය",
"config_successfully_saved": "වින්‍යාසය සාර්ථකව සුරකින ලදි",
"interval_24_hour": "පැය 24",
"interval_days": "{{count}} දිනය",
"interval_days_plural": "දින {{count}}",
"domain": "වසම",
"answer": "පිළිතුර",
"filter_added_successfully": "පෙරහන සාර්ථකව එකතු කරන ලදි",
"filter_updated": "ලැයිස්තුව සාර්ථකව යාවත්කාලීන කර ඇත",
"statistics_configuration": "සංඛ්‍යාලේඛන වින්‍යාසය",
"statistics_retention": "සංඛ්‍යාලේඛන රඳවා තබා ගැනීම",
"statistics_retention_desc": "ඔබ කාල පරතරය අඩු කළහොත් සමහර දත්ත නැති වනු ඇත",
"statistics_clear": " සංඛ්‍යාලේඛන ඉවත් කරන්න",
"statistics_clear_confirm": "සංඛ්‍යාලේඛන ඉවත් කිරීමට අවශ්‍ය බව ඔබට විශ්වාස ද?",
"statistics_retention_confirm": "සංඛ්‍යාලේඛන රඳවා තබා ගැනීම වෙනස් කිරීමට අවශ්‍ය බව ඔබට විශ්වාසද? ඔබ කාල පරතරයෙහි අගය අඩු කළහොත් සමහර දත්ත නැති වී යනු ඇත",
"statistics_cleared": "සංඛ්‍යාලේඛන සාර්ථකව ඉවත් කරන ලදි",
"interval_hours": "පැය {{count}}",
"interval_hours_plural": "පැය {{count}}",
"filters_configuration": "පෙරහන් වින්‍යාසය",
"filters_enable": "පෙරහන් සබල කරන්න",
"filters_interval": "පෙරහන් යාවත්කාල කාල පරතරය",
"disabled": "අබල කර ඇත",
"username_label": "පරිශීලක නාමය",
"username_placeholder": "පරිශීලක නාමය ඇතුළත් කරන්න",
"password_label": "මුරපදය",
"password_placeholder": "මුරපදය ඇතුළත් කරන්න",
"sign_in": "පුරන්න",
"sign_out": "වරන්න",
"forgot_password": "මුරපදය අමතක වුණා ද?",
"forgot_password_desc": "ඔබගේ පරිශීලක ගිණුම සඳහා නව මුරපදයක් සෑදීමට කරුණාකර <0>මෙම පියවර</0> අනුගමනය කරන්න.",
"location": "ස්ථානය",
"orgname": "සංවිධානයේ නම",
"netname": "ජාල‌යේ නම",
"network": "ජාලය",
"descr": "විස්තරය",
"whois": "Whois",
"blocked_by_response": "ප්‍රතිචාරය අන්වර්ථ නාමයක් (CNAME) හෝ අ.ජා. කෙ. (IP) මගින් අවහිර කර ඇත",
"try_again": "නැවත උත්සහා කරන්න",
"example_rewrite_domain": "මෙම වසම් නාමය සඳහා පමණක් ප්‍රතිචාර නැවත ලියන්න.",
"example_rewrite_wildcard": "<0>example.org</0> සහ එහි සියලුම උප වසම් සඳහා ප්‍රතිචාර නැවත ලියන්න.",
"disable_ipv6": "IPv6 අබල කරන්න",
"disable_ipv6_desc": "මෙම අංගය සක්‍රීය කර ඇත්නම්, IPv6 ලිපින සඳහා වන සියලුම DNS විමසුම් (AAAA වර්ගය) අතහැර දමනු ලැබේ.",
"fastest_addr": "වේගවත්ම අන්තර්ජාල කෙටුම්පත් (IP) ලිපිනය",
"autofix_warning_text": "ඔබ \"නිරාකරණය කරන්න\" මත ක්ලික් කළහොත්, AdGuard Home ඔබගේ පද්ධතිය AdGuard Home DNS සේවාදායකය භාවිතා කිරීමට වින්‍යාසගත කරනු ඇත.",
"tags_title": "හැඳුනුම් සංකේත",
"tags_desc": "අනුග්‍රාහකයට අනුරූප වන හැඳුනුම් සංකේත ඔබට තෝරා ගත හැකිය. පෙරහන් නීති වලට හැඳුනුම් සංකේත ඇතුළත් කළ හැකි අතර ඒවා වඩාත් නිවැරදිව යෙදීමට ඔබට ඉඩ සලසයි. <0>වැඩිදුර ඉගෙන ගන්න</0>",
"form_select_tags": "අනුග්‍රාහක හැඳුනුම් සංකේත",
"check_title": "පෙරීම පරීක්ෂා කරන්න",
"check": "පරීක්ෂා කරන්න",
"form_enter_host": "ධාරක නාමයක් ඇතුළත් කරන්න",
"filtered_custom_rules": "අභිරුචි පෙරීමේ නීති මගින් පෙරහන් කරන ලදි",
"check_ip": "අ.ජා. කෙ. (IP) ලිපින: {{ip}}",
"check_cname": "CNAME: {{cname}}",
"check_reason": "හේතුව: {{reason}}",
"check_rule": "නීතිය: {{rule}}",
"check_service": "සේවාවෙහි නම: {{service}}",
"check_not_found": "ඔබගේ පෙරහන් ලැයිස්තු තුළ සොයා ගත නොහැක",
"client_confirm_block": "{{ip}} අනුග්‍රාහකය අවහිර කිරීමට අවශ්‍ය බව ඔබට විශ්වාසද?",
"client_confirm_unblock": "{{ip}} අනුග්‍රාහකය අනවහිර කිරීමට අවශ්‍ය බව ඔබට විශ්වාසද?",
"client_blocked": "අනුග්‍රාහකය \"{{ip}}\" සාර්ථකව අවහිර කරන ලදි",
"client_unblocked": "අනුග්‍රාහකය \"{{ip}}\" සාර්ථකව අනවහිර කරන ලදි",
"static_ip": "ස්ථිතික අ.ජා. කෙ. (IP) ලිපිනය",
"static_ip_desc": "AdGuard Home යනු සේවාදායකයක් බැවින් එය නිසි ලෙස ක්‍රියා කිරීමට ස්ථිතික අන්තර්ජාල කෙටුම්පත් (IP) ලිපිනයක් අවශ්‍ය වේ. එසේ නොමැතිනම්, යම් අවස්ථාවක දී ඔබගේ මාර්ගකාරකය මෙම උපාංගයට වෙනත් අ.ජා. කෙ. ලිපිනයක් ලබා දිය හැකිය.",
"set_static_ip": "ස්ථිතික අ.ජා. කෙ. (IP) ලිපිනයක් සකසන්න",
"install_static_ok": "සුභ තොරතුරක්! ස්ථිතික අන්තර්ජාල කෙටුම්පත් (IP) ලිපිනය දැනටමත් වින්‍යාසගත කර ඇත",
"install_static_error": "මෙම ජාල අතුරුමුහුණත සඳහා AdGuard Home හට එය ස්වයංක්‍රීයව වින්‍යාසගත කළ නොහැක. කරුණාකර මෙය අතින් කරන්නේ කෙසේද යන්න පිළිබඳ උපදෙස් සොයා ගන්න.",
"install_static_configure": "ගතික අ.ජා. කෙ. (IP) ලිපිනයක් භාවිතා කරන බව අපි අනාවරණය කර ‌ග‌ෙන ඇත්තෙමු - <0>{{ip}}</0>. එය ඔබගේ ස්ථිතික ලිපිනය ලෙස භාවිතා කිරීමට අවශ්‍යද?",
"confirm_static_ip": "AdGuard Home ඔබේ ස්ථිතික අ.ජා. කෙ. (IP) ලිපිනය ලෙස {{ip}} වින්‍යාසගත කරනු ඇත. ඔබට ඉදිරියට යාමට අවශ්‍යද?",
"list_updated": "{{count}} ලැයිස්තුව යාවත්කාලීන කරන ලදි",
"list_updated_plural": "ලැයිස්තු {{count}} ක් යාවත්කාලීන කරන ලදි",
"dnssec_enable": "DNSSEC සබල කරන්න",
"validated_with_dnssec": "DNSSEC සමඟ තහවුරු කර ඇත",
"show_blocked_responses": "අවහිර කර ඇත",
"blocked_safebrowsing": "ආරක්ෂිත සෙවීම මගින් අවහිර කරන ලද",
"blocked_adult_websites": "අවහිර කළ වැඩිහිටි වෙබ් අඩවි",
"blocked_threats": "අවහිර කළ තර්ජන",
"allowed": "අවසර ලත්",
"filtered": "පෙරහන් කරන ලද",
"rewritten": "නැවත ලියන ලද",
"safe_search": "ආරක්ෂිත සෙවීම",
"blocklist": "අවහිර කිරී‌‌‌‌‌මේ ලැයිස්තුව",
"milliseconds_abbreviation": "මිලි තත්."
}

View File

@ -3,7 +3,7 @@ import i18next from 'i18next';
import axios from 'axios';
import { splitByNewLine, sortClients } from '../helpers/helpers';
import { CHECK_TIMEOUT, SETTINGS_NAMES } from '../helpers/constants';
import { CHECK_TIMEOUT, STATUS_RESPONSE, SETTINGS_NAMES } from '../helpers/constants';
import { areEqualVersions } from '../helpers/version';
import { getTlsStatus } from './encryption';
import apiClient from '../api/Api';
@ -342,6 +342,8 @@ export const getDhcpStatus = () => async (dispatch) => {
dispatch(getDhcpStatusRequest());
try {
const status = await apiClient.getDhcpStatus();
const globalStatus = await apiClient.getGlobalStatus();
status.dhcp_available = globalStatus.dhcp_available;
dispatch(getDhcpStatusSuccess(status));
} catch (error) {
dispatch(addErrorToast({ error }));
@ -368,11 +370,56 @@ export const findActiveDhcpRequest = createAction('FIND_ACTIVE_DHCP_REQUEST');
export const findActiveDhcpSuccess = createAction('FIND_ACTIVE_DHCP_SUCCESS');
export const findActiveDhcpFailure = createAction('FIND_ACTIVE_DHCP_FAILURE');
export const findActiveDhcp = (name) => async (dispatch) => {
export const findActiveDhcp = (name) => async (dispatch, getState) => {
dispatch(findActiveDhcpRequest());
try {
const activeDhcp = await apiClient.findActiveDhcp(name);
dispatch(findActiveDhcpSuccess(activeDhcp));
const { check, interface_name } = getState().dhcp;
const v4 = check?.v4 ?? { static_ip: {}, other_server: {} };
const v6 = check?.v6 ?? { other_server: {} };
let isError = false;
if (v4.other_server.found === STATUS_RESPONSE.ERROR
|| v6.other_server.found === STATUS_RESPONSE.ERROR) {
isError = true;
dispatch(addErrorToast({ error: 'dhcp_error' }));
if (v4.other_server.error) {
dispatch(addErrorToast({ error: v4.other_server.error }));
}
if (v6.other_server.error) {
dispatch(addErrorToast({ error: v6.other_server.error }));
}
}
if (v4.static_ip.static === STATUS_RESPONSE.ERROR) {
isError = true;
dispatch(addErrorToast({ error: 'dhcp_static_ip_error' }));
}
if (isError) {
return;
}
if (v4.other_server.found === STATUS_RESPONSE.YES
|| v6.other_server.found === STATUS_RESPONSE.YES) {
dispatch(addErrorToast({ error: 'dhcp_found' }));
} else if (v4.static_ip.static === STATUS_RESPONSE.NO
&& v4.static_ip.ip
&& interface_name) {
const warning = i18next.t('dhcp_dynamic_ip_found', {
interfaceName: interface_name,
ipAddress: v4.static_ip.ip,
interpolation: {
prefix: '<0>{{',
suffix: '}}</0>',
},
});
dispatch(addErrorToast({ error: warning }));
} else {
dispatch(addSuccessToast('dhcp_not_found'));
}
} catch (error) {
dispatch(addErrorToast({ error }));
dispatch(findActiveDhcpFailure());
@ -383,14 +430,11 @@ export const setDhcpConfigRequest = createAction('SET_DHCP_CONFIG_REQUEST');
export const setDhcpConfigSuccess = createAction('SET_DHCP_CONFIG_SUCCESS');
export const setDhcpConfigFailure = createAction('SET_DHCP_CONFIG_FAILURE');
export const setDhcpConfig = (values) => async (dispatch, getState) => {
const { config } = getState().dhcp;
const updatedConfig = { ...config, ...values };
export const setDhcpConfig = (values) => async (dispatch) => {
dispatch(setDhcpConfigRequest());
dispatch(findActiveDhcp(values.interface_name));
try {
await apiClient.setDhcpConfig(updatedConfig);
dispatch(setDhcpConfigSuccess(updatedConfig));
await apiClient.setDhcpConfig(values);
dispatch(setDhcpConfigSuccess(values));
dispatch(addSuccessToast('dhcp_config_saved'));
} catch (error) {
dispatch(addErrorToast({ error }));
@ -416,7 +460,6 @@ export const toggleDhcp = (values) => async (dispatch) => {
enabled: true,
};
successMessage = 'enabled_dhcp';
dispatch(findActiveDhcp(values.interface_name));
}
try {

View File

@ -32,25 +32,13 @@ class Api {
}
// Global methods
GLOBAL_STATUS = {
path: 'status',
method: 'GET',
};
GLOBAL_STATUS = { path: 'status', method: 'GET' }
GLOBAL_TEST_UPSTREAM_DNS = {
path: 'test_upstream_dns',
method: 'POST',
};
GLOBAL_TEST_UPSTREAM_DNS = { path: 'test_upstream_dns', method: 'POST' };
GLOBAL_VERSION = {
path: 'version.json',
method: 'POST',
};
GLOBAL_VERSION = { path: 'version.json', method: 'POST' };
GLOBAL_UPDATE = {
path: 'update',
method: 'POST',
};
GLOBAL_UPDATE = { path: 'update', method: 'POST' };
getGlobalStatus() {
const { path, method } = this.GLOBAL_STATUS;
@ -81,45 +69,21 @@ class Api {
}
// Filtering
FILTERING_STATUS = {
path: 'filtering/status',
method: 'GET',
};
FILTERING_STATUS = { path: 'filtering/status', method: 'GET' };
FILTERING_ADD_FILTER = {
path: 'filtering/add_url',
method: 'POST',
};
FILTERING_ADD_FILTER = { path: 'filtering/add_url', method: 'POST' };
FILTERING_REMOVE_FILTER = {
path: 'filtering/remove_url',
method: 'POST',
};
FILTERING_REMOVE_FILTER = { path: 'filtering/remove_url', method: 'POST' };
FILTERING_SET_RULES = {
path: 'filtering/set_rules',
method: 'POST',
};
FILTERING_SET_RULES = { path: 'filtering/set_rules', method: 'POST' };
FILTERING_REFRESH = {
path: 'filtering/refresh',
method: 'POST',
};
FILTERING_REFRESH = { path: 'filtering/refresh', method: 'POST' };
FILTERING_SET_URL = {
path: 'filtering/set_url',
method: 'POST',
};
FILTERING_SET_URL = { path: 'filtering/set_url', method: 'POST' };
FILTERING_CONFIG = {
path: 'filtering/config',
method: 'POST',
};
FILTERING_CONFIG = { path: 'filtering/config', method: 'POST' };
FILTERING_CHECK_HOST = {
path: 'filtering/check_host',
method: 'GET',
};
FILTERING_CHECK_HOST = { path: 'filtering/check_host', method: 'GET' };
getFilteringStatus() {
const { path, method } = this.FILTERING_STATUS;
@ -190,20 +154,11 @@ class Api {
}
// Parental
PARENTAL_STATUS = {
path: 'parental/status',
method: 'GET',
};
PARENTAL_STATUS = { path: 'parental/status', method: 'GET' };
PARENTAL_ENABLE = {
path: 'parental/enable',
method: 'POST',
};
PARENTAL_ENABLE = { path: 'parental/enable', method: 'POST' };
PARENTAL_DISABLE = {
path: 'parental/disable',
method: 'POST',
};
PARENTAL_DISABLE = { path: 'parental/disable', method: 'POST' };
getParentalStatus() {
const { path, method } = this.PARENTAL_STATUS;
@ -226,20 +181,11 @@ class Api {
}
// Safebrowsing
SAFEBROWSING_STATUS = {
path: 'safebrowsing/status',
method: 'GET',
};
SAFEBROWSING_STATUS = { path: 'safebrowsing/status', method: 'GET' };
SAFEBROWSING_ENABLE = {
path: 'safebrowsing/enable',
method: 'POST',
};
SAFEBROWSING_ENABLE = { path: 'safebrowsing/enable', method: 'POST' };
SAFEBROWSING_DISABLE = {
path: 'safebrowsing/disable',
method: 'POST',
};
SAFEBROWSING_DISABLE = { path: 'safebrowsing/disable', method: 'POST' };
getSafebrowsingStatus() {
const { path, method } = this.SAFEBROWSING_STATUS;
@ -257,20 +203,11 @@ class Api {
}
// Safesearch
SAFESEARCH_STATUS = {
path: 'safesearch/status',
method: 'GET',
};
SAFESEARCH_STATUS = { path: 'safesearch/status', method: 'GET' };
SAFESEARCH_ENABLE = {
path: 'safesearch/enable',
method: 'POST',
};
SAFESEARCH_ENABLE = { path: 'safesearch/enable', method: 'POST' };
SAFESEARCH_DISABLE = {
path: 'safesearch/disable',
method: 'POST',
};
SAFESEARCH_DISABLE = { path: 'safesearch/disable', method: 'POST' };
getSafesearchStatus() {
const { path, method } = this.SAFESEARCH_STATUS;
@ -288,15 +225,9 @@ class Api {
}
// Language
CURRENT_LANGUAGE = {
path: 'i18n/current_language',
method: 'GET',
};
CURRENT_LANGUAGE = { path: 'i18n/current_language', method: 'GET' };
CHANGE_LANGUAGE = {
path: 'i18n/change_language',
method: 'POST',
};
CHANGE_LANGUAGE = { path: 'i18n/change_language', method: 'POST' };
getCurrentLanguage() {
const { path, method } = this.CURRENT_LANGUAGE;
@ -313,40 +244,19 @@ class Api {
}
// DHCP
DHCP_STATUS = {
path: 'dhcp/status',
method: 'GET',
};
DHCP_STATUS = { path: 'dhcp/status', method: 'GET' };
DHCP_SET_CONFIG = {
path: 'dhcp/set_config',
method: 'POST',
};
DHCP_SET_CONFIG = { path: 'dhcp/set_config', method: 'POST' };
DHCP_FIND_ACTIVE = {
path: 'dhcp/find_active_dhcp',
method: 'POST',
};
DHCP_FIND_ACTIVE = { path: 'dhcp/find_active_dhcp', method: 'POST' };
DHCP_INTERFACES = {
path: 'dhcp/interfaces',
method: 'GET',
};
DHCP_INTERFACES = { path: 'dhcp/interfaces', method: 'GET' };
DHCP_ADD_STATIC_LEASE = {
path: 'dhcp/add_static_lease',
method: 'POST',
};
DHCP_ADD_STATIC_LEASE = { path: 'dhcp/add_static_lease', method: 'POST' };
DHCP_REMOVE_STATIC_LEASE = {
path: 'dhcp/remove_static_lease',
method: 'POST',
};
DHCP_REMOVE_STATIC_LEASE = { path: 'dhcp/remove_static_lease', method: 'POST' };
DHCP_RESET = {
path: 'dhcp/reset',
method: 'POST',
};
DHCP_RESET = { path: 'dhcp/reset', method: 'POST' };
getDhcpStatus() {
const { path, method } = this.DHCP_STATUS;
@ -400,20 +310,11 @@ class Api {
}
// Installation
INSTALL_GET_ADDRESSES = {
path: 'install/get_addresses',
method: 'GET',
};
INSTALL_GET_ADDRESSES = { path: 'install/get_addresses', method: 'GET' };
INSTALL_CONFIGURE = {
path: 'install/configure',
method: 'POST',
};
INSTALL_CONFIGURE = { path: 'install/configure', method: 'POST' };
INSTALL_CHECK_CONFIG = {
path: 'install/check_config',
method: 'POST',
};
INSTALL_CHECK_CONFIG = { path: 'install/check_config', method: 'POST' };
getDefaultAddresses() {
const { path, method } = this.INSTALL_GET_ADDRESSES;
@ -439,20 +340,11 @@ class Api {
}
// DNS-over-HTTPS and DNS-over-TLS
TLS_STATUS = {
path: 'tls/status',
method: 'GET',
};
TLS_STATUS = { path: 'tls/status', method: 'GET' };
TLS_CONFIG = {
path: 'tls/configure',
method: 'POST',
};
TLS_CONFIG = { path: 'tls/configure', method: 'POST' };
TLS_VALIDATE = {
path: 'tls/validate',
method: 'POST',
};
TLS_VALIDATE = { path: 'tls/validate', method: 'POST' };
getTlsStatus() {
const { path, method } = this.TLS_STATUS;
@ -478,30 +370,15 @@ class Api {
}
// Per-client settings
GET_CLIENTS = {
path: 'clients',
method: 'GET',
};
GET_CLIENTS = { path: 'clients', method: 'GET' };
FIND_CLIENTS = {
path: 'clients/find',
method: 'GET',
};
FIND_CLIENTS = { path: 'clients/find', method: 'GET' };
ADD_CLIENT = {
path: 'clients/add',
method: 'POST',
};
ADD_CLIENT = { path: 'clients/add', method: 'POST' };
DELETE_CLIENT = {
path: 'clients/delete',
method: 'POST',
};
DELETE_CLIENT = { path: 'clients/delete', method: 'POST' };
UPDATE_CLIENT = {
path: 'clients/update',
method: 'POST',
};
UPDATE_CLIENT = { path: 'clients/update', method: 'POST' };
getClients() {
const { path, method } = this.GET_CLIENTS;
@ -542,15 +419,9 @@ class Api {
}
// DNS access settings
ACCESS_LIST = {
path: 'access/list',
method: 'GET',
};
ACCESS_LIST = { path: 'access/list', method: 'GET' };
ACCESS_SET = {
path: 'access/set',
method: 'POST',
};
ACCESS_SET = { path: 'access/set', method: 'POST' };
getAccessList() {
const { path, method } = this.ACCESS_LIST;
@ -567,20 +438,11 @@ class Api {
}
// DNS rewrites
REWRITES_LIST = {
path: 'rewrite/list',
method: 'GET',
};
REWRITES_LIST = { path: 'rewrite/list', method: 'GET' };
REWRITE_ADD = {
path: 'rewrite/add',
method: 'POST',
};
REWRITE_ADD = { path: 'rewrite/add', method: 'POST' };
REWRITE_DELETE = {
path: 'rewrite/delete',
method: 'POST',
};
REWRITE_DELETE = { path: 'rewrite/delete', method: 'POST' };
getRewritesList() {
const { path, method } = this.REWRITES_LIST;
@ -606,15 +468,9 @@ class Api {
}
// Blocked services
BLOCKED_SERVICES_LIST = {
path: 'blocked_services/list',
method: 'GET',
};
BLOCKED_SERVICES_LIST = { path: 'blocked_services/list', method: 'GET' };
BLOCKED_SERVICES_SET = {
path: 'blocked_services/set',
method: 'POST',
};
BLOCKED_SERVICES_SET = { path: 'blocked_services/set', method: 'POST' };
getBlockedServices() {
const { path, method } = this.BLOCKED_SERVICES_LIST;
@ -631,25 +487,13 @@ class Api {
}
// Settings for statistics
GET_STATS = {
path: 'stats',
method: 'GET',
};
GET_STATS = { path: 'stats', method: 'GET' };
STATS_INFO = {
path: 'stats_info',
method: 'GET',
};
STATS_INFO = { path: 'stats_info', method: 'GET' };
STATS_CONFIG = {
path: 'stats_config',
method: 'POST',
};
STATS_CONFIG = { path: 'stats_config', method: 'POST' };
STATS_RESET = {
path: 'stats_reset',
method: 'POST',
};
STATS_RESET = { path: 'stats_reset', method: 'POST' };
getStats() {
const { path, method } = this.GET_STATS;
@ -676,25 +520,13 @@ class Api {
}
// Query log
GET_QUERY_LOG = {
path: 'querylog',
method: 'GET',
};
GET_QUERY_LOG = { path: 'querylog', method: 'GET' };
QUERY_LOG_CONFIG = {
path: 'querylog_config',
method: 'POST',
};
QUERY_LOG_CONFIG = { path: 'querylog_config', method: 'POST' };
QUERY_LOG_INFO = {
path: 'querylog_info',
method: 'GET',
};
QUERY_LOG_INFO = { path: 'querylog_info', method: 'GET' };
QUERY_LOG_CLEAR = {
path: 'querylog_clear',
method: 'POST',
};
QUERY_LOG_CLEAR = { path: 'querylog_clear', method: 'POST' };
getQueryLog(params) {
const { path, method } = this.GET_QUERY_LOG;
@ -722,10 +554,7 @@ class Api {
}
// Login
LOGIN = {
path: 'login',
method: 'POST',
};
LOGIN = { path: 'login', method: 'POST' };
login(data) {
const { path, method } = this.LOGIN;
@ -737,10 +566,7 @@ class Api {
}
// Profile
GET_PROFILE = {
path: 'profile',
method: 'GET',
};
GET_PROFILE = { path: 'profile', method: 'GET' };
getProfile() {
const { path, method } = this.GET_PROFILE;
@ -748,15 +574,9 @@ class Api {
}
// DNS config
GET_DNS_CONFIG = {
path: 'dns_info',
method: 'GET',
};
GET_DNS_CONFIG = { path: 'dns_info', method: 'GET' };
SET_DNS_CONFIG = {
path: 'dns_config',
method: 'POST',
};
SET_DNS_CONFIG = { path: 'dns_config', method: 'POST' };
getDnsConfig() {
const { path, method } = this.GET_DNS_CONFIG;

View File

@ -42,13 +42,6 @@ body {
background: linear-gradient(45deg, rgba(99, 125, 120, 1) 0%, rgba(88, 177, 101, 1) 100%);
}
@media (max-width: 575px) {
.container {
padding-right: 0;
padding-left: 0;
}
}
.modal-body--medium {
max-height: 20rem;
overflow-y: scroll;
@ -65,3 +58,11 @@ body {
.mw-75 {
max-width: 75% !important;
}
.cursor--not-allowed {
cursor: not-allowed;
}
.select--no-warning {
margin-bottom: 1.375rem;
}

View File

@ -31,7 +31,7 @@ import SetupGuide from '../../containers/SetupGuide';
import Settings from '../../containers/Settings';
import Dns from '../../containers/Dns';
import Encryption from '../../containers/Encryption';
import Dhcp from '../../containers/Dhcp';
import Dhcp from '../Settings/Dhcp';
import Clients from '../../containers/Clients';
import DnsBlocklist from '../../containers/DnsBlocklist';
import DnsAllowlist from '../../containers/DnsAllowlist';
@ -39,6 +39,7 @@ import DnsRewrites from '../../containers/DnsRewrites';
import CustomRules from '../../containers/CustomRules';
import Services from '../Filters/Services';
const ROUTES = [
{
path: MENU_URLS.root,
@ -96,10 +97,10 @@ const ROUTES = [
];
const renderRoute = ({ path, component, exact }, idx) => <Route
key={idx}
exact={exact}
path={path}
component={component}
key={idx}
exact={exact}
path={path}
component={component}
/>;
const App = () => {
@ -142,34 +143,28 @@ const App = () => {
window.location.reload();
};
return (
<HashRouter hashType="noslash">
<>
{updateAvailable && <>
<UpdateTopline />
<UpdateOverlay />
</>}
{!processingEncryption && <EncryptionTopline />}
<LoadingBar className="loading-bar" updateTime={1000} />
<Header />
<div className="container container--wrap pb-5">
{processing && <Loading />}
{!isCoreRunning && (
<div className="row row-cards">
<div className="col-lg-12">
<Status reloadPage={reloadPage} message="dns_start" />
<Loading />
</div>
</div>
)}
{!processing && isCoreRunning && ROUTES.map(renderRoute)}
return <HashRouter hashType="noslash">
{updateAvailable && <>
<UpdateTopline />
<UpdateOverlay />
</>}
{!processingEncryption && <EncryptionTopline />}
<LoadingBar className="loading-bar" updateTime={1000} />
<Header />
<div className="container container--wrap pb-5">
{processing && <Loading />}
{!isCoreRunning && <div className="row row-cards">
<div className="col-lg-12">
<Status reloadPage={reloadPage} message="dns_start" />
<Loading />
</div>
<Footer />
<Toasts />
<Icons />
</>
</HashRouter>
);
</div>}
{!processing && isCoreRunning && ROUTES.map(renderRoute)}
</div>
<Footer />
<Toasts />
<Icons />
</HashRouter>;
};
renderRoute.propTypes = {

View File

@ -1,6 +1,6 @@
import React, { Component, Fragment } from 'react';
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { Trans, withTranslation } from 'react-i18next';
import { Trans, useTranslation } from 'react-i18next';
import Statistics from './Statistics';
import Counters from './Counters';
@ -13,144 +13,141 @@ import Loading from '../ui/Loading';
import { BLOCK_ACTIONS } from '../../helpers/constants';
import './Dashboard.css';
class Dashboard extends Component {
componentDidMount() {
this.getAllStats();
}
const Dashboard = ({
getAccessList,
getStats,
getStatsConfig,
dashboard,
toggleProtection,
toggleClientBlock,
stats,
access,
}) => {
const { t } = useTranslation();
getAllStats = () => {
this.props.getAccessList();
this.props.getStats();
this.props.getStatsConfig();
const getAllStats = () => {
getAccessList();
getStats();
getStatsConfig();
};
getToggleFilteringButton = () => {
const { protectionEnabled, processingProtection } = this.props.dashboard;
useEffect(() => {
getAllStats();
}, []);
const getToggleFilteringButton = () => {
const { protectionEnabled, processingProtection } = dashboard;
const buttonText = protectionEnabled ? 'disable_protection' : 'enable_protection';
const buttonClass = protectionEnabled ? 'btn-gray' : 'btn-success';
return (
<button
return <button
type="button"
className={`btn btn-sm mr-2 ${buttonClass}`}
onClick={() => this.props.toggleProtection(protectionEnabled)}
onClick={() => toggleProtection(protectionEnabled)}
disabled={processingProtection}
>
<Trans>{buttonText}</Trans>
</button>
);
>
<Trans>{buttonText}</Trans>
</button>;
};
toggleClientStatus = (type, ip) => {
const toggleClientStatus = (type, ip) => {
const confirmMessage = type === BLOCK_ACTIONS.BLOCK ? 'client_confirm_block' : 'client_confirm_unblock';
if (window.confirm(this.props.t(confirmMessage, { ip }))) {
this.props.toggleClientBlock(type, ip);
if (window.confirm(t(confirmMessage, { ip }))) {
toggleClientBlock(type, ip);
}
};
render() {
const {
dashboard, stats, access, t,
} = this.props;
const statsProcessing = stats.processingStats
const refreshButton = <button
type="button"
className="btn btn-icon btn-outline-primary btn-sm"
onClick={() => getAllStats()}
>
<svg className="icons">
<use xlinkHref="#refresh" />
</svg>
</button>;
const subtitle = stats.interval === 1
? t('for_last_24_hours')
: t('for_last_days', { count: stats.interval });
const refreshFullButton = <button
type="button"
className="btn btn-outline-primary btn-sm"
onClick={() => getAllStats()}
>
<Trans>refresh_statics</Trans>
</button>;
const statsProcessing = stats.processingStats
|| stats.processingGetConfig
|| access.processing;
const subtitle = stats.interval === 1
? t('for_last_24_hours')
: t('for_last_days', { count: stats.interval });
return <>
<PageTitle title={t('dashboard')}>
<div className="page-title__actions">
{getToggleFilteringButton()}
{refreshFullButton}
</div>
</PageTitle>
{statsProcessing && <Loading />}
{!statsProcessing && <div className="row row-cards">
<div className="col-lg-12">
<Statistics
interval={stats.interval}
dnsQueries={stats.dnsQueries}
blockedFiltering={stats.blockedFiltering}
replacedSafebrowsing={stats.replacedSafebrowsing}
replacedParental={stats.replacedParental}
numDnsQueries={stats.numDnsQueries}
numBlockedFiltering={stats.numBlockedFiltering}
numReplacedSafebrowsing={stats.numReplacedSafebrowsing}
numReplacedParental={stats.numReplacedParental}
refreshButton={refreshButton}
/>
</div>
<div className="col-lg-6">
<Counters
subtitle={subtitle}
const refreshFullButton = (
<button
type="button"
className="btn btn-outline-primary btn-sm"
onClick={() => this.getAllStats()}
>
<Trans>refresh_statics</Trans>
</button>
);
const refreshButton = (
<button
type="button"
className="btn btn-icon btn-outline-primary btn-sm"
onClick={() => this.getAllStats()}
>
<svg className="icons">
<use xlinkHref="#refresh" />
</svg>
</button>
);
return (
<Fragment>
<PageTitle title={t('dashboard')}>
<div className="page-title__actions">
{this.getToggleFilteringButton()}
{refreshFullButton}
</div>
</PageTitle>
{statsProcessing && <Loading />}
{!statsProcessing && (
<div className="row row-cards">
<div className="col-lg-12">
<Statistics
interval={stats.interval}
dnsQueries={stats.dnsQueries}
blockedFiltering={stats.blockedFiltering}
replacedSafebrowsing={stats.replacedSafebrowsing}
replacedParental={stats.replacedParental}
numDnsQueries={stats.numDnsQueries}
numBlockedFiltering={stats.numBlockedFiltering}
numReplacedSafebrowsing={stats.numReplacedSafebrowsing}
numReplacedParental={stats.numReplacedParental}
refreshButton={refreshButton}
/>
</div>
<div className="col-lg-6">
<Counters
subtitle={subtitle}
refreshButton={refreshButton}
/>
</div>
<div className="col-lg-6">
<Clients
subtitle={subtitle}
dnsQueries={stats.numDnsQueries}
topClients={stats.topClients}
clients={dashboard.clients}
autoClients={dashboard.autoClients}
refreshButton={refreshButton}
toggleClientStatus={this.toggleClientStatus}
processingAccessSet={access.processingSet}
disallowedClients={access.disallowed_clients}
/>
</div>
<div className="col-lg-6">
<QueriedDomains
subtitle={subtitle}
dnsQueries={stats.numDnsQueries}
topQueriedDomains={stats.topQueriedDomains}
refreshButton={refreshButton}
/>
</div>
<div className="col-lg-6">
<BlockedDomains
subtitle={subtitle}
topBlockedDomains={stats.topBlockedDomains}
blockedFiltering={stats.numBlockedFiltering}
replacedSafebrowsing={stats.numReplacedSafebrowsing}
replacedParental={stats.numReplacedParental}
refreshButton={refreshButton}
/>
</div>
</div>
)}
</Fragment>
);
}
}
refreshButton={refreshButton}
/>
</div>
<div className="col-lg-6">
<Clients
subtitle={subtitle}
dnsQueries={stats.numDnsQueries}
topClients={stats.topClients}
clients={dashboard.clients}
autoClients={dashboard.autoClients}
refreshButton={refreshButton}
toggleClientStatus={toggleClientStatus}
processingAccessSet={access.processingSet}
disallowedClients={access.disallowed_clients}
/>
</div>
<div className="col-lg-6">
<QueriedDomains
subtitle={subtitle}
dnsQueries={stats.numDnsQueries}
topQueriedDomains={stats.topQueriedDomains}
refreshButton={refreshButton}
/>
</div>
<div className="col-lg-6">
<BlockedDomains
subtitle={subtitle}
topBlockedDomains={stats.topBlockedDomains}
blockedFiltering={stats.numBlockedFiltering}
replacedSafebrowsing={stats.numReplacedSafebrowsing}
replacedParental={stats.numReplacedParental}
refreshButton={refreshButton}
/>
</div>
</div>}
</>;
};
Dashboard.propTypes = {
dashboard: PropTypes.object.isRequired,
@ -160,9 +157,8 @@ Dashboard.propTypes = {
getStatsConfig: PropTypes.func.isRequired,
toggleProtection: PropTypes.func.isRequired,
getClients: PropTypes.func.isRequired,
t: PropTypes.func.isRequired,
toggleClientBlock: PropTypes.func.isRequired,
getAccessList: PropTypes.func.isRequired,
};
export default withTranslation()(Dashboard);
export default Dashboard;

View File

@ -5,7 +5,7 @@ import { withTranslation } from 'react-i18next';
import flow from 'lodash/flow';
import classNames from 'classnames';
import { validatePath, validateRequiredValue } from '../../helpers/validators';
import { renderInputField, renderSelectField } from '../../helpers/form';
import { renderCheckboxField, renderInputField } from '../../helpers/form';
import { MODAL_OPEN_TIMEOUT, MODAL_TYPE, FORM_NAME } from '../../helpers/constants';
const getIconsData = (homepage, source) => ([
@ -60,7 +60,7 @@ const renderFilters = ({ categories, filters }, selectedSources, t) => Object.ke
<Field
name={`${filter.id}`}
type="checkbox"
component={renderSelectField}
component={renderCheckboxField}
placeholder={t(name)}
disabled={isSelected}
checked={isSelected}
@ -148,13 +148,13 @@ const Form = (props) => {
>
{t('cancel_btn')}
</button>
<button
{modalType !== MODAL_TYPE.SELECT_MODAL_TYPE && <button
type="submit"
className="btn btn-success"
disabled={processingAddFilter || processingConfigFilter}
>
{t('save_btn')}
</button>
</button>}
</div>
</form>;
};

View File

@ -1,7 +1,7 @@
import React, { useState } from 'react';
import { Link, useLocation } from 'react-router-dom';
import { shallowEqual, useSelector } from 'react-redux';
import { Trans } from 'react-i18next';
import { useTranslation } from 'react-i18next';
import classnames from 'classnames';
import Menu from './Menu';
import logo from '../ui/svg/logo.svg';
@ -9,6 +9,7 @@ import './Header.css';
const Header = () => {
const [isMenuOpen, setIsMenuOpen] = useState(false);
const { t } = useTranslation();
const {
protectionEnabled,
@ -33,45 +34,42 @@ const Header = () => {
'badge-danger': !protectionEnabled,
});
return (
<div className="header">
<div className="header__container">
<div className="header__row">
<div
className="header-toggler d-lg-none ml-lg-0 collapsed"
onClick={toggleMenuOpen}
>
<span className="header-toggler-icon" />
return <div className="header">
<div className="header__container">
<div className="header__row">
<div
className="header-toggler d-lg-none ml-lg-0 collapsed"
onClick={toggleMenuOpen}
>
<span className="header-toggler-icon" />
</div>
<div className="header__column">
<div className="d-flex align-items-center">
<Link to="/" className="nav-link pl-0 pr-1">
<img src={logo} alt="" className="header-brand-img" />
</Link>
{!processing && isCoreRunning
&& <span className={badgeClass}
>{t(protectionEnabled ? 'on' : 'off')}
</span>}
</div>
<div className="header__column">
<div className="d-flex align-items-center">
<Link to="/" className="nav-link pl-0 pr-1">
<img src={logo} alt="" className="header-brand-img" />
</Link>
{!processing && isCoreRunning && (
<span className={badgeClass}>
<Trans>{protectionEnabled ? 'on' : 'off'}</Trans>
</span>
)}
</div>
</div>
<Menu
pathname={pathname}
isMenuOpen={isMenuOpen}
closeMenu={closeMenu}
/>
<div className="header__column">
<div className="header__right">
{!processingProfile && name
&& <a href="control/logout" className="btn btn-sm btn-outline-secondary">
<Trans>sign_out</Trans>
</a>}
</div>
</div>
<Menu
pathname={pathname}
isMenuOpen={isMenuOpen}
closeMenu={closeMenu}
/>
<div className="header__column">
<div className="header__right">
{!processingProfile && name
&& <a href="control/logout" className="btn btn-sm btn-outline-secondary">
{t('sign_out')}
</a>}
</div>
</div>
</div>
</div>
);
</div>;
};
export default Header;

View File

@ -1,29 +1,31 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Trans } from 'react-i18next';
import { useTranslation } from 'react-i18next';
import Form from './Form';
const Filters = ({ filter, refreshLogs, setIsLoading }) => (
<div className="page-header page-header--logs">
<h1 className="page-title page-title--large">
<Trans>query_log</Trans>
<button
const Filters = ({ filter, refreshLogs, setIsLoading }) => {
const { t } = useTranslation();
return <div className="page-header page-header--logs">
<h1 className="page-title page-title--large">
{t('query_log')}
<button
type="button"
className="btn btn-icon--green logs__refresh"
onClick={refreshLogs}
>
<svg className="icons icon--24">
<use xlinkHref="#update" />
</svg>
</button>
</h1>
<Form
>
<svg className="icons icon--24">
<use xlinkHref="#update" />
</svg>
</button>
</h1>
<Form
responseStatusClass="d-sm-block"
initialValues={filter}
setIsLoading={setIsLoading}
/>
</div>
);
/>
</div>;
};
Filters.propTypes = {
filter: PropTypes.object.isRequired,

View File

@ -15,7 +15,7 @@ import { toggleAllServices } from '../../../helpers/helpers';
import {
renderInputField,
renderGroupField,
renderSelectField,
renderCheckboxField,
renderServiceField,
} from '../../../helpers/form';
import { validateClientId, validateRequiredValue } from '../../../helpers/validators';
@ -151,7 +151,7 @@ let Form = (props) => {
<Field
name={setting.name}
type="checkbox"
component={renderSelectField}
component={renderCheckboxField}
placeholder={t(setting.placeholder)}
disabled={
setting.name !== 'use_global_settings'

View File

@ -1,235 +0,0 @@
import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import { Field, reduxForm, formValueSelector } from 'redux-form';
import { Trans, withTranslation } from 'react-i18next';
import flow from 'lodash/flow';
import { renderInputField, toNumber } from '../../../helpers/form';
import { FORM_NAME } from '../../../helpers/constants';
import { validateIpv4, validateIsPositiveValue, validateRequiredValue } from '../../../helpers/validators';
const renderInterfaces = ((interfaces) => (
Object.keys(interfaces).map((item) => {
const option = interfaces[item];
const { name } = option;
const onlyIPv6 = option.ip_addresses.every((ip) => ip.includes(':'));
let interfaceIP = option.ip_addresses[0];
if (!onlyIPv6) {
option.ip_addresses.forEach((ip) => {
if (!ip.includes(':')) {
interfaceIP = ip;
}
});
}
return (
<option value={name} key={name} disabled={onlyIPv6}>
{name} - {interfaceIP}
</option>
);
})
));
const renderInterfaceValues = ((interfaceValues) => (
<ul className="list-unstyled mt-1 mb-0">
<li>
<span className="interface__title">MTU: </span>
{interfaceValues.mtu}
</li>
<li>
<span className="interface__title"><Trans>dhcp_hardware_address</Trans>: </span>
{interfaceValues.hardware_address}
</li>
<li>
<span className="interface__title"><Trans>dhcp_ip_addresses</Trans>: </span>
{
interfaceValues.ip_addresses
.map((ip) => <span key={ip} className="interface__ip">{ip}</span>)
}
</li>
</ul>
));
const clearFields = (change, resetDhcp, t) => {
const fields = {
interface_name: '',
gateway_ip: '',
subnet_mask: '',
range_start: '',
range_end: '',
lease_duration: 86400,
};
// eslint-disable-next-line no-alert
if (window.confirm(t('dhcp_reset'))) {
Object.keys(fields).forEach((field) => change(field, fields[field]));
resetDhcp();
}
};
let Form = (props) => {
const {
t,
handleSubmit,
submitting,
invalid,
enabled,
interfaces,
interfaceValue,
processingConfig,
processingInterfaces,
resetDhcp,
change,
} = props;
return (
<form onSubmit={handleSubmit}>
{!processingInterfaces && interfaces
&& <div className="row">
<div className="col-sm-12 col-md-6">
<div className="form__group form__group--settings">
<label>{t('dhcp_interface_select')}</label>
<Field
name="interface_name"
component="select"
className="form-control custom-select"
validate={[validateRequiredValue]}
>
<option value="" disabled={enabled}>
{t('dhcp_interface_select')}
</option>
{renderInterfaces(interfaces)}
</Field>
</div>
</div>
{interfaceValue
&& <div className="col-sm-12 col-md-6">
{interfaces[interfaceValue]
&& renderInterfaceValues(interfaces[interfaceValue])}
</div>
}
</div>
}
<hr/>
<div className="row">
<div className="col-lg-6">
<div className="form__group form__group--settings">
<label>{t('dhcp_form_gateway_input')}</label>
<Field
id="gateway_ip"
name="gateway_ip"
component={renderInputField}
type="text"
className="form-control"
placeholder={t('dhcp_form_gateway_input')}
validate={[validateIpv4, validateRequiredValue]}
/>
</div>
<div className="form__group form__group--settings">
<label>{t('dhcp_form_subnet_input')}</label>
<Field
id="subnet_mask"
name="subnet_mask"
component={renderInputField}
type="text"
className="form-control"
placeholder={t('dhcp_form_subnet_input')}
validate={[validateIpv4, validateRequiredValue]}
/>
</div>
</div>
<div className="col-lg-6">
<div className="form__group form__group--settings">
<div className="row">
<div className="col-12">
<label>{t('dhcp_form_range_title')}</label>
</div>
<div className="col">
<Field
id="range_start"
name="range_start"
component={renderInputField}
type="text"
className="form-control"
placeholder={t('dhcp_form_range_start')}
validate={[validateIpv4, validateRequiredValue]}
/>
</div>
<div className="col">
<Field
id="range_end"
name="range_end"
component={renderInputField}
type="text"
className="form-control"
placeholder={t('dhcp_form_range_end')}
validate={[validateIpv4, validateRequiredValue]}
/>
</div>
</div>
</div>
<div className="form__group form__group--settings">
<label>{t('dhcp_form_lease_title')}</label>
<Field
name="lease_duration"
component={renderInputField}
type="number"
className="form-control"
placeholder={t('dhcp_form_lease_input')}
validate={[validateRequiredValue, validateIsPositiveValue]}
normalize={toNumber}
/>
</div>
</div>
</div>
<div className="btn-list">
<button
type="submit"
className="btn btn-success btn-standard"
disabled={submitting || invalid || processingConfig}
>
{t('save_config')}
</button>
<button
type="button"
className="btn btn-secondary btn-standart"
disabled={submitting || processingConfig}
onClick={() => clearFields(change, resetDhcp, t)}
>
<Trans>reset_settings</Trans>
</button>
</div>
</form>
);
};
Form.propTypes = {
handleSubmit: PropTypes.func.isRequired,
submitting: PropTypes.bool.isRequired,
invalid: PropTypes.bool.isRequired,
interfaces: PropTypes.object.isRequired,
interfaceValue: PropTypes.string,
initialValues: PropTypes.object.isRequired,
processingConfig: PropTypes.bool.isRequired,
processingInterfaces: PropTypes.bool.isRequired,
enabled: PropTypes.bool.isRequired,
t: PropTypes.func.isRequired,
resetDhcp: PropTypes.func.isRequired,
change: PropTypes.func.isRequired,
};
const selector = formValueSelector(FORM_NAME.DHCP);
Form = connect((state) => {
const interfaceValue = selector(state, 'interface_name');
return {
interfaceValue,
};
})(Form);
export default flow([
withTranslation(),
reduxForm({ form: FORM_NAME.DHCP }),
])(Form);

View File

@ -0,0 +1,145 @@
import React, { useCallback } from 'react';
import { shallowEqual, useSelector } from 'react-redux';
import PropTypes from 'prop-types';
import { Field, reduxForm } from 'redux-form';
import { useTranslation } from 'react-i18next';
import {
renderInputField,
toNumber,
} from '../../../helpers/form';
import { FORM_NAME } from '../../../helpers/constants';
import {
validateIpv4,
validateIsPositiveValue,
validateRequiredValue,
validateIpv4RangeEnd,
} from '../../../helpers/validators';
const FormDHCPv4 = ({
handleSubmit,
submitting,
processingConfig,
ipv4placeholders,
}) => {
const { t } = useTranslation();
const dhcp = useSelector((state) => state.form[FORM_NAME.DHCPv4], shallowEqual);
const interfaces = useSelector((state) => state.form[FORM_NAME.DHCP_INTERFACES], shallowEqual);
const interface_name = interfaces?.values?.interface_name;
const isInterfaceIncludesIpv4 = useSelector(
(state) => !!state.dhcp?.interfaces?.[interface_name]?.ipv4_addresses,
);
const isEmptyConfig = !Object.values(dhcp?.values?.v4 ?? {})
.some(Boolean);
const invalid = dhcp?.syncErrors || interfaces?.syncErrors || !isInterfaceIncludesIpv4
|| isEmptyConfig || submitting || processingConfig;
const validateRequired = useCallback((value) => {
if (isEmptyConfig) {
return undefined;
}
return validateRequiredValue(value);
}, [isEmptyConfig]);
return <form onSubmit={handleSubmit}>
<div className="row">
<div className="col-lg-6">
<div className="form__group form__group--settings">
<label>{t('dhcp_form_gateway_input')}</label>
<Field
name="v4.gateway_ip"
component={renderInputField}
type="text"
className="form-control"
placeholder={t(ipv4placeholders.gateway_ip)}
validate={[validateIpv4, validateRequired]}
disabled={!isInterfaceIncludesIpv4}
/>
</div>
<div className="form__group form__group--settings">
<label>{t('dhcp_form_subnet_input')}</label>
<Field
name="v4.subnet_mask"
component={renderInputField}
type="text"
className="form-control"
placeholder={t(ipv4placeholders.subnet_mask)}
validate={[validateIpv4, validateRequired]}
disabled={!isInterfaceIncludesIpv4}
/>
</div>
</div>
<div className="col-lg-6">
<div className="form__group form__group--settings">
<div className="row">
<div className="col-12">
<label>{t('dhcp_form_range_title')}</label>
</div>
<div className="col">
<Field
name="v4.range_start"
component={renderInputField}
type="text"
className="form-control"
placeholder={t(ipv4placeholders.range_start)}
validate={[validateIpv4]}
disabled={!isInterfaceIncludesIpv4}
/>
</div>
<div className="col">
<Field
name="v4.range_end"
component={renderInputField}
type="text"
className="form-control"
placeholder={t(ipv4placeholders.range_end)}
validate={[validateIpv4, validateIpv4RangeEnd]}
disabled={!isInterfaceIncludesIpv4}
/>
</div>
</div>
</div>
<div className="form__group form__group--settings">
<label>{t('dhcp_form_lease_title')}</label>
<Field
name="v4.lease_duration"
component={renderInputField}
type="number"
className="form-control"
placeholder={t(ipv4placeholders.lease_duration)}
validate={[validateIsPositiveValue, validateRequired]}
normalize={toNumber}
min={0}
disabled={!isInterfaceIncludesIpv4}
/>
</div>
</div>
</div>
<div className="btn-list">
<button
type="submit"
className="btn btn-success btn-standard"
disabled={invalid}
>
{t('save_config')}
</button>
</div>
</form>;
};
FormDHCPv4.propTypes = {
handleSubmit: PropTypes.func.isRequired,
submitting: PropTypes.bool.isRequired,
initialValues: PropTypes.object.isRequired,
processingConfig: PropTypes.bool.isRequired,
change: PropTypes.func.isRequired,
reset: PropTypes.func.isRequired,
ipv4placeholders: PropTypes.object.isRequired,
};
export default reduxForm({
form: FORM_NAME.DHCPv4,
})(FormDHCPv4);

View File

@ -0,0 +1,120 @@
import React, { useCallback } from 'react';
import { shallowEqual, useSelector } from 'react-redux';
import PropTypes from 'prop-types';
import { Field, reduxForm } from 'redux-form';
import { useTranslation } from 'react-i18next';
import {
renderInputField,
toNumber,
} from '../../../helpers/form';
import { FORM_NAME } from '../../../helpers/constants';
import {
validateIpv6,
validateIsPositiveValue,
validateRequiredValue,
} from '../../../helpers/validators';
const FormDHCPv6 = ({
handleSubmit,
submitting,
processingConfig,
ipv6placeholders,
}) => {
const { t } = useTranslation();
const dhcp = useSelector((state) => state.form[FORM_NAME.DHCPv6], shallowEqual);
const interfaces = useSelector((state) => state.form[FORM_NAME.DHCP_INTERFACES], shallowEqual);
const interface_name = interfaces?.values?.interface_name;
const isInterfaceIncludesIpv6 = useSelector(
(state) => !!state.dhcp?.interfaces?.[interface_name]?.ipv6_addresses,
);
const isEmptyConfig = !Object.values(dhcp?.values?.v6 ?? {})
.some(Boolean);
const invalid = dhcp?.syncErrors || interfaces?.syncErrors || !isInterfaceIncludesIpv6
|| isEmptyConfig || submitting || processingConfig;
const validateRequired = useCallback((value) => {
if (isEmptyConfig) {
return undefined;
}
return validateRequiredValue(value);
}, [isEmptyConfig]);
return <form onSubmit={handleSubmit}>
<div className="row">
<div className="col-lg-6">
<div className="form__group form__group--settings">
<div className="row">
<div className="col-12">
<label>{t('dhcp_form_range_title')}</label>
</div>
<div className="col">
<Field
name="v6.range_start"
component={renderInputField}
type="text"
className="form-control"
placeholder={t(ipv6placeholders.range_start)}
validate={[validateIpv6, validateRequired]}
disabled={!isInterfaceIncludesIpv6}
/>
</div>
<div className="col">
<Field
name="v6.range_end"
component="input"
type="text"
className="form-control disabled cursor--not-allowed"
placeholder={t(ipv6placeholders.range_end)}
value={t(ipv6placeholders.range_end)}
disabled
/>
</div>
</div>
</div>
</div>
</div>
<div className="row">
<div className="col-lg-6 form__group form__group--settings">
<label>{t('dhcp_form_lease_title')}</label>
<Field
name="v6.lease_duration"
component={renderInputField}
type="number"
className="form-control"
placeholder={t(ipv6placeholders.lease_duration)}
validate={[validateIsPositiveValue, validateRequired]}
normalizeOnBlur={toNumber}
min={0}
disabled={!isInterfaceIncludesIpv6}
/>
</div>
</div>
<div className="btn-list">
<button
type="submit"
className="btn btn-success btn-standard"
disabled={invalid}
>
{t('save_config')}
</button>
</div>
</form>;
};
FormDHCPv6.propTypes = {
handleSubmit: PropTypes.func.isRequired,
submitting: PropTypes.bool.isRequired,
initialValues: PropTypes.object.isRequired,
processingConfig: PropTypes.bool.isRequired,
change: PropTypes.func.isRequired,
reset: PropTypes.func.isRequired,
ipv6placeholders: PropTypes.object.isRequired,
};
export default reduxForm({
form: FORM_NAME.DHCPv6,
})(FormDHCPv6);

View File

@ -0,0 +1,109 @@
import React from 'react';
import { shallowEqual, useSelector } from 'react-redux';
import { Field, reduxForm } from 'redux-form';
import { Trans, useTranslation } from 'react-i18next';
import propTypes from 'prop-types';
import { renderSelectField } from '../../../helpers/form';
import { validateRequiredValue } from '../../../helpers/validators';
import { FORM_NAME } from '../../../helpers/constants';
const renderInterfaces = (interfaces) => Object.keys(interfaces)
.map((item) => {
const option = interfaces[item];
const { name } = option;
const [interfaceIPv4] = option?.ipv4_addresses ?? [];
const [interfaceIPv6] = option?.ipv6_addresses ?? [];
const optionContent = [name, interfaceIPv4, interfaceIPv6].filter(Boolean).join(' - ');
return <option value={name} key={name}>{optionContent}</option>;
});
const getInterfaceValues = ({
gateway_ip,
hardware_address,
ip_addresses,
}) => [
{
name: 'dhcp_form_gateway_input',
value: gateway_ip,
},
{
name: 'dhcp_hardware_address',
value: hardware_address,
},
{
name: 'dhcp_ip_addresses',
value: ip_addresses,
render: (ip_addresses) => ip_addresses
.map((ip) => <span key={ip} className="interface__ip">{ip}</span>),
},
];
const renderInterfaceValues = ({
gateway_ip,
hardware_address,
ip_addresses,
}) => <div className='d-flex align-items-end col-6'>
<ul className="list-unstyled m-0">
{getInterfaceValues({
gateway_ip,
hardware_address,
ip_addresses,
}).map(({ name, value, render }) => value && <li key={name}>
<span className="interface__title"><Trans>{name}</Trans>: </span>
{render?.(value) || value}
</li>)}
</ul>
</div>;
const Interfaces = () => {
const { t } = useTranslation();
const {
processingInterfaces,
interfaces,
enabled,
} = useSelector((store) => store.dhcp, shallowEqual);
const interface_name = useSelector(
(store) => store.form[FORM_NAME.DHCP_INTERFACES]?.values?.interface_name,
);
const interfaceValue = interface_name && interfaces[interface_name];
return !processingInterfaces
&& interfaces
&& <>
<div className="row align-items-center pb-2">
<div className="col-6">
<Field
name="interface_name"
component={renderSelectField}
className="form-control custom-select"
validate={[validateRequiredValue]}
label='dhcp_interface_select'
>
<option value='' disabled={enabled}>
{t('dhcp_interface_select')}
</option>
{renderInterfaces(interfaces)}
</Field>
</div>
{interfaceValue
&& renderInterfaceValues(interfaceValue)}
</div>
</>;
};
renderInterfaceValues.propTypes = {
gateway_ip: propTypes.string.isRequired,
hardware_address: propTypes.string.isRequired,
ip_addresses: propTypes.arrayOf(propTypes.string).isRequired,
};
export default reduxForm({
form: FORM_NAME.DHCP_INTERFACES,
})(Interfaces);

View File

@ -1,22 +1,27 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Field, reduxForm } from 'redux-form';
import { Trans, withTranslation } from 'react-i18next';
import flow from 'lodash/flow';
import { Trans, useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { renderInputField } from '../../../../helpers/form';
import { validateIpv4, validateMac, validateRequiredValue } from '../../../../helpers/validators';
import { FORM_NAME } from '../../../../helpers/constants';
import { toggleLeaseModal } from '../../../../actions';
const Form = (props) => {
const {
t,
handleSubmit,
reset,
pristine,
submitting,
toggleLeaseModal,
processingAdding,
} = props;
const Form = ({
handleSubmit,
reset,
pristine,
submitting,
processingAdding,
}) => {
const { t } = useTranslation();
const dispatch = useDispatch();
const onClick = () => {
reset();
dispatch(toggleLeaseModal());
};
return (
<form onSubmit={handleSubmit}>
@ -61,10 +66,7 @@ const Form = (props) => {
type="button"
className="btn btn-secondary btn-standard"
disabled={submitting}
onClick={() => {
reset();
toggleLeaseModal();
}}
onClick={onClick}
>
<Trans>cancel_btn</Trans>
</button>
@ -86,12 +88,7 @@ Form.propTypes = {
handleSubmit: PropTypes.func.isRequired,
reset: PropTypes.func.isRequired,
submitting: PropTypes.bool.isRequired,
toggleLeaseModal: PropTypes.func.isRequired,
processingAdding: PropTypes.bool.isRequired,
t: PropTypes.func.isRequired,
};
export default flow([
withTranslation(),
reduxForm({ form: FORM_NAME.LEASE }),
])(Form);
export default reduxForm({ form: FORM_NAME.LEASE })(Form);

View File

@ -2,36 +2,37 @@ import React from 'react';
import PropTypes from 'prop-types';
import { Trans, withTranslation } from 'react-i18next';
import ReactModal from 'react-modal';
import { useDispatch } from 'react-redux';
import Form from './Form';
import { toggleLeaseModal } from '../../../../actions';
const Modal = (props) => {
const {
isModalOpen,
handleSubmit,
toggleLeaseModal,
processingAdding,
} = props;
const Modal = ({
isModalOpen,
handleSubmit,
processingAdding,
}) => {
const dispatch = useDispatch();
const toggleModal = () => dispatch(toggleLeaseModal());
return (
<ReactModal
className="Modal__Bootstrap modal-dialog modal-dialog-centered modal-dialog--clients"
closeTimeoutMS={0}
isOpen={isModalOpen}
onRequestClose={() => toggleLeaseModal()}
onRequestClose={toggleModal}
>
<div className="modal-content">
<div className="modal-header">
<h4 className="modal-title">
<Trans>dhcp_new_static_lease</Trans>
</h4>
<button type="button" className="close" onClick={() => toggleLeaseModal()}>
<button type="button" className="close" onClick={toggleModal}>
<span className="sr-only">Close</span>
</button>
</div>
<Form
onSubmit={handleSubmit}
toggleLeaseModal={toggleLeaseModal}
processingAdding={processingAdding}
/>
</div>
@ -42,7 +43,6 @@ const Modal = (props) => {
Modal.propTypes = {
isModalOpen: PropTypes.bool.isRequired,
handleSubmit: PropTypes.func.isRequired,
toggleLeaseModal: PropTypes.func.isRequired,
processingAdding: PropTypes.bool.isRequired,
};

View File

@ -1,115 +1,116 @@
import React, { Component, Fragment } from 'react';
import React from 'react';
import PropTypes from 'prop-types';
import ReactTable from 'react-table';
import { Trans, withTranslation } from 'react-i18next';
import { Trans, useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { LEASES_TABLE_DEFAULT_PAGE_SIZE } from '../../../../helpers/constants';
import { sortIp } from '../../../../helpers/helpers';
import Modal from './Modal';
import { addStaticLease, removeStaticLease } from '../../../../actions';
class StaticLeases extends Component {
cellWrap = ({ value }) => (
<div className="logs__row o-hidden">
const cellWrap = ({ value }) => (
<div className="logs__row o-hidden">
<span className="logs__text" title={value}>
{value}
</span>
</div>
);
</div>
);
handleSubmit = (data) => {
this.props.addStaticLease(data);
const StaticLeases = ({
isModalOpen,
processingAdding,
processingDeleting,
staticLeases,
}) => {
const [t] = useTranslation();
const dispatch = useDispatch();
const handleSubmit = (data) => {
dispatch(addStaticLease(data));
};
handleDelete = (ip, mac, hostname = '') => {
const handleDelete = (ip, mac, hostname = '') => {
const name = hostname || ip;
// eslint-disable-next-line no-alert
if (window.confirm(this.props.t('delete_confirm', { key: name }))) {
this.props.removeStaticLease({ ip, mac, hostname });
if (window.confirm(t('delete_confirm', { key: name }))) {
dispatch(removeStaticLease({
ip,
mac,
hostname,
}));
}
};
render() {
const {
isModalOpen,
toggleLeaseModal,
processingAdding,
processingDeleting,
staticLeases,
t,
} = this.props;
return (
<Fragment>
<ReactTable
data={staticLeases || []}
columns={[
{
Header: 'MAC',
accessor: 'mac',
Cell: this.cellWrap,
},
{
Header: 'IP',
accessor: 'ip',
sortMethod: sortIp,
Cell: this.cellWrap,
},
{
Header: <Trans>dhcp_table_hostname</Trans>,
accessor: 'hostname',
Cell: this.cellWrap,
},
{
Header: <Trans>actions_table_header</Trans>,
accessor: 'actions',
maxWidth: 150,
Cell: (row) => {
const { ip, mac, hostname } = row.original;
return (
<>
<ReactTable
data={staticLeases || []}
columns={[
{
Header: 'MAC',
accessor: 'mac',
Cell: cellWrap,
},
{
Header: 'IP',
accessor: 'ip',
sortMethod: sortIp,
Cell: cellWrap,
},
{
Header: <Trans>dhcp_table_hostname</Trans>,
accessor: 'hostname',
Cell: cellWrap,
},
{
Header: <Trans>actions_table_header</Trans>,
accessor: 'actions',
maxWidth: 150,
// eslint-disable-next-line react/display-name
Cell: (row) => {
const { ip, mac, hostname } = row.original;
return (
<div className="logs__row logs__row--center">
<button
type="button"
className="btn btn-icon btn-icon--green btn-outline-secondary btn-sm"
title={t('delete_table_action')}
disabled={processingDeleting}
onClick={() => this.handleDelete(ip, mac, hostname)
}
>
<svg className="icons">
<use xlinkHref="#delete"/>
</svg>
</button>
</div>
);
},
return <div className="logs__row logs__row--center">
<button
type="button"
className="btn btn-icon btn-icon--green btn-outline-secondary btn-sm"
title={t('delete_table_action')}
disabled={processingDeleting}
onClick={() => handleDelete(ip, mac, hostname)}
>
<svg className="icons">
<use xlinkHref="#delete" />
</svg>
</button>
</div>;
},
]}
pageSize={LEASES_TABLE_DEFAULT_PAGE_SIZE}
showPageSizeOptions={false}
showPagination={staticLeases.length > LEASES_TABLE_DEFAULT_PAGE_SIZE}
noDataText={t('dhcp_static_leases_not_found')}
className="-striped -highlight card-table-overflow"
minRows={6}
/>
<Modal
isModalOpen={isModalOpen}
toggleLeaseModal={toggleLeaseModal}
handleSubmit={this.handleSubmit}
processingAdding={processingAdding}
/>
</Fragment>
);
}
}
},
]}
pageSize={LEASES_TABLE_DEFAULT_PAGE_SIZE}
showPageSizeOptions={false}
showPagination={staticLeases.length > LEASES_TABLE_DEFAULT_PAGE_SIZE}
noDataText={t('dhcp_static_leases_not_found')}
className="-striped -highlight card-table-overflow"
minRows={6}
/>
<Modal
isModalOpen={isModalOpen}
handleSubmit={handleSubmit}
processingAdding={processingAdding}
/>
</>
);
};
StaticLeases.propTypes = {
staticLeases: PropTypes.array.isRequired,
isModalOpen: PropTypes.bool.isRequired,
toggleLeaseModal: PropTypes.func.isRequired,
removeStaticLease: PropTypes.func.isRequired,
addStaticLease: PropTypes.func.isRequired,
processingAdding: PropTypes.bool.isRequired,
processingDeleting: PropTypes.bool.isRequired,
t: PropTypes.func.isRequired,
};
export default withTranslation()(StaticLeases);
cellWrap.propTypes = {
value: PropTypes.string.isRequired,
};
export default StaticLeases;

View File

@ -1,274 +1,277 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { Trans, withTranslation } from 'react-i18next';
import React, { useEffect, useState } from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { DHCP_STATUS_RESPONSE } from '../../../helpers/constants';
import Form from './Form';
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
import classNames from 'classnames';
import { destroy } from 'redux-form';
import {
DHCP_DESCRIPTION_PLACEHOLDERS,
DHCP_FORM_NAMES,
STATUS_RESPONSE,
FORM_NAME,
} from '../../../helpers/constants';
import Leases from './Leases';
import StaticLeases from './StaticLeases/index';
import Card from '../../ui/Card';
import Accordion from '../../ui/Accordion';
import PageTitle from '../../ui/PageTitle';
import Loading from '../../ui/Loading';
import {
findActiveDhcp,
getDhcpInterfaces,
getDhcpStatus,
resetDhcp,
setDhcpConfig,
toggleDhcp,
toggleLeaseModal,
} from '../../../actions';
import FormDHCPv4 from './FormDHCPv4';
import FormDHCPv6 from './FormDHCPv6';
import Interfaces from './Interfaces';
import {
calculateDhcpPlaceholdersIpv4,
calculateDhcpPlaceholdersIpv6,
} from '../../../helpers/helpers';
class Dhcp extends Component {
componentDidMount() {
this.props.getDhcpStatus();
this.props.getDhcpInterfaces();
}
const Dhcp = () => {
const { t } = useTranslation();
const dispatch = useDispatch();
const {
processingStatus,
processingConfig,
processing,
processingInterfaces,
check,
leases,
staticLeases,
isModalOpen,
processingAdding,
processingDeleting,
processingDhcp,
v4,
v6,
interface_name: interfaceName,
enabled,
dhcp_available,
interfaces,
} = useSelector((state) => state.dhcp, shallowEqual);
handleFormSubmit = (values) => {
if (values.interface_name) {
this.props.setDhcpConfig(values);
const interface_name = useSelector(
(state) => state.form[FORM_NAME.DHCP_INTERFACES]?.values?.interface_name,
);
const [ipv4placeholders, setIpv4Placeholders] = useState(DHCP_DESCRIPTION_PLACEHOLDERS.ipv4);
const [ipv6placeholders, setIpv6Placeholders] = useState(DHCP_DESCRIPTION_PLACEHOLDERS.ipv6);
useEffect(() => {
dispatch(getDhcpStatus());
dispatch(getDhcpInterfaces());
}, []);
useEffect(() => {
const [ipv4] = interfaces?.[interface_name]?.ipv4_addresses ?? [];
const [ipv6] = interfaces?.[interface_name]?.ipv6_addresses ?? [];
const gateway_ip = interfaces?.[interface_name]?.gateway_ip;
const v4placeholders = ipv4
? calculateDhcpPlaceholdersIpv4(ipv4, gateway_ip)
: DHCP_DESCRIPTION_PLACEHOLDERS.ipv4;
const v6placeholders = ipv6
? calculateDhcpPlaceholdersIpv6()
: DHCP_DESCRIPTION_PLACEHOLDERS.ipv6;
setIpv4Placeholders(v4placeholders);
setIpv6Placeholders(v6placeholders);
}, [interface_name]);
const clear = () => {
// eslint-disable-next-line no-alert
if (window.confirm(t('dhcp_reset'))) {
Object.values(DHCP_FORM_NAMES)
.forEach((formName) => dispatch(destroy(formName)));
dispatch(resetDhcp());
}
};
handleToggle = (config) => {
this.props.toggleDhcp(config);
const handleSubmit = (values) => {
dispatch(setDhcpConfig({
interface_name,
...values,
}));
};
getToggleDhcpButton = () => {
const {
config, check, processingDhcp, processingConfig,
} = this.props.dhcp;
const otherDhcpFound = check?.otherServer
&& check.otherServer.found === DHCP_STATUS_RESPONSE.YES;
const filledConfig = Object.keys(config)
.every((key) => {
if (key === 'enabled' || key === 'icmp_timeout_msec') {
return true;
}
const enteredSomeV4Value = Object.values(v4)
.some(Boolean);
const enteredSomeV6Value = Object.values(v6)
.some(Boolean);
const enteredSomeValue = enteredSomeV4Value || enteredSomeV6Value || interfaceName;
return config[key];
});
const getToggleDhcpButton = () => {
const otherDhcpFound = check && (check.v4.other_server.found === STATUS_RESPONSE.YES
|| check.v6.other_server.found === STATUS_RESPONSE.YES);
if (config.enabled) {
return (
<button
type="button"
className="btn btn-standard mr-2 btn-gray"
onClick={() => this.props.toggleDhcp(config)}
disabled={processingDhcp || processingConfig}
>
<Trans>dhcp_disable</Trans>
</button>
);
}
const filledConfig = interface_name && (Object.values(v4)
.every(Boolean) || Object.values(v6)
.every(Boolean));
return (
<button
type="button"
className="btn btn-standard mr-2 btn-success"
onClick={() => this.handleToggle(config)}
disabled={
!filledConfig || !check || otherDhcpFound || processingDhcp || processingConfig
}
>
<Trans>dhcp_enable</Trans>
</button>
);
};
getActiveDhcpMessage = (t, check) => {
const { found } = check.otherServer;
if (found === DHCP_STATUS_RESPONSE.ERROR) {
return (
<div className="text-danger mb-2">
<Trans>dhcp_error</Trans>
<div className="mt-2 mb-2">
<Accordion label={t('error_details')}>
<span>{check.otherServer.error}</span>
</Accordion>
</div>
</div>
);
}
return (
<div className="mb-2">
{found === DHCP_STATUS_RESPONSE.YES ? (
<div className="text-danger">
<Trans>dhcp_found</Trans>
</div>
) : (
<div className="text-secondary">
<Trans>dhcp_not_found</Trans>
</div>
)}
</div>
);
};
getDhcpWarning = (check) => {
if (check.otherServer.found === DHCP_STATUS_RESPONSE.NO) {
return '';
}
return (
<div className="text-danger">
<Trans>dhcp_warning</Trans>
</div>
);
};
getStaticIpWarning = (t, check, interfaceName) => {
if (check.staticIP.static === DHCP_STATUS_RESPONSE.ERROR) {
return <>
<div className="text-danger mb-2">
<Trans>dhcp_static_ip_error</Trans>
<div className="mt-2 mb-2">
<Accordion label={t('error_details')}>
<span>{check.staticIP.error}</span>
</Accordion>
</div>
</div>
<hr className="mt-4 mb-4" />
</>;
}
if (check.staticIP.static === DHCP_STATUS_RESPONSE.NO
&& check.staticIP.ip
&& interfaceName) {
return <>
<div className="text-secondary mb-2">
<Trans
components={[<strong key="0">example</strong>]}
values={{
interfaceName,
ipAddress: check.staticIP.ip,
}}
>
dhcp_dynamic_ip_found
</Trans>
</div>
<hr className="mt-4 mb-4" />
</>;
}
return '';
};
render() {
const {
t,
dhcp,
resetDhcp,
findActiveDhcp,
addStaticLease,
removeStaticLease,
toggleLeaseModal,
} = this.props;
const statusButtonClass = classnames({
'btn btn-primary btn-standard': true,
'btn btn-primary btn-standard btn-loading': dhcp.processingStatus,
const className = classNames('btn btn-sm mr-2', {
'btn-gray': enabled,
'btn-outline-success': !enabled,
});
const { enabled, interface_name, ...values } = dhcp.config;
return <>
<PageTitle title={t('dhcp_settings')} />
{(dhcp.processing || dhcp.processingInterfaces) && <Loading />}
{!dhcp.processing && !dhcp.processingInterfaces && <>
<Card
title={t('dhcp_title')}
subtitle={t('dhcp_description')}
bodyType="card-body box-body--settings"
>
<div className="dhcp">
<>
<Form
onSubmit={this.handleFormSubmit}
initialValues={{
interface_name,
...values,
}}
interfaces={dhcp.interfaces}
processingConfig={dhcp.processingConfig}
processingInterfaces={dhcp.processingInterfaces}
enabled={enabled}
resetDhcp={resetDhcp}
/>
<hr />
<div className="card-actions mb-3">
{this.getToggleDhcpButton()}
<button
type="button"
className={statusButtonClass}
onClick={() => findActiveDhcp(interface_name)}
disabled={
enabled || !interface_name || dhcp.processingConfig
}
>
<Trans>check_dhcp_servers</Trans>
</button>
</div>
{!enabled && dhcp.check && (
<>
{this.getStaticIpWarning(t, dhcp.check, interface_name)}
{this.getActiveDhcpMessage(t, dhcp.check)}
{this.getDhcpWarning(dhcp.check)}
</>
)}
</>
</div>
</Card>
{dhcp.config.enabled && (
<Card
title={t('dhcp_leases')}
bodyType="card-body box-body--settings"
>
<div className="row">
<div className="col">
<Leases leases={dhcp.leases} />
</div>
</div>
</Card>
)}
<Card
title={t('dhcp_static_leases')}
bodyType="card-body box-body--settings"
>
<div className="row">
<div className="col-12">
<StaticLeases
staticLeases={dhcp.staticLeases}
isModalOpen={dhcp.isModalOpen}
addStaticLease={addStaticLease}
removeStaticLease={removeStaticLease}
toggleLeaseModal={toggleLeaseModal}
processingAdding={dhcp.processingAdding}
processingDeleting={dhcp.processingDeleting}
/>
</div>
<div className="col-12">
<button
type="button"
className="btn btn-success btn-standard mt-3"
onClick={() => toggleLeaseModal()}
>
<Trans>dhcp_add_static_lease</Trans>
</button>
</div>
</div>
</Card>
</>}
</>;
const onClickDisable = () => dispatch(toggleDhcp({ enabled }));
const onClickEnable = () => {
const values = {
enabled,
interface_name,
v4: enteredSomeV4Value ? v4 : {},
v6: enteredSomeV6Value ? v6 : {},
};
dispatch(toggleDhcp(values));
};
return <button
type="button"
className={className}
onClick={enabled ? onClickDisable : onClickEnable}
disabled={processingDhcp || processingConfig
|| (!enabled && (!filledConfig || !check || otherDhcpFound))}
>
<Trans>{enabled ? 'dhcp_disable' : 'dhcp_enable'}</Trans>
</button>;
};
const statusButtonClass = classNames('btn btn-sm mx-2', {
'btn-loading btn-primary': processingStatus,
'btn-outline-primary': !processingStatus,
});
const onClick = () => dispatch(findActiveDhcp(interface_name));
const toggleModal = () => dispatch(toggleLeaseModal());
const initialV4 = enteredSomeV4Value ? v4 : {};
const initialV6 = enteredSomeV6Value ? v6 : {};
if (processing || processingInterfaces) {
return <Loading />;
}
}
Dhcp.propTypes = {
dhcp: PropTypes.object.isRequired,
toggleDhcp: PropTypes.func.isRequired,
getDhcpStatus: PropTypes.func.isRequired,
setDhcpConfig: PropTypes.func.isRequired,
findActiveDhcp: PropTypes.func.isRequired,
addStaticLease: PropTypes.func.isRequired,
removeStaticLease: PropTypes.func.isRequired,
toggleLeaseModal: PropTypes.func.isRequired,
getDhcpInterfaces: PropTypes.func.isRequired,
t: PropTypes.func.isRequired,
resetDhcp: PropTypes.func.isRequired,
if (!processing && !dhcp_available) {
return <div className="text-center pt-5">
<h2>
<Trans>unavailable_dhcp</Trans>
</h2>
<h4>
<Trans>unavailable_dhcp_desc</Trans>
</h4>
</div>;
}
const toggleDhcpButton = getToggleDhcpButton();
return <>
<PageTitle title={t('dhcp_settings')} subtitle={t('dhcp_description')}>
<div className="page-title__actions">
<div className="mb-3">
{toggleDhcpButton}
<button
type="button"
className={statusButtonClass}
onClick={onClick}
disabled={enabled || !interface_name || processingConfig}
>
<Trans>check_dhcp_servers</Trans>
</button>
<button
type="button"
className='btn btn-sm mx-2 btn-outline-secondary'
disabled={!enteredSomeValue || processingConfig}
onClick={clear}
>
<Trans>reset_settings</Trans>
</button>
</div>
</div>
</PageTitle>
{!processing && !processingInterfaces
&& <>
{!enabled
&& check
&& (check.v4.other_server.found !== STATUS_RESPONSE.NO
|| check.v6.other_server.found !== STATUS_RESPONSE.NO)
&& <div className="mb-5">
<hr />
<div className="text-danger">
<Trans>dhcp_warning</Trans>
</div>
</div>}
<Interfaces
initialValues={{ interface_name: interfaceName }}
/>
<Card
title={t('dhcp_ipv4_settings')}
bodyType="card-body box-body--settings"
>
<div>
<FormDHCPv4
onSubmit={handleSubmit}
initialValues={{ v4: initialV4 }}
processingConfig={processingConfig}
ipv4placeholders={ipv4placeholders}
/>
</div>
</Card>
<Card
title={t('dhcp_ipv6_settings')}
bodyType="card-body box-body--settings"
>
<div>
<FormDHCPv6
onSubmit={handleSubmit}
initialValues={{ v6: initialV6 }}
processingConfig={processingConfig}
ipv6placeholders={ipv6placeholders}
/>
</div>
</Card>
{enabled
&& <Card
title={t('dhcp_leases')}
bodyType="card-body box-body--settings"
>
<div className="row">
<div className="col">
<Leases leases={leases} />
</div>
</div>
</Card>}
<Card
title={t('dhcp_static_leases')}
bodyType="card-body box-body--settings"
>
<div className="row">
<div className="col-12">
<StaticLeases
staticLeases={staticLeases}
isModalOpen={isModalOpen}
processingAdding={processingAdding}
processingDeleting={processingDeleting}
/>
</div>
<div className="col-12">
<button
type="button"
className="btn btn-success btn-standard mt-3"
onClick={toggleModal}
>
<Trans>dhcp_add_static_lease</Trans>
</button>
</div>
</div>
</Card>
</>}
</>;
};
export default withTranslation()(Dhcp);
export default Dhcp;

View File

@ -6,7 +6,7 @@ import { Trans, useTranslation } from 'react-i18next';
import {
renderInputField,
renderRadioField,
renderSelectField,
renderCheckboxField,
toNumber,
} from '../../../../helpers/form';
import {
@ -96,7 +96,7 @@ const Form = ({
<Field
name={name}
type="checkbox"
component={renderSelectField}
component={renderCheckboxField}
placeholder={t(placeholder)}
disabled={processing}
subtitle={t(subtitle)}

View File

@ -7,7 +7,7 @@ import flow from 'lodash/flow';
import {
renderInputField,
renderSelectField,
renderCheckboxField,
renderRadioField,
toNumber,
} from '../../../helpers/form';
@ -15,7 +15,7 @@ import { validateIsSafePort, validatePort, validatePortTLS } from '../../../help
import i18n from '../../../i18n';
import KeyStatus from './KeyStatus';
import CertificateStatus from './CertificateStatus';
import { FORM_NAME } from '../../../helpers/constants';
import { DNS_OVER_TLS_PORT, FORM_NAME, STANDARD_HTTPS_PORT } from '../../../helpers/constants';
const validate = (values) => {
const errors = {};
@ -36,8 +36,8 @@ const clearFields = (change, setTlsConfig, t) => {
certificate_chain: '',
private_key_path: '',
certificate_path: '',
port_https: 443,
port_dns_over_tls: 853,
port_https: STANDARD_HTTPS_PORT,
port_dns_over_tls: DNS_OVER_TLS_PORT,
server_name: '',
force_https: false,
enabled: false,
@ -96,7 +96,7 @@ let Form = (props) => {
<Field
name="enabled"
type="checkbox"
component={renderSelectField}
component={renderCheckboxField}
placeholder={t('encryption_enable')}
onChange={handleChange}
/>
@ -133,7 +133,7 @@ let Form = (props) => {
<Field
name="force_https"
type="checkbox"
component={renderSelectField}
component={renderCheckboxField}
placeholder={t('encryption_redirect')}
onChange={handleChange}
disabled={!isEnabled}

View File

@ -4,7 +4,7 @@ import { Field, reduxForm } from 'redux-form';
import { Trans, withTranslation } from 'react-i18next';
import flow from 'lodash/flow';
import { renderSelectField, toNumber } from '../../../helpers/form';
import { renderCheckboxField, toNumber } from '../../../helpers/form';
import { FILTERS_INTERVALS_HOURS, FORM_NAME } from '../../../helpers/constants';
const getTitleForInterval = (interval, t) => {
@ -49,7 +49,7 @@ const Form = (props) => {
name="enabled"
type="checkbox"
modifier="checkbox--settings"
component={renderSelectField}
component={renderCheckboxField}
placeholder={t('block_domain_use_filters_and_hosts')}
subtitle={t('filters_block_toggle_hint')}
onChange={handleChange}

View File

@ -4,7 +4,7 @@ import { Field, reduxForm } from 'redux-form';
import { Trans, withTranslation } from 'react-i18next';
import flow from 'lodash/flow';
import { renderSelectField, renderRadioField, toNumber } from '../../../helpers/form';
import { renderCheckboxField, renderRadioField, toNumber } from '../../../helpers/form';
import { FORM_NAME, QUERY_LOG_INTERVALS_DAYS } from '../../../helpers/constants';
const getIntervalFields = (processing, t, toNumber) => QUERY_LOG_INTERVALS_DAYS.map((interval) => {
@ -35,7 +35,7 @@ const Form = (props) => {
<Field
name="enabled"
type="checkbox"
component={renderSelectField}
component={renderCheckboxField}
placeholder={t('query_log_enable')}
disabled={processing}
/>
@ -44,7 +44,7 @@ const Form = (props) => {
<Field
name="anonymize_client_ip"
type="checkbox"
component={renderSelectField}
component={renderCheckboxField}
placeholder={t('anonymize_client_ip')}
subtitle={t('anonymize_client_ip_desc')}
disabled={processing}

View File

@ -54,7 +54,11 @@
}
.form__message--error {
color: #cd201f;
color: var(--red);
}
.form__message--left-pad {
padding-left: 0.85rem;
}
.interface__title {
@ -70,10 +74,6 @@
content: "";
}
.dhcp {
min-height: 450px;
}
.form__desc {
margin-top: 10px;
font-size: 13px;

View File

@ -1,43 +0,0 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import './Accordion.css';
class Accordion extends Component {
state = {
isOpen: false,
};
handleClick = () => {
this.setState((prevState) => ({ isOpen: !prevState.isOpen }));
};
render() {
const accordionClass = this.state.isOpen
? 'accordion__label accordion__label--open'
: 'accordion__label';
return (
<div className="accordion">
<div
className={accordionClass}
onClick={this.handleClick}
>
{this.props.label}
</div>
{this.state.isOpen && (
<div className="accordion__content">
{this.props.children}
</div>
)}
</div>
);
}
}
Accordion.propTypes = {
children: PropTypes.node.isRequired,
label: PropTypes.string.isRequired,
};
export default Accordion;

View File

@ -48,7 +48,6 @@ export const PRIVACY_POLICY_LINK = 'https://adguard.com/privacy/home.html';
export const PORT_53_FAQ_LINK = 'https://github.com/AdguardTeam/AdGuardHome/wiki/FAQ#bindinuse';
export const ADDRESS_IN_USE_TEXT = 'address already in use';
export const UBUNTU_SYSTEM_PORT = 53;
export const INSTALL_FIRST_STEP = 1;
export const INSTALL_TOTAL_STEPS = 5;
@ -63,6 +62,8 @@ export const SETTINGS_NAMES = {
export const STANDARD_DNS_PORT = 53;
export const STANDARD_WEB_PORT = 80;
export const STANDARD_HTTPS_PORT = 443;
export const DNS_OVER_TLS_PORT = 853;
export const MAX_PORT = 65535;
export const EMPTY_DATE = '0001-01-01T00:00:00Z';
@ -144,7 +145,7 @@ export const UNSAFE_PORTS = [
export const ALL_INTERFACES_IP = '0.0.0.0';
export const DHCP_STATUS_RESPONSE = {
export const STATUS_RESPONSE = {
YES: 'yes',
NO: 'no',
ERROR: 'error',
@ -458,6 +459,12 @@ export const IP_MATCH_LIST_STATUS = {
CIDR: 'CIDR', // the ip is in the specified CIDR range
};
export const DHCP_FORM_NAMES = {
DHCPv4: 'dhcpv4',
DHCPv6: 'dhcpv6',
DHCP_INTERFACES: 'dhcpInterfaces',
};
export const FORM_NAME = {
UPSTREAM: 'upstream',
DOMAIN_CHECK: 'domainCheck',
@ -465,7 +472,6 @@ export const FORM_NAME = {
REWRITES: 'rewrites',
LOGS_FILTER: 'logsFilter',
CLIENT: 'client',
DHCP: 'dhcp',
LEASE: 'lease',
ACCESS: 'access',
BLOCKING_MODE: 'blockingMode',
@ -477,9 +483,39 @@ export const FORM_NAME = {
INSTALL: 'install',
LOGIN: 'login',
CACHE: 'cache',
...DHCP_FORM_NAMES,
};
export const SMALL_SCREEN_SIZE = 767;
export const MEDIUM_SCREEN_SIZE = 1023;
export const SECONDS_IN_HOUR = 60 * 60;
export const SECONDS_IN_DAY = SECONDS_IN_HOUR * 24;
export const DHCP_VALUES_PLACEHOLDERS = {
ipv4: {
subnet_mask: '255.255.255.0',
lease_duration: SECONDS_IN_DAY.toString(),
},
ipv6: {
range_start: '2001::1',
range_end: 'ff',
lease_duration: SECONDS_IN_DAY.toString(),
},
};
export const DHCP_DESCRIPTION_PLACEHOLDERS = {
ipv4: {
gateway_ip: 'dhcp_form_gateway_input',
subnet_mask: 'dhcp_form_subnet_input',
range_start: 'dhcp_form_range_start',
range_end: 'dhcp_form_range_end',
lease_duration: 'dhcp_form_lease_input',
},
ipv6: {
range_start: 'dhcp_form_range_start',
range_end: 'dhcp_form_range_end',
lease_duration: 'dhcp_form_lease_input',
},
};

View File

@ -1,5 +1,7 @@
import React, { Fragment } from 'react';
import PropTypes from 'prop-types';
import { Trans } from 'react-i18next';
import classNames from 'classnames';
import { createOnBlurHandler } from './helpers';
import { R_UNIX_ABSOLUTE_PATH, R_WIN_ABSOLUTE_PATH } from './constants';
@ -24,11 +26,12 @@ export const renderField = (props, elementType) => {
step,
onBlur,
});
return (
<>
{element}
{!disabled && touched && error
&& <span className="form__message form__message--error">{error}</span>}
&& <span className="form__message form__message--error"><Trans>{error}</Trans></span>}
</>
);
};
@ -47,7 +50,7 @@ renderField.propTypes = {
step: PropTypes.number,
meta: PropTypes.shape({
touched: PropTypes.bool,
error: PropTypes.object,
error: PropTypes.string,
}).isRequired,
};
@ -71,7 +74,7 @@ export const renderGroupField = ({
const onBlur = (event) => createOnBlurHandler(event, input, normalizeOnBlur);
return (
<Fragment>
<>
<div className="input-group">
<input
{...input}
@ -98,8 +101,8 @@ export const renderGroupField = ({
}
</div>
{!disabled && touched && error
&& <span className="form__message form__message--error">{error}</span>}
</Fragment>
&& <span className="form__message form__message--error"><Trans>{error}</Trans></span>}
</>
);
};
@ -115,7 +118,7 @@ renderGroupField.propTypes = {
removeField: PropTypes.func,
meta: PropTypes.shape({
touched: PropTypes.bool,
error: PropTypes.object,
error: PropTypes.string,
}).isRequired,
normalizeOnBlur: PropTypes.func,
};
@ -137,7 +140,8 @@ export const renderRadioField = ({
</label>
{!disabled
&& touched
&& (error && <span className="form__message form__message--error">{error}</span>)}
&& error
&& <span className="form__message form__message--error"><Trans>{error}</Trans></span>}
</Fragment>;
renderRadioField.propTypes = {
@ -147,11 +151,11 @@ renderRadioField.propTypes = {
disabled: PropTypes.bool,
meta: PropTypes.shape({
touched: PropTypes.bool,
error: PropTypes.object,
error: PropTypes.string,
}).isRequired,
};
export const renderSelectField = ({
export const renderCheckboxField = ({
input,
placeholder,
subtitle,
@ -163,7 +167,8 @@ export const renderSelectField = ({
}) => <>
<label className={`checkbox ${modifier}`} onClick={onClick}>
<span className="checkbox__marker" />
<input {...input} type="checkbox" className="checkbox__input" disabled={disabled} checked={input.checked || checked}/>
<input {...input} type="checkbox" className="checkbox__input" disabled={disabled}
checked={input.checked || checked} />
<span className="checkbox__label">
<span className="checkbox__label-text checkbox__label-text--long">
<span className="checkbox__label-title">{placeholder}</span>
@ -178,10 +183,11 @@ export const renderSelectField = ({
</label>
{!disabled
&& touched
&& error && <span className="form__message form__message--error">{error}</span>}
&& error
&& <span className="form__message form__message--error"><Trans>{error}</Trans></span>}
</>;
renderSelectField.propTypes = {
renderCheckboxField.propTypes = {
input: PropTypes.object.isRequired,
placeholder: PropTypes.string,
subtitle: PropTypes.string,
@ -191,7 +197,37 @@ renderSelectField.propTypes = {
checked: PropTypes.bool,
meta: PropTypes.shape({
touched: PropTypes.bool,
error: PropTypes.object,
error: PropTypes.string,
}).isRequired,
};
export const renderSelectField = ({
input,
meta: { touched, error },
children,
label,
}) => {
const showWarning = touched && error;
const selectClass = classNames('form-control custom-select', {
'select--no-warning': !showWarning,
});
return <>
{label && <label><Trans>{label}</Trans></label>}
<select {...input} className={selectClass}>{children}</select>
{showWarning
&& <span className="form__message form__message--error form__message--left-pad"><Trans>{error}</Trans></span>}
</>;
};
renderSelectField.propTypes = {
input: PropTypes.object.isRequired,
disabled: PropTypes.bool,
label: PropTypes.string,
children: PropTypes.oneOfType([PropTypes.array, PropTypes.element]).isRequired,
meta: PropTypes.shape({
touched: PropTypes.bool,
error: PropTypes.string,
}).isRequired,
};
@ -218,7 +254,7 @@ export const renderServiceField = ({
</svg>
</label>
{!disabled && touched && error
&& <span className="form__message form__message--error">{error}</span>}
&& <span className="form__message form__message--error"><Trans>{error}</Trans></span>}
</Fragment>;
renderServiceField.propTypes = {
@ -229,10 +265,12 @@ renderServiceField.propTypes = {
icon: PropTypes.string,
meta: PropTypes.shape({
touched: PropTypes.bool,
error: PropTypes.object,
error: PropTypes.string,
}).isRequired,
};
export const getLastIpv4Octet = (ipv4) => parseInt(ipv4.slice(ipv4.lastIndexOf('.') + 1), 10);
/**
* @param value {string}
* @returns {*|number}

View File

@ -19,6 +19,7 @@ import {
DEFAULT_LANGUAGE,
DEFAULT_TIME_FORMAT,
DETAILED_DATE_FORMAT_OPTIONS,
DHCP_VALUES_PLACEHOLDERS,
FILTERED,
FILTERED_STATUS,
IP_MATCH_LIST_STATUS,
@ -190,7 +191,7 @@ export const captitalizeWords = (text) => text.split(/[ -_]/g)
export const getInterfaceIp = (option) => {
const onlyIPv6 = option.ip_addresses.every((ip) => ip.includes(':'));
let interfaceIP = option.ip_addresses[0];
let [interfaceIP] = option.ip_addresses;
if (!onlyIPv6) {
option.ip_addresses.forEach((ip) => {
@ -203,16 +204,9 @@ export const getInterfaceIp = (option) => {
return interfaceIP;
};
export const getIpList = (interfaces) => {
let list = [];
Object.keys(interfaces)
.forEach((item) => {
list = [...list, ...interfaces[item].ip_addresses];
});
return list.sort();
};
export const getIpList = (interfaces) => Object.values(interfaces)
.reduce((acc, curr) => acc.concat(curr.ip_addresses), [])
.sort();
export const getDnsAddress = (ip, port = '') => {
const isStandardDnsPort = port === STANDARD_DNS_PORT;
@ -668,11 +662,12 @@ export const getLogsUrlParams = (search, response_status) => `?${queryString.str
response_status,
})}`;
export const processContent = (content) => (Array.isArray(content)
? content.filter(([, value]) => value).reduce((acc, val) => acc.concat(val), [])
: content
);
export const processContent = (
content,
) => (Array.isArray(content)
? content.filter(([, value]) => value)
.reduce((acc, val) => acc.concat(val), [])
: content);
/**
* @param object {object}
* @param sortKey {string}
@ -746,3 +741,65 @@ export const sortIp = (a, b) => {
return 0;
}
};
/**
* @param ip {string}
* @param gateway_ip {string}
* @returns {{range_end: string, subnet_mask: string, range_start: string,
* lease_duration: string, gateway_ip: string}}
*/
export const calculateDhcpPlaceholdersIpv4 = (ip, gateway_ip) => {
const LAST_OCTET_IDX = 3;
const LAST_OCTET_RANGE_START = 100;
const LAST_OCTET_RANGE_END = 200;
const addr = ipaddr.parse(ip);
addr.octets[LAST_OCTET_IDX] = LAST_OCTET_RANGE_START;
const range_start = addr.toString();
addr.octets[LAST_OCTET_IDX] = LAST_OCTET_RANGE_END;
const range_end = addr.toString();
const {
subnet_mask,
lease_duration,
} = DHCP_VALUES_PLACEHOLDERS.ipv4;
return {
gateway_ip: gateway_ip || ip,
subnet_mask,
range_start,
range_end,
lease_duration,
};
};
export const calculateDhcpPlaceholdersIpv6 = () => {
const {
range_start,
range_end,
lease_duration,
} = DHCP_VALUES_PLACEHOLDERS.ipv6;
return {
range_start,
range_end,
lease_duration,
};
};
/**
* Add ip_addresses property - concatenated ipv4_addresses and ipv6_addresses for every interface
* @param interfaces
* @param interfaces.ipv4_addresses {string[]}
* @param interfaces.ipv6_addresses {string[]}
* @returns interfaces Interfaces enriched with ip_addresses property
*/
export const enrichWithConcatenatedIpAddresses = (interfaces) => Object.entries(interfaces)
.reduce((acc, [k, v]) => {
const ipv4_addresses = v.ipv4_addresses ?? [];
const ipv6_addresses = v.ipv6_addresses ?? [];
acc[k].ip_addresses = ipv4_addresses.concat(ipv6_addresses);
return acc;
}, interfaces);

View File

@ -1,7 +1,6 @@
import { Trans } from 'react-i18next';
import React from 'react';
import i18next from 'i18next';
import {
MAX_PORT,
R_CIDR,
R_CIDR_IPV6,
R_HOST,
@ -9,10 +8,10 @@ import {
R_IPV6,
R_MAC,
R_URL_REQUIRES_PROTOCOL,
STANDARD_WEB_PORT,
UNSAFE_PORTS,
} from './constants';
import { isValidAbsolutePath } from './form';
import { getLastIpv4Octet, isValidAbsolutePath } from './form';
// Validation functions
// https://redux-form.com/8.3.0/examples/fieldlevelvalidation/
@ -26,7 +25,7 @@ export const validateRequiredValue = (value) => {
if (formattedValue || formattedValue === 0 || (formattedValue && formattedValue.length !== 0)) {
return undefined;
}
return <Trans>form_error_required</Trans>;
return 'form_error_required';
};
/**
@ -35,11 +34,28 @@ export const validateRequiredValue = (value) => {
*/
export const getMaxValueValidator = (maximum) => (value) => {
if (value && value > maximum) {
i18next.t('value_not_larger_than', { maximum });
return i18next.t('value_not_larger_than', { maximum });
}
return undefined;
};
/**
* @param value {string}
* @returns {undefined|string}
*/
export const validateIpv4RangeEnd = (_, allValues) => {
if (!allValues || !allValues.v4 || !allValues.v4.range_end || !allValues.v4.range_start) {
return undefined;
}
const { range_end, range_start } = allValues.v4;
if (getLastIpv4Octet(range_end) <= getLastIpv4Octet(range_start)) {
return 'range_end_error';
}
return undefined;
};
/**
* @param value {string}
@ -47,7 +63,7 @@ export const getMaxValueValidator = (maximum) => (value) => {
*/
export const validateIpv4 = (value) => {
if (value && !R_IPV4.test(value)) {
return <Trans>form_error_ip4_format</Trans>;
return 'form_error_ip4_format';
}
return undefined;
};
@ -63,12 +79,12 @@ export const validateClientId = (value) => {
const formattedValue = value ? value.trim() : value;
if (formattedValue && !(
R_IPV4.test(formattedValue)
|| R_IPV6.test(formattedValue)
|| R_MAC.test(formattedValue)
|| R_CIDR.test(formattedValue)
|| R_CIDR_IPV6.test(formattedValue)
|| R_IPV6.test(formattedValue)
|| R_MAC.test(formattedValue)
|| R_CIDR.test(formattedValue)
|| R_CIDR_IPV6.test(formattedValue)
)) {
return <Trans>form_error_client_id_format</Trans>;
return 'form_error_client_id_format';
}
return undefined;
};
@ -79,7 +95,7 @@ export const validateClientId = (value) => {
*/
export const validateIpv6 = (value) => {
if (value && !R_IPV6.test(value)) {
return <Trans>form_error_ip6_format</Trans>;
return 'form_error_ip6_format';
}
return undefined;
};
@ -90,7 +106,7 @@ export const validateIpv6 = (value) => {
*/
export const validateIp = (value) => {
if (value && !R_IPV4.test(value) && !R_IPV6.test(value)) {
return <Trans>form_error_ip_format</Trans>;
return 'form_error_ip_format';
}
return undefined;
};
@ -101,76 +117,76 @@ export const validateIp = (value) => {
*/
export const validateMac = (value) => {
if (value && !R_MAC.test(value)) {
return <Trans>form_error_mac_format</Trans>;
return 'form_error_mac_format';
}
return undefined;
};
/**
* @param value {string}
* @param value {number}
* @returns {undefined|string}
*/
export const validateIsPositiveValue = (value) => {
if ((value || value === 0) && value <= 0) {
return <Trans>form_error_positive</Trans>;
return 'form_error_positive';
}
return undefined;
};
/**
* @param value {string}
* @param value {number}
* @returns {boolean|*}
*/
export const validateBiggerOrEqualZeroValue = (value) => {
if (value < 0) {
return <Trans>form_error_negative</Trans>;
return 'form_error_negative';
}
return false;
};
/**
* @param value {string}
* @param value {number}
* @returns {undefined|string}
*/
export const validatePort = (value) => {
if ((value || value === 0) && (value < 80 || value > 65535)) {
return <Trans>form_error_port_range</Trans>;
if ((value || value === 0) && (value < STANDARD_WEB_PORT || value > MAX_PORT)) {
return 'form_error_port_range';
}
return undefined;
};
/**
* @param value {string}
* @param value {number}
* @returns {undefined|string}
*/
export const validateInstallPort = (value) => {
if (value < 1 || value > 65535) {
return <Trans>form_error_port</Trans>;
if (value < 1 || value > MAX_PORT) {
return 'form_error_port';
}
return undefined;
};
/**
* @param value {string}
* @param value {number}
* @returns {undefined|string}
*/
export const validatePortTLS = (value) => {
if (value === 0) {
return undefined;
}
if (value && (value < 80 || value > 65535)) {
return <Trans>form_error_port_range</Trans>;
if (value && (value < STANDARD_WEB_PORT || value > MAX_PORT)) {
return 'form_error_port_range';
}
return undefined;
};
/**
* @param value {string}
* @param value {number}
* @returns {undefined|string}
*/
export const validateIsSafePort = (value) => {
if (UNSAFE_PORTS.includes(value)) {
return <Trans>form_error_port_unsafe</Trans>;
return 'form_error_port_unsafe';
}
return undefined;
};
@ -181,7 +197,7 @@ export const validateIsSafePort = (value) => {
*/
export const validateDomain = (value) => {
if (value && !R_HOST.test(value)) {
return <Trans>form_error_domain_format</Trans>;
return 'form_error_domain_format';
}
return undefined;
};
@ -192,7 +208,7 @@ export const validateDomain = (value) => {
*/
export const validateAnswer = (value) => {
if (value && (!R_IPV4.test(value) && !R_IPV6.test(value) && !R_HOST.test(value))) {
return <Trans>form_error_answer_format</Trans>;
return 'form_error_answer_format';
}
return undefined;
};
@ -203,7 +219,7 @@ export const validateAnswer = (value) => {
*/
export const validatePath = (value) => {
if (value && !isValidAbsolutePath(value) && !R_URL_REQUIRES_PROTOCOL.test(value)) {
return <Trans>form_error_url_or_path_format</Trans>;
return 'form_error_url_or_path_format';
}
return undefined;
};

View File

@ -30,9 +30,11 @@ import sl from './__locales/sl.json';
import tr from './__locales/tr.json';
import srCS from './__locales/sr-cs.json';
import hr from './__locales/hr.json';
import hu from './__locales/hu.json';
import fa from './__locales/fa.json';
import th from './__locales/th.json';
import ro from './__locales/ro.json';
import siLk from './__locales/si-lk.json';
import { setHtmlLangAttr } from './helpers/helpers';
const resources = {
@ -114,6 +116,9 @@ const resources = {
hr: {
translation: hr,
},
hu: {
translation: hu,
},
fa: {
translation: fa,
},
@ -123,6 +128,9 @@ const resources = {
ro: {
translation: ro,
},
'si-lk': {
translation: siLk,
},
};
const availableLanguages = Object.keys(LANGUAGES);

View File

@ -4,50 +4,40 @@ import PropTypes from 'prop-types';
import { getIpList, getDnsAddress, getWebAddress } from '../../helpers/helpers';
import { ALL_INTERFACES_IP } from '../../helpers/constants';
const AddressList = (props) => {
let webAddress = getWebAddress(props.address, props.port);
let dnsAddress = getDnsAddress(props.address, props.port);
const renderItem = ({
ip, port, isDns,
}) => {
const webAddress = getWebAddress(ip, port);
const dnsAddress = getDnsAddress(ip, port);
if (props.address === ALL_INTERFACES_IP) {
return getIpList(props.interfaces).map((ip) => {
webAddress = getWebAddress(ip, props.port);
dnsAddress = getDnsAddress(ip, props.port);
if (props.isDns) {
return (
<li key={ip}>
<strong>
{dnsAddress}
</strong>
</li>
);
}
return (
<li key={ip}>
<a href={webAddress}>
{webAddress}
</a>
</li>
);
});
return <li key={ip}>{isDns
? <strong>{dnsAddress}</strong>
: <a href={webAddress}>{webAddress}</a>
}
if (props.isDns) {
return (
<strong>
{dnsAddress}
</strong>
);
}
return (
<a href={webAddress}>
{webAddress}
</a>
);
</li>;
};
const AddressList = ({
address,
interfaces,
port,
isDns,
}) => <ul className="list-group pl-4">{
address === ALL_INTERFACES_IP
? getIpList(interfaces)
.map((ip) => renderItem({
ip,
port,
isDns,
}))
: renderItem({
ip: address,
port,
isDns,
})
}
</ul>;
AddressList.propTypes = {
interfaces: PropTypes.object.isRequired,
address: PropTypes.string.isRequired,
@ -58,4 +48,10 @@ AddressList.propTypes = {
isDns: PropTypes.bool,
};
renderItem.propTypes = {
ip: PropTypes.string.isRequired,
port: PropTypes.string.isRequired,
isDns: PropTypes.bool.isRequired,
};
export default AddressList;

View File

@ -26,7 +26,7 @@ let Devices = (props) => (
interfaces={props.interfaces}
address={props.dnsIp}
port={props.dnsPort}
isDns={true}
isDns
/>
</div>
</div>

View File

@ -10,51 +10,36 @@ import AddressList from './AddressList';
import { getInterfaceIp } from '../../helpers/helpers';
import {
ALL_INTERFACES_IP, FORM_NAME, ADDRESS_IN_USE_TEXT, PORT_53_FAQ_LINK, UBUNTU_SYSTEM_PORT,
ALL_INTERFACES_IP,
FORM_NAME,
ADDRESS_IN_USE_TEXT,
PORT_53_FAQ_LINK,
STATUS_RESPONSE,
STANDARD_DNS_PORT,
STANDARD_WEB_PORT,
} from '../../helpers/constants';
import { renderInputField, toNumber } from '../../helpers/form';
import { validateRequiredValue, validateInstallPort } from '../../helpers/validators';
const STATIC_STATUS = {
ENABLED: 'yes',
DISABLED: 'no',
ERROR: 'error',
};
const renderInterfaces = (interfaces) => Object.values(interfaces)
.map((option) => {
const {
name,
ip_addresses,
flags,
} = option;
const renderInterfaces = ((interfaces) => (
Object.keys(interfaces)
.map((item) => {
const option = interfaces[item];
const {
name,
ip_addresses,
flags,
} = option;
if (option && ip_addresses?.length > 0) {
const ip = getInterfaceIp(option);
const isDown = flags?.includes('down');
if (option && ip_addresses?.length > 0) {
const ip = getInterfaceIp(option);
const isDown = flags?.includes('down');
return <option value={ip} key={name} disabled={isDown}>
{name} - {ip} {isDown && `(${<Trans>down</Trans>})`}
</option>;
}
if (isDown) {
return (
<option value={ip} key={name} disabled>
<>
{name} - {ip} (<Trans>down</Trans>)
</>
</option>
);
}
return (
<option value={ip} key={name}>
{name} - {ip}
</option>
);
}
return false;
})
));
return null;
});
class Settings extends Component {
componentDidMount() {
@ -77,42 +62,36 @@ class Settings extends Component {
getStaticIpMessage = (staticIp) => {
const { static: status, ip } = staticIp;
if (!status) {
return '';
}
return (
<>
{status === STATIC_STATUS.DISABLED && (
<>
<div className="mb-2">
<Trans values={{ ip }} components={[<strong key="0">text</strong>]}>
install_static_configure
</Trans>
</div>
<button
type="button"
className="btn btn-outline-primary btn-sm"
onClick={() => this.handleStaticIp(ip)}
>
<Trans>set_static_ip</Trans>
</button>
</>
)}
{status === STATIC_STATUS.ERROR && (
<div className="text-danger">
<Trans>install_static_error</Trans>
</div>
)}
{status === STATIC_STATUS.ENABLED && (
<div className="text-success">
<Trans>
install_static_ok
switch (status) {
case STATUS_RESPONSE.NO: {
return <>
<div className="mb-2">
<Trans values={{ ip }} components={[<strong key="0">text</strong>]}>
install_static_configure
</Trans>
</div>
)}
</>
);
<button
type="button"
className="btn btn-outline-primary btn-sm"
onClick={() => this.handleStaticIp(ip)}
>
<Trans>set_static_ip</Trans>
</button>
</>;
}
case STATUS_RESPONSE.ERROR: {
return <div className="text-danger">
<Trans>install_static_error</Trans>
</div>;
}
case STATUS_RESPONSE.YES: {
return <div className="text-success">
<Trans>install_static_ok</Trans>
</div>;
}
default:
return null;
}
};
handleAutofix = (type) => {
@ -229,7 +208,7 @@ class Settings extends Component {
component={renderInputField}
type="number"
className="form-control"
placeholder="80"
placeholder={STANDARD_WEB_PORT.toString()}
validate={[validateInstallPort, validateRequiredValue]}
normalize={toNumber}
onChange={handleChange}
@ -297,7 +276,7 @@ class Settings extends Component {
component={renderInputField}
type="number"
className="form-control"
placeholder="80"
placeholder={STANDARD_WEB_PORT.toString()}
validate={[validateInstallPort, validateRequiredValue]}
normalize={toNumber}
onChange={handleChange}
@ -332,7 +311,7 @@ class Settings extends Component {
</p>
</div>}
</>}
{dnsPort === UBUNTU_SYSTEM_PORT && !isDnsFixAvailable
{dnsPort === STANDARD_DNS_PORT && !isDnsFixAvailable
&& dnsStatus.includes(ADDRESS_IN_USE_TEXT)
&& <Trans
components={[<a href={PORT_53_FAQ_LINK} key="0">link</a>]}>

View File

@ -107,7 +107,7 @@
}
.form__message--error {
color: #cd201f;
color: var(--red);
}
.setup__button {

View File

@ -43,5 +43,5 @@
}
.form__message--error {
color: #cd201f;
color: var(--red);
}

View File

@ -0,0 +1,175 @@
import { handleActions } from 'redux-actions';
import * as actions from '../actions';
import { areEqualVersions } from '../helpers/version';
import { STANDARD_DNS_PORT, STANDARD_WEB_PORT } from '../helpers/constants';
const dashboard = handleActions(
{
[actions.setDnsRunningStatus]: (state, { payload }) => (
{
...state,
isCoreRunning: payload,
}
),
[actions.dnsStatusRequest]: (state) => ({
...state,
processing: true,
}),
[actions.dnsStatusFailure]: (state) => ({
...state,
processing: false,
}),
[actions.dnsStatusSuccess]: (state, { payload }) => {
const {
version,
dns_port: dnsPort,
dns_addresses: dnsAddresses,
protection_enabled: protectionEnabled,
http_port: httpPort,
language,
} = payload;
const newState = {
...state,
isCoreRunning: true,
processing: false,
dnsVersion: version,
dnsPort,
dnsAddresses,
protectionEnabled,
language,
httpPort,
};
return newState;
},
[actions.getVersionRequest]: (state) => ({
...state,
processingVersion: true,
}),
[actions.getVersionFailure]: (state) => ({
...state,
processingVersion: false,
}),
[actions.getVersionSuccess]: (state, { payload }) => {
const currentVersion = state.dnsVersion === 'undefined' ? 0 : state.dnsVersion;
if (!payload.disabled && !areEqualVersions(currentVersion, payload.new_version)) {
const {
announcement_url: announcementUrl,
new_version: newVersion,
can_autoupdate: canAutoUpdate,
} = payload;
const newState = {
...state,
announcementUrl,
newVersion,
canAutoUpdate,
isUpdateAvailable: true,
processingVersion: false,
checkUpdateFlag: !payload.disabled,
};
return newState;
}
return {
...state,
processingVersion: false,
checkUpdateFlag: !payload.disabled,
};
},
[actions.getUpdateRequest]: (state) => ({
...state,
processingUpdate: true,
}),
[actions.getUpdateFailure]: (state) => ({
...state,
processingUpdate: false,
}),
[actions.getUpdateSuccess]: (state) => {
const newState = {
...state,
processingUpdate: false,
};
return newState;
},
[actions.toggleProtectionRequest]: (state) => ({
...state,
processingProtection: true,
}),
[actions.toggleProtectionFailure]: (state) => ({
...state,
processingProtection: false,
}),
[actions.toggleProtectionSuccess]: (state) => {
const newState = {
...state,
protectionEnabled: !state.protectionEnabled,
processingProtection: false,
};
return newState;
},
[actions.getLanguageSuccess]: (state, { payload }) => {
const newState = {
...state,
language: payload,
};
return newState;
},
[actions.getClientsRequest]: (state) => ({
...state,
processingClients: true,
}),
[actions.getClientsFailure]: (state) => ({
...state,
processingClients: false,
}),
[actions.getClientsSuccess]: (state, { payload }) => {
const newState = {
...state,
...payload,
processingClients: false,
};
return newState;
},
[actions.getProfileRequest]: (state) => ({
...state,
processingProfile: true,
}),
[actions.getProfileFailure]: (state) => ({
...state,
processingProfile: false,
}),
[actions.getProfileSuccess]: (state, { payload }) => ({
...state,
name: payload.name,
processingProfile: false,
}),
},
{
processing: true,
isCoreRunning: true,
processingVersion: true,
processingClients: true,
processingUpdate: false,
processingProfile: true,
protectionEnabled: false,
processingProtection: false,
httpPort: STANDARD_WEB_PORT,
dnsPort: STANDARD_DNS_PORT,
dnsAddresses: [],
dnsVersion: '',
clients: [],
autoClients: [],
supportedTags: [],
name: '',
checkUpdateFlag: false,
},
);
export default dashboard;

203
client/src/reducers/dhcp.js Normal file
View File

@ -0,0 +1,203 @@
import { handleActions } from 'redux-actions';
import * as actions from '../actions';
import { enrichWithConcatenatedIpAddresses } from '../helpers/helpers';
// todo: figure out if we cat get rid of redux-form state duplication
const dhcp = handleActions(
{
[actions.getDhcpStatusRequest]: (state) => ({
...state,
processing: true,
}),
[actions.getDhcpStatusFailure]: (state) => ({
...state,
processing: false,
}),
[actions.getDhcpStatusSuccess]: (state, { payload }) => {
const { static_leases: staticLeases, ...values } = payload;
const newState = {
...state,
staticLeases,
processing: false,
...values,
};
return newState;
},
[actions.getDhcpInterfacesRequest]: (state) => ({
...state,
processingInterfaces: true,
}),
[actions.getDhcpInterfacesFailure]: (state) => ({
...state,
processingInterfaces: false,
}),
[actions.getDhcpInterfacesSuccess]: (state, { payload }) => {
const newState = {
...state,
interfaces: enrichWithConcatenatedIpAddresses(payload),
processingInterfaces: false,
};
return newState;
},
[actions.findActiveDhcpRequest]: (state) => ({
...state,
processingStatus: true,
}),
[actions.findActiveDhcpFailure]: (state) => ({
...state,
processingStatus: false,
}),
[actions.findActiveDhcpSuccess]: (state, { payload }) => {
const newState = {
...state,
check: payload,
processingStatus: false,
};
return newState;
},
[actions.toggleDhcpRequest]: (state) => ({
...state,
processingDhcp: true,
}),
[actions.toggleDhcpFailure]: (state) => ({
...state,
processingDhcp: false,
}),
[actions.toggleDhcpSuccess]: (state) => {
const { enabled } = state;
const newState = {
...state,
enabled: !enabled,
check: null,
processingDhcp: false,
};
return newState;
},
[actions.setDhcpConfigRequest]: (state) => ({
...state,
processingConfig: true,
}),
[actions.setDhcpConfigFailure]: (state) => ({
...state,
processingConfig: false,
}),
[actions.setDhcpConfigSuccess]: (state, { payload }) => {
const { v4, v6 } = state;
const newConfigV4 = { ...v4, ...payload.v4 };
const newConfigV6 = { ...v6, ...payload.v6 };
const newState = {
...state,
v4: newConfigV4,
v6: newConfigV6,
interface_name: payload.interface_name,
processingConfig: false,
};
return newState;
},
[actions.resetDhcpRequest]: (state) => ({
...state,
processingReset: true,
}),
[actions.resetDhcpFailure]: (state) => ({
...state,
processingReset: false,
}),
[actions.resetDhcpSuccess]: (state) => ({
...state,
processingReset: false,
enabled: false,
v4: {},
v6: {},
interface_name: '',
}),
[actions.toggleLeaseModal]: (state) => {
const newState = {
...state,
isModalOpen: !state.isModalOpen,
};
return newState;
},
[actions.addStaticLeaseRequest]: (state) => ({
...state,
processingAdding: true,
}),
[actions.addStaticLeaseFailure]: (state) => ({
...state,
processingAdding: false,
}),
[actions.addStaticLeaseSuccess]: (state, { payload }) => {
const { ip, mac, hostname } = payload;
const newLease = {
ip,
mac,
hostname: hostname || '',
};
const leases = [...state.staticLeases, newLease];
const newState = {
...state,
staticLeases: leases,
processingAdding: false,
};
return newState;
},
[actions.removeStaticLeaseRequest]: (state) => ({
...state,
processingDeleting: true,
}),
[actions.removeStaticLeaseFailure]: (state) => ({
...state,
processingDeleting: false,
}),
[actions.removeStaticLeaseSuccess]: (state, { payload }) => {
const leaseToRemove = payload.ip;
const leases = state.staticLeases.filter((item) => item.ip !== leaseToRemove);
const newState = {
...state,
staticLeases: leases,
processingDeleting: false,
};
return newState;
},
},
{
processing: true,
processingStatus: false,
processingInterfaces: false,
processingDhcp: false,
processingConfig: false,
processingAdding: false,
processingDeleting: false,
enabled: false,
interface_name: '',
check: null,
v4: {
gateway_ip: '',
subnet_mask: '',
range_start: '',
range_end: '',
lease_duration: 0,
},
v6: {
range_start: '',
lease_duration: 0,
},
leases: [],
staticLeases: [],
isModalOpen: false,
dhcp_available: false,
},
);
export default dhcp;

View File

@ -1,9 +1,9 @@
import { handleActions } from 'redux-actions';
import * as actions from '../actions/dnsConfig';
import { BLOCKING_MODES } from '../helpers/constants';
import { ALL_INTERFACES_IP, BLOCKING_MODES } from '../helpers/constants';
const DEFAULT_BLOCKING_IPV4 = '0.0.0.0';
const DEFAULT_BLOCKING_IPV4 = ALL_INTERFACES_IP;
const DEFAULT_BLOCKING_IPV6 = '::';
const dnsConfig = handleActions(

View File

@ -1,9 +1,6 @@
import { combineReducers } from 'redux';
import { handleActions } from 'redux-actions';
import { loadingBarReducer } from 'react-redux-loading-bar';
import { reducer as formReducer } from 'redux-form';
import * as actions from '../actions';
import toasts from './toasts';
import encryption from './encryption';
import clients from './clients';
@ -14,296 +11,9 @@ import stats from './stats';
import queryLogs from './queryLogs';
import dnsConfig from './dnsConfig';
import filtering from './filtering';
import { areEqualVersions } from '../helpers/version';
const settings = handleActions(
{
[actions.initSettingsRequest]: (state) => ({ ...state, processing: true }),
[actions.initSettingsFailure]: (state) => ({ ...state, processing: false }),
[actions.initSettingsSuccess]: (state, { payload }) => {
const { settingsList } = payload;
const newState = { ...state, settingsList, processing: false };
return newState;
},
[actions.toggleSettingStatus]: (state, { payload }) => {
const { settingsList } = state;
const { settingKey } = payload;
const setting = settingsList[settingKey];
const newSetting = { ...setting, enabled: !setting.enabled };
const newSettingsList = { ...settingsList, [settingKey]: newSetting };
return { ...state, settingsList: newSettingsList };
},
[actions.testUpstreamRequest]: (state) => ({ ...state, processingTestUpstream: true }),
[actions.testUpstreamFailure]: (state) => ({ ...state, processingTestUpstream: false }),
[actions.testUpstreamSuccess]: (state) => ({ ...state, processingTestUpstream: false }),
},
{
processing: true,
processingTestUpstream: false,
processingDhcpStatus: false,
settingsList: {},
},
);
const dashboard = handleActions(
{
[actions.setDnsRunningStatus]: (state, { payload }) => (
{ ...state, isCoreRunning: payload }
),
[actions.dnsStatusRequest]: (state) => ({ ...state, processing: true }),
[actions.dnsStatusFailure]: (state) => ({ ...state, processing: false }),
[actions.dnsStatusSuccess]: (state, { payload }) => {
const {
version,
dns_port: dnsPort,
dns_addresses: dnsAddresses,
protection_enabled: protectionEnabled,
http_port: httpPort,
language,
} = payload;
const newState = {
...state,
isCoreRunning: true,
processing: false,
dnsVersion: version,
dnsPort,
dnsAddresses,
protectionEnabled,
language,
httpPort,
};
return newState;
},
[actions.getVersionRequest]: (state) => ({ ...state, processingVersion: true }),
[actions.getVersionFailure]: (state) => ({ ...state, processingVersion: false }),
[actions.getVersionSuccess]: (state, { payload }) => {
const currentVersion = state.dnsVersion === 'undefined' ? 0 : state.dnsVersion;
if (!payload.disabled && !areEqualVersions(currentVersion, payload.new_version)) {
const {
announcement_url: announcementUrl,
new_version: newVersion,
can_autoupdate: canAutoUpdate,
} = payload;
const newState = {
...state,
announcementUrl,
newVersion,
canAutoUpdate,
isUpdateAvailable: true,
processingVersion: false,
checkUpdateFlag: !payload.disabled,
};
return newState;
}
return {
...state,
processingVersion: false,
checkUpdateFlag: !payload.disabled,
};
},
[actions.getUpdateRequest]: (state) => ({ ...state, processingUpdate: true }),
[actions.getUpdateFailure]: (state) => ({ ...state, processingUpdate: false }),
[actions.getUpdateSuccess]: (state) => {
const newState = { ...state, processingUpdate: false };
return newState;
},
[actions.toggleProtectionRequest]: (state) => ({ ...state, processingProtection: true }),
[actions.toggleProtectionFailure]: (state) => ({ ...state, processingProtection: false }),
[actions.toggleProtectionSuccess]: (state) => {
const newState = {
...state,
protectionEnabled: !state.protectionEnabled,
processingProtection: false,
};
return newState;
},
[actions.getLanguageSuccess]: (state, { payload }) => {
const newState = { ...state, language: payload };
return newState;
},
[actions.getClientsRequest]: (state) => ({ ...state, processingClients: true }),
[actions.getClientsFailure]: (state) => ({ ...state, processingClients: false }),
[actions.getClientsSuccess]: (state, { payload }) => {
const newState = {
...state,
...payload,
processingClients: false,
};
return newState;
},
[actions.getProfileRequest]: (state) => ({ ...state, processingProfile: true }),
[actions.getProfileFailure]: (state) => ({ ...state, processingProfile: false }),
[actions.getProfileSuccess]: (state, { payload }) => ({
...state,
name: payload.name,
processingProfile: false,
}),
},
{
processing: true,
isCoreRunning: true,
processingVersion: true,
processingClients: true,
processingUpdate: false,
processingProfile: true,
protectionEnabled: false,
processingProtection: false,
httpPort: 80,
dnsPort: 53,
dnsAddresses: [],
dnsVersion: '',
clients: [],
autoClients: [],
supportedTags: [],
name: '',
checkUpdateFlag: false,
},
);
const dhcp = handleActions(
{
[actions.getDhcpStatusRequest]: (state) => ({ ...state, processing: true }),
[actions.getDhcpStatusFailure]: (state) => ({ ...state, processing: false }),
[actions.getDhcpStatusSuccess]: (state, { payload }) => {
const { static_leases: staticLeases, ...values } = payload;
const newState = {
...state,
staticLeases,
processing: false,
...values,
};
return newState;
},
[actions.getDhcpInterfacesRequest]: (state) => ({ ...state, processingInterfaces: true }),
[actions.getDhcpInterfacesFailure]: (state) => ({ ...state, processingInterfaces: false }),
[actions.getDhcpInterfacesSuccess]: (state, { payload }) => {
const newState = {
...state,
interfaces: payload,
processingInterfaces: false,
};
return newState;
},
[actions.findActiveDhcpRequest]: (state) => ({ ...state, processingStatus: true }),
[actions.findActiveDhcpFailure]: (state) => ({ ...state, processingStatus: false }),
[actions.findActiveDhcpSuccess]: (state, { payload }) => {
const { other_server: otherServer, static_ip: staticIP } = payload;
const newState = {
...state,
check: {
otherServer,
staticIP,
},
processingStatus: false,
};
return newState;
},
[actions.toggleDhcpRequest]: (state) => ({ ...state, processingDhcp: true }),
[actions.toggleDhcpFailure]: (state) => ({ ...state, processingDhcp: false }),
[actions.toggleDhcpSuccess]: (state) => {
const { config } = state;
const newConfig = { ...config, enabled: !config.enabled };
const newState = {
...state,
config: newConfig,
check: null,
processingDhcp: false,
};
return newState;
},
[actions.setDhcpConfigRequest]: (state) => ({ ...state, processingConfig: true }),
[actions.setDhcpConfigFailure]: (state) => ({ ...state, processingConfig: false }),
[actions.setDhcpConfigSuccess]: (state, { payload }) => {
const { config } = state;
const newConfig = { ...config, ...payload };
const newState = { ...state, config: newConfig, processingConfig: false };
return newState;
},
[actions.resetDhcpRequest]: (state) => ({ ...state, processingReset: true }),
[actions.resetDhcpFailure]: (state) => ({ ...state, processingReset: false }),
[actions.resetDhcpSuccess]: (state) => ({
...state,
processingReset: false,
config: {
enabled: false,
},
}),
[actions.toggleLeaseModal]: (state) => {
const newState = {
...state,
isModalOpen: !state.isModalOpen,
};
return newState;
},
[actions.addStaticLeaseRequest]: (state) => ({ ...state, processingAdding: true }),
[actions.addStaticLeaseFailure]: (state) => ({ ...state, processingAdding: false }),
[actions.addStaticLeaseSuccess]: (state, { payload }) => {
const { ip, mac, hostname } = payload;
const newLease = {
ip,
mac,
hostname: hostname || '',
};
const leases = [...state.staticLeases, newLease];
const newState = {
...state,
staticLeases: leases,
processingAdding: false,
};
return newState;
},
[actions.removeStaticLeaseRequest]: (state) => ({ ...state, processingDeleting: true }),
[actions.removeStaticLeaseFailure]: (state) => ({ ...state, processingDeleting: false }),
[actions.removeStaticLeaseSuccess]: (state, { payload }) => {
const leaseToRemove = payload.ip;
const leases = state.staticLeases.filter((item) => item.ip !== leaseToRemove);
const newState = {
...state,
staticLeases: leases,
processingDeleting: false,
};
return newState;
},
},
{
processing: true,
processingStatus: false,
processingInterfaces: false,
processingDhcp: false,
processingConfig: false,
processingAdding: false,
processingDeleting: false,
config: {
enabled: false,
},
check: null,
leases: [],
staticLeases: [],
isModalOpen: false,
},
);
import settings from './settings';
import dashboard from './dashboard';
import dhcp from './dhcp';
export default combineReducers({
settings,

View File

@ -4,7 +4,9 @@ import { reducer as formReducer } from 'redux-form';
import * as actions from '../actions/install';
import toasts from './toasts';
import { INSTALL_FIRST_STEP } from '../helpers/constants';
import {
ALL_INTERFACES_IP, INSTALL_FIRST_STEP, STANDARD_DNS_PORT, STANDARD_WEB_PORT,
} from '../helpers/constants';
const install = handleActions({
[actions.getDefaultAddressesRequest]: (state) => ({ ...state, processingDefault: true }),
@ -45,14 +47,14 @@ const install = handleActions({
processingSubmit: false,
processingCheck: false,
web: {
ip: '0.0.0.0',
port: 80,
ip: ALL_INTERFACES_IP,
port: STANDARD_WEB_PORT,
status: '',
can_autofix: false,
},
dns: {
ip: '0.0.0.0',
port: 53,
ip: ALL_INTERFACES_IP,
port: STANDARD_DNS_PORT,
status: '',
can_autofix: false,
},

View File

@ -0,0 +1,63 @@
import { handleActions } from 'redux-actions';
import * as actions from '../actions';
const settings = handleActions(
{
[actions.initSettingsRequest]: (state) => ({
...state,
processing: true,
}),
[actions.initSettingsFailure]: (state) => ({
...state,
processing: false,
}),
[actions.initSettingsSuccess]: (state, { payload }) => {
const { settingsList } = payload;
const newState = {
...state,
settingsList,
processing: false,
};
return newState;
},
[actions.toggleSettingStatus]: (state, { payload }) => {
const { settingsList } = state;
const { settingKey } = payload;
const setting = settingsList[settingKey];
const newSetting = {
...setting,
enabled: !setting.enabled,
};
const newSettingsList = {
...settingsList,
[settingKey]: newSetting,
};
return {
...state,
settingsList: newSettingsList,
};
},
[actions.testUpstreamRequest]: (state) => ({
...state,
processingTestUpstream: true,
}),
[actions.testUpstreamFailure]: (state) => ({
...state,
processingTestUpstream: false,
}),
[actions.testUpstreamSuccess]: (state) => ({
...state,
processingTestUpstream: false,
}),
},
{
processing: true,
processingTestUpstream: false,
processingDhcpStatus: false,
settingsList: {},
},
);
export default settings;

View File

@ -8,9 +8,12 @@ import { removeToast } from '../actions';
const toasts = handleActions({
[addErrorToast]: (state, { payload }) => {
const message = payload.error.toString();
console.error(message);
const errorToast = {
id: nanoid(),
message: payload.error.toString(),
message,
type: 'error',
};

View File

@ -39,9 +39,11 @@ var allowedLanguages = map[string]bool{
"tr": true,
"sr-cs": true,
"hr": true,
"hu": true,
"fa": true,
"th": true,
"ro": true,
"si-lk": true,
}
func isLanguageAllowed(language string) bool {