feat: content blocking в родительском контроле (блокировка доменов на устройство)

Новые опции config schedule:
- blocked_domains: домены для блокировки (full:/keyword:/regex: префиксы)
- mode: block (список) | allow (whitelist — всё кроме списка)
- dns_level: блокировка на DNS-уровне (NXDOMAIN через dns-block-in)
- notify: уведомление в Telegram при попытке доступа

Механизм:
- sing-box: новый dns-block-in inbound (127.0.0.43:1053) с reject-правилами
  для заблокированных доменов; source_ip_cidr для IP-устройств;
  invert для whitelist-режима
- nftables: nat chain dns_block — REDIRECT DNS-запросов устройств на
  dns-block-in; time-based через meta hour для расписаний; MAC-устройства
  матчатся по ether saddr (DHCP-динамические IP поддерживаются)
- route rules reject для always-on расписаний (fallback при обходе DNS)
- telegram worker: проверка nft counters каждые 60s, уведомление о
  заблокированной активности

UI (parental.js):
- колонка Blocked Sites в таблице
- textarea с пресетами: соцсети, видео, игры, взрослый контент,
  мессенджеры, магазины
- mode / dns_level / notify переключатели

Валидация: домены, mode, dns_level, notify в validator.uc
Тесты: tests/content_blocking.sh (98/98 backend, 522/522 frontend)
This commit is contained in:
Dushnilin
2026-08-21 05:11:39 +03:00
parent 7084b9ba3d
commit bd083cc5d3
14 changed files with 1573 additions and 181 deletions
+217 -61
View File
@@ -146,7 +146,7 @@
"call": "Action Mode",
"key": "Action Mode",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:348"
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:441"
]
},
{
@@ -165,16 +165,16 @@
"call": "Active (Allowed)",
"key": "Active (Allowed)",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:260",
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:280"
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:353",
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:373"
]
},
{
"call": "Active (Blocking)",
"key": "Active (Blocking)",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:260",
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:280"
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:353",
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:373"
]
},
{
@@ -335,6 +335,13 @@
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/settings.js:2145"
]
},
{
"call": "Adult content",
"key": "Adult content",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:73"
]
},
{
"call": "Advanced self-healing features. Requires Watchdog to be enabled.",
"key": "Advanced self-healing features. Requires Watchdog to be enabled.",
@@ -509,8 +516,8 @@
"call": "All devices",
"key": "All devices",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:139",
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:159"
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:201",
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:221"
]
},
{
@@ -524,15 +531,15 @@
"call": "All Internet",
"key": "All Internet",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:169",
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:194"
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:231",
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:256"
]
},
{
"call": "All Internet (Complete internet cutoff)",
"key": "All Internet (Complete internet cutoff)",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:320"
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:413"
]
},
{
@@ -584,15 +591,15 @@
"call": "Allow / Enable ONLY in scheduled hours (White period)",
"key": "Allow / Enable ONLY in scheduled hours (White period)",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:354"
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:447"
]
},
{
"call": "Allow in interval",
"key": "Allow in interval",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:206",
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:212"
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:268",
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:274"
]
},
{
@@ -602,6 +609,27 @@
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js:8514"
]
},
{
"call": "Allow only",
"key": "Allow only",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:314"
]
},
{
"call": "Allow only listed sites (Whitelist)",
"key": "Allow only listed sites (Whitelist)",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:587"
]
},
{
"call": "Allow only:",
"key": "Allow only:",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:330"
]
},
{
"call": "Allowed domain prefixes are full:, keyword:, and regex:",
"key": "Allowed domain prefixes are full:, keyword:, and regex:",
@@ -904,7 +932,7 @@
"call": "Beginning of the schedule interval (e.g. 22:00)",
"key": "Beginning of the schedule interval (e.g. 22:00)",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:361"
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:454"
]
},
{
@@ -922,19 +950,33 @@
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js:9554"
]
},
{
"call": "Block",
"key": "Block",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:315"
]
},
{
"call": "Block / Disable in scheduled hours (Standard)",
"key": "Block / Disable in scheduled hours (Standard)",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:353"
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:446"
]
},
{
"call": "Block at DNS level",
"key": "Block at DNS level",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:593"
]
},
{
"call": "Block in interval",
"key": "Block in interval",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:208",
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:212"
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:270",
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:274"
]
},
{
@@ -944,6 +986,41 @@
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/tachyon.js:467"
]
},
{
"call": "Block listed sites (Standard)",
"key": "Block listed sites (Standard)",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:586"
]
},
{
"call": "Block:",
"key": "Block:",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:330"
]
},
{
"call": "Block: deny the listed sites. Allow only (whitelist): deny everything except the listed sites. Whitelist mode requires DNS-level blocking and applies to the schedule window.",
"key": "Block: deny the listed sites. Allow only (whitelist): deny everything except the listed sites. Whitelist mode requires DNS-level blocking and applies to the schedule window.",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:582"
]
},
{
"call": "Blocked Sites",
"key": "Blocked Sites",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:303"
]
},
{
"call": "Blocked Sites (Domains)",
"key": "Blocked Sites (Domains)",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:492"
]
},
{
"call": "Bootstrap DNS",
"key": "Bootstrap DNS",
@@ -1361,8 +1438,8 @@
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:8845",
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:9323",
"src/tachyon/tabs/diagnostic/checks/getCheckItemsMeta.ts:10",
"src/tachyon/tabs/diagnostic/helpers/getMeta.ts:26"
"src/tachyon/tabs/diagnostic/helpers/getMeta.ts:26",
"src/tachyon/tabs/diagnostic/checks/getCheckItemsMeta.ts:10"
]
},
{
@@ -1372,9 +1449,9 @@
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:8834",
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:9280",
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:9334",
"src/tachyon/tabs/diagnostic/helpers/getMeta.ts:13",
"src/tachyon/tabs/diagnostic/checks/getCheckItemsMeta.ts:23",
"src/tachyon/tabs/diagnostic/checks/runFakeIPCheck.ts:43",
"src/tachyon/tabs/diagnostic/helpers/getMeta.ts:13"
"src/tachyon/tabs/diagnostic/checks/runFakeIPCheck.ts:43"
]
},
{
@@ -1403,7 +1480,7 @@
"call": "Choose whether to restrict the entire internet or only specific Tachyon routing sections.",
"key": "Choose whether to restrict the entire internet or only specific Tachyon routing sections.",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:316"
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:409"
]
},
{
@@ -1417,7 +1494,7 @@
"call": "Choose which routing sections to apply this schedule to.",
"key": "Choose which routing sections to apply this schedule to.",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:328"
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:421"
]
},
{
@@ -1770,6 +1847,13 @@
"src/tachyon/tabs/diagnostic/checks/runSectionsCheck.ts:174"
]
},
{
"call": "Content Mode",
"key": "Content Mode",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:581"
]
},
{
"call": "Cookie packet junk size (S3)",
"key": "Cookie packet junk size (S3)",
@@ -1958,7 +2042,7 @@
"call": "Days of Week",
"key": "Days of Week",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:384"
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:477"
]
},
{
@@ -2055,7 +2139,7 @@
"call": "Device / IP / MAC",
"key": "Device / IP / MAC",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:132"
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:194"
]
},
{
@@ -2187,8 +2271,8 @@
"call": "Disabled",
"key": "Disabled",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:250",
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:277"
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:343",
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:370"
]
},
{
@@ -2532,6 +2616,13 @@
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js:9310"
]
},
{
"call": "Domains to block for the selected devices. Leave empty to block all internet. Enter one domain per line, e.g. youtube.com. Use the preset buttons to quickly add common categories.",
"key": "Domains to block for the selected devices. Leave empty to block all internet. Enter one domain per line, e.g. youtube.com. Use the preset buttons to quickly add common categories.",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:493"
]
},
{
"call": "Done",
"key": "Done",
@@ -2699,7 +2790,7 @@
"call": "e.g. Kids Phone (Night YouTube)",
"key": "e.g. Kids Phone (Night YouTube)",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:290"
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:383"
]
},
{
@@ -2728,7 +2819,7 @@
"call": "Enable",
"key": "Enable",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:125",
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:187",
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js:7235",
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/server.js:2618"
]
@@ -2958,14 +3049,14 @@
"call": "End of the schedule interval (e.g. 08:00). Supports spans crossing midnight.",
"key": "End of the schedule interval (e.g. 08:00). Supports spans crossing midnight.",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:373"
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:466"
]
},
{
"call": "End Time (HH:MM)",
"key": "End Time (HH:MM)",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:372"
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:465"
]
},
{
@@ -3070,7 +3161,7 @@
"call": "Every day",
"key": "Every day",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:98"
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:160"
]
},
{
@@ -3256,12 +3347,12 @@
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:16728",
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:16786",
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:16957",
"src/tachyon/methods/shell/index.ts:172",
"src/tachyon/methods/shell/index.ts:179",
"src/tachyon/tabs/updates/initController.ts:631",
"src/tachyon/tabs/updates/initController.ts:632",
"src/tachyon/tabs/updates/initController.ts:707",
"src/tachyon/tabs/updates/initController.ts:920"
"src/tachyon/tabs/updates/initController.ts:920",
"src/tachyon/methods/shell/index.ts:172",
"src/tachyon/methods/shell/index.ts:179"
]
},
{
@@ -3668,6 +3759,13 @@
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/settings.js:1285"
]
},
{
"call": "Gaming",
"key": "Gaming",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:63"
]
},
{
"call": "Generate a random 16-character hexadecimal HWID",
"key": "Generate a random 16-character hexadecimal HWID",
@@ -4431,6 +4529,13 @@
"src/validators/validateDomain.ts:73"
]
},
{
"call": "Invalid domain: empty prefix value",
"key": "Invalid domain: empty prefix value",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:507"
]
},
{
"call": "Invalid format. Use an IP address or CIDR subnet",
"key": "Invalid format. Use an IP address or CIDR subnet",
@@ -4565,7 +4670,7 @@
"call": "Invalid IP or MAC address. Expected e.g. 192.168.1.150 or AA:BB:CC:DD:EE:FF",
"key": "Invalid IP or MAC address. Expected e.g. 192.168.1.150 or AA:BB:CC:DD:EE:FF",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:120"
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:182"
]
},
{
@@ -4771,7 +4876,7 @@
"call": "Invalid time format. Expected HH:MM (e.g. 22:00)",
"key": "Invalid time format. Expected HH:MM (e.g. 22:00)",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:47"
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:109"
]
},
{
@@ -5005,8 +5110,8 @@
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:8840",
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:9329",
"src/tachyon/tabs/diagnostic/checks/getCheckItemsMeta.ts:17",
"src/tachyon/tabs/diagnostic/helpers/getMeta.ts:20"
"src/tachyon/tabs/diagnostic/helpers/getMeta.ts:20",
"src/tachyon/tabs/diagnostic/checks/getCheckItemsMeta.ts:17"
]
},
{
@@ -5554,6 +5659,13 @@
"src/tachyon/tabs/dashboard/initController.ts:1867"
]
},
{
"call": "Messengers",
"key": "Messengers",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:84"
]
},
{
"call": "Method",
"key": "Method",
@@ -5607,7 +5719,7 @@
"call": "Mode",
"key": "Mode",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:200"
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:262"
]
},
{
@@ -5980,8 +6092,8 @@
"call": "No sections selected",
"key": "No sections selected",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:177",
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:196"
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:239",
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:258"
]
},
{
@@ -6012,6 +6124,8 @@
"key": "None",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:12555",
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:310",
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:329",
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/server.js:449",
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/server.js:2741",
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/server.js:3033",
@@ -6130,6 +6244,13 @@
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js:10197"
]
},
{
"call": "Notify in Telegram",
"key": "Notify in Telegram",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:604"
]
},
{
"call": "Notify on Certificate Warnings",
"key": "Notify on Certificate Warnings",
@@ -6630,8 +6751,8 @@
"key": "Pending",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:5187",
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:271",
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:281",
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:364",
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:374",
"src/tachyon/tabs/diagnostic/diagnostic.store.ts:63"
]
},
@@ -7469,6 +7590,13 @@
"src/tachyon/tabs/monitoring/initController.ts:737"
]
},
{
"call": "Return NXDOMAIN for blocked domains (recommended). When enabled, blocked domains are not resolved at all. When disabled, only direct connections are rejected.",
"key": "Return NXDOMAIN for blocked domains (recommended). When enabled, blocked domains are not resolved at all. When disabled, only direct connections are rejected.",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:594"
]
},
{
"call": "Route",
"key": "Route",
@@ -7552,7 +7680,7 @@
"call": "Rule Name",
"key": "Rule Name",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:287"
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:380"
]
},
{
@@ -7760,7 +7888,7 @@
"call": "Schedule",
"key": "Schedule",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:216"
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:278"
]
},
{
@@ -7932,7 +8060,7 @@
"call": "Select active days. If none selected, the rule applies every day.",
"key": "Select active days. If none selected, the rule applies every day.",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:385"
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:478"
]
},
{
@@ -7981,7 +8109,7 @@
"call": "Select LAN devices to apply this schedule to, or enter IP or MAC addresses manually.",
"key": "Select LAN devices to apply this schedule to, or enter IP or MAC addresses manually.",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:297"
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:390"
]
},
{
@@ -8030,7 +8158,7 @@
"call": "Select Sections",
"key": "Select Sections",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:327"
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:420"
]
},
{
@@ -8074,7 +8202,7 @@
"call": "Select whether to disable the target during the schedule or enable it ONLY during the schedule.",
"key": "Select whether to disable the target during the schedule or enable it ONLY during the schedule.",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:349"
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:442"
]
},
{
@@ -8138,6 +8266,13 @@
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/settings.js:2203"
]
},
{
"call": "Send a Telegram notification to admins when a blocked site is accessed. Requires the Telegram bot to be enabled.",
"key": "Send a Telegram notification to admins when a blocked site is accessed. Requires the Telegram bot to be enabled.",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:605"
]
},
{
"call": "Send a Telegram notification when a URLTest group switches to a new server.",
"key": "Send a Telegram notification when a URLTest group switches to a new server.",
@@ -8309,9 +8444,9 @@
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:3410",
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:3429",
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:12323",
"src/tachyon/tabs/diagnostic/initController.ts:604",
"src/tachyon/methods/shell/index.ts:422",
"src/tachyon/methods/shell/index.ts:443",
"src/tachyon/tabs/diagnostic/initController.ts:604"
"src/tachyon/methods/shell/index.ts:443"
]
},
{
@@ -8376,6 +8511,13 @@
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/settings.js:411"
]
},
{
"call": "Shopping",
"key": "Shopping",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:93"
]
},
{
"call": "Show metadata on dashboard",
"key": "Show metadata on dashboard",
@@ -8583,6 +8725,13 @@
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js:8542"
]
},
{
"call": "Social media",
"key": "Social media",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:43"
]
},
{
"call": "Sort Alphabetically",
"key": "Sort Alphabetically",
@@ -8633,7 +8782,7 @@
"call": "Specific Sections (Select sections below)",
"key": "Specific Sections (Select sections below)",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:321"
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:414"
]
},
{
@@ -8781,7 +8930,7 @@
"call": "Start Time (HH:MM)",
"key": "Start Time (HH:MM)",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:360"
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:453"
]
},
{
@@ -8812,7 +8961,7 @@
"key": "Status",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:2424",
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:241",
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:334",
"src/tachyon/tabs/dashboard/partials/renderAdvancedSettings.ts:465",
"src/tachyon/tabs/dashboard/partials/renderSections.ts:666"
]
@@ -9221,21 +9370,21 @@
"call": "Target",
"key": "Target",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:163"
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:225"
]
},
{
"call": "Target Devices (IP / MAC)",
"key": "Target Devices (IP / MAC)",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:296"
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:389"
]
},
{
"call": "Target Scope",
"key": "Target Scope",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:315"
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:408"
]
},
{
@@ -9514,7 +9663,7 @@
"call": "Time is required (e.g. 22:00)",
"key": "Time is required (e.g. 22:00)",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:43"
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:105"
]
},
{
@@ -10473,6 +10622,13 @@
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js:1986"
]
},
{
"call": "Video streaming",
"key": "Video streaming",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:53"
]
},
{
"call": "View logs",
"key": "View logs",
@@ -10609,14 +10765,14 @@
"call": "Weekdays (Mon-Fri)",
"key": "Weekdays (Mon-Fri)",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:106"
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:168"
]
},
{
"call": "Weekends (Sat-Sun)",
"key": "Weekends (Sat-Sun)",
"places": [
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:110"
"../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:172"
]
},
{
+148 -58
View File
@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: TACHYON\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-08-20 19:26+0300\n"
"PO-Revision-Date: 2026-08-20 19:26+0300\n"
"POT-Creation-Date: 2026-08-21 02:02+0300\n"
"PO-Revision-Date: 2026-08-21 02:02+0300\n"
"Last-Translator: Dushnilin <dushnilin@gmail.com>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
@@ -105,7 +105,7 @@ msgstr ""
msgid "Action"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:348
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:441
msgid "Action Mode"
msgstr ""
@@ -118,13 +118,13 @@ msgstr ""
msgid "Active"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:260
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:280
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:353
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:373
msgid "Active (Allowed)"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:260
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:280
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:353
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:373
msgid "Active (Blocking)"
msgstr ""
@@ -223,6 +223,10 @@ msgstr ""
msgid "Administrator Chat IDs"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:73
msgid "Adult content"
msgstr ""
#: src/tachyon/tabs/dashboard/partials/renderAdvancedSettings.ts:595
msgid "Advanced self-healing features. Requires Watchdog to be enabled."
msgstr ""
@@ -327,8 +331,8 @@ msgstr ""
msgid "All checks passed — Telegram bot should work correctly"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:139
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:159
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:201
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:221
msgid "All devices"
msgstr ""
@@ -336,12 +340,12 @@ msgstr ""
msgid "All except selected"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:169
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:194
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:231
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:256
msgid "All Internet"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:320
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:413
msgid "All Internet (Complete internet cutoff)"
msgstr ""
@@ -372,12 +376,12 @@ msgstr ""
msgid "All traffic from these IP addresses will be routed through the section unconditionally, ignoring all other conditions."
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:354
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:447
msgid "Allow / Enable ONLY in scheduled hours (White period)"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:206
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:212
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:268
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:274
msgid "Allow in interval"
msgstr ""
@@ -385,6 +389,18 @@ msgstr ""
msgid "Allow Insecure TLS"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:314
msgid "Allow only"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:587
msgid "Allow only listed sites (Whitelist)"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:330
msgid "Allow only:"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js:5332
msgid "Allowed domain prefixes are full:, keyword:, and regex:"
msgstr ""
@@ -563,7 +579,7 @@ msgstr ""
msgid "Bearer Auth Token"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:361
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:454
msgid "Beginning of the schedule interval (e.g. 22:00)"
msgstr ""
@@ -576,12 +592,20 @@ msgstr ""
msgid "BitTorrent / P2P"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:353
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:315
msgid "Block"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:446
msgid "Block / Disable in scheduled hours (Standard)"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:208
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:212
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:593
msgid "Block at DNS level"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:270
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:274
msgid "Block in interval"
msgstr ""
@@ -589,6 +613,26 @@ msgstr ""
msgid "Block internet access or specific proxy sections for individual devices based on time and days."
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:586
msgid "Block listed sites (Standard)"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:330
msgid "Block:"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:582
msgid "Block: deny the listed sites. Allow only (whitelist): deny everything except the listed sites. Whitelist mode requires DNS-level blocking and applies to the schedule window."
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:303
msgid "Blocked Sites"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:492
msgid "Blocked Sites (Domains)"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:8905
#: src/tachyon/tabs/diagnostic/checks/runDnsCheck.ts:57
msgid "Bootstrap DNS"
@@ -861,17 +905,17 @@ msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:8845
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:9323
#: src/tachyon/tabs/diagnostic/checks/getCheckItemsMeta.ts:10
#: src/tachyon/tabs/diagnostic/helpers/getMeta.ts:26
#: src/tachyon/tabs/diagnostic/checks/getCheckItemsMeta.ts:10
msgid "Checks failed"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:8834
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:9280
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:9334
#: src/tachyon/tabs/diagnostic/helpers/getMeta.ts:13
#: src/tachyon/tabs/diagnostic/checks/getCheckItemsMeta.ts:23
#: src/tachyon/tabs/diagnostic/checks/runFakeIPCheck.ts:43
#: src/tachyon/tabs/diagnostic/helpers/getMeta.ts:13
msgid "Checks passed"
msgstr ""
@@ -888,7 +932,7 @@ msgstr ""
msgid "Choose Hosts File..."
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:316
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:409
msgid "Choose whether to restrict the entire internet or only specific Tachyon routing sections."
msgstr ""
@@ -896,7 +940,7 @@ msgstr ""
msgid "Choose whether to route traffic to all destinations except selected countries (Exclude) or only to selected countries (Include)."
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:328
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:421
msgid "Choose which routing sections to apply this schedule to."
msgstr ""
@@ -1118,6 +1162,10 @@ msgstr ""
msgid "Connectivity probe failed"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:581
msgid "Content Mode"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/server.js:3415
msgid "Cookie packet junk size (S3)"
msgstr ""
@@ -1230,7 +1278,7 @@ msgstr ""
msgid "Dashboard currently unavailable"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:384
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:477
msgid "Days of Week"
msgstr ""
@@ -1288,7 +1336,7 @@ msgstr ""
msgid "detour must be a non-empty string"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:132
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:194
msgid "Device / IP / MAC"
msgstr ""
@@ -1366,8 +1414,8 @@ msgstr ""
msgid "Disable the QUIC protocol to improve compatibility or fix issues with video streaming"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:250
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:277
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:343
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:370
msgid "Disabled"
msgstr ""
@@ -1577,6 +1625,10 @@ msgstr ""
msgid "Domains automatically detected by Smart Detect or added via Telegram bot. You can manually edit or remove them here."
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:493
msgid "Domains to block for the selected devices. Leave empty to block all internet. Enter one domain per line, e.g. youtube.com. Use the preset buttons to quickly add common categories."
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js:8301
msgid "Done"
msgstr ""
@@ -1674,7 +1726,7 @@ msgstr ""
msgid "e.g. google.com, 1.1.1.1, 443"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:290
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:383
msgid "e.g. Kids Phone (Night YouTube)"
msgstr ""
@@ -1691,7 +1743,7 @@ msgstr ""
msgid "Edit"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:125
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:187
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js:7235
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/server.js:2618
msgid "Enable"
@@ -1825,11 +1877,11 @@ msgstr ""
msgid "Enables the background watchdog process to monitor services and auto-recover on failures."
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:373
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:466
msgid "End of the schedule interval (e.g. 08:00). Supports spans crossing midnight."
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:372
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:465
msgid "End Time (HH:MM)"
msgstr ""
@@ -1895,7 +1947,7 @@ msgstr ""
msgid "Error updating lists:"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:98
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:160
msgid "Every day"
msgstr ""
@@ -2012,12 +2064,12 @@ msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:16728
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:16786
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:16957
#: src/tachyon/methods/shell/index.ts:172
#: src/tachyon/methods/shell/index.ts:179
#: src/tachyon/tabs/updates/initController.ts:631
#: src/tachyon/tabs/updates/initController.ts:632
#: src/tachyon/tabs/updates/initController.ts:707
#: src/tachyon/tabs/updates/initController.ts:920
#: src/tachyon/methods/shell/index.ts:172
#: src/tachyon/methods/shell/index.ts:179
msgid "Failed to execute"
msgstr ""
@@ -2269,6 +2321,10 @@ msgstr ""
msgid "Game Console Optimizer (NAT Type 1)"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:63
msgid "Gaming"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js:2489
msgid "Generate a random 16-character hexadecimal HWID"
msgstr ""
@@ -2729,6 +2785,10 @@ msgstr ""
msgid "Invalid domain address"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:507
msgid "Invalid domain: empty prefix value"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:206
#: src/validators/validateSubnet.ts:10
msgid "Invalid format. Use an IP address or CIDR subnet"
@@ -2811,7 +2871,7 @@ msgstr ""
msgid "Invalid IP address"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:120
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:182
msgid "Invalid IP or MAC address. Expected e.g. 192.168.1.150 or AA:BB:CC:DD:EE:FF"
msgstr ""
@@ -2939,7 +2999,7 @@ msgstr ""
msgid "Invalid SOCKS URL: parsing failed"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:47
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:109
msgid "Invalid time format. Expected HH:MM (e.g. 22:00)"
msgstr ""
@@ -3086,8 +3146,8 @@ msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:8840
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:9329
#: src/tachyon/tabs/diagnostic/checks/getCheckItemsMeta.ts:17
#: src/tachyon/tabs/diagnostic/helpers/getMeta.ts:20
#: src/tachyon/tabs/diagnostic/checks/getCheckItemsMeta.ts:17
msgid "Issues detected"
msgstr ""
@@ -3420,6 +3480,10 @@ msgstr ""
msgid "Memory Usage"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:84
msgid "Messengers"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/server.js:2968
msgid "Method"
msgstr ""
@@ -3448,7 +3512,7 @@ msgstr ""
msgid "Mixed Proxy Username"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:200
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:262
msgid "Mode"
msgstr ""
@@ -3677,8 +3741,8 @@ msgstr ""
msgid "No rules use Zapret2"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:177
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:196
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:239
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:258
msgid "No sections selected"
msgstr ""
@@ -3697,6 +3761,8 @@ msgid "Nodes"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:12555
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:310
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:329
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/server.js:449
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/server.js:2741
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/server.js:3033
@@ -3786,6 +3852,10 @@ msgstr ""
msgid "Not set"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:604
msgid "Notify in Telegram"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/settings.js:2241
msgid "Notify on Certificate Warnings"
msgstr ""
@@ -4084,8 +4154,8 @@ msgid "PEM-encoded client private key"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:5187
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:271
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:281
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:364
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:374
#: src/tachyon/tabs/diagnostic/diagnostic.store.ts:63
msgid "Pending"
msgstr ""
@@ -4594,6 +4664,10 @@ msgstr ""
msgid "Resume updates"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:594
msgid "Return NXDOMAIN for blocked domains (recommended). When enabled, blocked domains are not resolved at all. When disabled, only direct connections are rejected."
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:14571
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:14619
#: src/tachyon/tabs/monitoring/initController.ts:871
@@ -4643,7 +4717,7 @@ msgstr ""
msgid "Rule"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:287
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:380
msgid "Rule Name"
msgstr ""
@@ -4773,7 +4847,7 @@ msgstr ""
msgid "Saving…"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:216
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:278
msgid "Schedule"
msgstr ""
@@ -4873,7 +4947,7 @@ msgstr ""
msgid "Select a transit section"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:385
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:478
msgid "Select active days. If none selected, the rule applies every day."
msgstr ""
@@ -4901,7 +4975,7 @@ msgstr ""
msgid "Select DNS protocol to use"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:297
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:390
msgid "Select LAN devices to apply this schedule to, or enter IP or MAC addresses manually."
msgstr ""
@@ -4929,7 +5003,7 @@ msgstr ""
msgid "Select path for sing-box config file. Change this ONLY if you know what you are doing"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:327
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:420
msgid "Select Sections"
msgstr ""
@@ -4955,7 +5029,7 @@ msgstr ""
msgid "Select the WAN interfaces to be monitored"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:349
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:442
msgid "Select whether to disable the target during the schedule or enable it ONLY during the schedule."
msgstr ""
@@ -4996,6 +5070,10 @@ msgstr ""
msgid "Send a Telegram notification if the Tachyon service is restarted by the watchdog."
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:605
msgid "Send a Telegram notification to admins when a blocked site is accessed. Requires the Telegram bot to be enabled."
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/settings.js:2216
msgid "Send a Telegram notification when a URLTest group switches to a new server."
msgstr ""
@@ -5100,9 +5178,9 @@ msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:3410
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:3429
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:12323
#: src/tachyon/tabs/diagnostic/initController.ts:604
#: src/tachyon/methods/shell/index.ts:422
#: src/tachyon/methods/shell/index.ts:443
#: src/tachyon/tabs/diagnostic/initController.ts:604
msgid "Service action failed"
msgstr ""
@@ -5144,6 +5222,10 @@ msgstr ""
msgid "Settings have been reset to defaults. The page will reload."
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:93
msgid "Shopping"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js:2529
msgid "Show metadata on dashboard"
msgstr ""
@@ -5267,6 +5349,10 @@ msgstr ""
msgid "Snell Version"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:43
msgid "Social media"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js:7810
msgid "Sort Alphabetically"
msgstr ""
@@ -5295,7 +5381,7 @@ msgstr ""
msgid "Source Network Interface"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:321
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:414
msgid "Specific Sections (Select sections below)"
msgstr ""
@@ -5386,7 +5472,7 @@ msgstr ""
msgid "Start Tachyon"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:360
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:453
msgid "Start Time (HH:MM)"
msgstr ""
@@ -5405,7 +5491,7 @@ msgid "Starting…"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:2424
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:241
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:334
#: src/tachyon/tabs/dashboard/partials/renderAdvancedSettings.ts:465
#: src/tachyon/tabs/dashboard/partials/renderSections.ts:666
msgid "Status"
@@ -5655,15 +5741,15 @@ msgstr ""
msgid "Tailscale hostname"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:163
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:225
msgid "Target"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:296
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:389
msgid "Target Devices (IP / MAC)"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:315
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:408
msgid "Target Scope"
msgstr ""
@@ -5834,7 +5920,7 @@ msgstr ""
msgid "Time in seconds for DNS record caching (default: 60)"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:43
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:105
msgid "Time is required (e.g. 22:00)"
msgstr ""
@@ -6446,6 +6532,10 @@ msgstr ""
msgid "Via country.is"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:53
msgid "Video streaming"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:10216
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:12485
#: src/tachyon/tabs/diagnostic/initController.ts:820
@@ -6524,11 +6614,11 @@ msgstr ""
msgid "Wednesday"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:106
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:168
msgid "Weekdays (Mon-Fri)"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:110
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:172
msgid "Weekends (Sat-Sun)"
msgstr ""
+68 -2
View File
@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: TACHYON\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-08-20 22:26+0300\n"
"PO-Revision-Date: 2026-08-20 22:26+0300\n"
"POT-Creation-Date: 2026-08-21 05:02+0300\n"
"PO-Revision-Date: 2026-08-21 05:02+0300\n"
"Last-Translator: Dushnilin\n"
"Language-Team: none\n"
"Language: ru\n"
@@ -146,6 +146,9 @@ msgstr "Адрес"
msgid "Administrator Chat IDs"
msgstr "Chat ID администраторов"
msgid "Adult content"
msgstr ""
msgid "Advanced self-healing features. Requires Watchdog to be enabled."
msgstr "Расширенные функции самовосстановления. Требуется включённый Watchdog."
@@ -251,6 +254,15 @@ msgstr "Разрешено в интервале"
msgid "Allow Insecure TLS"
msgstr "Разрешить небезопасный TLS"
msgid "Allow only"
msgstr ""
msgid "Allow only listed sites (Whitelist)"
msgstr ""
msgid "Allow only:"
msgstr ""
msgid "Allowed domain prefixes are full:, keyword:, and regex:"
msgstr "Допустимые префиксы доменов: full:, keyword:, и regex:"
@@ -380,15 +392,36 @@ msgstr "Беларусь (by)"
msgid "BitTorrent / P2P"
msgstr "BitTorrent / P2P"
msgid "Block"
msgstr ""
msgid "Block / Disable in scheduled hours (Standard)"
msgstr "Блокировать / Отключать в указанные часы (Стандарт)"
msgid "Block at DNS level"
msgstr ""
msgid "Block in interval"
msgstr "Блокируется в интервале"
msgid "Block internet access or specific proxy sections for individual devices based on time and days."
msgstr "Блокировка доступа в интернет или определенных секций маршрутизации для отдельных устройств по времени и дням недели."
msgid "Block listed sites (Standard)"
msgstr ""
msgid "Block:"
msgstr ""
msgid "Block: deny the listed sites. Allow only (whitelist): deny everything except the listed sites. Whitelist mode requires DNS-level blocking and applies to the schedule window."
msgstr ""
msgid "Blocked Sites"
msgstr ""
msgid "Blocked Sites (Domains)"
msgstr ""
msgid "Bootstrap DNS"
msgstr "Bootstrap DNS"
@@ -686,6 +719,9 @@ msgstr "Подключения недоступны"
msgid "Connectivity probe failed"
msgstr "Проверка соединения не прошла"
msgid "Content Mode"
msgstr ""
msgid "Cookie packet junk size (S3)"
msgstr "Размер мусора в пакете cookie (S3)"
@@ -989,6 +1025,9 @@ msgstr "Домены"
msgid "Domains automatically detected by Smart Detect or added via Telegram bot. You can manually edit or remove them here."
msgstr "Домены, автоматически определённые Smart Detect или добавленные через Telegram-бота. Вы можете вручную отредактировать или удалить их здесь."
msgid "Domains to block for the selected devices. Leave empty to block all internet. Enter one domain per line, e.g. youtube.com. Use the preset buttons to quickly add common categories."
msgstr ""
msgid "Done"
msgstr "Готово"
@@ -1433,6 +1472,9 @@ msgstr "IP игровых консолей"
msgid "Game Console Optimizer (NAT Type 1)"
msgstr "Оптимизатор игровых консолей (NAT Type 1)"
msgid "Gaming"
msgstr ""
msgid "Generate a random 16-character hexadecimal HWID"
msgstr "Сгенерировать случайный 16-символьный шестнадцатеричный HWID"
@@ -1736,6 +1778,9 @@ msgstr "Неверный порт DNS-сервера"
msgid "Invalid domain address"
msgstr "Неверный домен"
msgid "Invalid domain: empty prefix value"
msgstr ""
msgid "Invalid format. Use an IP address or CIDR subnet"
msgstr "Неверный формат. Используйте IP-адрес или подсеть CIDR"
@@ -2168,6 +2213,9 @@ msgstr "Максимальное время ожидания разрешени
msgid "Memory Usage"
msgstr "Использование памяти"
msgid "Messengers"
msgstr ""
msgid "Method"
msgstr "Метод"
@@ -2378,6 +2426,9 @@ msgstr "Не запущено"
msgid "Not set"
msgstr "Не задано"
msgid "Notify in Telegram"
msgstr ""
msgid "Notify on Certificate Warnings"
msgstr "Уведомлять о предупреждениях сертификатов"
@@ -2909,6 +2960,9 @@ msgstr "Восстановление прямого интернета (оста
msgid "Resume updates"
msgstr "Продолжить обновление"
msgid "Return NXDOMAIN for blocked domains (recommended). When enabled, blocked domains are not resolved at all. When disabled, only direct connections are rejected."
msgstr ""
msgid "Route"
msgstr "Маршрут"
@@ -3176,6 +3230,9 @@ msgstr "Отправлять уведомление в Telegram, если SSL/TL
msgid "Send a Telegram notification if the Tachyon service is restarted by the watchdog."
msgstr "Отправлять уведомление в Telegram, если сервис Tachyon перезапущен watchdog."
msgid "Send a Telegram notification to admins when a blocked site is accessed. Requires the Telegram bot to be enabled."
msgstr ""
msgid "Send a Telegram notification when a URLTest group switches to a new server."
msgstr "Отправлять уведомление в Telegram при переключении группы URLTest на новый сервер."
@@ -3266,6 +3323,9 @@ msgstr "Настройки"
msgid "Settings have been reset to defaults. The page will reload."
msgstr "Настройки сброшены к значениям по умолчанию. Страница будет перезагружена."
msgid "Shopping"
msgstr ""
msgid "Show metadata on dashboard"
msgstr "Показывать метаданные на дашборде"
@@ -3350,6 +3410,9 @@ msgstr "Снапшот восстановлен. Сервис перезапус
msgid "Snell Version"
msgstr "Версия Snell"
msgid "Social media"
msgstr ""
msgid "Sort Alphabetically"
msgstr "Сортировать по алфавиту"
@@ -4061,6 +4124,9 @@ msgstr "Проверка готовности роутера и служб..."
msgid "Via country.is"
msgstr "Через country.is"
msgid "Video streaming"
msgstr ""
msgid "View logs"
msgstr "Посмотреть логи"
@@ -38,6 +38,68 @@ const DAY_INDEX_MAP = {
6: "saturday",
};
const DOMAIN_PRESETS = {
social: {
label: _("Social media"),
icon: "📱",
domains: [
"facebook.com", "instagram.com", "tiktok.com", "twitter.com", "x.com",
"vk.com", "snapchat.com", "reddit.com", "discord.com", "discord.gg",
"twitch.tv", "pinterest.com", "telegram.org", "t.me", "whatsapp.com",
"signal.org", "wechat.com", "youtube.com", "tumblr.com",
],
},
video: {
label: _("Video streaming"),
icon: "🎬",
domains: [
"youtube.com", "youtu.be", "googlevideo.com", "ytimg.com",
"netflix.com", "primevideo.com", "hulu.com", "disneyplus.com",
"hbomax.com", "okko.tv", "ivi.ru", "kinopoisk.ru",
"twitch.tv", "vimeo.com", "dailymotion.com", "rutube.ru",
],
},
gaming: {
label: _("Gaming"),
icon: "🎮",
domains: [
"steampowered.com", "steamcommunity.com", "epicgames.com",
"roblox.com", "fortnite.com", "xbox.com", "playstation.com",
"battle.net", "ea.com", "ubisoft.com", "riotgames.com",
"minecraft.net", "origin.com", "discord.com", "discord.gg",
],
},
adult: {
label: _("Adult content"),
icon: "🔞",
domains: [
"pornhub.com", "xvideos.com", "xhamster.com", "xnxx.com",
"onlyfans.com", "stripchat.com", "chaturbate.com",
"youporn.com", "redtube.com", "tubegalore.com",
"porntrex.com", "pornhd.com", "nudevista.com",
"porn.com", "spankbang.com", "eporner.com",
],
},
messengers: {
label: _("Messengers"),
icon: "💬",
domains: [
"telegram.org", "t.me", "whatsapp.com", "wa.me", "viber.com",
"signal.org", "wechat.com", "qq.com", "icq.com", "discord.com",
"discord.gg", "skype.com", "zoom.us", "slack.com", "teams.microsoft.com",
],
},
shopping: {
label: _("Shopping"),
icon: "🛒",
domains: [
"amazon.com", "ebay.com", "aliexpress.com", "ozon.ru",
"wildberries.ru", "wb.ru", "yandex.market", "market.yandex.ru",
"avito.ru", "lamoda.ru", "shein.com", "temu.com", "wish.com",
],
},
};
function validateTime(sectionId, value) {
if (!value) {
return _("Time is required (e.g. 22:00)");
@@ -237,6 +299,37 @@ function createParentalContent(section) {
return `${s} - ${e}, ${days}`;
};
// Blocked sites column in table
o = section.option(form.DummyValue, "_sites_display", _("Blocked Sites"));
o.rawhtml = true;
o.modalonly = false;
o.cfgvalue = function (sectionId) {
const domains = normalizeListValues(uci.get(UCI_PACKAGE, sectionId, "blocked_domains"));
const mode = uci.get(UCI_PACKAGE, sectionId, "mode") || "block";
if (domains.length === 0) {
return '<span style="opacity:0.5;">' + _("None") + "</span>";
}
const modeBadge =
mode === "allow"
? '<span style="color:#68d391;font-weight:600;">🟢 ' + _("Allow only") + "</span>"
: '<span style="color:#fc8181;font-weight:600;">🚫 ' + _("Block") + "</span>";
const badges = domains
.map(
(d) =>
'<span class="badge" style="background:#2d3748;color:#f687b3;padding:2px 6px;border-radius:4px;font-size:11px;font-family:monospace;margin-right:4px;border:1px solid rgba(246,135,179,0.3);display:inline-block;margin-bottom:2px;">' +
d +
"</span>",
)
.join("");
return "<div>" + modeBadge + "<br>" + badges + "</div>";
};
o.textvalue = function (sectionId) {
const domains = normalizeListValues(uci.get(UCI_PACKAGE, sectionId, "blocked_domains"));
const mode = uci.get(UCI_PACKAGE, sectionId, "mode") || "block";
if (domains.length === 0) return _("None");
return (mode === "allow" ? _("Allow only: ") : _("Block: ")) + domains.join(", ");
};
// Live status badge in table
o = section.option(form.DummyValue, "_live_status", _("Status"));
o.rawhtml = true;
@@ -389,6 +482,131 @@ function createParentalContent(section) {
Object.entries(DAY_LABELS).forEach(([key, label]) => {
o.value(key, label);
});
// ─── Content blocking (domains) ────────────────────────────────────────────
// Blocked domains with quick presets
o = section.option(
form.TextValue,
"blocked_domains",
_("Blocked Sites (Domains)"),
_("Domains to block for the selected devices. Leave empty to block all internet. Enter one domain per line, e.g. youtube.com. Use the preset buttons to quickly add common categories."),
);
o.modalonly = true;
o.rmempty = true;
o.rows = 6;
o.wrap = "soft";
o.textarea = true;
o.placeholder = "youtube.com\ngooglevideo.com";
o.validate = function (sectionId, value) {
if (!value) return true;
const lines = `${value}`.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
for (const line of lines) {
const clean = line.replace(/^(full:|keyword:|regex:)/, "");
if (/^(full:|keyword:|regex:)/.test(line) && !clean) {
return _("Invalid domain: empty prefix value");
}
if (/^regex:/.test(line)) continue;
if (!/^([a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/.test(clean)) {
return _("Invalid domain: " + line);
}
}
return true;
};
o.write = function (sectionId, value) {
const lines = `${value || ""}`.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
const existingList = normalizeListValues(uci.get(UCI_PACKAGE, sectionId, "blocked_domains"));
for (const d of lines) {
if (!existingList.includes(d)) {
uci.add_list(UCI_PACKAGE, sectionId, "blocked_domains", d);
}
}
for (const d of existingList) {
if (!lines.includes(d)) {
uci.remove_list(UCI_PACKAGE, sectionId, "blocked_domains", d);
}
}
};
o.load = function (sectionId) {
return normalizeListValues(uci.get(UCI_PACKAGE, sectionId, "blocked_domains")).join("\n");
};
o.remove = function (sectionId) {
uci.unset(UCI_PACKAGE, sectionId, "blocked_domains");
};
o.renderWidget = function (sectionId, optionIndex, cfgvalue) {
const container = E("div", { style: "width:100%;" });
// Preset buttons row
const presetRow = E("div", { style: "margin-bottom:6px;display:flex;flex-wrap:wrap;gap:4px;" });
Object.entries(DOMAIN_PRESETS).forEach(([key, preset]) => {
const btn = E(
"button",
{
class: "cbi-button cbi-button-neutral",
type: "button",
style: "padding:2px 8px;font-size:11px;",
title: preset.domains.join(", "),
},
preset.icon + " " + preset.label,
);
btn.addEventListener("click", function () {
const textarea = container.querySelector("textarea");
if (!textarea) return;
const current = textarea.value.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
const merged = current.slice();
for (const d of preset.domains) {
if (!merged.includes(d)) merged.push(d);
}
textarea.value = merged.join("\n");
});
presetRow.appendChild(btn);
});
container.appendChild(presetRow);
const textarea = E("textarea", {
id: `cbid.tachyon.${sectionId}.blocked_domains`,
class: "cbi-input-textarea",
style: "width:100%;min-height:110px;",
placeholder: "youtube.com\ngooglevideo.com",
});
if (cfgvalue) textarea.value = cfgvalue;
container.appendChild(textarea);
return container;
};
// Content mode: block listed vs allow only listed (whitelist)
o = section.option(
form.ListValue,
"mode",
_("Content Mode"),
_("Block: deny the listed sites. Allow only (whitelist): deny everything except the listed sites. Whitelist mode requires DNS-level blocking and applies to the schedule window."),
);
o.modalonly = true;
o.default = "block";
o.value("block", _("Block listed sites (Standard)"));
o.value("allow", _("Allow only listed sites (Whitelist)"));
// DNS-level blocking
o = section.option(
form.Flag,
"dns_level",
_("Block at DNS level"),
_("Return NXDOMAIN for blocked domains (recommended). When enabled, blocked domains are not resolved at all. When disabled, only direct connections are rejected."),
);
o.modalonly = true;
o.default = "1";
o.rmempty = false;
// Telegram notification
o = section.option(
form.Flag,
"notify",
_("Notify in Telegram"),
_("Send a Telegram notification to admins when a blocked site is accessed. Requires the Telegram bot to be enabled."),
);
o.modalonly = true;
o.default = "0";
o.rmempty = false;
}
const EntryPoint = {
+68 -2
View File
@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: TACHYON\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-08-20 22:26+0300\n"
"PO-Revision-Date: 2026-08-20 22:26+0300\n"
"POT-Creation-Date: 2026-08-21 05:02+0300\n"
"PO-Revision-Date: 2026-08-21 05:02+0300\n"
"Last-Translator: Dushnilin\n"
"Language-Team: none\n"
"Language: ru\n"
@@ -146,6 +146,9 @@ msgstr "Адрес"
msgid "Administrator Chat IDs"
msgstr "Chat ID администраторов"
msgid "Adult content"
msgstr ""
msgid "Advanced self-healing features. Requires Watchdog to be enabled."
msgstr "Расширенные функции самовосстановления. Требуется включённый Watchdog."
@@ -251,6 +254,15 @@ msgstr "Разрешено в интервале"
msgid "Allow Insecure TLS"
msgstr "Разрешить небезопасный TLS"
msgid "Allow only"
msgstr ""
msgid "Allow only listed sites (Whitelist)"
msgstr ""
msgid "Allow only:"
msgstr ""
msgid "Allowed domain prefixes are full:, keyword:, and regex:"
msgstr "Допустимые префиксы доменов: full:, keyword:, и regex:"
@@ -380,15 +392,36 @@ msgstr "Беларусь (by)"
msgid "BitTorrent / P2P"
msgstr "BitTorrent / P2P"
msgid "Block"
msgstr ""
msgid "Block / Disable in scheduled hours (Standard)"
msgstr "Блокировать / Отключать в указанные часы (Стандарт)"
msgid "Block at DNS level"
msgstr ""
msgid "Block in interval"
msgstr "Блокируется в интервале"
msgid "Block internet access or specific proxy sections for individual devices based on time and days."
msgstr "Блокировка доступа в интернет или определенных секций маршрутизации для отдельных устройств по времени и дням недели."
msgid "Block listed sites (Standard)"
msgstr ""
msgid "Block:"
msgstr ""
msgid "Block: deny the listed sites. Allow only (whitelist): deny everything except the listed sites. Whitelist mode requires DNS-level blocking and applies to the schedule window."
msgstr ""
msgid "Blocked Sites"
msgstr ""
msgid "Blocked Sites (Domains)"
msgstr ""
msgid "Bootstrap DNS"
msgstr "Bootstrap DNS"
@@ -686,6 +719,9 @@ msgstr "Подключения недоступны"
msgid "Connectivity probe failed"
msgstr "Проверка соединения не прошла"
msgid "Content Mode"
msgstr ""
msgid "Cookie packet junk size (S3)"
msgstr "Размер мусора в пакете cookie (S3)"
@@ -989,6 +1025,9 @@ msgstr "Домены"
msgid "Domains automatically detected by Smart Detect or added via Telegram bot. You can manually edit or remove them here."
msgstr "Домены, автоматически определённые Smart Detect или добавленные через Telegram-бота. Вы можете вручную отредактировать или удалить их здесь."
msgid "Domains to block for the selected devices. Leave empty to block all internet. Enter one domain per line, e.g. youtube.com. Use the preset buttons to quickly add common categories."
msgstr ""
msgid "Done"
msgstr "Готово"
@@ -1433,6 +1472,9 @@ msgstr "IP игровых консолей"
msgid "Game Console Optimizer (NAT Type 1)"
msgstr "Оптимизатор игровых консолей (NAT Type 1)"
msgid "Gaming"
msgstr ""
msgid "Generate a random 16-character hexadecimal HWID"
msgstr "Сгенерировать случайный 16-символьный шестнадцатеричный HWID"
@@ -1736,6 +1778,9 @@ msgstr "Неверный порт DNS-сервера"
msgid "Invalid domain address"
msgstr "Неверный домен"
msgid "Invalid domain: empty prefix value"
msgstr ""
msgid "Invalid format. Use an IP address or CIDR subnet"
msgstr "Неверный формат. Используйте IP-адрес или подсеть CIDR"
@@ -2168,6 +2213,9 @@ msgstr "Максимальное время ожидания разрешени
msgid "Memory Usage"
msgstr "Использование памяти"
msgid "Messengers"
msgstr ""
msgid "Method"
msgstr "Метод"
@@ -2378,6 +2426,9 @@ msgstr "Не запущено"
msgid "Not set"
msgstr "Не задано"
msgid "Notify in Telegram"
msgstr ""
msgid "Notify on Certificate Warnings"
msgstr "Уведомлять о предупреждениях сертификатов"
@@ -2909,6 +2960,9 @@ msgstr "Восстановление прямого интернета (оста
msgid "Resume updates"
msgstr "Продолжить обновление"
msgid "Return NXDOMAIN for blocked domains (recommended). When enabled, blocked domains are not resolved at all. When disabled, only direct connections are rejected."
msgstr ""
msgid "Route"
msgstr "Маршрут"
@@ -3176,6 +3230,9 @@ msgstr "Отправлять уведомление в Telegram, если SSL/TL
msgid "Send a Telegram notification if the Tachyon service is restarted by the watchdog."
msgstr "Отправлять уведомление в Telegram, если сервис Tachyon перезапущен watchdog."
msgid "Send a Telegram notification to admins when a blocked site is accessed. Requires the Telegram bot to be enabled."
msgstr ""
msgid "Send a Telegram notification when a URLTest group switches to a new server."
msgstr "Отправлять уведомление в Telegram при переключении группы URLTest на новый сервер."
@@ -3266,6 +3323,9 @@ msgstr "Настройки"
msgid "Settings have been reset to defaults. The page will reload."
msgstr "Настройки сброшены к значениям по умолчанию. Страница будет перезагружена."
msgid "Shopping"
msgstr ""
msgid "Show metadata on dashboard"
msgstr "Показывать метаданные на дашборде"
@@ -3350,6 +3410,9 @@ msgstr "Снапшот восстановлен. Сервис перезапус
msgid "Snell Version"
msgstr "Версия Snell"
msgid "Social media"
msgstr ""
msgid "Sort Alphabetically"
msgstr "Сортировать по алфавиту"
@@ -4061,6 +4124,9 @@ msgstr "Проверка готовности роутера и служб..."
msgid "Via country.is"
msgstr "Через country.is"
msgid "Video streaming"
msgstr ""
msgid "View logs"
msgstr "Посмотреть логи"
+148 -58
View File
@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: TACHYON\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-08-20 19:26+0300\n"
"PO-Revision-Date: 2026-08-20 19:26+0300\n"
"POT-Creation-Date: 2026-08-21 02:02+0300\n"
"PO-Revision-Date: 2026-08-21 02:02+0300\n"
"Last-Translator: Dushnilin <dushnilin@gmail.com>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
@@ -105,7 +105,7 @@ msgstr ""
msgid "Action"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:348
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:441
msgid "Action Mode"
msgstr ""
@@ -118,13 +118,13 @@ msgstr ""
msgid "Active"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:260
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:280
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:353
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:373
msgid "Active (Allowed)"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:260
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:280
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:353
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:373
msgid "Active (Blocking)"
msgstr ""
@@ -223,6 +223,10 @@ msgstr ""
msgid "Administrator Chat IDs"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:73
msgid "Adult content"
msgstr ""
#: src/tachyon/tabs/dashboard/partials/renderAdvancedSettings.ts:595
msgid "Advanced self-healing features. Requires Watchdog to be enabled."
msgstr ""
@@ -327,8 +331,8 @@ msgstr ""
msgid "All checks passed — Telegram bot should work correctly"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:139
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:159
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:201
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:221
msgid "All devices"
msgstr ""
@@ -336,12 +340,12 @@ msgstr ""
msgid "All except selected"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:169
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:194
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:231
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:256
msgid "All Internet"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:320
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:413
msgid "All Internet (Complete internet cutoff)"
msgstr ""
@@ -372,12 +376,12 @@ msgstr ""
msgid "All traffic from these IP addresses will be routed through the section unconditionally, ignoring all other conditions."
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:354
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:447
msgid "Allow / Enable ONLY in scheduled hours (White period)"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:206
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:212
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:268
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:274
msgid "Allow in interval"
msgstr ""
@@ -385,6 +389,18 @@ msgstr ""
msgid "Allow Insecure TLS"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:314
msgid "Allow only"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:587
msgid "Allow only listed sites (Whitelist)"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:330
msgid "Allow only:"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js:5332
msgid "Allowed domain prefixes are full:, keyword:, and regex:"
msgstr ""
@@ -563,7 +579,7 @@ msgstr ""
msgid "Bearer Auth Token"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:361
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:454
msgid "Beginning of the schedule interval (e.g. 22:00)"
msgstr ""
@@ -576,12 +592,20 @@ msgstr ""
msgid "BitTorrent / P2P"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:353
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:315
msgid "Block"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:446
msgid "Block / Disable in scheduled hours (Standard)"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:208
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:212
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:593
msgid "Block at DNS level"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:270
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:274
msgid "Block in interval"
msgstr ""
@@ -589,6 +613,26 @@ msgstr ""
msgid "Block internet access or specific proxy sections for individual devices based on time and days."
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:586
msgid "Block listed sites (Standard)"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:330
msgid "Block:"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:582
msgid "Block: deny the listed sites. Allow only (whitelist): deny everything except the listed sites. Whitelist mode requires DNS-level blocking and applies to the schedule window."
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:303
msgid "Blocked Sites"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:492
msgid "Blocked Sites (Domains)"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:8905
#: src/tachyon/tabs/diagnostic/checks/runDnsCheck.ts:57
msgid "Bootstrap DNS"
@@ -861,17 +905,17 @@ msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:8845
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:9323
#: src/tachyon/tabs/diagnostic/checks/getCheckItemsMeta.ts:10
#: src/tachyon/tabs/diagnostic/helpers/getMeta.ts:26
#: src/tachyon/tabs/diagnostic/checks/getCheckItemsMeta.ts:10
msgid "Checks failed"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:8834
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:9280
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:9334
#: src/tachyon/tabs/diagnostic/helpers/getMeta.ts:13
#: src/tachyon/tabs/diagnostic/checks/getCheckItemsMeta.ts:23
#: src/tachyon/tabs/diagnostic/checks/runFakeIPCheck.ts:43
#: src/tachyon/tabs/diagnostic/helpers/getMeta.ts:13
msgid "Checks passed"
msgstr ""
@@ -888,7 +932,7 @@ msgstr ""
msgid "Choose Hosts File..."
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:316
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:409
msgid "Choose whether to restrict the entire internet or only specific Tachyon routing sections."
msgstr ""
@@ -896,7 +940,7 @@ msgstr ""
msgid "Choose whether to route traffic to all destinations except selected countries (Exclude) or only to selected countries (Include)."
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:328
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:421
msgid "Choose which routing sections to apply this schedule to."
msgstr ""
@@ -1118,6 +1162,10 @@ msgstr ""
msgid "Connectivity probe failed"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:581
msgid "Content Mode"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/server.js:3415
msgid "Cookie packet junk size (S3)"
msgstr ""
@@ -1230,7 +1278,7 @@ msgstr ""
msgid "Dashboard currently unavailable"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:384
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:477
msgid "Days of Week"
msgstr ""
@@ -1288,7 +1336,7 @@ msgstr ""
msgid "detour must be a non-empty string"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:132
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:194
msgid "Device / IP / MAC"
msgstr ""
@@ -1366,8 +1414,8 @@ msgstr ""
msgid "Disable the QUIC protocol to improve compatibility or fix issues with video streaming"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:250
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:277
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:343
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:370
msgid "Disabled"
msgstr ""
@@ -1577,6 +1625,10 @@ msgstr ""
msgid "Domains automatically detected by Smart Detect or added via Telegram bot. You can manually edit or remove them here."
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:493
msgid "Domains to block for the selected devices. Leave empty to block all internet. Enter one domain per line, e.g. youtube.com. Use the preset buttons to quickly add common categories."
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js:8301
msgid "Done"
msgstr ""
@@ -1674,7 +1726,7 @@ msgstr ""
msgid "e.g. google.com, 1.1.1.1, 443"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:290
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:383
msgid "e.g. Kids Phone (Night YouTube)"
msgstr ""
@@ -1691,7 +1743,7 @@ msgstr ""
msgid "Edit"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:125
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:187
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js:7235
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/server.js:2618
msgid "Enable"
@@ -1825,11 +1877,11 @@ msgstr ""
msgid "Enables the background watchdog process to monitor services and auto-recover on failures."
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:373
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:466
msgid "End of the schedule interval (e.g. 08:00). Supports spans crossing midnight."
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:372
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:465
msgid "End Time (HH:MM)"
msgstr ""
@@ -1895,7 +1947,7 @@ msgstr ""
msgid "Error updating lists:"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:98
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:160
msgid "Every day"
msgstr ""
@@ -2012,12 +2064,12 @@ msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:16728
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:16786
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:16957
#: src/tachyon/methods/shell/index.ts:172
#: src/tachyon/methods/shell/index.ts:179
#: src/tachyon/tabs/updates/initController.ts:631
#: src/tachyon/tabs/updates/initController.ts:632
#: src/tachyon/tabs/updates/initController.ts:707
#: src/tachyon/tabs/updates/initController.ts:920
#: src/tachyon/methods/shell/index.ts:172
#: src/tachyon/methods/shell/index.ts:179
msgid "Failed to execute"
msgstr ""
@@ -2269,6 +2321,10 @@ msgstr ""
msgid "Game Console Optimizer (NAT Type 1)"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:63
msgid "Gaming"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js:2489
msgid "Generate a random 16-character hexadecimal HWID"
msgstr ""
@@ -2729,6 +2785,10 @@ msgstr ""
msgid "Invalid domain address"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:507
msgid "Invalid domain: empty prefix value"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:206
#: src/validators/validateSubnet.ts:10
msgid "Invalid format. Use an IP address or CIDR subnet"
@@ -2811,7 +2871,7 @@ msgstr ""
msgid "Invalid IP address"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:120
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:182
msgid "Invalid IP or MAC address. Expected e.g. 192.168.1.150 or AA:BB:CC:DD:EE:FF"
msgstr ""
@@ -2939,7 +2999,7 @@ msgstr ""
msgid "Invalid SOCKS URL: parsing failed"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:47
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:109
msgid "Invalid time format. Expected HH:MM (e.g. 22:00)"
msgstr ""
@@ -3086,8 +3146,8 @@ msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:8840
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:9329
#: src/tachyon/tabs/diagnostic/checks/getCheckItemsMeta.ts:17
#: src/tachyon/tabs/diagnostic/helpers/getMeta.ts:20
#: src/tachyon/tabs/diagnostic/checks/getCheckItemsMeta.ts:17
msgid "Issues detected"
msgstr ""
@@ -3420,6 +3480,10 @@ msgstr ""
msgid "Memory Usage"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:84
msgid "Messengers"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/server.js:2968
msgid "Method"
msgstr ""
@@ -3448,7 +3512,7 @@ msgstr ""
msgid "Mixed Proxy Username"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:200
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:262
msgid "Mode"
msgstr ""
@@ -3677,8 +3741,8 @@ msgstr ""
msgid "No rules use Zapret2"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:177
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:196
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:239
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:258
msgid "No sections selected"
msgstr ""
@@ -3697,6 +3761,8 @@ msgid "Nodes"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:12555
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:310
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:329
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/server.js:449
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/server.js:2741
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/server.js:3033
@@ -3786,6 +3852,10 @@ msgstr ""
msgid "Not set"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:604
msgid "Notify in Telegram"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/settings.js:2241
msgid "Notify on Certificate Warnings"
msgstr ""
@@ -4084,8 +4154,8 @@ msgid "PEM-encoded client private key"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:5187
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:271
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:281
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:364
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:374
#: src/tachyon/tabs/diagnostic/diagnostic.store.ts:63
msgid "Pending"
msgstr ""
@@ -4594,6 +4664,10 @@ msgstr ""
msgid "Resume updates"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:594
msgid "Return NXDOMAIN for blocked domains (recommended). When enabled, blocked domains are not resolved at all. When disabled, only direct connections are rejected."
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:14571
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:14619
#: src/tachyon/tabs/monitoring/initController.ts:871
@@ -4643,7 +4717,7 @@ msgstr ""
msgid "Rule"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:287
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:380
msgid "Rule Name"
msgstr ""
@@ -4773,7 +4847,7 @@ msgstr ""
msgid "Saving…"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:216
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:278
msgid "Schedule"
msgstr ""
@@ -4873,7 +4947,7 @@ msgstr ""
msgid "Select a transit section"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:385
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:478
msgid "Select active days. If none selected, the rule applies every day."
msgstr ""
@@ -4901,7 +4975,7 @@ msgstr ""
msgid "Select DNS protocol to use"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:297
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:390
msgid "Select LAN devices to apply this schedule to, or enter IP or MAC addresses manually."
msgstr ""
@@ -4929,7 +5003,7 @@ msgstr ""
msgid "Select path for sing-box config file. Change this ONLY if you know what you are doing"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:327
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:420
msgid "Select Sections"
msgstr ""
@@ -4955,7 +5029,7 @@ msgstr ""
msgid "Select the WAN interfaces to be monitored"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:349
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:442
msgid "Select whether to disable the target during the schedule or enable it ONLY during the schedule."
msgstr ""
@@ -4996,6 +5070,10 @@ msgstr ""
msgid "Send a Telegram notification if the Tachyon service is restarted by the watchdog."
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:605
msgid "Send a Telegram notification to admins when a blocked site is accessed. Requires the Telegram bot to be enabled."
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/settings.js:2216
msgid "Send a Telegram notification when a URLTest group switches to a new server."
msgstr ""
@@ -5100,9 +5178,9 @@ msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:3410
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:3429
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:12323
#: src/tachyon/tabs/diagnostic/initController.ts:604
#: src/tachyon/methods/shell/index.ts:422
#: src/tachyon/methods/shell/index.ts:443
#: src/tachyon/tabs/diagnostic/initController.ts:604
msgid "Service action failed"
msgstr ""
@@ -5144,6 +5222,10 @@ msgstr ""
msgid "Settings have been reset to defaults. The page will reload."
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:93
msgid "Shopping"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js:2529
msgid "Show metadata on dashboard"
msgstr ""
@@ -5267,6 +5349,10 @@ msgstr ""
msgid "Snell Version"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:43
msgid "Social media"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/section.js:7810
msgid "Sort Alphabetically"
msgstr ""
@@ -5295,7 +5381,7 @@ msgstr ""
msgid "Source Network Interface"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:321
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:414
msgid "Specific Sections (Select sections below)"
msgstr ""
@@ -5386,7 +5472,7 @@ msgstr ""
msgid "Start Tachyon"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:360
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:453
msgid "Start Time (HH:MM)"
msgstr ""
@@ -5405,7 +5491,7 @@ msgid "Starting…"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:2424
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:241
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:334
#: src/tachyon/tabs/dashboard/partials/renderAdvancedSettings.ts:465
#: src/tachyon/tabs/dashboard/partials/renderSections.ts:666
msgid "Status"
@@ -5655,15 +5741,15 @@ msgstr ""
msgid "Tailscale hostname"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:163
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:225
msgid "Target"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:296
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:389
msgid "Target Devices (IP / MAC)"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:315
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:408
msgid "Target Scope"
msgstr ""
@@ -5834,7 +5920,7 @@ msgstr ""
msgid "Time in seconds for DNS record caching (default: 60)"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:43
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:105
msgid "Time is required (e.g. 22:00)"
msgstr ""
@@ -6446,6 +6532,10 @@ msgstr ""
msgid "Via country.is"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:53
msgid "Video streaming"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:10216
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/main.js:12485
#: src/tachyon/tabs/diagnostic/initController.ts:820
@@ -6524,11 +6614,11 @@ msgstr ""
msgid "Wednesday"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:106
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:168
msgid "Weekdays (Mon-Fri)"
msgstr ""
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:110
#: ../luci-app-tachyon/htdocs/luci-static/resources/view/tachyon/parental.js:172
msgid "Weekends (Sat-Sun)"
msgstr ""
+23
View File
@@ -1735,6 +1735,29 @@ function validate_schedule(schedule, sections) {
if (!found)
fail_validation("Schedule '" + name + "' references unknown section '" + sec_name + "'. Aborted.");
}
let blocked_domains = list_option(schedule, "blocked_domains");
if (length(blocked_domains) == 0) {
let single_domain = option(schedule, "blocked_domains", "");
if (single_domain != "") blocked_domains = [ single_domain ];
}
for (let domain in blocked_domains) {
let clean_domain = trim(as_string(domain));
if (clean_domain != "" && !combined_domain_valid(clean_domain))
fail_validation("Schedule '" + name + "' has invalid blocked domain '" + clean_domain + "'. Use plain domains or full:, keyword:, regex: prefixes. Aborted.");
}
let mode = option(schedule, "mode", "block");
if (mode != "" && mode != "block" && mode != "allow")
fail_validation("Schedule '" + name + "' has invalid mode '" + mode + "'. Expected 'block' or 'allow'. Aborted.");
let dns_level = option(schedule, "dns_level", "1");
if (dns_level != "" && dns_level != "0" && dns_level != "1")
fail_validation("Schedule '" + name + "' has invalid dns_level '" + dns_level + "'. Expected 0 or 1. Aborted.");
let notify = option(schedule, "notify", "0");
if (notify != "" && notify != "0" && notify != "1")
fail_validation("Schedule '" + name + "' has invalid notify '" + notify + "'. Expected 0 or 1. Aborted.");
}
function validate_runtime_config(context) {
+3
View File
@@ -135,6 +135,9 @@ function constants_map() {
c.SB_DNS_INBOUND_TAG = env("SB_DNS_INBOUND_TAG", "dns-in");
c.SB_DNS_INBOUND_ADDRESS = env("SB_DNS_INBOUND_ADDRESS", "127.0.0.42");
c.SB_DNS_INBOUND_PORT = env("SB_DNS_INBOUND_PORT", "53");
c.SB_DNS_BLOCK_INBOUND_TAG = env("SB_DNS_BLOCK_INBOUND_TAG", "dns-block-in");
c.SB_DNS_BLOCK_INBOUND_ADDRESS = env("SB_DNS_BLOCK_INBOUND_ADDRESS", "127.0.0.43");
c.SB_DNS_BLOCK_INBOUND_PORT = env("SB_DNS_BLOCK_INBOUND_PORT", "1053");
c.SB_SERVICE_MIXED_INBOUND_TAG = env("SB_SERVICE_MIXED_INBOUND_TAG", "service-mixed-in");
c.SB_SERVICE_MIXED_INBOUND_ADDRESS = env("SB_SERVICE_MIXED_INBOUND_ADDRESS", "127.0.0.1");
c.SB_SERVICE_MIXED_INBOUND_PORT = env("SB_SERVICE_MIXED_INBOUND_PORT", "4534");
+2
View File
@@ -11,6 +11,7 @@ const SB_SERVICE_FAKEIP_DNS_RULE_TAG = getenv("SB_SERVICE_FAKEIP_DNS_RULE_TAG")
const SB_TPROXY_INBOUND_TAG = getenv("SB_TPROXY_INBOUND_TAG") || "tproxy-in";
const SB_TPROXY_INBOUND6_TAG = getenv("SB_TPROXY_INBOUND6_TAG") || "tproxy6-in";
const SB_DNS_INBOUND_TAG = getenv("SB_DNS_INBOUND_TAG") || "dns-in";
const SB_DNS_BLOCK_INBOUND_TAG = getenv("SB_DNS_BLOCK_INBOUND_TAG") || "dns-block-in";
const SB_SERVICE_MIXED_INBOUND_TAG = getenv("SB_SERVICE_MIXED_INBOUND_TAG") || "service-mixed-in";
const SB_DIRECT_OUTBOUND_TAG = getenv("SB_DIRECT_OUTBOUND_TAG") || "direct-out";
const SB_BYPASS_OUTBOUND_TAG = getenv("SB_BYPASS_OUTBOUND_TAG") || "bypass-out";
@@ -540,6 +541,7 @@ function default_reserved_runtime_tags() {
SB_TPROXY_INBOUND_TAG,
SB_TPROXY_INBOUND6_TAG,
SB_DNS_INBOUND_TAG,
SB_DNS_BLOCK_INBOUND_TAG,
SB_SERVICE_MIXED_INBOUND_TAG,
SB_DIRECT_OUTBOUND_TAG,
SB_BYPASS_OUTBOUND_TAG
+91
View File
@@ -10,6 +10,7 @@ let domain_config = require("config.domain");
let connections = require("config.connections");
let routing_rulesets = require("routing.rulesets");
const CONFIG_NAME = getenv("TACHYON_CONFIG_NAME") || "tachyon";
const DNS_BLOCK_TARGET = getenv("SB_DNS_BLOCK_INBOUND_ADDRESS") || "127.0.0.43:1053";
let common_read_json_file = common.read_json_file;
let list_option = common.list_option;
@@ -975,6 +976,90 @@ function nft_add_schedule_rules_from_uci(table, sections) {
return nft_add_schedule_rules_from_schedules(uci_sections("schedule"), sections, table);
}
// ─── Content blocking DNS redirect ───────────────────────────────────────────
// Schedules with blocked_domains redirect the device's DNS queries (udp/tcp
// 53) to the dedicated sing-box dns-block-in inbound (127.0.0.43:1053) when
// the schedule's time window is active. Inside the window the block inbound
// rejects the blocked domains; outside it the query flows to the normal
// dns-in inbound as usual. MAC-address devices match via ether saddr, which
// also covers devices whose IP is DHCP-assigned.
function nft_add_dns_block_rules_from_schedules(schedules, table) {
let added = false;
for (let schedule in schedules) {
schedule = object_or_empty(schedule);
if (!bool_option(schedule, "enabled", true))
continue;
if (length(list_option(schedule, "blocked_domains")) == 0) {
let single_domain = option(schedule, "blocked_domains", "");
if (single_domain == "") continue;
}
let raw_ips = list_option(schedule, "device_ip");
if (length(raw_ips) == 0) {
let single_ip = option(schedule, "device_ip", "");
if (single_ip != "") raw_ips = [ single_ip ];
}
if (length(raw_ips) == 0)
continue;
let label = as_string(option(schedule, "label", schedule[".name"]));
let comment = "tachyon-block:" + label;
let start_time = option(schedule, "start_time", "");
let end_time = option(schedule, "end_time", "");
let intervals = nft_schedule_time_intervals(start_time, end_time);
let days_args = nft_schedule_days_match_args(schedule);
let always_on = start_time == "" && end_time == "";
for (let raw_ip in raw_ips) {
let dev_str = trim(as_string(raw_ip));
if (dev_str == "") continue;
let is_mac = match(dev_str, /^([0-9a-fA-F]{2}[:-]){5}[0-9a-fA-F]{2}$/) != null;
let family = is_mac ? 0 : core_ip.ip_family(dev_str);
if (!is_mac && family != 4)
continue; // dns-block-in listens on IPv4 only
for (let interval in intervals) {
let match_args = [];
if (is_mac) {
append_array(match_args, [ "ether", "saddr", lc(replace(dev_str, "-", ":")) ]);
} else {
append_array(match_args, [ "ip", "saddr", dev_str ]);
}
if (!always_on) {
append_array(match_args, [ "meta", "hour", sprintf("\"%s\"-\"%s\"", interval[0], interval[1]) ]);
}
append_array(match_args, days_args);
append_array(match_args, [ "udp", "dport", "53", "redirect", "to", DNS_BLOCK_TARGET, "counter", "comment", "\"" + comment + "\"" ]);
if (!nft_add_rule(table, "dns_block", match_args))
return false;
added = true;
let tcp_args = [];
if (is_mac) {
append_array(tcp_args, [ "ether", "saddr", lc(replace(dev_str, "-", ":")) ]);
} else {
append_array(tcp_args, [ "ip", "saddr", dev_str ]);
}
if (!always_on) {
append_array(tcp_args, [ "meta", "hour", sprintf("\"%s\"-\"%s\"", interval[0], interval[1]) ]);
}
append_array(tcp_args, days_args);
append_array(tcp_args, [ "tcp", "dport", "53", "redirect", "to", DNS_BLOCK_TARGET, "counter", "comment", "\"" + comment + "\"" ]);
if (!nft_add_rule(table, "dns_block", tcp_args))
return false;
added = true;
}
}
}
return added;
}
function nft_add_dns_block_rules_from_uci(table) {
return nft_add_dns_block_rules_from_schedules(uci_sections("schedule"), table);
}
function nft_create_runtime_base(table, localv4_set, common_set, port_set, ip_port_set, interface_set, source_interfaces, fakeip_mark, outbound_mark, fakeip_range, tproxy_port, exclude_ntp, localv6_set, common6_set, ip_port6_set, fakeip6_range, tproxy6_address) {
localv6_set = default_arg(localv6_set, "localv6");
common6_set = default_arg(common6_set, "tachyon_subnets6");
@@ -1004,6 +1089,7 @@ function nft_create_runtime_base(table, localv4_set, common_set, port_set, ip_po
!nft_create_priority_chains(table) ||
!nft_create_chain(table, "parental_control", "{ }") ||
!nft_create_chain(table, "parental_forward", "{ }") ||
!nft_create_chain(table, "dns_block", "{ type nat hook prerouting priority -101; policy accept; }") ||
!nft_create_chain(table, "proxy", "{ type filter hook prerouting priority -100; policy accept; }"))
return false;
@@ -1781,6 +1867,8 @@ function nft_schedule_signature_body(body, schedule) {
body = signature_add_value(body, "schedule." + name + ".start_time", option(schedule, "start_time", ""));
body = signature_add_value(body, "schedule." + name + ".end_time", option(schedule, "end_time", ""));
body = signature_add_value(body, "schedule." + name + ".days", join(",", list_option(schedule, "days")));
body = signature_add_value(body, "schedule." + name + ".blocked_domains", join(",", list_option(schedule, "blocked_domains")));
body = signature_add_value(body, "schedule." + name + ".mode", option(schedule, "mode", "block"));
return body;
}
@@ -1849,6 +1937,7 @@ function nft_create_full_runtime_from_uci(rt_table, table, localv4_set, common_s
nft_create_runtime_base_from_uci(table, localv4_set, common_set, port_set, ip_port_set, interface_set, fakeip_mark, outbound_mark, fakeip_range, tproxy_port, localv6_set, common6_set, ip_port6_set, fakeip6_range, tproxy6_address) &&
nft_add_section_priority_rules_from_sections(uci_sections("section"), table, interface_set, localv4_set, localv6_set, fakeip_mark) &&
nft_add_schedule_rules_from_uci(table, uci_sections("section")) &&
nft_add_dns_block_rules_from_uci(table) &&
nft_create_provider_output_rules_from_uci(table, "zapret", zapret_bin, zapret_route_mark_base, zapret_queue_base, zapret_desync_mark, zapret_desync_mark_postnat) &&
nft_create_provider_output_rules_from_uci(table, "zapret2", zapret2_bin, zapret2_route_mark_base, zapret2_queue_base, zapret2_desync_mark, zapret2_desync_mark_postnat) &&
nft_create_runtime_output_rules(table, localv4_set, common_set, port_set, ip_port_set, fakeip_mark, fakeip_range, localv6_set, common6_set, ip_port6_set, fakeip6_range);
@@ -2135,6 +2224,8 @@ else if (mode == "nft-add-section-priority-rules-fixture")
exit(nft_add_section_priority_rules_from_sections(fixture_section_list(object_or_empty(common_read_json_file(ARGV[1])), "section"), ARGV[2], ARGV[3], ARGV[4], ARGV[5], ARGV[6]) ? 0 : 1);
else if (mode == "nft-add-schedule-rules-fixture")
exit(nft_add_schedule_rules_from_schedules(fixture_section_list(object_or_empty(common_read_json_file(ARGV[1])), "schedule"), fixture_section_list(object_or_empty(common_read_json_file(ARGV[1])), "section"), ARGV[2]) ? 0 : 1);
else if (mode == "nft-add-dns-block-rules-fixture")
exit(nft_add_dns_block_rules_from_schedules(fixture_section_list(object_or_empty(common_read_json_file(ARGV[1])), "schedule"), ARGV[2]) ? 0 : 1);
else if (mode == "nft-create-full-runtime-from-uci")
exit(nft_create_full_runtime_from_uci(ARGV[1], ARGV[2], ARGV[3], ARGV[4], ARGV[5], ARGV[6], ARGV[7], ARGV[8], ARGV[9], ARGV[10], ARGV[11], ARGV[12], ARGV[13], ARGV[14], ARGV[15], ARGV[16], ARGV[17], ARGV[18], ARGV[19], ARGV[20], ARGV[21], ARGV[22], ARGV[23], ARGV[24], ARGV[25], ARGV[26]) ? 0 : 1);
else if (mode == "nft-rebuild-runtime-from-uci")
+87
View File
@@ -2748,6 +2748,87 @@ function check_notified_updates(token, admin_ids) {
}
}
// ─── Content blocking notifications ──────────────────────────────────────────
// Reads the dns_block nftables chain counters and notifies admins when a
// device tried to reach a blocked domain (parental control content rules).
// The counters are cumulative; we persist the last-seen total per rule and
// only report the delta. The per-rule label is written into nft comments
// by the apply step so the counter line can be matched back to the rule.
const BLOCKED_STATE_FILE = "/var/run/tachyon_blocked_counts.json";
const BLOCKED_POLL_INTERVAL = 60;
function block_schedules_with_notify() {
let result = [];
for (let s in uci_core.sections(CONFIG_NAME, "schedule")) {
s = object_or_empty(s);
if (option(s, "enabled", "1") != "0" &&
option(s, "notify", "0") == "1" &&
length(common.list_option(s, "blocked_domains")) > 0)
push(result, s);
}
return result;
}
function load_blocked_counts() {
try {
let raw = trim(fs.readfile(BLOCKED_STATE_FILE) || "");
if (raw == "") return {};
return json(raw);
} catch (e) {
return {};
}
}
function save_blocked_counts(counts) {
try {
fs.writefile(BLOCKED_STATE_FILE, sprintf("%J", counts));
} catch (e) {}
}
function check_blocked_activity(cfg) {
let schedules = block_schedules_with_notify();
if (length(schedules) == 0) return;
let out = command_capture(command_from_args([ "nft", "list", "chain", "inet", "tachyon", "dns_block" ]));
if (!out || out.status != 0) return;
let previous = load_blocked_counts();
let current = {};
for (let schedule in schedules) {
let label = as_string(option(schedule, "label", schedule[".name"]));
let needle = "tachyon-block:" + label;
let total = 0;
let match_count = 0;
for (let line in split(out.output || "", "\n")) {
if (index(line, needle) < 0)
continue;
let m = match(line, /packets[= ]+([0-9]+)/);
if (!m || !m[1]) continue;
total += int(m[1]);
match_count++;
}
if (match_count == 0)
continue;
current[label] = total;
let prev = int(previous[label] || "0");
if (total > prev) {
let delta = total - prev;
let admins = split(cfg.admin_ids || "", /,/);
for (let admin in admins) {
let cid = trim(admin);
if (cid != "")
send_message(cfg.bot_token, cid,
"🚫 <b>Заблокированная активность</b>\n" +
"Правило: <code>" + escape_html(label) + "</code>\n" +
"Попыток доступа к заблокированным сайтам: <code>" + as_string(delta) + "</code>",
"HTML", null);
}
}
}
save_blocked_counts(current);
}
function worker() {
let cfg = settings();
if (cfg.enabled != "1" || !cfg.bot_token) return 0;
@@ -2791,6 +2872,7 @@ function worker() {
let last_report_day = -1;
let last_update_check = 0;
let last_blocked_check = 0;
let consecutive_failures = 0;
while (true) {
@@ -2825,6 +2907,11 @@ function worker() {
check_notified_updates(cfg.bot_token, cfg.admin_ids);
last_update_check = now;
}
if (now - last_blocked_check > BLOCKED_POLL_INTERVAL) {
check_blocked_activity(cfg);
last_blocked_check = now;
}
} catch (e) {
consecutive_failures++;
command_success_from_args(["logger", "-t", "tachyon-telegram", "[err] Worker loop error: " + as_string(e)]);
@@ -15,6 +15,9 @@ const TPROXY_INBOUND_PORT = 1602;
const DNS_INBOUND_TAG = "dns-in";
const DNS_INBOUND_ADDRESS = "127.0.0.42";
const DNS_INBOUND_PORT = 53;
const DNS_BLOCK_INBOUND_TAG = "dns-block-in";
const DNS_BLOCK_INBOUND_ADDRESS = "127.0.0.43";
const DNS_BLOCK_INBOUND_PORT = 1053;
const SERVICE_MIXED_INBOUND_TAG = "service-mixed-in";
const SERVICE_MIXED_INBOUND_ADDRESS = "127.0.0.1";
@@ -49,6 +52,7 @@ const RESERVED_TAGS = {
[TPROXY_INBOUND_TAG]: true,
[TPROXY_INBOUND6_TAG]: true,
[DNS_INBOUND_TAG]: true,
[DNS_BLOCK_INBOUND_TAG]: true,
[SERVICE_MIXED_INBOUND_TAG]: true,
[DIRECT_OUTBOUND_TAG]: true,
[BYPASS_OUTBOUND_TAG]: true
@@ -94,6 +98,9 @@ return {
DNS_INBOUND_TAG,
DNS_INBOUND_ADDRESS,
DNS_INBOUND_PORT,
DNS_BLOCK_INBOUND_TAG,
DNS_BLOCK_INBOUND_ADDRESS,
DNS_BLOCK_INBOUND_PORT,
SERVICE_MIXED_INBOUND_TAG,
SERVICE_MIXED_INBOUND_ADDRESS,
SERVICE_MIXED_INBOUND_PORT,
+166
View File
@@ -686,6 +686,170 @@ function add_service_mixed_proxy(config, settings, sections) {
runtime_generate_unsupported("download components via proxy section is not set");
}
// ─── Content blocking (parental control domains) ─────────────────────────────
// Each enabled `config schedule` with blocked_domains generates:
// 1. DNS rules (on the dedicated dns-block-in inbound) that reject the
// blocked domains for the schedule's devices. IP-addressed devices get
// source_ip_cidr scoping; MAC-only devices are covered by the nftables
// DNS redirect (the DNS packets only reach dns-block-in when the
// nftables redirect rule fires, which already matches by MAC+time).
// 2. Route rules with action reject as a fallback when DNS cannot be
// redirected (always-on schedules only; time-gated schedules rely on
// the nftables redirect so their route rules would leak outside the
// active window).
// mode=allow (whitelist) inverts the DNS match: everything except the
// listed domains is rejected for the device.
function schedule_list_value(schedule, key) {
let values = list_option(schedule, key);
if (length(values) > 0)
return values;
let single = option(schedule, key, "");
return single == "" ? [] : [ single ];
}
function schedule_has_time_window(schedule) {
return trim(option(schedule, "start_time", "")) != "" ||
trim(option(schedule, "end_time", "")) != "";
}
function schedule_source_ip_cidrs(schedule) {
let result = [];
for (let raw in schedule_list_value(schedule, "device_ip")) {
let device = trim(as_string(raw));
if (device == "")
continue;
let is_mac = match(device, /^([0-9a-fA-F]{2}[:-]){5}[0-9a-fA-F]{2}$/) != null;
if (is_mac)
continue;
if (core_ip.valid_ip(device))
push(result, core_ip.valid_ipv6(device) ? device + "/128" : device + "/32");
else if (core_ip.valid_ip_cidr(device))
push(result, device);
}
return result;
}
function schedule_domain_matcher_rule(rule, schedule) {
let raw = schedule_list_value(schedule, "blocked_domains");
let plain = [];
let keyword = [];
let regex = [];
let full = [];
for (let domain in raw) {
let value = trim(as_string(domain));
if (value == "")
continue;
if (substr(value, 0, 6) == "full:")
push(full, trim(substr(value, 6)));
else if (substr(value, 0, 8) == "keyword:")
push(keyword, trim(substr(value, 8)));
else if (substr(value, 0, 6) == "regex:")
push(regex, trim(substr(value, 6)));
else if (substr(value, 0, 1) == "." || index(value, "*") >= 0)
push(plain, value);
else
push(full, value);
}
if (length(full) > 0)
rule.domain = full;
if (length(plain) > 0)
rule.domain_suffix = plain;
if (length(keyword) > 0)
rule.domain_keyword = keyword;
if (length(regex) > 0)
rule.domain_regex = regex;
return rule;
}
function schedule_blocked_domains(schedule) {
let result = [];
for (let domain in schedule_list_value(schedule, "blocked_domains")) {
let value = trim(as_string(domain));
if (value != "")
push(result, value);
}
return result;
}
function enabled_content_block_schedules() {
let result = [];
ctx.uci_cursor().foreach(CONFIG_NAME, "schedule", function(schedule) {
if (section_enabled(schedule) && length(schedule_blocked_domains(schedule)) > 0)
push(result, schedule);
});
return result;
}
function add_content_block_dns_inbound(config) {
push(config.inbounds, {
type: "direct",
tag: runtime_constants.DNS_BLOCK_INBOUND_TAG,
listen: runtime_constants.DNS_BLOCK_INBOUND_ADDRESS,
listen_port: runtime_constants.DNS_BLOCK_INBOUND_PORT
});
}
function add_content_block_dns_rules(config, schedules) {
let added = false;
for (let schedule in schedules) {
let mode = option(schedule, "mode", "block");
let sources = schedule_source_ip_cidrs(schedule);
for (let raw_domain in schedule_blocked_domains(schedule)) {
let domain_value = trim(as_string(raw_domain));
if (domain_value == "")
continue;
let rule = {
action: "reject",
inbound: [ runtime_constants.DNS_BLOCK_INBOUND_TAG ]
};
let matchers = {};
schedule_domain_matcher_rule(matchers, { blocked_domains: [ domain_value ] });
for (let key in [ "domain", "domain_suffix", "domain_keyword", "domain_regex" ]) {
if (matchers[key] != null)
rule[key] = matchers[key];
}
if (length(sources) > 0)
rule.source_ip_cidr = sources;
if (mode == "allow")
rule.invert = true;
push(config.dns.rules, rule);
added = true;
}
}
return added;
}
function add_content_block_route_rules(config, schedules) {
for (let schedule in schedules) {
if (schedule_has_time_window(schedule))
continue;
let mode = option(schedule, "mode", "block");
let sources = schedule_source_ip_cidrs(schedule);
if (length(sources) == 0)
continue;
let rule = {
action: "reject",
inbound: tproxy_inbound_matcher()
};
schedule_domain_matcher_rule(rule, schedule);
rule.source_ip_cidr = sources;
if (mode == "allow")
rule.invert = true;
push(config.route.rules, rule);
}
}
function add_content_blocking(config) {
let schedules = enabled_content_block_schedules();
if (length(schedules) == 0)
return;
add_content_block_dns_inbound(config);
add_content_block_dns_rules(config, schedules);
add_content_block_route_rules(config, schedules);
}
function generate_config(output_path, service_address, mwan3_active, supports_xhttp) {
ctx.runtime_ruleset_folder = runtime_ruleset_folder;
runtime_supports_xhttp = supports_xhttp == null || as_string(supports_xhttp) == ""
@@ -728,6 +892,8 @@ function generate_config(output_path, service_address, mwan3_active, supports_xh
for (let section in sections)
add_mixed_proxy_for_section(config, section, service_address);
add_content_blocking(config);
assert_unique_outbound_tags(config);
strip_internal_fields(config);
if (!atomic_write_json_file(output_path, config)) {
+327
View File
@@ -0,0 +1,327 @@
#!/usr/bin/env bash
set -eo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
TACHYON_LIB="$ROOT_DIR/tachyon/files/usr/lib"
GENERATOR_UC="$TACHYON_LIB/singbox/generator.uc"
NFT_RUNTIME="$TACHYON_LIB/nft/apply.uc"
VALIDATOR_RUNTIME="$TACHYON_LIB/config/validator.uc"
WORK_DIR="$(mktemp -d)"
NFT_LOG="$WORK_DIR/nft.log"
nft_ucode() {
ucode -L "$TACHYON_LIB" "$NFT_RUNTIME" "$@"
}
cleanup() {
rm -rf "$WORK_DIR"
}
trap cleanup EXIT
fail() {
printf 'FAIL: %s\n' "$1" >&2
cat "$NFT_LOG" >&2 2>/dev/null || true
exit 1
}
assert_contains() {
local file="$1"
local expected="$2"
local label="$3"
grep -Fq "$expected" "$file" || fail "$label: expected '$expected'"
}
generate_config() {
local fixture="$1"
local output="$2"
mkdir -p "$output.section-cache" "$output.rulesets"
ucode -L "$TACHYON_LIB" "$GENERATOR_UC" generate-config-fixture \
"$fixture" "$output" "127.0.0.1" "0"
}
# ─── Fixture: always-on block with IP device ─────────────────────────────────
cat >"$WORK_DIR/fixture_always_on.json" <<'JSON'
{
"settings": {
".name": "settings",
".type": "settings",
"config_path": "/tmp/sing-box/config.json",
"dns_server": "1.1.1.1",
"service_listen_address": "127.0.0.1"
},
"section": [
{
".name": "sec_proxy",
"enabled": "1",
"action": "connection",
"label": "Main proxy",
"selector_proxy_links": [ "socks5://127.0.0.1:1080#Proxy" ]
}
],
"schedule": [
{
".name": "kids_youtube",
"enabled": "1",
"label": "Kids YouTube",
"device_ip": [ "192.168.1.150" ],
"blocked_domains": [ "youtube.com", "googlevideo.com" ],
"mode": "block",
"dns_level": "1",
"notify": "1"
}
]
}
JSON
OUT="$WORK_DIR/always_on.json"
generate_config "$WORK_DIR/fixture_always_on.json" "$OUT"
# dns-block-in inbound must exist
assert_contains "$OUT" '"tag": "dns-block-in"' "always-on: dns-block-in inbound"
assert_contains "$OUT" '"listen_port": 1053' "always-on: block inbound port 1053"
# DNS reject rules with source_ip_cidr for IP devices
grep -o '"source_ip_cidr": \[ [^]]*\]' "$OUT" | grep -q '192.168.1.150/32' ||
fail "always-on: DNS rule must carry source_ip_cidr 192.168.1.150/32"
assert_contains "$OUT" '"action": "reject"' "always-on: DNS reject action"
# Route rule fallback (always-on schedule, IP device)
grep -o '"action": "reject"' "$OUT" | head -2 >/dev/null ||
fail "always-on: route reject rule missing"
# ─── Fixture: whitelist (allow only) mode ────────────────────────────────────
cat >"$WORK_DIR/fixture_allow.json" <<'JSON'
{
"settings": {
".name": "settings",
".type": "settings",
"config_path": "/tmp/sing-box/config.json",
"dns_server": "1.1.1.1",
"service_listen_address": "127.0.0.1"
},
"section": [
{
".name": "sec_proxy",
"enabled": "1",
"action": "connection",
"label": "Main proxy",
"selector_proxy_links": [ "socks5://127.0.0.1:1080#Proxy" ]
}
],
"schedule": [
{
".name": "kids_whitelist",
"enabled": "1",
"label": "Kids Whitelist",
"device_ip": [ "192.168.1.151" ],
"blocked_domains": [ "school.edu", "khanacademy.org" ],
"mode": "allow",
"dns_level": "1",
"notify": "0"
}
]
}
JSON
OUT="$WORK_DIR/allow.json"
generate_config "$WORK_DIR/fixture_allow.json" "$OUT"
# Whitelist mode must invert the DNS rule
grep -o '"invert": true' "$OUT" | head -1 >/dev/null ||
fail "whitelist: DNS rule must be inverted"
assert_contains "$OUT" '"source_ip_cidr": [ "192.168.1.151/32" ]' "whitelist: source_ip_cidr scoped"
# ─── Fixture: MAC-only device (no IP in DNS rules, nft redirect only) ────────
cat >"$WORK_DIR/fixture_mac.json" <<'JSON'
{
"settings": {
".name": "settings",
".type": "settings",
"config_path": "/tmp/sing-box/config.json",
"dns_server": "1.1.1.1",
"service_listen_address": "127.0.0.1"
},
"section": [
{
".name": "sec_proxy",
"enabled": "1",
"action": "connection",
"label": "Main proxy",
"selector_proxy_links": [ "socks5://127.0.0.1:1080#Proxy" ]
}
],
"schedule": [
{
".name": "tv_block",
"enabled": "1",
"label": "TV Block",
"device_ip": [ "11:22:33:44:55:66" ],
"blocked_domains": [ "netflix.com" ],
"mode": "block",
"dns_level": "1",
"notify": "0"
}
]
}
JSON
OUT="$WORK_DIR/mac.json"
generate_config "$WORK_DIR/fixture_mac.json" "$OUT"
# MAC-only device: DNS rules must NOT carry source_ip_cidr (no IP known)
if grep -o '"source_ip_cidr"' "$OUT" | head -1 >/dev/null; then
fail "mac-only: DNS rule must not carry source_ip_cidr for MAC-only device"
fi
# But dns-block-in must still exist
assert_contains "$OUT" '"tag": "dns-block-in"' "mac-only: dns-block-in inbound"
# ─── nftables DNS redirect rules ─────────────────────────────────────────────
mkdir -p "$WORK_DIR/bin"
cat >"$WORK_DIR/bin/nft" <<NFT
#!/usr/bin/env bash
set -eo pipefail
{
printf 'nft'
for arg in "\$@"; do
printf '\t%s' "\$arg"
done
printf '\n'
} >>"$NFT_LOG"
exit 0
NFT
chmod +x "$WORK_DIR/bin/nft"
export PATH="$WORK_DIR/bin:$PATH"
cat >"$WORK_DIR/nft_fixture.json" <<'JSON'
{
"schedule": [
{
".name": "kids_youtube",
"enabled": "1",
"label": "Kids YouTube",
"device_ip": [ "192.168.1.150", "11:22:33:44:55:66" ],
"blocked_domains": [ "youtube.com" ],
"start_time": "22:00",
"end_time": "08:00",
"mode": "block",
"dns_level": "1",
"notify": "1"
},
{
".name": "tv_always",
"enabled": "1",
"label": "TV Always",
"device_ip": [ "192.168.1.160" ],
"blocked_domains": [ "netflix.com" ],
"mode": "block",
"dns_level": "1",
"notify": "0"
}
]
}
JSON
nft_ucode nft-add-dns-block-rules-fixture "$WORK_DIR/nft_fixture.json" "tachyon"
# Time-gated rule: IP device, meta hour window, redirect to block target
assert_contains "$NFT_LOG" "dns_block ip saddr 192.168.1.150 meta hour \"22:00:00\"-\"23:59:59\" udp dport 53 redirect to 127.0.0.43:1053 counter comment" "time-gated udp redirect"
assert_contains "$NFT_LOG" "dns_block ip saddr 192.168.1.150 meta hour \"00:00:00\"-\"08:00:00\" udp dport 53 redirect to 127.0.0.43:1053" "midnight wrap udp redirect"
assert_contains "$NFT_LOG" "tcp dport 53 redirect to 127.0.0.43:1053" "tcp dns redirect"
# MAC device redirect
assert_contains "$NFT_LOG" "dns_block ether saddr 11:22:33:44:55:66 meta hour" "mac redirect with time"
# Always-on schedule: no meta hour matcher
if grep -Fq "192.168.1.160 meta hour" "$NFT_LOG"; then
fail "always-on schedule must not carry meta hour matcher"
fi
assert_contains "$NFT_LOG" "ip saddr 192.168.1.160 udp dport 53 redirect to 127.0.0.43:1053" "always-on redirect"
# ─── Validator: invalid blocked domain rejected ──────────────────────────────
cat >"$WORK_DIR/invalid_domain.json" <<'JSON'
{
"settings": { ".name": "settings", ".type": "settings", "dns_server": ["77.88.8.8"], "bootstrap_dns_server": ["77.88.8.8"] },
"schedule": [
{
".name": "bad_domain",
"enabled": "1",
"device_ip": "192.168.1.100",
"blocked_domains": [ "not a domain!!" ]
}
]
}
JSON
if ucode -L "$TACHYON_LIB" "$VALIDATOR_RUNTIME" validate-runtime-fixture "$WORK_DIR/invalid_domain.json" "{}" >/dev/null 2>&1; then
fail "Validator must reject invalid blocked_domains"
fi
# ─── Validator: invalid mode rejected ────────────────────────────────────────
cat >"$WORK_DIR/invalid_mode.json" <<'JSON'
{
"settings": { ".name": "settings", ".type": "settings", "dns_server": ["77.88.8.8"], "bootstrap_dns_server": ["77.88.8.8"] },
"schedule": [
{
".name": "bad_mode",
"enabled": "1",
"device_ip": "192.168.1.100",
"blocked_domains": [ "youtube.com" ],
"mode": "banana"
}
]
}
JSON
if ucode -L "$TACHYON_LIB" "$VALIDATOR_RUNTIME" validate-runtime-fixture "$WORK_DIR/invalid_mode.json" "{}" >/dev/null 2>&1; then
fail "Validator must reject invalid mode"
fi
# ─── Validator: invalid dns_level rejected ───────────────────────────────────
cat >"$WORK_DIR/invalid_dns_level.json" <<'JSON'
{
"settings": { ".name": "settings", ".type": "settings", "dns_server": ["77.88.8.8"], "bootstrap_dns_server": ["77.88.8.8"] },
"schedule": [
{
".name": "bad_level",
"enabled": "1",
"device_ip": "192.168.1.100",
"blocked_domains": [ "youtube.com" ],
"dns_level": "5"
}
]
}
JSON
if ucode -L "$TACHYON_LIB" "$VALIDATOR_RUNTIME" validate-runtime-fixture "$WORK_DIR/invalid_dns_level.json" "{}" >/dev/null 2>&1; then
fail "Validator must reject invalid dns_level"
fi
# ─── Signature changes when blocked_domains change ───────────────────────────
SIG1="$(nft_ucode nft-runtime-signature-fixture "$WORK_DIR/nft_fixture.json")"
cat >"$WORK_DIR/nft_fixture2.json" <<'JSON'
{
"schedule": [
{
".name": "kids_youtube",
"enabled": "1",
"label": "Kids YouTube",
"device_ip": [ "192.168.1.150" ],
"blocked_domains": [ "tiktok.com" ],
"start_time": "22:00",
"end_time": "08:00",
"mode": "block",
"dns_level": "1",
"notify": "1"
}
]
}
JSON
SIG2="$(nft_ucode nft-runtime-signature-fixture "$WORK_DIR/nft_fixture2.json")"
[ "$SIG1" != "$SIG2" ] || fail "Signature must change when blocked_domains are updated"
printf 'Content blocking checks passed\n'