Add automatic component update checks

This commit is contained in:
ushan0v
2026-07-11 02:08:39 +03:00
parent 3292766afe
commit 6bfdca6229
25 changed files with 2014 additions and 1076 deletions
+160 -158
View File
@@ -80,7 +80,7 @@
"call": "Active",
"key": "Active",
"places": [
"src/podkop/tabs/monitoring/initController.ts:663",
"src/podkop/tabs/monitoring/initController.ts:667",
"src/podkop/tabs/monitoring/render.ts:20"
]
},
@@ -179,7 +179,7 @@
"call": "All",
"key": "All",
"places": [
"src/podkop/tabs/monitoring/initController.ts:622",
"src/podkop/tabs/monitoring/initController.ts:626",
"src/podkop/tabs/monitoring/render.ts:39"
]
},
@@ -302,6 +302,13 @@
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/section.js:2000"
]
},
{
"call": "Automatic component update checks",
"key": "Automatic component update checks",
"places": [
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:495"
]
},
{
"call": "Automatic User-Agent selection",
"key": "Automatic User-Agent selection",
@@ -316,6 +323,13 @@
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/section.js:2034"
]
},
{
"call": "Automatically check installed components for new versions",
"key": "Automatically check installed components for new versions",
"places": [
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:496"
]
},
{
"call": "Automatically select the fastest node in the current level",
"key": "Automatically select the fastest node in the current level",
@@ -481,14 +495,14 @@
"call": "Cache File Path",
"key": "Cache File Path",
"places": [
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:573"
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:617"
]
},
{
"call": "Cache file path cannot be empty",
"key": "Cache file path cannot be empty",
"places": [
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:587"
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:631"
]
},
{
@@ -553,7 +567,7 @@
"call": "Check update",
"key": "Check update",
"places": [
"src/podkop/tabs/updates/initController.ts:709"
"src/podkop/tabs/updates/initController.ts:696"
]
},
{
@@ -655,8 +669,8 @@
"src/partials/modal/renderModal.ts:263",
"src/podkop/tabs/dashboard/initController.ts:1027",
"src/podkop/tabs/dashboard/initController.ts:1283",
"src/podkop/tabs/monitoring/initController.ts:828",
"src/podkop/tabs/monitoring/initController.ts:878"
"src/podkop/tabs/monitoring/initController.ts:832",
"src/podkop/tabs/monitoring/initController.ts:882"
]
},
{
@@ -671,18 +685,32 @@
"call": "Close connection",
"key": "Close connection",
"places": [
"src/podkop/tabs/monitoring/initController.ts:795",
"src/podkop/tabs/monitoring/initController.ts:796"
"src/podkop/tabs/monitoring/initController.ts:799",
"src/podkop/tabs/monitoring/initController.ts:800"
]
},
{
"call": "Closed",
"key": "Closed",
"places": [
"src/podkop/tabs/monitoring/initController.ts:670",
"src/podkop/tabs/monitoring/initController.ts:674",
"src/podkop/tabs/monitoring/render.ts:29"
]
},
{
"call": "Component update available",
"key": "Component update available",
"places": [
"src/podkop/services/core.service.ts:43"
]
},
{
"call": "Component update check interval",
"key": "Component update check interval",
"places": [
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:504"
]
},
{
"call": "Components",
"key": "Components",
@@ -694,7 +722,7 @@
"call": "compressed",
"key": "compressed",
"places": [
"src/podkop/helpers/singBoxVariant.ts:52"
"src/podkop/helpers/singBoxVariant.ts:51"
]
},
{
@@ -715,7 +743,7 @@
"call": "Config File Path",
"key": "Config File Path",
"places": [
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:560"
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:604"
]
},
{
@@ -750,14 +778,14 @@
"call": "Connections are unavailable",
"key": "Connections are unavailable",
"places": [
"src/podkop/tabs/monitoring/initController.ts:943"
"src/podkop/tabs/monitoring/initController.ts:946"
]
},
{
"call": "Connectivity probe failed",
"key": "Connectivity probe failed",
"places": [
"src/podkop/tabs/diagnostic/checks/runSectionsCheck.ts:144"
"src/podkop/tabs/diagnostic/checks/runSectionsCheck.ts:145"
]
},
{
@@ -839,7 +867,7 @@
"call": "Default address for checking server availability and latency. URLTest uses its own address.",
"key": "Default address for checking server availability and latency. URLTest uses its own address.",
"places": [
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:496"
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:540"
]
},
{
@@ -878,13 +906,6 @@
"src/validators/validateOutboundJson.ts:115"
]
},
{
"call": "Dev",
"key": "Dev",
"places": [
"src/podkop/tabs/updates/initController.ts:109"
]
},
{
"call": "Device filter",
"key": "Device filter",
@@ -1114,7 +1135,7 @@
"call": "Dont Touch My DHCP!",
"key": "Dont Touch My DHCP!",
"places": [
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:551"
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:595"
]
},
{
@@ -1136,42 +1157,42 @@
"call": "Download component packages via the selected section",
"key": "Download component packages via the selected section",
"places": [
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:532"
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:576"
]
},
{
"call": "Download components through",
"key": "Download components through",
"places": [
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:539"
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:583"
]
},
{
"call": "Download components through a section",
"key": "Download components through a section",
"places": [
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:531"
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:575"
]
},
{
"call": "Download lists through",
"key": "Download lists through",
"places": [
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:519"
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:563"
]
},
{
"call": "Download lists through a section",
"key": "Download lists through a section",
"places": [
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:511"
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:555"
]
},
{
"call": "Download remote lists and rule sets via the selected section",
"key": "Download remote lists and rule sets via the selected section",
"places": [
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:512"
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:556"
]
},
{
@@ -1199,8 +1220,8 @@
"call": "Downloaded",
"key": "Downloaded",
"places": [
"src/podkop/tabs/monitoring/initController.ts:821",
"src/podkop/tabs/monitoring/initController.ts:875"
"src/podkop/tabs/monitoring/initController.ts:825",
"src/podkop/tabs/monitoring/initController.ts:879"
]
},
{
@@ -1371,14 +1392,14 @@
"call": "Exclude NTP",
"key": "Exclude NTP",
"places": [
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:625"
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:669"
]
},
{
"call": "Exclude NTP protocol traffic from the tunnel to prevent it from being routed through the proxy or VPN",
"key": "Exclude NTP protocol traffic from the tunnel to prevent it from being routed through the proxy or VPN",
"places": [
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:626"
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:670"
]
},
{
@@ -1428,16 +1449,16 @@
"call": "Failed to close connection",
"key": "Failed to close connection",
"places": [
"src/podkop/tabs/monitoring/initController.ts:1210",
"src/podkop/tabs/monitoring/initController.ts:1225"
"src/podkop/tabs/monitoring/initController.ts:1213",
"src/podkop/tabs/monitoring/initController.ts:1228"
]
},
{
"call": "Failed to close connections",
"key": "Failed to close connections",
"places": [
"src/podkop/tabs/monitoring/initController.ts:1244",
"src/podkop/tabs/monitoring/initController.ts:1257"
"src/podkop/tabs/monitoring/initController.ts:1247",
"src/podkop/tabs/monitoring/initController.ts:1260"
]
},
{
@@ -1454,10 +1475,10 @@
"places": [
"src/podkop/methods/shell/index.ts:170",
"src/podkop/methods/shell/index.ts:177",
"src/podkop/tabs/updates/initController.ts:466",
"src/podkop/tabs/updates/initController.ts:467",
"src/podkop/tabs/updates/initController.ts:527",
"src/podkop/tabs/updates/initController.ts:677"
"src/podkop/tabs/updates/initController.ts:464",
"src/podkop/tabs/updates/initController.ts:465",
"src/podkop/tabs/updates/initController.ts:525",
"src/podkop/tabs/updates/initController.ts:675"
]
},
{
@@ -1690,8 +1711,8 @@
"call": "Host",
"key": "Host",
"places": [
"src/podkop/tabs/monitoring/initController.ts:811",
"src/podkop/tabs/monitoring/initController.ts:871"
"src/podkop/tabs/monitoring/initController.ts:815",
"src/podkop/tabs/monitoring/initController.ts:875"
]
},
{
@@ -1829,45 +1850,22 @@
"call": "Install",
"key": "Install",
"places": [
"src/podkop/tabs/updates/initController.ts:123",
"src/podkop/tabs/updates/initController.ts:822",
"src/podkop/tabs/updates/initController.ts:853",
"src/podkop/tabs/updates/initController.ts:884"
"src/podkop/tabs/updates/initController.ts:710"
]
},
{
"call": "Install extended",
"key": "Install extended",
"call": "Install another build:",
"key": "Install another build:",
"places": [
"src/podkop/tabs/updates/initController.ts:762"
]
},
{
"call": "Install extended compressed",
"key": "Install extended compressed",
"places": [
"src/podkop/tabs/updates/initController.ts:772"
]
},
{
"call": "Install stable",
"key": "Install stable",
"places": [
"src/podkop/tabs/updates/initController.ts:742"
]
},
{
"call": "Install tiny",
"key": "Install tiny",
"places": [
"src/podkop/tabs/updates/initController.ts:752"
"src/podkop/tabs/updates/initController.ts:1111"
]
},
{
"call": "Installed version is newer than release",
"key": "Installed version is newer than release",
"places": [
"src/podkop/tabs/updates/initController.ts:249"
"src/podkop/tabs/updates/initController.ts:247",
"src/podkop/tabs/updates/initController.ts:969"
]
},
{
@@ -1948,8 +1946,8 @@
"key": "Invalid domain address",
"places": [
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/section.js:4405",
"src/validators/validateDomain.ts:71",
"src/validators/validateDomain.ts:77"
"src/validators/validateDomain.ts:67",
"src/validators/validateDomain.ts:73"
]
},
{
@@ -2464,43 +2462,30 @@
"call": "Latency test failed",
"key": "Latency test failed",
"places": [
"src/podkop/methods/shell/index.ts:463",
"src/podkop/methods/shell/index.ts:484"
"src/podkop/methods/shell/index.ts:462",
"src/podkop/methods/shell/index.ts:483"
]
},
{
"call": "Latency test URL",
"key": "Latency test URL",
"places": [
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:495"
]
},
{
"call": "Latest",
"key": "Latest",
"places": [
"src/podkop/tabs/updates/initController.ts:102"
]
},
{
"call": "Latest release",
"key": "Latest release",
"places": [
"src/podkop/tabs/updates/initController.ts:941"
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:539"
]
},
{
"call": "Latest version is installed",
"key": "Latest version is installed",
"places": [
"src/podkop/tabs/updates/initController.ts:252"
"src/podkop/tabs/updates/initController.ts:250",
"src/podkop/tabs/updates/initController.ts:967"
]
},
{
"call": "Latest: %s",
"key": "Latest: %s",
"call": "Latest version:",
"key": "Latest version:",
"places": [
"src/podkop/tabs/updates/initController.ts:940"
"src/podkop/tabs/updates/initController.ts:969"
]
},
{
@@ -2563,15 +2548,24 @@
"call": "Loading connections",
"key": "Loading connections",
"places": [
"src/podkop/tabs/monitoring/initController.ts:933",
"src/podkop/tabs/monitoring/initController.ts:936",
"src/podkop/tabs/monitoring/render.ts:88"
]
},
{
"call": "Loading...",
"key": "Loading...",
"places": [
"src/podkop/tabs/updates/initController.ts:879",
"src/podkop/tabs/updates/initController.ts:892",
"src/podkop/tabs/updates/initController.ts:905"
]
},
{
"call": "Log Level",
"key": "Log Level",
"places": [
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:609"
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:653"
]
},
{
@@ -2689,6 +2683,13 @@
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/section.js:6518"
]
},
{
"call": "New version %s is available for %s",
"key": "New version %s is available for %s",
"places": [
"src/podkop/services/core.service.ts:38"
]
},
{
"call": "NFQUEUE range is available",
"key": "NFQUEUE range is available",
@@ -2747,14 +2748,14 @@
"call": "No active connections",
"key": "No active connections",
"places": [
"src/podkop/tabs/monitoring/initController.ts:957"
"src/podkop/tabs/monitoring/initController.ts:960"
]
},
{
"call": "No closed connections",
"key": "No closed connections",
"places": [
"src/podkop/tabs/monitoring/initController.ts:958"
"src/podkop/tabs/monitoring/initController.ts:961"
]
},
{
@@ -2858,11 +2859,11 @@
"call": "Not installed",
"key": "Not installed",
"places": [
"src/podkop/helpers/singBoxVariant.ts:30",
"src/podkop/helpers/singBoxVariant.ts:38",
"src/podkop/tabs/updates/initController.ts:804",
"src/podkop/tabs/updates/initController.ts:835",
"src/podkop/tabs/updates/initController.ts:866"
"src/podkop/helpers/singBoxVariant.ts:29",
"src/podkop/helpers/singBoxVariant.ts:37",
"src/podkop/tabs/updates/initController.ts:882",
"src/podkop/tabs/updates/initController.ts:895",
"src/podkop/tabs/updates/initController.ts:908"
]
},
{
@@ -2884,9 +2885,9 @@
"key": "Not responding",
"places": [
"src/podkop/tabs/diagnostic/checks/runSectionsCheck.ts:90",
"src/podkop/tabs/diagnostic/checks/runSectionsCheck.ts:116",
"src/podkop/tabs/diagnostic/checks/runSectionsCheck.ts:122",
"src/podkop/tabs/diagnostic/checks/runSectionsCheck.ts:150"
"src/podkop/tabs/diagnostic/checks/runSectionsCheck.ts:117",
"src/podkop/tabs/diagnostic/checks/runSectionsCheck.ts:123",
"src/podkop/tabs/diagnostic/checks/runSectionsCheck.ts:151"
]
},
{
@@ -2951,6 +2952,14 @@
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/section.js:2384"
]
},
{
"call": "Open",
"key": "Open",
"places": [
"src/podkop/tabs/updates/initController.ts:960",
"src/podkop/tabs/updates/initController.ts:982"
]
},
{
"call": "Open priority settings and enter a display name",
"key": "Open priority settings and enter a display name",
@@ -2970,8 +2979,8 @@
"key": "Operation timed out",
"places": [
"src/helpers/withTimeout.ts:7",
"src/podkop/methods/shell/index.ts:433",
"src/podkop/methods/shell/index.ts:521"
"src/podkop/methods/shell/index.ts:432",
"src/podkop/methods/shell/index.ts:520"
]
},
{
@@ -3004,13 +3013,6 @@
"src/validators/validateOutboundJson.ts:111"
]
},
{
"call": "Outdated",
"key": "Outdated",
"places": [
"src/podkop/tabs/updates/initController.ts:106"
]
},
{
"call": "Output Network Interface",
"key": "Output Network Interface",
@@ -3050,28 +3052,28 @@
"call": "Path must be absolute (start with /)",
"key": "Path must be absolute (start with /)",
"places": [
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:591"
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:635"
]
},
{
"call": "Path must contain at least one directory (like /tmp/cache.db)",
"key": "Path must contain at least one directory (like /tmp/cache.db)",
"places": [
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:600"
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:644"
]
},
{
"call": "Path must end with cache.db",
"key": "Path must end with cache.db",
"places": [
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:595"
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:639"
]
},
{
"call": "Pause updates",
"key": "Pause updates",
"places": [
"src/podkop/tabs/monitoring/initController.ts:687",
"src/podkop/tabs/monitoring/initController.ts:691",
"src/podkop/tabs/monitoring/render.ts:70",
"src/podkop/tabs/monitoring/render.ts:71"
]
@@ -3102,7 +3104,7 @@
"call": "Podkop Plus Error",
"key": "Podkop Plus Error",
"places": [
"src/podkop/services/core.service.ts:22"
"src/podkop/services/core.service.ts:52"
]
},
{
@@ -3137,7 +3139,7 @@
"call": "Podkop Plus service is stopped. Start the service to display connections.",
"key": "Podkop Plus service is stopped. Start the service to display connections.",
"places": [
"src/podkop/tabs/monitoring/initController.ts:921"
"src/podkop/tabs/monitoring/initController.ts:925"
]
},
{
@@ -3158,7 +3160,7 @@
"call": "Podkop Plus will not modify your DHCP configuration",
"key": "Podkop Plus will not modify your DHCP configuration",
"places": [
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:552"
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:596"
]
},
{
@@ -3439,9 +3441,7 @@
"call": "Remove",
"key": "Remove",
"places": [
"src/podkop/tabs/updates/initController.ts:813",
"src/podkop/tabs/updates/initController.ts:844",
"src/podkop/tabs/updates/initController.ts:875"
"src/podkop/tabs/updates/initController.ts:755"
]
},
{
@@ -3525,15 +3525,15 @@
"call": "Resume updates",
"key": "Resume updates",
"places": [
"src/podkop/tabs/monitoring/initController.ts:687"
"src/podkop/tabs/monitoring/initController.ts:691"
]
},
{
"call": "Route",
"key": "Route",
"places": [
"src/podkop/tabs/monitoring/initController.ts:815",
"src/podkop/tabs/monitoring/initController.ts:873"
"src/podkop/tabs/monitoring/initController.ts:819",
"src/podkop/tabs/monitoring/initController.ts:877"
]
},
{
@@ -3787,14 +3787,14 @@
"call": "Select or enter path for sing-box cache file. Change this ONLY if you know what you are doing",
"key": "Select or enter path for sing-box cache file. Change this ONLY if you know what you are doing",
"places": [
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:574"
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:618"
]
},
{
"call": "Select path for sing-box config file. Change this ONLY if you know what you are doing",
"key": "Select path for sing-box config file. Change this ONLY if you know what you are doing",
"places": [
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:561"
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:605"
]
},
{
@@ -3809,7 +3809,7 @@
"call": "Select the log level for sing-box",
"key": "Select the log level for sing-box",
"places": [
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:610"
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:654"
]
},
{
@@ -3945,8 +3945,8 @@
"call": "Service action failed",
"key": "Service action failed",
"places": [
"src/podkop/methods/shell/index.ts:384",
"src/podkop/methods/shell/index.ts:405",
"src/podkop/methods/shell/index.ts:383",
"src/podkop/methods/shell/index.ts:404",
"src/podkop/tabs/diagnostic/initController.ts:594"
]
},
@@ -4069,8 +4069,8 @@
"call": "Source",
"key": "Source",
"places": [
"src/podkop/tabs/monitoring/initController.ts:827",
"src/podkop/tabs/monitoring/initController.ts:877"
"src/podkop/tabs/monitoring/initController.ts:831",
"src/podkop/tabs/monitoring/initController.ts:881"
]
},
{
@@ -4218,8 +4218,8 @@
"call": "Subscription update failed",
"key": "Subscription update failed",
"places": [
"src/podkop/methods/shell/index.ts:717",
"src/podkop/methods/shell/index.ts:737"
"src/podkop/methods/shell/index.ts:720",
"src/podkop/methods/shell/index.ts:740"
]
},
{
@@ -4453,8 +4453,8 @@
"call": "Time",
"key": "Time",
"places": [
"src/podkop/tabs/monitoring/initController.ts:818",
"src/podkop/tabs/monitoring/initController.ts:874"
"src/podkop/tabs/monitoring/initController.ts:822",
"src/podkop/tabs/monitoring/initController.ts:878"
]
},
{
@@ -4475,7 +4475,7 @@
"call": "tiny",
"key": "tiny",
"places": [
"src/podkop/helpers/singBoxVariant.ts:54"
"src/podkop/helpers/singBoxVariant.ts:53"
]
},
{
@@ -4583,8 +4583,8 @@
"key": "Type",
"places": [
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/server.js:1627",
"src/podkop/tabs/monitoring/initController.ts:812",
"src/podkop/tabs/monitoring/initController.ts:872"
"src/podkop/tabs/monitoring/initController.ts:816",
"src/podkop/tabs/monitoring/initController.ts:876"
]
},
{
@@ -4684,7 +4684,7 @@
"src/podkop/services/systemInfo.service.ts:22",
"src/podkop/services/systemInfo.service.ts:25",
"src/podkop/services/systemInfo.service.ts:26",
"src/podkop/tabs/updates/initController.ts:287",
"src/podkop/tabs/updates/initController.ts:285",
"src/podkop/tabs/diagnostic/checks/runInboundsCheck.ts:25"
]
},
@@ -4743,14 +4743,21 @@
"call": "Update",
"key": "Update",
"places": [
"src/podkop/tabs/updates/initController.ts:120"
"src/podkop/tabs/updates/initController.ts:710"
]
},
{
"call": "Update is available",
"key": "Update is available",
"places": [
"src/podkop/tabs/updates/initController.ts:245"
"src/podkop/tabs/updates/initController.ts:243"
]
},
{
"call": "Update is available:",
"key": "Update is available:",
"places": [
"src/podkop/tabs/updates/initController.ts:946"
]
},
{
@@ -4781,8 +4788,8 @@
"call": "Uploaded",
"key": "Uploaded",
"places": [
"src/podkop/tabs/monitoring/initController.ts:824",
"src/podkop/tabs/monitoring/initController.ts:876"
"src/podkop/tabs/monitoring/initController.ts:828",
"src/podkop/tabs/monitoring/initController.ts:880"
]
},
{
@@ -4951,7 +4958,9 @@
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/section.js:3754",
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:460",
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:482",
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:489"
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:489",
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:505",
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:533"
]
},
{
@@ -4990,8 +4999,8 @@
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/section.js:4453",
"src/validators/validateDns.ts:21",
"src/validators/validateDns.ts:25",
"src/validators/validateDomain.ts:61",
"src/validators/validateDomain.ts:80",
"src/validators/validateDomain.ts:57",
"src/validators/validateDomain.ts:76",
"src/validators/validateHttpProxyUrl.ts:97",
"src/validators/validateHysteriaUrl.ts:162",
"src/validators/validateIp.ts:25",
@@ -5031,13 +5040,6 @@
"../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/server.js:1827"
]
},
{
"call": "Version",
"key": "Version",
"places": [
"src/podkop/tabs/updates/initController.ts:966"
]
},
{
"call": "Via country.is",
"key": "Via country.is",
+135 -133
View File
@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: PODKOP PLUS\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-10 20:17+0300\n"
"PO-Revision-Date: 2026-07-10 20:17+0300\n"
"POT-Creation-Date: 2026-07-10 23:05+0300\n"
"PO-Revision-Date: 2026-07-10 23:05+0300\n"
"Last-Translator: ushan0v <ushanov2204@gmail.com>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
@@ -63,7 +63,7 @@ msgstr ""
msgid "Action"
msgstr ""
#: src/podkop/tabs/monitoring/initController.ts:663
#: src/podkop/tabs/monitoring/initController.ts:667
#: src/podkop/tabs/monitoring/render.ts:20
msgid "Active"
msgstr ""
@@ -120,7 +120,7 @@ msgstr ""
msgid "Advertise routes"
msgstr ""
#: src/podkop/tabs/monitoring/initController.ts:622
#: src/podkop/tabs/monitoring/initController.ts:626
#: src/podkop/tabs/monitoring/render.ts:39
msgid "All"
msgstr ""
@@ -193,6 +193,10 @@ msgstr ""
msgid "Auto-generate HWID"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:495
msgid "Automatic component update checks"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/section.js:1969
msgid "Automatic User-Agent selection"
msgstr ""
@@ -201,6 +205,10 @@ msgstr ""
msgid "Automatically add text to the name of each server from this subscription for convenient filtering."
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:496
msgid "Automatically check installed components for new versions"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/section.js:2536
#: src/podkop/tabs/dashboard/initController.ts:1155
msgid "Automatically select the fastest node in the current level"
@@ -296,11 +304,11 @@ msgstr ""
msgid "ByeDPI strategy cannot be empty"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:573
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:617
msgid "Cache File Path"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:587
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:631
msgid "Cache file path cannot be empty"
msgstr ""
@@ -341,7 +349,7 @@ msgstr ""
msgid "Check timeout after which the server is considered dead"
msgstr ""
#: src/podkop/tabs/updates/initController.ts:709
#: src/podkop/tabs/updates/initController.ts:696
msgid "Check update"
msgstr ""
@@ -407,8 +415,8 @@ msgstr ""
#: src/partials/modal/renderModal.ts:263
#: src/podkop/tabs/dashboard/initController.ts:1027
#: src/podkop/tabs/dashboard/initController.ts:1283
#: src/podkop/tabs/monitoring/initController.ts:828
#: src/podkop/tabs/monitoring/initController.ts:878
#: src/podkop/tabs/monitoring/initController.ts:832
#: src/podkop/tabs/monitoring/initController.ts:882
msgid "Close"
msgstr ""
@@ -417,21 +425,29 @@ msgstr ""
msgid "Close all connections"
msgstr ""
#: src/podkop/tabs/monitoring/initController.ts:795
#: src/podkop/tabs/monitoring/initController.ts:796
#: src/podkop/tabs/monitoring/initController.ts:799
#: src/podkop/tabs/monitoring/initController.ts:800
msgid "Close connection"
msgstr ""
#: src/podkop/tabs/monitoring/initController.ts:670
#: src/podkop/tabs/monitoring/initController.ts:674
#: src/podkop/tabs/monitoring/render.ts:29
msgid "Closed"
msgstr ""
#: src/podkop/services/core.service.ts:43
msgid "Component update available"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:504
msgid "Component update check interval"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/podkop.js:476
msgid "Components"
msgstr ""
#: src/podkop/helpers/singBoxVariant.ts:52
#: src/podkop/helpers/singBoxVariant.ts:51
msgid "compressed"
msgstr ""
@@ -443,7 +459,7 @@ msgstr ""
msgid "Conditions"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:560
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:604
msgid "Config File Path"
msgstr ""
@@ -463,11 +479,11 @@ msgstr ""
msgid "Connection URL"
msgstr ""
#: src/podkop/tabs/monitoring/initController.ts:943
#: src/podkop/tabs/monitoring/initController.ts:946
msgid "Connections are unavailable"
msgstr ""
#: src/podkop/tabs/diagnostic/checks/runSectionsCheck.ts:144
#: src/podkop/tabs/diagnostic/checks/runSectionsCheck.ts:145
msgid "Connectivity probe failed"
msgstr ""
@@ -516,7 +532,7 @@ msgstr ""
msgid "Dashboard currently unavailable"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:496
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:540
msgid "Default address for checking server availability and latency. URLTest uses its own address."
msgstr ""
@@ -541,10 +557,6 @@ msgstr ""
msgid "detour must be a non-empty string"
msgstr ""
#: src/podkop/tabs/updates/initController.ts:109
msgid "Dev"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/section.js:7056
msgid "Device filter"
msgstr ""
@@ -674,7 +686,7 @@ msgstr ""
msgid "Domains"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:551
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:595
msgid "Dont Touch My DHCP!"
msgstr ""
@@ -687,27 +699,27 @@ msgstr ""
msgid "Download"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:532
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:576
msgid "Download component packages via the selected section"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:539
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:583
msgid "Download components through"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:531
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:575
msgid "Download components through a section"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:519
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:563
msgid "Download lists through"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:511
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:555
msgid "Download lists through a section"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:512
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:556
msgid "Download remote lists and rule sets via the selected section"
msgstr ""
@@ -723,8 +735,8 @@ msgstr ""
msgid "Download through"
msgstr ""
#: src/podkop/tabs/monitoring/initController.ts:821
#: src/podkop/tabs/monitoring/initController.ts:875
#: src/podkop/tabs/monitoring/initController.ts:825
#: src/podkop/tabs/monitoring/initController.ts:879
msgid "Downloaded"
msgstr ""
@@ -826,11 +838,11 @@ msgstr ""
msgid "Exclude countries"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:625
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:669
msgid "Exclude NTP"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:626
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:670
msgid "Exclude NTP protocol traffic from the tunnel to prevent it from being routed through the proxy or VPN"
msgstr ""
@@ -859,13 +871,13 @@ msgstr ""
msgid "External nfqws2 config files bypass Podkop Plus queue management and explicit validation."
msgstr ""
#: src/podkop/tabs/monitoring/initController.ts:1210
#: src/podkop/tabs/monitoring/initController.ts:1225
#: src/podkop/tabs/monitoring/initController.ts:1213
#: src/podkop/tabs/monitoring/initController.ts:1228
msgid "Failed to close connection"
msgstr ""
#: src/podkop/tabs/monitoring/initController.ts:1244
#: src/podkop/tabs/monitoring/initController.ts:1257
#: src/podkop/tabs/monitoring/initController.ts:1247
#: src/podkop/tabs/monitoring/initController.ts:1260
msgid "Failed to close connections"
msgstr ""
@@ -876,10 +888,10 @@ msgstr ""
#: src/podkop/methods/shell/index.ts:170
#: src/podkop/methods/shell/index.ts:177
#: src/podkop/tabs/updates/initController.ts:466
#: src/podkop/tabs/updates/initController.ts:467
#: src/podkop/tabs/updates/initController.ts:527
#: src/podkop/tabs/updates/initController.ts:677
#: src/podkop/tabs/updates/initController.ts:464
#: src/podkop/tabs/updates/initController.ts:465
#: src/podkop/tabs/updates/initController.ts:525
#: src/podkop/tabs/updates/initController.ts:675
msgid "Failed to execute"
msgstr ""
@@ -1016,8 +1028,8 @@ msgstr ""
msgid "Higher-priority DNS Check"
msgstr ""
#: src/podkop/tabs/monitoring/initController.ts:811
#: src/podkop/tabs/monitoring/initController.ts:871
#: src/podkop/tabs/monitoring/initController.ts:815
#: src/podkop/tabs/monitoring/initController.ts:875
msgid "Host"
msgstr ""
@@ -1098,30 +1110,16 @@ msgstr ""
msgid "Information"
msgstr ""
#: src/podkop/tabs/updates/initController.ts:123
#: src/podkop/tabs/updates/initController.ts:822
#: src/podkop/tabs/updates/initController.ts:853
#: src/podkop/tabs/updates/initController.ts:884
#: src/podkop/tabs/updates/initController.ts:710
msgid "Install"
msgstr ""
#: src/podkop/tabs/updates/initController.ts:762
msgid "Install extended"
#: src/podkop/tabs/updates/initController.ts:1111
msgid "Install another build:"
msgstr ""
#: src/podkop/tabs/updates/initController.ts:772
msgid "Install extended compressed"
msgstr ""
#: src/podkop/tabs/updates/initController.ts:742
msgid "Install stable"
msgstr ""
#: src/podkop/tabs/updates/initController.ts:752
msgid "Install tiny"
msgstr ""
#: src/podkop/tabs/updates/initController.ts:249
#: src/podkop/tabs/updates/initController.ts:247
#: src/podkop/tabs/updates/initController.ts:969
msgid "Installed version is newer than release"
msgstr ""
@@ -1169,8 +1167,8 @@ msgid "Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com or dns.ex
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/section.js:4405
#: src/validators/validateDomain.ts:71
#: src/validators/validateDomain.ts:77
#: src/validators/validateDomain.ts:67
#: src/validators/validateDomain.ts:73
msgid "Invalid domain address"
msgstr ""
@@ -1469,29 +1467,22 @@ msgstr ""
msgid "Keyword must not contain spaces or commas"
msgstr ""
#: src/podkop/methods/shell/index.ts:463
#: src/podkop/methods/shell/index.ts:484
#: src/podkop/methods/shell/index.ts:462
#: src/podkop/methods/shell/index.ts:483
msgid "Latency test failed"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:495
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:539
msgid "Latency test URL"
msgstr ""
#: src/podkop/tabs/updates/initController.ts:102
msgid "Latest"
msgstr ""
#: src/podkop/tabs/updates/initController.ts:941
msgid "Latest release"
msgstr ""
#: src/podkop/tabs/updates/initController.ts:252
#: src/podkop/tabs/updates/initController.ts:250
#: src/podkop/tabs/updates/initController.ts:967
msgid "Latest version is installed"
msgstr ""
#: src/podkop/tabs/updates/initController.ts:940
msgid "Latest: %s"
#: src/podkop/tabs/updates/initController.ts:969
msgid "Latest version:"
msgstr ""
#: src/podkop/tabs/dashboard/initController.ts:1100
@@ -1526,12 +1517,18 @@ msgstr ""
msgid "Loading client link..."
msgstr ""
#: src/podkop/tabs/monitoring/initController.ts:933
#: src/podkop/tabs/monitoring/initController.ts:936
#: src/podkop/tabs/monitoring/render.ts:88
msgid "Loading connections"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:609
#: src/podkop/tabs/updates/initController.ts:879
#: src/podkop/tabs/updates/initController.ts:892
#: src/podkop/tabs/updates/initController.ts:905
msgid "Loading..."
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:653
msgid "Log Level"
msgstr ""
@@ -1602,6 +1599,10 @@ msgstr ""
msgid "Network Interface"
msgstr ""
#: src/podkop/services/core.service.ts:38
msgid "New version %s is available for %s"
msgstr ""
#: src/podkop/tabs/diagnostic/checks/runZapret2Check.ts:100
#: src/podkop/tabs/diagnostic/checks/runZapretCheck.ts:97
msgid "NFQUEUE range is available"
@@ -1635,11 +1636,11 @@ msgstr ""
msgid "No"
msgstr ""
#: src/podkop/tabs/monitoring/initController.ts:957
#: src/podkop/tabs/monitoring/initController.ts:960
msgid "No active connections"
msgstr ""
#: src/podkop/tabs/monitoring/initController.ts:958
#: src/podkop/tabs/monitoring/initController.ts:961
msgid "No closed connections"
msgstr ""
@@ -1701,11 +1702,11 @@ msgstr ""
msgid "Not implement yet"
msgstr ""
#: src/podkop/helpers/singBoxVariant.ts:30
#: src/podkop/helpers/singBoxVariant.ts:38
#: src/podkop/tabs/updates/initController.ts:804
#: src/podkop/tabs/updates/initController.ts:835
#: src/podkop/tabs/updates/initController.ts:866
#: src/podkop/helpers/singBoxVariant.ts:29
#: src/podkop/helpers/singBoxVariant.ts:37
#: src/podkop/tabs/updates/initController.ts:882
#: src/podkop/tabs/updates/initController.ts:895
#: src/podkop/tabs/updates/initController.ts:908
msgid "Not installed"
msgstr ""
@@ -1718,9 +1719,9 @@ msgid "Not required for this listen address"
msgstr ""
#: src/podkop/tabs/diagnostic/checks/runSectionsCheck.ts:90
#: src/podkop/tabs/diagnostic/checks/runSectionsCheck.ts:116
#: src/podkop/tabs/diagnostic/checks/runSectionsCheck.ts:122
#: src/podkop/tabs/diagnostic/checks/runSectionsCheck.ts:150
#: src/podkop/tabs/diagnostic/checks/runSectionsCheck.ts:117
#: src/podkop/tabs/diagnostic/checks/runSectionsCheck.ts:123
#: src/podkop/tabs/diagnostic/checks/runSectionsCheck.ts:151
msgid "Not responding"
msgstr ""
@@ -1762,6 +1763,11 @@ msgstr ""
msgid "Only the specified servers."
msgstr ""
#: src/podkop/tabs/updates/initController.ts:960
#: src/podkop/tabs/updates/initController.ts:982
msgid "Open"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/section.js:3079
msgid "Open priority settings and enter a display name"
msgstr ""
@@ -1771,8 +1777,8 @@ msgid "Open Project Page"
msgstr ""
#: src/helpers/withTimeout.ts:7
#: src/podkop/methods/shell/index.ts:433
#: src/podkop/methods/shell/index.ts:521
#: src/podkop/methods/shell/index.ts:432
#: src/podkop/methods/shell/index.ts:520
msgid "Operation timed out"
msgstr ""
@@ -1794,10 +1800,6 @@ msgstr ""
msgid "outbounds must be a non-empty array of strings"
msgstr ""
#: src/podkop/tabs/updates/initController.ts:106
msgid "Outdated"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:307
msgid "Output Network Interface"
msgstr ""
@@ -1818,19 +1820,19 @@ msgstr ""
msgid "Path cannot be empty"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:591
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:635
msgid "Path must be absolute (start with /)"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:600
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:644
msgid "Path must contain at least one directory (like /tmp/cache.db)"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:595
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:639
msgid "Path must end with cache.db"
msgstr ""
#: src/podkop/tabs/monitoring/initController.ts:687
#: src/podkop/tabs/monitoring/initController.ts:691
#: src/podkop/tabs/monitoring/render.ts:70
#: src/podkop/tabs/monitoring/render.ts:71
msgid "Pause updates"
@@ -1849,7 +1851,7 @@ msgstr ""
msgid "Pin on dashboard"
msgstr ""
#: src/podkop/services/core.service.ts:22
#: src/podkop/services/core.service.ts:52
msgid "Podkop Plus Error"
msgstr ""
@@ -1869,7 +1871,7 @@ msgstr ""
msgid "Podkop Plus manages the nfqws2 process lifecycle itself, so daemon mode is not allowed here."
msgstr ""
#: src/podkop/tabs/monitoring/initController.ts:921
#: src/podkop/tabs/monitoring/initController.ts:925
msgid "Podkop Plus service is stopped. Start the service to display connections."
msgstr ""
@@ -1881,7 +1883,7 @@ msgstr ""
msgid "Podkop Plus Settings"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:552
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:596
msgid "Podkop Plus will not modify your DHCP configuration"
msgstr ""
@@ -2045,9 +2047,7 @@ msgstr ""
msgid "Regular expression must not contain spaces or commas"
msgstr ""
#: src/podkop/tabs/updates/initController.ts:813
#: src/podkop/tabs/updates/initController.ts:844
#: src/podkop/tabs/updates/initController.ts:875
#: src/podkop/tabs/updates/initController.ts:755
msgid "Remove"
msgstr ""
@@ -2095,12 +2095,12 @@ msgstr ""
msgid "Restart Podkop Plus"
msgstr ""
#: src/podkop/tabs/monitoring/initController.ts:687
#: src/podkop/tabs/monitoring/initController.ts:691
msgid "Resume updates"
msgstr ""
#: src/podkop/tabs/monitoring/initController.ts:815
#: src/podkop/tabs/monitoring/initController.ts:873
#: src/podkop/tabs/monitoring/initController.ts:819
#: src/podkop/tabs/monitoring/initController.ts:877
msgid "Route"
msgstr ""
@@ -2246,11 +2246,11 @@ msgstr ""
msgid "Select or enter DNS server addresses. The first available server has priority."
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:574
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:618
msgid "Select or enter path for sing-box cache file. Change this ONLY if you know what you are doing"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:561
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:605
msgid "Select path for sing-box config file. Change this ONLY if you know what you are doing"
msgstr ""
@@ -2259,7 +2259,7 @@ msgstr ""
msgid "Select the fastest node"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:610
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:654
msgid "Select the log level for sing-box"
msgstr ""
@@ -2338,8 +2338,8 @@ msgstr ""
msgid "Servers"
msgstr ""
#: src/podkop/methods/shell/index.ts:384
#: src/podkop/methods/shell/index.ts:405
#: src/podkop/methods/shell/index.ts:383
#: src/podkop/methods/shell/index.ts:404
#: src/podkop/tabs/diagnostic/initController.ts:594
msgid "Service action failed"
msgstr ""
@@ -2411,8 +2411,8 @@ msgstr ""
msgid "Sorts servers in this section by lowest latency in the dashboard."
msgstr ""
#: src/podkop/tabs/monitoring/initController.ts:827
#: src/podkop/tabs/monitoring/initController.ts:877
#: src/podkop/tabs/monitoring/initController.ts:831
#: src/podkop/tabs/monitoring/initController.ts:881
msgid "Source"
msgstr ""
@@ -2497,8 +2497,8 @@ msgstr ""
msgid "Subscription info:"
msgstr ""
#: src/podkop/methods/shell/index.ts:717
#: src/podkop/methods/shell/index.ts:737
#: src/podkop/methods/shell/index.ts:720
#: src/podkop/methods/shell/index.ts:740
msgid "Subscription update failed"
msgstr ""
@@ -2636,8 +2636,8 @@ msgstr ""
msgid "This option requires a value."
msgstr ""
#: src/podkop/tabs/monitoring/initController.ts:818
#: src/podkop/tabs/monitoring/initController.ts:874
#: src/podkop/tabs/monitoring/initController.ts:822
#: src/podkop/tabs/monitoring/initController.ts:878
msgid "Time"
msgstr ""
@@ -2649,7 +2649,7 @@ msgstr ""
msgid "Time skew tolerance"
msgstr ""
#: src/podkop/helpers/singBoxVariant.ts:54
#: src/podkop/helpers/singBoxVariant.ts:53
msgid "tiny"
msgstr ""
@@ -2712,8 +2712,8 @@ msgid "TTL value cannot be empty"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/server.js:1627
#: src/podkop/tabs/monitoring/initController.ts:812
#: src/podkop/tabs/monitoring/initController.ts:872
#: src/podkop/tabs/monitoring/initController.ts:816
#: src/podkop/tabs/monitoring/initController.ts:876
msgid "Type"
msgstr ""
@@ -2777,7 +2777,7 @@ msgstr ""
#: src/podkop/services/systemInfo.service.ts:22
#: src/podkop/services/systemInfo.service.ts:25
#: src/podkop/services/systemInfo.service.ts:26
#: src/podkop/tabs/updates/initController.ts:287
#: src/podkop/tabs/updates/initController.ts:285
#: src/podkop/tabs/diagnostic/checks/runInboundsCheck.ts:25
msgid "unknown"
msgstr ""
@@ -2812,14 +2812,18 @@ msgstr ""
msgid "Unsupported security mode for this protocol"
msgstr ""
#: src/podkop/tabs/updates/initController.ts:120
#: src/podkop/tabs/updates/initController.ts:710
msgid "Update"
msgstr ""
#: src/podkop/tabs/updates/initController.ts:245
#: src/podkop/tabs/updates/initController.ts:243
msgid "Update is available"
msgstr ""
#: src/podkop/tabs/updates/initController.ts:946
msgid "Update is available:"
msgstr ""
#: src/podkop/tabs/dashboard/partials/renderSections.ts:219
#: src/podkop/tabs/dashboard/partials/renderSections.ts:232
#: src/podkop/tabs/dashboard/partials/renderSections.ts:233
@@ -2835,8 +2839,8 @@ msgstr ""
msgid "Uplink"
msgstr ""
#: src/podkop/tabs/monitoring/initController.ts:824
#: src/podkop/tabs/monitoring/initController.ts:876
#: src/podkop/tabs/monitoring/initController.ts:828
#: src/podkop/tabs/monitoring/initController.ts:880
msgid "Uploaded"
msgstr ""
@@ -2940,6 +2944,8 @@ msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:460
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:482
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:489
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:505
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:533
msgid "Use sing-box duration format like 1d, 12h or 30m"
msgstr ""
@@ -2963,8 +2969,8 @@ msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/section.js:4453
#: src/validators/validateDns.ts:21
#: src/validators/validateDns.ts:25
#: src/validators/validateDomain.ts:61
#: src/validators/validateDomain.ts:80
#: src/validators/validateDomain.ts:57
#: src/validators/validateDomain.ts:76
#: src/validators/validateHttpProxyUrl.ts:97
#: src/validators/validateHysteriaUrl.ts:162
#: src/validators/validateIp.ts:25
@@ -2996,10 +3002,6 @@ msgstr ""
msgid "Value must not contain control characters"
msgstr ""
#: src/podkop/tabs/updates/initController.ts:966
msgid "Version"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/section.js:1663
msgid "Via country.is"
msgstr ""
+30 -30
View File
@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: PODKOP PLUS\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-10 23:17+0300\n"
"PO-Revision-Date: 2026-07-10 23:17+0300\n"
"POT-Creation-Date: 2026-07-10 02:05+0300\n"
"PO-Revision-Date: 2026-07-10 02:05+0300\n"
"Last-Translator: ushan0v\n"
"Language-Team: none\n"
"Language: ru\n"
@@ -143,12 +143,18 @@ msgstr "Автообновление"
msgid "Auto-generate HWID"
msgstr "Авто-генерация HWID"
msgid "Automatic component update checks"
msgstr "Автоматическая проверка обновлений компонентов"
msgid "Automatic User-Agent selection"
msgstr "Авто-подбор User-Agent"
msgid "Automatically add text to the name of each server from this subscription for convenient filtering."
msgstr "Автоматически добавлять текст к имени каждого сервера из этой подписки для удобной фильтрации."
msgid "Automatically check installed components for new versions"
msgstr "Автоматически проверять наличие новых версий установленных компонентов"
msgid "Automatically select the fastest node in the current level"
msgstr "Автовыбор самого быстрого узла уровня"
@@ -290,6 +296,12 @@ msgstr "Закрыть подключение"
msgid "Closed"
msgstr "Закрытые"
msgid "Component update available"
msgstr "Доступно обновление компонента"
msgid "Component update check interval"
msgstr "Период проверки обновлений"
msgid "Components"
msgstr "Компоненты"
@@ -371,9 +383,6 @@ msgstr "Определять страну сервера"
msgid "detour must be a non-empty string"
msgstr "detour должен быть непустой строкой"
msgid "Dev"
msgstr "Dev"
msgid "Device filter"
msgstr "Фильтр по устройствам"
@@ -764,17 +773,8 @@ msgstr "Информация"
msgid "Install"
msgstr "Установить"
msgid "Install extended"
msgstr "Установить extended"
msgid "Install extended compressed"
msgstr "Установить extended compressed"
msgid "Install stable"
msgstr "Установить stable"
msgid "Install tiny"
msgstr "Установить tiny"
msgid "Install another build:"
msgstr "Установить другую сборку:"
msgid "Installed version is newer than release"
msgstr "Установлена версия новее релиза"
@@ -1031,17 +1031,11 @@ msgstr "Проверка задержки не удалась"
msgid "Latency test URL"
msgstr "URL для проверки задержки"
msgid "Latest"
msgstr "Последняя"
msgid "Latest release"
msgstr "Последний релиз"
msgid "Latest version is installed"
msgstr "Установлена последняя версия"
msgid "Latest: %s"
msgstr "Последняя: %s"
msgid "Latest version:"
msgstr "Последняя версия:"
msgid "Level"
msgstr "Уровень"
@@ -1070,6 +1064,9 @@ msgstr "Загрузка клиентской ссылки..."
msgid "Loading connections"
msgstr "Загрузка подключений"
msgid "Loading..."
msgstr "Загрузка..."
msgid "Log Level"
msgstr "Уровень логов"
@@ -1121,6 +1118,9 @@ msgstr "Имя, которое отображается в списке уров
msgid "Network Interface"
msgstr "Сетевой интерфейс"
msgid "New version %s is available for %s"
msgstr "Доступна новая версия %s для %s"
msgid "NFQUEUE range is available"
msgstr "Диапазон NFQUEUE свободен"
@@ -1223,6 +1223,9 @@ msgstr "Только серверы, имена которых соответс
msgid "Only the specified servers."
msgstr "Только указанные серверы."
msgid "Open"
msgstr ""
msgid "Open priority settings and enter a display name"
msgstr "Откройте настройки приоритета и укажите отображаемое имя"
@@ -1244,9 +1247,6 @@ msgstr "Необязательные значения здесь должны з
msgid "outbounds must be a non-empty array of strings"
msgstr "outbounds должен быть непустым массивом строк"
msgid "Outdated"
msgstr "Устаревшая"
msgid "Output Network Interface"
msgstr "Выходной сетевой интерфейс"
@@ -1967,6 +1967,9 @@ msgstr "Обновить"
msgid "Update is available"
msgstr "Доступно обновление"
msgid "Update is available:"
msgstr "Доступна новая версия:"
msgid "Update subscriptions"
msgstr "Обновить подписки"
@@ -2069,9 +2072,6 @@ msgstr "Значение не может быть пустым"
msgid "Value must not contain control characters"
msgstr "Значение не должно содержать управляющие символы"
msgid "Version"
msgstr "Версия"
msgid "Via country.is"
msgstr "Через сервис country.is"
@@ -334,10 +334,9 @@ export const PodkopShellMethods = {
'/etc/init.d/podkop-plus',
),
globalCheck: async (masked = true) =>
callBaseMethod<unknown>(
Podkop.AvailableMethods.GLOBAL_CHECK,
[masked ? 'masked' : 'raw'],
),
callBaseMethod<unknown>(Podkop.AvailableMethods.GLOBAL_CHECK, [
masked ? 'masked' : 'raw',
]),
showSingBoxConfig: async (masked = true) =>
callBaseMethod<unknown>(Podkop.AvailableMethods.SHOW_SING_BOX_CONFIG, [
masked ? 'masked' : 'raw',
@@ -582,6 +581,10 @@ export const PodkopShellMethods = {
data: parsedResponse,
} as Podkop.MethodSuccessResponse<Podkop.ComponentActionResult>;
},
componentUpdateCheckCache: async () =>
callBaseMethod<Podkop.ComponentUpdateCheckCache>(
Podkop.AvailableMethods.COMPONENT_UPDATE_CHECK_CACHE,
),
waitComponentActionJob: async (
jobId: string,
component: Podkop.ComponentName,
@@ -2,7 +2,11 @@ import { TabServiceInstance } from './tab.service';
import { store } from './store.service';
import { logger } from './logger.service';
import { PodkopLogWatcher } from './podkopLogWatcher.service';
import { LogNotificationDeduper } from './logNotificationDeduper.service';
import {
getPodkopLogNotification,
LogNotificationDeduper,
PodkopLogNotification,
} from './logNotificationDeduper.service';
import { PodkopShellMethods } from '../methods';
import {
registerRuntimeStateResumeRefresh,
@@ -17,10 +21,36 @@ type CoreServiceOptions = {
const LOG_WATCHER_INTERVAL_MS = 10000;
const LOG_WATCHER_START_DELAY_MS = 5000;
function showLogErrorNotification(line: string) {
function componentDisplayName(component: string) {
const names: Record<string, string> = {
podkop: 'Podkop Plus',
sing_box: 'sing-box',
zapret: 'Zapret',
zapret2: 'Zapret2',
byedpi: 'ByeDPI',
};
return names[component] || component;
}
function showLogNotification(notification: PodkopLogNotification) {
if (notification.kind === 'component-update') {
const message = _('New version %s is available for %s')
.replace('%s', notification.version)
.replace('%s', componentDisplayName(notification.component));
ui.addNotification(
_('Component update available'),
E('div', {}, message),
'warning',
'pdk-component-update-notification',
);
return;
}
ui.addNotification(
_('Podkop Plus Error'),
E('div', {}, line),
E('div', {}, notification.line),
'error',
'pdk-log-error-notification',
);
@@ -54,7 +84,10 @@ export function coreService(options: CoreServiceOptions = {}) {
intervalMs: LOG_WATCHER_INTERVAL_MS,
onNewLog: (line) => {
if (logNotificationDeduper.shouldNotify(line)) {
showLogErrorNotification(line);
const notification = getPodkopLogNotification(line);
if (notification) {
showLogNotification(notification);
}
}
},
},
@@ -70,12 +103,9 @@ export function coreService(options: CoreServiceOptions = {}) {
watcher.start();
};
const scheduleStartWatcher = () =>
window.setTimeout(
() => {
void startWatcher();
},
options.logWatcherStartDelayMs ?? LOG_WATCHER_START_DELAY_MS,
);
window.setTimeout(() => {
void startWatcher();
}, options.logWatcherStartDelayMs ?? LOG_WATCHER_START_DELAY_MS);
if (typeof window !== 'undefined') {
scheduleStartWatcher();
@@ -2,6 +2,15 @@ const LOG_NOTIFICATION_STORAGE_KEY =
'podkop-plus:shown-log-error-notifications:v1';
const MAX_STORED_LOG_NOTIFICATIONS = 500;
export type PodkopLogNotification =
| { kind: 'error'; line: string }
| {
kind: 'component-update';
line: string;
component: string;
version: string;
};
function getSessionStorage(): Storage | null {
if (typeof window === 'undefined') {
return null;
@@ -52,6 +61,29 @@ export function isErrorLogLine(line: string) {
return lower.includes('[error]') || lower.includes('[fatal]');
}
export function getPodkopLogNotification(
line: string,
): PodkopLogNotification | null {
if (isErrorLogLine(line)) {
return { kind: 'error', line };
}
const update = line.match(
/\[component-update\]\s+(podkop|sing_box|zapret|zapret2|byedpi)\s+(\S+)/i,
);
if (!update) {
return null;
}
return {
kind: 'component-update',
line,
component: update[1].toLowerCase(),
version: update[2],
};
}
export function getLogNotificationKey(line: string) {
return line.trim();
}
@@ -66,7 +98,7 @@ export class LogNotificationDeduper {
}
shouldNotify(line: string) {
if (!isErrorLogLine(line)) {
if (!getPodkopLogNotification(line)) {
return false;
}
@@ -2,6 +2,7 @@ import { describe, expect, it } from 'vitest';
import {
getLogNotificationKey,
getPodkopLogNotification,
isErrorLogLine,
LogNotificationDeduper,
} from '../logNotificationDeduper.service';
@@ -35,10 +36,21 @@ class MemoryStorage implements Storage {
}
describe('LogNotificationDeduper', () => {
it('only accepts error and fatal log lines', () => {
it('accepts error, fatal, and component update log lines', () => {
expect(isErrorLogLine('podkop-plus: [info] ok')).toBe(false);
expect(isErrorLogLine('podkop-plus: [error] failed')).toBe(true);
expect(isErrorLogLine('podkop-plus: [fatal] failed')).toBe(true);
expect(
getPodkopLogNotification(
'podkop-plus: [info] [component-update] zapret2 v1.2.3',
),
).toEqual({
kind: 'component-update',
line: 'podkop-plus: [info] [component-update] zapret2 v1.2.3',
component: 'zapret2',
version: 'v1.2.3',
});
expect(getPodkopLogNotification('podkop-plus: [info] ok')).toBeNull();
});
it('dedupes already shown log lines through session storage', () => {
@@ -50,6 +62,9 @@ describe('LogNotificationDeduper', () => {
expect(first.shouldNotify('podkop-plus: [error] another failure')).toBe(
true,
);
expect(
first.shouldNotify('podkop-plus: [info] [component-update] podkop 1.2.3'),
).toBe(true);
const afterReload = new LogNotificationDeduper(storage);
@@ -13,11 +13,17 @@ export function shouldPreserveCompletedCheckResultOnNextMount({
export function shouldResetCheckResultsOnMount({
anyActionLoading,
preserveCheckResultsOnNextMount,
persistentCacheEnabled = false,
}: {
anyActionLoading: boolean;
preserveCheckResultsOnNextMount: boolean;
persistentCacheEnabled?: boolean;
}) {
return !anyActionLoading && !preserveCheckResultsOnNextMount;
return (
!persistentCacheEnabled &&
!anyActionLoading &&
!preserveCheckResultsOnNextMount
);
}
export function shouldRefreshComponentStateBeforeRender(
@@ -56,14 +56,12 @@ interface ComponentActionButton {
}
interface ComponentCard {
component: Podkop.ComponentName;
column: 0 | 1;
title: string;
version: string;
latestVersion?: string;
releaseUrl?: string;
tag?: {
label: string;
kind: 'neutral' | 'success' | 'warning';
};
actions: ComponentActionButton[];
}
@@ -91,38 +89,12 @@ function isNotInstalled(version: string | undefined) {
return !version || version === 'not installed';
}
function getCheckTag(component: Podkop.ComponentName): ComponentCard['tag'] {
const status = store.get().updatesChecks[component].status;
if (!status) {
return undefined;
}
if (status === 'latest') {
return { label: _('Latest'), kind: 'success' };
}
if (status === 'outdated') {
return { label: _('Outdated'), kind: 'warning' };
}
return { label: _('Dev'), kind: 'neutral' };
}
function shouldShowInstallAfterCheck(component: Podkop.ComponentName) {
const status = store.get().updatesChecks[component].status;
return status === 'outdated' || status === 'dev';
}
function getInstallActionText(component: Podkop.ComponentName) {
if (shouldShowInstallAfterCheck(component)) {
return _('Update');
}
return _('Install');
}
function getLatestVersion(component: Podkop.ComponentName) {
const checkResult = store.get().updatesChecks[component];
@@ -214,6 +186,32 @@ function resetCheckResult(component: Podkop.ComponentName) {
setCheckResult(component, null, '');
}
function applyCachedCheckResults(results: Podkop.ComponentActionResult[]) {
results.forEach((result) => {
const status = result.status || null;
if (status === 'latest' || status === 'outdated' || status === 'dev') {
setCheckResult(
result.component,
status,
result.latest_version || '',
result.release_url || '',
);
}
});
}
async function loadComponentUpdateCheckCache() {
const response = await PodkopShellMethods.componentUpdateCheckCache();
return response.success
? response.data
: ({
enabled: false,
results: [],
} satisfies Podkop.ComponentUpdateCheckCache);
}
function getErrorMessage(error: unknown, fallback: string) {
return error instanceof Error && error.message ? error.message : fallback;
}
@@ -689,23 +687,12 @@ async function handleComponentAction(button: ComponentActionButton) {
}
}
function getPrimaryUpdateAction(
function getCheckAction(
component: Podkop.ComponentName,
checkKey: UpdatesActionKey,
installKey: UpdatesActionKey,
key: UpdatesActionKey,
): ComponentActionButton {
if (shouldShowInstallAfterCheck(component)) {
return {
key: installKey,
text: getInstallActionText(component),
icon: renderRotateCcwIcon24,
component,
action: 'install',
};
}
return {
key: checkKey,
key,
text: _('Check update'),
icon: renderSearchIcon24,
component,
@@ -713,6 +700,66 @@ function getPrimaryUpdateAction(
};
}
function getInstallAction(
component: Podkop.ComponentName,
key: UpdatesActionKey,
installed: boolean,
): ComponentActionButton {
return {
key,
text: installed ? _('Update') : _('Install'),
icon: installed ? renderRotateCcwIcon24 : renderDownloadIcon24,
component,
action: 'install',
};
}
function getInstalledUpdateActions(
component: Podkop.ComponentName,
checkKey: UpdatesActionKey,
installKey: UpdatesActionKey,
installed = true,
) {
if (!installed) {
return [];
}
const actions = [getCheckAction(component, checkKey)];
if (shouldShowInstallAfterCheck(component)) {
actions.push(getInstallAction(component, installKey, true));
}
return actions;
}
function getOptionalComponentActions({
component,
installed,
checkKey,
installKey,
removeKey,
}: {
component: 'zapret' | 'zapret2' | 'byedpi';
installed: boolean;
checkKey: UpdatesActionKey;
installKey: UpdatesActionKey;
removeKey: UpdatesActionKey;
}) {
if (!installed) {
return [getInstallAction(component, installKey, false)];
}
return [
...getInstalledUpdateActions(component, checkKey, installKey),
{
key: removeKey,
text: _('Remove'),
icon: renderXIcon24,
component,
action: 'remove' as const,
},
];
}
function getComponentCards(): ComponentCard[] {
const systemInfo = normalizeSingBoxVariantFields(
store.get().diagnosticsSystemInfo,
@@ -732,264 +779,368 @@ function getComponentCards(): ComponentCard[] {
Boolean(systemInfo.sing_box_extended) &&
Boolean(systemInfo.sing_box_compressed);
const singBoxTiny = Boolean(systemInfo.sing_box_tiny);
const singBoxActions: ComponentActionButton[] = [
getPrimaryUpdateAction('sing_box', 'singBoxCheck', 'singBoxInstall'),
];
const podkopActions = getInstalledUpdateActions(
'podkop',
'podkopCheck',
'podkopInstall',
);
const singBoxActions = getInstalledUpdateActions(
'sing_box',
'singBoxCheck',
'singBoxInstall',
singBoxInstalled,
);
// Add Sing-box variant actions (only show names, no 'Install' prefix)
if (!singBoxStable) {
singBoxActions.push({
key: 'singBoxInstallStable',
text: _('Install stable'),
text: 'Stable',
icon: renderDownloadIcon24,
component: 'sing_box',
action: 'install_stable',
});
}
if (!singBoxTiny) {
singBoxActions.push({
key: 'singBoxInstallTiny',
text: _('Install tiny'),
text: 'Tiny',
icon: renderDownloadIcon24,
component: 'sing_box',
action: 'install_tiny',
});
}
if (!singBoxExtended) {
singBoxActions.push({
key: 'singBoxInstallExtended',
text: _('Install extended'),
text: 'Extended',
icon: renderDownloadIcon24,
component: 'sing_box',
action: 'install_extended',
});
}
if (!singBoxExtendedCompressed) {
singBoxActions.push({
key: 'singBoxInstallExtendedCompressed',
text: _('Install extended compressed'),
text: 'Extended compressed',
icon: renderDownloadIcon24,
component: 'sing_box',
action: 'install_extended_compressed',
});
}
const zapretActions = getOptionalComponentActions({
component: 'zapret',
installed: zapretInstalled,
checkKey: 'zapretCheck',
installKey: 'zapretInstall',
removeKey: 'zapretRemove',
});
const zapret2Actions = getOptionalComponentActions({
component: 'zapret2',
installed: zapret2Installed,
checkKey: 'zapret2Check',
installKey: 'zapret2Install',
removeKey: 'zapret2Remove',
});
const byedpiActions = getOptionalComponentActions({
component: 'byedpi',
installed: byedpiInstalled,
checkKey: 'byedpiCheck',
installKey: 'byedpiInstall',
removeKey: 'byedpiRemove',
});
return [
{
component: 'podkop',
column: 0,
title: 'Podkop Plus',
version: normalizeCompiledVersion(systemInfo.podkop_version),
latestVersion: getLatestVersion('podkop'),
releaseUrl: getGitHubReleaseUrl('podkop'),
tag: getCheckTag('podkop'),
actions: [
getPrimaryUpdateAction('podkop', 'podkopCheck', 'podkopInstall'),
],
actions: podkopActions,
},
{
component: 'sing_box',
column: 0,
title: 'Sing-box',
version: formatSingBoxVersion(systemInfo),
latestVersion: getLatestVersion('sing_box'),
releaseUrl: getGitHubReleaseUrl('sing_box'),
tag: getCheckTag('sing_box'),
actions: singBoxActions,
},
{
component: 'zapret',
column: 1,
title: 'Zapret',
version: systemInfoLoading
? 'loading'
? _('Loading...')
: zapretInstalled
? systemInfo.zapret_version
: _('Not installed'),
latestVersion: getLatestVersion('zapret'),
releaseUrl: getGitHubReleaseUrl('zapret'),
tag: zapretInstalled ? getCheckTag('zapret') : undefined,
actions: zapretInstalled
? [
getPrimaryUpdateAction('zapret', 'zapretCheck', 'zapretInstall'),
{
key: 'zapretRemove',
text: _('Remove'),
icon: renderXIcon24,
component: 'zapret',
action: 'remove',
},
]
: [
{
key: 'zapretInstall',
text: _('Install'),
icon: renderDownloadIcon24,
component: 'zapret',
action: 'install',
},
],
actions: zapretActions,
},
{
component: 'zapret2',
column: 1,
title: 'Zapret2',
version: systemInfoLoading
? 'loading'
? _('Loading...')
: zapret2Installed
? systemInfo.zapret2_version
: _('Not installed'),
latestVersion: getLatestVersion('zapret2'),
releaseUrl: getGitHubReleaseUrl('zapret2'),
tag: zapret2Installed ? getCheckTag('zapret2') : undefined,
actions: zapret2Installed
? [
getPrimaryUpdateAction('zapret2', 'zapret2Check', 'zapret2Install'),
{
key: 'zapret2Remove',
text: _('Remove'),
icon: renderXIcon24,
component: 'zapret2',
action: 'remove',
},
]
: [
{
key: 'zapret2Install',
text: _('Install'),
icon: renderDownloadIcon24,
component: 'zapret2',
action: 'install',
},
],
actions: zapret2Actions,
},
{
component: 'byedpi',
column: 1,
title: 'ByeDPI',
version: systemInfoLoading
? 'loading'
? _('Loading...')
: byedpiInstalled
? systemInfo.byedpi_version
: _('Not installed'),
latestVersion: getLatestVersion('byedpi'),
releaseUrl: getGitHubReleaseUrl('byedpi'),
tag: byedpiInstalled ? getCheckTag('byedpi') : undefined,
actions: byedpiInstalled
? [
getPrimaryUpdateAction('byedpi', 'byedpiCheck', 'byedpiInstall'),
{
key: 'byedpiRemove',
text: _('Remove'),
icon: renderXIcon24,
component: 'byedpi',
action: 'remove',
},
]
: [
{
key: 'byedpiInstall',
text: _('Install'),
icon: renderDownloadIcon24,
component: 'byedpi',
action: 'install',
},
],
actions: byedpiActions,
},
];
}
function renderComponentTag(card: ComponentCard) {
if (!card.tag) {
return null;
}
return E(
'span',
{
class: [
'pdk_updates-page__component__tag',
card.tag.kind === 'success'
? 'pdk_updates-page__component__tag--success'
: '',
card.tag.kind === 'warning'
? 'pdk_updates-page__component__tag--warning'
: '',
]
.filter(Boolean)
.join(' '),
},
card.tag.label,
);
}
function renderComponentCard(card: ComponentCard) {
const updatesActions = store.get().updatesActions;
const anyActionLoading = isAnyActionLoading();
const serviceRuntimeActionLoading = isServiceRuntimeActionLoading();
const systemInfoLoading = isSystemInfoLoading();
const tag = renderComponentTag(card);
// 1. Header (displays Title, Current Version, no badges)
const headerChildren: Node[] = [
E('b', { class: 'pdk_updates-page__component__title' }, card.title),
];
const statusChildren: Node[] = [];
if (card.releaseUrl) {
statusChildren.push(
E(
'a',
{
class: 'pdk_updates-page__component__release-link',
href: card.releaseUrl,
target: '_blank',
rel: 'noopener noreferrer',
},
card.latestVersion
? _('Latest: %s').replace('%s', card.latestVersion)
: _('Latest release'),
),
);
}
if (tag) {
statusChildren.push(tag);
}
if (statusChildren.length > 0) {
headerChildren.push(
E(
'div',
{ class: 'pdk_updates-page__component__status' },
statusChildren,
),
);
}
return E('div', { class: 'pdk_updates-page__component' }, [
E('div', { class: 'pdk_updates-page__component__header' }, headerChildren),
E('div', { class: 'pdk_updates-page__component__version' }, [
E(
'span',
{ class: 'pdk_updates-page__component__version__label' },
_('Version'),
),
E(
'span',
{ class: 'pdk_updates-page__component__version__value' },
card.version,
),
]),
E(
'div',
{ class: 'pdk_updates-page__component__actions' },
card.actions.map((action) => {
const loading = updatesActions[action.key].loading;
return renderButton({
text: action.text,
icon: action.icon,
loading,
disabled:
systemInfoLoading ||
serviceRuntimeActionLoading ||
(anyActionLoading && !loading),
onClick: () => void handleComponentAction(action),
});
}),
'span',
{ class: 'pdk_updates-page__component__header-version' },
card.version,
),
]);
];
const header = E(
'div',
{ class: 'pdk_updates-page__component__header' },
headerChildren,
);
// 2. Details (renders status messages for check results)
const detailsChildren: Node[] = [];
const checkResult = store.get().updatesChecks[card.component];
if (checkResult && checkResult.status) {
let labelText = '';
const latestValueNodes: Node[] = [];
if (checkResult.status === 'outdated') {
labelText = _('Update is available:');
const versionToShow =
checkResult.latest_version || card.latestVersion || card.version;
if (checkResult.release_url) {
latestValueNodes.push(
E(
'a',
{
class: 'pdk_updates-page__component__release-version-link',
href: checkResult.release_url,
target: '_blank',
rel: 'noopener noreferrer',
},
versionToShow || _('Open'),
),
);
} else if (versionToShow) {
latestValueNodes.push(document.createTextNode(versionToShow));
}
} else if (checkResult.status === 'latest') {
labelText = _('Latest version is installed');
} else if (checkResult.status === 'dev') {
labelText = `${_('Installed version is newer than release')}. ${_('Latest version:')}`;
const versionToShow = checkResult.latest_version || card.latestVersion;
if (checkResult.release_url) {
latestValueNodes.push(
E(
'a',
{
class: 'pdk_updates-page__component__release-version-link',
href: checkResult.release_url,
target: '_blank',
rel: 'noopener noreferrer',
},
versionToShow || _('Open'),
),
);
} else if (versionToShow) {
latestValueNodes.push(document.createTextNode(versionToShow));
}
}
if (labelText) {
const rowChildren: Node[] = [
E(
'span',
{ class: 'pdk_updates-page__component__info-label' },
labelText,
),
];
if (latestValueNodes.length > 0) {
rowChildren.push(
E(
'span',
{
class:
'pdk_updates-page__component__info-value pdk_updates-page__component__info-value--latest',
},
latestValueNodes,
),
);
}
detailsChildren.push(
E(
'div',
{ class: 'pdk_updates-page__component__info-row' },
rowChildren,
),
);
}
}
const detailsContainer =
detailsChildren.length > 0
? E(
'div',
{ class: 'pdk_updates-page__component__details' },
detailsChildren,
)
: null;
// 3. Actions classification
const primaryActions: ComponentActionButton[] = [];
const dangerActions: ComponentActionButton[] = [];
const variantActions: ComponentActionButton[] = [];
card.actions.forEach((action) => {
if (action.action === 'remove') {
dangerActions.push(action);
} else if (action.action.startsWith('install_')) {
variantActions.push(action);
} else {
primaryActions.push(action);
}
});
const actionElements: Node[] = [];
// Render primary and danger buttons in a main row
const primaryButtons = primaryActions.map((action) => {
const loading = updatesActions[action.key].loading;
const isUpdateOrInstall = action.action === 'install';
return renderButton({
classNames: isUpdateOrInstall ? ['cbi-button-save'] : [],
text: action.text,
icon: action.icon,
loading,
disabled:
systemInfoLoading ||
serviceRuntimeActionLoading ||
(anyActionLoading && !loading),
onClick: () => void handleComponentAction(action),
});
});
const dangerButtons = dangerActions.map((action) => {
const loading = updatesActions[action.key].loading;
return renderButton({
classNames: ['cbi-button-remove'],
text: action.text,
icon: action.icon,
loading,
disabled:
systemInfoLoading ||
serviceRuntimeActionLoading ||
(anyActionLoading && !loading),
onClick: () => void handleComponentAction(action),
});
});
if (primaryButtons.length > 0 || dangerButtons.length > 0) {
actionElements.push(
E('div', { class: 'pdk_updates-page__component__actions-main' }, [
...primaryButtons,
...dangerButtons,
]),
);
}
// Render variant buttons if any
if (variantActions.length > 0) {
const variantButtons = variantActions.map((action) => {
const loading = updatesActions[action.key].loading;
return renderButton({
text: action.text,
icon: action.icon,
loading,
disabled:
systemInfoLoading ||
serviceRuntimeActionLoading ||
(anyActionLoading && !loading),
onClick: () => void handleComponentAction(action),
});
});
actionElements.push(
E('div', { class: 'pdk_updates-page__component__variants' }, [
E(
'div',
{ class: 'pdk_updates-page__component__variants-title' },
_('Install another build:'),
),
E(
'div',
{ class: 'pdk_updates-page__component__variants-buttons' },
variantButtons,
),
]),
);
}
const actionsContainer = E(
'div',
{
class: [
'pdk_updates-page__component__actions',
detailsContainer
? 'pdk_updates-page__component__actions--with-details'
: '',
]
.filter(Boolean)
.join(' '),
},
actionElements,
);
const cardChildren: Node[] = [header];
if (detailsContainer) {
cardChildren.push(detailsContainer);
}
cardChildren.push(actionsContainer);
return E('div', { class: 'pdk_updates-page__component' }, cardChildren);
}
function renderUpdatesComponents() {
@@ -1000,8 +1151,8 @@ function renderUpdatesComponents() {
}
const columns = [[], []] as Node[][];
getComponentCards().forEach((card, index) => {
columns[index % 2].push(renderComponentCard(card));
getComponentCards().forEach((card) => {
columns[card.column].push(renderComponentCard(card));
});
return preserveScrollForPage(() => {
@@ -1047,10 +1198,22 @@ async function onPageMount() {
}
}
const componentUpdateCheckCache = await loadComponentUpdateCheckCache();
if (!updatesMounted || mountId !== updatesMountId) {
return;
}
if (componentUpdateCheckCache.enabled) {
store.reset(['updatesChecks']);
applyCachedCheckResults(componentUpdateCheckCache.results);
}
if (
shouldResetCheckResultsOnMount({
anyActionLoading: isAnyActionLoading(),
preserveCheckResultsOnNextMount,
persistentCacheEnabled: componentUpdateCheckCache.enabled,
})
) {
store.reset(['updatesChecks']);
+84 -62
View File
@@ -18,14 +18,16 @@ export const styles = `
display: flex;
align-items: flex-start;
gap: 10px;
width: 100%;
flex-wrap: wrap;
}
.pdk_updates-page__components-column {
display: flex;
flex: 1 1 0;
flex: 1 1 auto;
flex-direction: column;
gap: 10px;
min-width: 0;
min-width: max-content;
}
@media (max-width: 760px) {
@@ -35,6 +37,7 @@ export const styles = `
.pdk_updates-page__components-column {
width: 100%;
min-width: 0;
}
}
@@ -42,100 +45,119 @@ export const styles = `
border: 2px var(--background-color-low, lightgray) solid;
border-radius: 4px;
padding: 10px;
min-width: 0;
display: flex;
flex-direction: column;
gap: 10px;
background-color: var(--background-color-high, #fff);
min-width: max-content;
}
.pdk_updates-page__component__header {
display: grid;
grid-template-columns: max-content minmax(0, 1fr);
align-items: start;
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 8px;
min-width: 0;
border-bottom: 1px var(--background-color-low, lightgray) solid;
padding-bottom: 8px;
margin-bottom: 2px;
}
.pdk_updates-page__component__title {
color: var(--text-color-high);
line-height: 1.25;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
font-size: 16px;
font-weight: bold;
line-height: 1.2;
}
.pdk_updates-page__component__header-version {
color: var(--text-color-medium, #888);
font-size: 13px;
font-weight: normal;
}
.pdk_updates-page__component__details {
display: flex;
flex-direction: column;
gap: 6px;
}
.pdk_updates-page__component__info-row {
display: flex;
justify-content: flex-start;
align-items: center;
min-height: 24px;
gap: 8px;
white-space: nowrap;
}
.pdk_updates-page__component__status {
.pdk_updates-page__component__info-label {
color: var(--text-color-medium, #888);
font-size: 12px;
}
.pdk_updates-page__component__info-value {
color: var(--text-color-high, #000);
font-weight: 500;
font-size: 13px;
text-align: left;
display: flex;
align-items: center;
justify-content: flex-end;
flex-wrap: wrap;
gap: 6px;
min-width: 0;
}
.pdk_updates-page__component__version {
display: grid;
grid-template-columns: auto 1fr;
grid-column-gap: 6px;
align-items: baseline;
min-width: 0;
}
.pdk_updates-page__component__version__label {
color: var(--text-color-medium);
}
.pdk_updates-page__component__version__value {
min-width: 0;
overflow-wrap: anywhere;
}
.pdk_updates-page__component__tag {
flex: 0 0 auto;
padding: 2px 5px;
border: 1px var(--background-color-high, gray) solid;
border-radius: 4px;
color: var(--text-color-medium, gray);
line-height: 1.2;
.pdk_updates-page__component__info-value--latest {
flex-wrap: wrap;
justify-content: flex-start;
}
.pdk_updates-page__component__tag--success {
border-color: var(--success-color-medium, green);
color: var(--success-color-medium, green);
.pdk_updates-page__component__release-version-link {
color: var(--link-color, #3498db) !important;
text-decoration: underline;
font-weight: bold;
}
.pdk_updates-page__component__tag--warning {
border-color: var(--warn-color-medium, orange);
color: var(--warn-color-medium, orange);
.pdk_updates-page__component__release-version-link:hover {
color: var(--link-color-dark, #2980b9) !important;
}
.pdk_updates-page__component__actions {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
align-content: flex-start;
flex: 0 0 auto;
flex-direction: column;
gap: 10px;
margin-top: auto;
}
.pdk_updates-page__component__actions--with-details {
border-top: 1px var(--background-color-low, lightgray) solid;
padding-top: 10px;
}
.pdk_updates-page__component__actions-main {
display: flex;
justify-content: flex-start;
align-items: center;
flex-wrap: nowrap;
gap: 6px;
min-height: 0;
}
.pdk_updates-page__component__actions > .pdk-partial-button {
margin-left: 0;
align-self: flex-start;
flex: 0 0 auto;
height: auto;
min-height: 0;
width: auto;
.pdk_updates-page__component__variants {
display: flex;
flex-direction: column;
gap: 6px;
margin-top: 4px;
}
.pdk_updates-page__component__release-link {
display: inline-block;
flex: 1 1 auto;
min-width: 0;
overflow-wrap: anywhere;
.pdk_updates-page__component__variants-title {
font-size: 11px;
line-height: 1.2;
text-align: right;
font-weight: bold;
color: var(--text-color-medium, gray);
}
.pdk_updates-page__component__variants-buttons {
display: flex;
flex-wrap: nowrap;
gap: 6px;
}
`;
@@ -41,6 +41,16 @@ describe('updates check result lifecycle', () => {
).toBe(false);
});
it('keeps cached results on every reopen when automatic checks are enabled', () => {
expect(
shouldResetCheckResultsOnMount({
anyActionLoading: false,
preserveCheckResultsOnNextMount: false,
persistentCacheEnabled: true,
}),
).toBe(false);
});
it('requires a fresh state read before rendering cached running component actions', () => {
expect(
shouldRefreshComponentStateBeforeRender({
+6
View File
@@ -88,6 +88,7 @@ export namespace Podkop {
UI_ACTION_ACK = 'ui_action_ack',
COMPONENT_ACTION_ASYNC = 'component_action_async',
COMPONENT_ACTION_STATUS = 'component_action_status',
COMPONENT_UPDATE_CHECK_CACHE = 'component_update_check_cache',
SUBSCRIPTION_UPDATE_ASYNC = 'subscription_update_async',
SUBSCRIPTION_UPDATE_STATUS = 'subscription_update_status',
}
@@ -572,6 +573,11 @@ export namespace Podkop {
exit_code?: number | null;
}
export interface ComponentUpdateCheckCache {
enabled: boolean;
results: ComponentActionResult[];
}
export type ComponentActionStartResult = UiActionStartResult;
export type SubscriptionUpdateStartResult = UiActionStartResult;
@@ -59,9 +59,7 @@ function validAsciiDomain(hostname, requireDot = true) {
if (parts.some((part) => !part || part.length > 63)) {
return false;
}
if (parts.some(
(part) => !/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(part)
)) {
if (parts.some((part) => !/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(part))) {
return false;
}
if (!requireDot) {
@@ -2590,6 +2588,7 @@ var Podkop;
AvailableMethods2["UI_ACTION_ACK"] = "ui_action_ack";
AvailableMethods2["COMPONENT_ACTION_ASYNC"] = "component_action_async";
AvailableMethods2["COMPONENT_ACTION_STATUS"] = "component_action_status";
AvailableMethods2["COMPONENT_UPDATE_CHECK_CACHE"] = "component_update_check_cache";
AvailableMethods2["SUBSCRIPTION_UPDATE_ASYNC"] = "subscription_update_async";
AvailableMethods2["SUBSCRIPTION_UPDATE_STATUS"] = "subscription_update_status";
})(AvailableMethods = Podkop2.AvailableMethods || (Podkop2.AvailableMethods = {}));
@@ -2862,10 +2861,9 @@ var PodkopShellMethods = {
[],
"/etc/init.d/podkop-plus"
),
globalCheck: async (masked = true) => callBaseMethod(
Podkop.AvailableMethods.GLOBAL_CHECK,
[masked ? "masked" : "raw"]
),
globalCheck: async (masked = true) => callBaseMethod(Podkop.AvailableMethods.GLOBAL_CHECK, [
masked ? "masked" : "raw"
]),
showSingBoxConfig: async (masked = true) => callBaseMethod(Podkop.AvailableMethods.SHOW_SING_BOX_CONFIG, [
masked ? "masked" : "raw"
]),
@@ -3054,6 +3052,9 @@ var PodkopShellMethods = {
data: parsedResponse
};
},
componentUpdateCheckCache: async () => callBaseMethod(
Podkop.AvailableMethods.COMPONENT_UPDATE_CHECK_CACHE
),
waitComponentActionJob: async (jobId, component, action, expectedLatestVersion) => {
let selfUpdateVersionMatchedAt = 0;
let lastStatusRefreshAt = 0;
@@ -4804,6 +4805,23 @@ function isErrorLogLine(line) {
const lower = line.toLowerCase();
return lower.includes("[error]") || lower.includes("[fatal]");
}
function getPodkopLogNotification(line) {
if (isErrorLogLine(line)) {
return { kind: "error", line };
}
const update = line.match(
/\[component-update\]\s+(podkop|sing_box|zapret|zapret2|byedpi)\s+(\S+)/i
);
if (!update) {
return null;
}
return {
kind: "component-update",
line,
component: update[1].toLowerCase(),
version: update[2]
};
}
function getLogNotificationKey(line) {
return line.trim();
}
@@ -4813,7 +4831,7 @@ var LogNotificationDeduper = class {
this.seenKeys = new Set(readStoredKeys(storage));
}
shouldNotify(line) {
if (!isErrorLogLine(line)) {
if (!getPodkopLogNotification(line)) {
return false;
}
const key = getLogNotificationKey(line);
@@ -5203,10 +5221,30 @@ function startRuntimeUiStatePolling() {
// src/podkop/services/core.service.ts
var LOG_WATCHER_INTERVAL_MS = 1e4;
var LOG_WATCHER_START_DELAY_MS = 5e3;
function showLogErrorNotification(line) {
function componentDisplayName(component) {
const names = {
podkop: "Podkop Plus",
sing_box: "sing-box",
zapret: "Zapret",
zapret2: "Zapret2",
byedpi: "ByeDPI"
};
return names[component] || component;
}
function showLogNotification(notification) {
if (notification.kind === "component-update") {
const message = _("New version %s is available for %s").replace("%s", notification.version).replace("%s", componentDisplayName(notification.component));
ui.addNotification(
_("Component update available"),
E("div", {}, message),
"warning",
"pdk-component-update-notification"
);
return;
}
ui.addNotification(
_("Podkop Plus Error"),
E("div", {}, line),
E("div", {}, notification.line),
"error",
"pdk-log-error-notification"
);
@@ -5235,7 +5273,10 @@ function coreService(options = {}) {
intervalMs: LOG_WATCHER_INTERVAL_MS,
onNewLog: (line) => {
if (logNotificationDeduper.shouldNotify(line)) {
showLogErrorNotification(line);
const notification = getPodkopLogNotification(line);
if (notification) {
showLogNotification(notification);
}
}
}
}
@@ -5246,12 +5287,9 @@ function coreService(options = {}) {
}
watcher.start();
};
const scheduleStartWatcher = () => window.setTimeout(
() => {
void startWatcher();
},
options.logWatcherStartDelayMs ?? LOG_WATCHER_START_DELAY_MS
);
const scheduleStartWatcher = () => window.setTimeout(() => {
void startWatcher();
}, options.logWatcherStartDelayMs ?? LOG_WATCHER_START_DELAY_MS);
if (typeof window !== "undefined") {
scheduleStartWatcher();
} else {
@@ -9363,7 +9401,9 @@ async function runSectionsCheck() {
latency: `[${selectedOutbound2.displayName ?? ""}] ${_("Not responding")}`
};
}
const latencyGroup = await PodkopShellMethods.getClashApiGroupLatency(section.code);
const latencyGroup = await PodkopShellMethods.getClashApiGroupLatency(
section.code
);
const success2 = latencyGroup.success && !latencyGroup.data.message;
if (success2) {
const latencyValues = Object.values(latencyGroup.data);
@@ -11123,7 +11163,10 @@ function buildRouteDisplayNames(sections) {
if (!owner || !id) {
return;
}
urltestsBySection.set(owner, [...urltestsBySection.get(owner) || [], id]);
urltestsBySection.set(owner, [
...urltestsBySection.get(owner) || [],
id
]);
});
sections.filter((section) => section[".type"] === "section").filter((section) => section.enabled !== "0").forEach((section) => {
const sectionName = section[".name"];
@@ -12915,9 +12958,10 @@ function shouldPreserveCompletedCheckResultOnNextMount({
}
function shouldResetCheckResultsOnMount({
anyActionLoading,
preserveCheckResultsOnNextMount: preserveCheckResultsOnNextMount2
preserveCheckResultsOnNextMount: preserveCheckResultsOnNextMount2,
persistentCacheEnabled = false
}) {
return !anyActionLoading && !preserveCheckResultsOnNextMount2;
return !persistentCacheEnabled && !anyActionLoading && !preserveCheckResultsOnNextMount2;
}
function shouldRefreshComponentStateBeforeRender(uiState) {
return Boolean(
@@ -12947,29 +12991,10 @@ if (typeof window !== "undefined") {
function isNotInstalled(version) {
return !version || version === "not installed";
}
function getCheckTag(component) {
const status = store.get().updatesChecks[component].status;
if (!status) {
return void 0;
}
if (status === "latest") {
return { label: _("Latest"), kind: "success" };
}
if (status === "outdated") {
return { label: _("Outdated"), kind: "warning" };
}
return { label: _("Dev"), kind: "neutral" };
}
function shouldShowInstallAfterCheck(component) {
const status = store.get().updatesChecks[component].status;
return status === "outdated" || status === "dev";
}
function getInstallActionText(component) {
if (shouldShowInstallAfterCheck(component)) {
return _("Update");
}
return _("Install");
}
function getLatestVersion(component) {
const checkResult = store.get().updatesChecks[component];
if (!shouldShowInstallAfterCheck(component)) {
@@ -13030,6 +13055,26 @@ function setCheckResult(component, status, latestVersion, releaseUrl = "") {
function resetCheckResult(component) {
setCheckResult(component, null, "");
}
function applyCachedCheckResults(results) {
results.forEach((result) => {
const status = result.status || null;
if (status === "latest" || status === "outdated" || status === "dev") {
setCheckResult(
result.component,
status,
result.latest_version || "",
result.release_url || ""
);
}
});
}
async function loadComponentUpdateCheckCache() {
const response = await PodkopShellMethods.componentUpdateCheckCache();
return response.success ? response.data : {
enabled: false,
results: []
};
}
function getErrorMessage(error, fallback) {
return error instanceof Error && error.message ? error.message : fallback;
}
@@ -13394,24 +13439,55 @@ async function handleComponentAction(button) {
}
}
}
function getPrimaryUpdateAction(component, checkKey, installKey) {
if (shouldShowInstallAfterCheck(component)) {
return {
key: installKey,
text: getInstallActionText(component),
icon: renderRotateCcwIcon24,
component,
action: "install"
};
}
function getCheckAction(component, key) {
return {
key: checkKey,
key,
text: _("Check update"),
icon: renderSearchIcon24,
component,
action: "check_update"
};
}
function getInstallAction(component, key, installed) {
return {
key,
text: installed ? _("Update") : _("Install"),
icon: installed ? renderRotateCcwIcon24 : renderDownloadIcon24,
component,
action: "install"
};
}
function getInstalledUpdateActions(component, checkKey, installKey, installed = true) {
if (!installed) {
return [];
}
const actions = [getCheckAction(component, checkKey)];
if (shouldShowInstallAfterCheck(component)) {
actions.push(getInstallAction(component, installKey, true));
}
return actions;
}
function getOptionalComponentActions({
component,
installed,
checkKey,
installKey,
removeKey
}) {
if (!installed) {
return [getInstallAction(component, installKey, false)];
}
return [
...getInstalledUpdateActions(component, checkKey, installKey),
{
key: removeKey,
text: _("Remove"),
icon: renderXIcon24,
component,
action: "remove"
}
];
}
function getComponentCards() {
const systemInfo = normalizeSingBoxVariantFields(
store.get().diagnosticsSystemInfo
@@ -13425,13 +13501,21 @@ function getComponentCards() {
const singBoxExtended = Boolean(systemInfo.sing_box_extended) && !systemInfo.sing_box_compressed;
const singBoxExtendedCompressed = Boolean(systemInfo.sing_box_extended) && Boolean(systemInfo.sing_box_compressed);
const singBoxTiny = Boolean(systemInfo.sing_box_tiny);
const singBoxActions = [
getPrimaryUpdateAction("sing_box", "singBoxCheck", "singBoxInstall")
];
const podkopActions = getInstalledUpdateActions(
"podkop",
"podkopCheck",
"podkopInstall"
);
const singBoxActions = getInstalledUpdateActions(
"sing_box",
"singBoxCheck",
"singBoxInstall",
singBoxInstalled
);
if (!singBoxStable) {
singBoxActions.push({
key: "singBoxInstallStable",
text: _("Install stable"),
text: "Stable",
icon: renderDownloadIcon24,
component: "sing_box",
action: "install_stable"
@@ -13440,7 +13524,7 @@ function getComponentCards() {
if (!singBoxTiny) {
singBoxActions.push({
key: "singBoxInstallTiny",
text: _("Install tiny"),
text: "Tiny",
icon: renderDownloadIcon24,
component: "sing_box",
action: "install_tiny"
@@ -13449,7 +13533,7 @@ function getComponentCards() {
if (!singBoxExtended) {
singBoxActions.push({
key: "singBoxInstallExtended",
text: _("Install extended"),
text: "Extended",
icon: renderDownloadIcon24,
component: "sing_box",
action: "install_extended"
@@ -13458,189 +13542,264 @@ function getComponentCards() {
if (!singBoxExtendedCompressed) {
singBoxActions.push({
key: "singBoxInstallExtendedCompressed",
text: _("Install extended compressed"),
text: "Extended compressed",
icon: renderDownloadIcon24,
component: "sing_box",
action: "install_extended_compressed"
});
}
const zapretActions = getOptionalComponentActions({
component: "zapret",
installed: zapretInstalled,
checkKey: "zapretCheck",
installKey: "zapretInstall",
removeKey: "zapretRemove"
});
const zapret2Actions = getOptionalComponentActions({
component: "zapret2",
installed: zapret2Installed,
checkKey: "zapret2Check",
installKey: "zapret2Install",
removeKey: "zapret2Remove"
});
const byedpiActions = getOptionalComponentActions({
component: "byedpi",
installed: byedpiInstalled,
checkKey: "byedpiCheck",
installKey: "byedpiInstall",
removeKey: "byedpiRemove"
});
return [
{
component: "podkop",
column: 0,
title: "Podkop Plus",
version: normalizeCompiledVersion(systemInfo.podkop_version),
latestVersion: getLatestVersion("podkop"),
releaseUrl: getGitHubReleaseUrl("podkop"),
tag: getCheckTag("podkop"),
actions: [
getPrimaryUpdateAction("podkop", "podkopCheck", "podkopInstall")
]
actions: podkopActions
},
{
component: "sing_box",
column: 0,
title: "Sing-box",
version: formatSingBoxVersion(systemInfo),
latestVersion: getLatestVersion("sing_box"),
releaseUrl: getGitHubReleaseUrl("sing_box"),
tag: getCheckTag("sing_box"),
actions: singBoxActions
},
{
component: "zapret",
column: 1,
title: "Zapret",
version: systemInfoLoading ? "loading" : zapretInstalled ? systemInfo.zapret_version : _("Not installed"),
version: systemInfoLoading ? _("Loading...") : zapretInstalled ? systemInfo.zapret_version : _("Not installed"),
latestVersion: getLatestVersion("zapret"),
releaseUrl: getGitHubReleaseUrl("zapret"),
tag: zapretInstalled ? getCheckTag("zapret") : void 0,
actions: zapretInstalled ? [
getPrimaryUpdateAction("zapret", "zapretCheck", "zapretInstall"),
{
key: "zapretRemove",
text: _("Remove"),
icon: renderXIcon24,
component: "zapret",
action: "remove"
}
] : [
{
key: "zapretInstall",
text: _("Install"),
icon: renderDownloadIcon24,
component: "zapret",
action: "install"
}
]
actions: zapretActions
},
{
component: "zapret2",
column: 1,
title: "Zapret2",
version: systemInfoLoading ? "loading" : zapret2Installed ? systemInfo.zapret2_version : _("Not installed"),
version: systemInfoLoading ? _("Loading...") : zapret2Installed ? systemInfo.zapret2_version : _("Not installed"),
latestVersion: getLatestVersion("zapret2"),
releaseUrl: getGitHubReleaseUrl("zapret2"),
tag: zapret2Installed ? getCheckTag("zapret2") : void 0,
actions: zapret2Installed ? [
getPrimaryUpdateAction("zapret2", "zapret2Check", "zapret2Install"),
{
key: "zapret2Remove",
text: _("Remove"),
icon: renderXIcon24,
component: "zapret2",
action: "remove"
}
] : [
{
key: "zapret2Install",
text: _("Install"),
icon: renderDownloadIcon24,
component: "zapret2",
action: "install"
}
]
actions: zapret2Actions
},
{
component: "byedpi",
column: 1,
title: "ByeDPI",
version: systemInfoLoading ? "loading" : byedpiInstalled ? systemInfo.byedpi_version : _("Not installed"),
version: systemInfoLoading ? _("Loading...") : byedpiInstalled ? systemInfo.byedpi_version : _("Not installed"),
latestVersion: getLatestVersion("byedpi"),
releaseUrl: getGitHubReleaseUrl("byedpi"),
tag: byedpiInstalled ? getCheckTag("byedpi") : void 0,
actions: byedpiInstalled ? [
getPrimaryUpdateAction("byedpi", "byedpiCheck", "byedpiInstall"),
{
key: "byedpiRemove",
text: _("Remove"),
icon: renderXIcon24,
component: "byedpi",
action: "remove"
}
] : [
{
key: "byedpiInstall",
text: _("Install"),
icon: renderDownloadIcon24,
component: "byedpi",
action: "install"
}
]
actions: byedpiActions
}
];
}
function renderComponentTag(card) {
if (!card.tag) {
return null;
}
return E(
"span",
{
class: [
"pdk_updates-page__component__tag",
card.tag.kind === "success" ? "pdk_updates-page__component__tag--success" : "",
card.tag.kind === "warning" ? "pdk_updates-page__component__tag--warning" : ""
].filter(Boolean).join(" ")
},
card.tag.label
);
}
function renderComponentCard(card) {
const updatesActions = store.get().updatesActions;
const anyActionLoading = isAnyActionLoading();
const serviceRuntimeActionLoading = isServiceRuntimeActionLoading();
const systemInfoLoading = isSystemInfoLoading();
const tag = renderComponentTag(card);
const headerChildren = [
E("b", { class: "pdk_updates-page__component__title" }, card.title)
];
const statusChildren = [];
if (card.releaseUrl) {
statusChildren.push(
E(
"a",
{
class: "pdk_updates-page__component__release-link",
href: card.releaseUrl,
target: "_blank",
rel: "noopener noreferrer"
},
card.latestVersion ? _("Latest: %s").replace("%s", card.latestVersion) : _("Latest release")
)
);
}
if (tag) {
statusChildren.push(tag);
}
if (statusChildren.length > 0) {
headerChildren.push(
E(
"div",
{ class: "pdk_updates-page__component__status" },
statusChildren
)
);
}
return E("div", { class: "pdk_updates-page__component" }, [
E("div", { class: "pdk_updates-page__component__header" }, headerChildren),
E("div", { class: "pdk_updates-page__component__version" }, [
E(
"span",
{ class: "pdk_updates-page__component__version__label" },
_("Version")
),
E(
"span",
{ class: "pdk_updates-page__component__version__value" },
card.version
)
]),
E("b", { class: "pdk_updates-page__component__title" }, card.title),
E(
"div",
{ class: "pdk_updates-page__component__actions" },
card.actions.map((action) => {
const loading2 = updatesActions[action.key].loading;
return renderButton({
text: action.text,
icon: action.icon,
loading: loading2,
disabled: systemInfoLoading || serviceRuntimeActionLoading || anyActionLoading && !loading2,
onClick: () => void handleComponentAction(action)
});
})
"span",
{ class: "pdk_updates-page__component__header-version" },
card.version
)
]);
];
const header = E(
"div",
{ class: "pdk_updates-page__component__header" },
headerChildren
);
const detailsChildren = [];
const checkResult = store.get().updatesChecks[card.component];
if (checkResult && checkResult.status) {
let labelText = "";
const latestValueNodes = [];
if (checkResult.status === "outdated") {
labelText = _("Update is available:");
const versionToShow = checkResult.latest_version || card.latestVersion || card.version;
if (checkResult.release_url) {
latestValueNodes.push(
E(
"a",
{
class: "pdk_updates-page__component__release-version-link",
href: checkResult.release_url,
target: "_blank",
rel: "noopener noreferrer"
},
versionToShow || _("Open")
)
);
} else if (versionToShow) {
latestValueNodes.push(document.createTextNode(versionToShow));
}
} else if (checkResult.status === "latest") {
labelText = _("Latest version is installed");
} else if (checkResult.status === "dev") {
labelText = `${_("Installed version is newer than release")}. ${_("Latest version:")}`;
const versionToShow = checkResult.latest_version || card.latestVersion;
if (checkResult.release_url) {
latestValueNodes.push(
E(
"a",
{
class: "pdk_updates-page__component__release-version-link",
href: checkResult.release_url,
target: "_blank",
rel: "noopener noreferrer"
},
versionToShow || _("Open")
)
);
} else if (versionToShow) {
latestValueNodes.push(document.createTextNode(versionToShow));
}
}
if (labelText) {
const rowChildren = [
E(
"span",
{ class: "pdk_updates-page__component__info-label" },
labelText
)
];
if (latestValueNodes.length > 0) {
rowChildren.push(
E(
"span",
{
class: "pdk_updates-page__component__info-value pdk_updates-page__component__info-value--latest"
},
latestValueNodes
)
);
}
detailsChildren.push(
E(
"div",
{ class: "pdk_updates-page__component__info-row" },
rowChildren
)
);
}
}
const detailsContainer = detailsChildren.length > 0 ? E(
"div",
{ class: "pdk_updates-page__component__details" },
detailsChildren
) : null;
const primaryActions = [];
const dangerActions = [];
const variantActions = [];
card.actions.forEach((action) => {
if (action.action === "remove") {
dangerActions.push(action);
} else if (action.action.startsWith("install_")) {
variantActions.push(action);
} else {
primaryActions.push(action);
}
});
const actionElements = [];
const primaryButtons = primaryActions.map((action) => {
const loading2 = updatesActions[action.key].loading;
const isUpdateOrInstall = action.action === "install";
return renderButton({
classNames: isUpdateOrInstall ? ["cbi-button-save"] : [],
text: action.text,
icon: action.icon,
loading: loading2,
disabled: systemInfoLoading || serviceRuntimeActionLoading || anyActionLoading && !loading2,
onClick: () => void handleComponentAction(action)
});
});
const dangerButtons = dangerActions.map((action) => {
const loading2 = updatesActions[action.key].loading;
return renderButton({
classNames: ["cbi-button-remove"],
text: action.text,
icon: action.icon,
loading: loading2,
disabled: systemInfoLoading || serviceRuntimeActionLoading || anyActionLoading && !loading2,
onClick: () => void handleComponentAction(action)
});
});
if (primaryButtons.length > 0 || dangerButtons.length > 0) {
actionElements.push(
E("div", { class: "pdk_updates-page__component__actions-main" }, [
...primaryButtons,
...dangerButtons
])
);
}
if (variantActions.length > 0) {
const variantButtons = variantActions.map((action) => {
const loading2 = updatesActions[action.key].loading;
return renderButton({
text: action.text,
icon: action.icon,
loading: loading2,
disabled: systemInfoLoading || serviceRuntimeActionLoading || anyActionLoading && !loading2,
onClick: () => void handleComponentAction(action)
});
});
actionElements.push(
E("div", { class: "pdk_updates-page__component__variants" }, [
E(
"div",
{ class: "pdk_updates-page__component__variants-title" },
_("Install another build:")
),
E(
"div",
{ class: "pdk_updates-page__component__variants-buttons" },
variantButtons
)
])
);
}
const actionsContainer = E(
"div",
{
class: [
"pdk_updates-page__component__actions",
detailsContainer ? "pdk_updates-page__component__actions--with-details" : ""
].filter(Boolean).join(" ")
},
actionElements
);
const cardChildren = [header];
if (detailsContainer) {
cardChildren.push(detailsContainer);
}
cardChildren.push(actionsContainer);
return E("div", { class: "pdk_updates-page__component" }, cardChildren);
}
function renderUpdatesComponents() {
const container = document.getElementById("pdk_updates-components");
@@ -13648,8 +13807,8 @@ function renderUpdatesComponents() {
return;
}
const columns = [[], []];
getComponentCards().forEach((card, index) => {
columns[index % 2].push(renderComponentCard(card));
getComponentCards().forEach((card) => {
columns[card.column].push(renderComponentCard(card));
});
return preserveScrollForPage(() => {
container.replaceChildren(
@@ -13677,9 +13836,18 @@ async function onPageMount4() {
return;
}
}
const componentUpdateCheckCache = await loadComponentUpdateCheckCache();
if (!updatesMounted || mountId !== updatesMountId) {
return;
}
if (componentUpdateCheckCache.enabled) {
store.reset(["updatesChecks"]);
applyCachedCheckResults(componentUpdateCheckCache.results);
}
if (shouldResetCheckResultsOnMount({
anyActionLoading: isAnyActionLoading(),
preserveCheckResultsOnNextMount
preserveCheckResultsOnNextMount,
persistentCacheEnabled: componentUpdateCheckCache.enabled
})) {
store.reset(["updatesChecks"]);
}
@@ -13747,14 +13915,16 @@ var styles6 = `
display: flex;
align-items: flex-start;
gap: 10px;
width: 100%;
flex-wrap: wrap;
}
.pdk_updates-page__components-column {
display: flex;
flex: 1 1 0;
flex: 1 1 auto;
flex-direction: column;
gap: 10px;
min-width: 0;
min-width: max-content;
}
@media (max-width: 760px) {
@@ -13764,6 +13934,7 @@ var styles6 = `
.pdk_updates-page__components-column {
width: 100%;
min-width: 0;
}
}
@@ -13771,101 +13942,120 @@ var styles6 = `
border: 2px var(--background-color-low, lightgray) solid;
border-radius: 4px;
padding: 10px;
min-width: 0;
display: flex;
flex-direction: column;
gap: 10px;
background-color: var(--background-color-high, #fff);
min-width: max-content;
}
.pdk_updates-page__component__header {
display: grid;
grid-template-columns: max-content minmax(0, 1fr);
align-items: start;
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 8px;
min-width: 0;
border-bottom: 1px var(--background-color-low, lightgray) solid;
padding-bottom: 8px;
margin-bottom: 2px;
}
.pdk_updates-page__component__title {
color: var(--text-color-high);
line-height: 1.25;
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
font-size: 16px;
font-weight: bold;
line-height: 1.2;
}
.pdk_updates-page__component__header-version {
color: var(--text-color-medium, #888);
font-size: 13px;
font-weight: normal;
}
.pdk_updates-page__component__details {
display: flex;
flex-direction: column;
gap: 6px;
}
.pdk_updates-page__component__info-row {
display: flex;
justify-content: flex-start;
align-items: center;
min-height: 24px;
gap: 8px;
white-space: nowrap;
}
.pdk_updates-page__component__status {
.pdk_updates-page__component__info-label {
color: var(--text-color-medium, #888);
font-size: 12px;
}
.pdk_updates-page__component__info-value {
color: var(--text-color-high, #000);
font-weight: 500;
font-size: 13px;
text-align: left;
display: flex;
align-items: center;
justify-content: flex-end;
flex-wrap: wrap;
gap: 6px;
min-width: 0;
}
.pdk_updates-page__component__version {
display: grid;
grid-template-columns: auto 1fr;
grid-column-gap: 6px;
align-items: baseline;
min-width: 0;
}
.pdk_updates-page__component__version__label {
color: var(--text-color-medium);
}
.pdk_updates-page__component__version__value {
min-width: 0;
overflow-wrap: anywhere;
}
.pdk_updates-page__component__tag {
flex: 0 0 auto;
padding: 2px 5px;
border: 1px var(--background-color-high, gray) solid;
border-radius: 4px;
color: var(--text-color-medium, gray);
line-height: 1.2;
.pdk_updates-page__component__info-value--latest {
flex-wrap: wrap;
justify-content: flex-start;
}
.pdk_updates-page__component__tag--success {
border-color: var(--success-color-medium, green);
color: var(--success-color-medium, green);
.pdk_updates-page__component__release-version-link {
color: var(--link-color, #3498db) !important;
text-decoration: underline;
font-weight: bold;
}
.pdk_updates-page__component__tag--warning {
border-color: var(--warn-color-medium, orange);
color: var(--warn-color-medium, orange);
.pdk_updates-page__component__release-version-link:hover {
color: var(--link-color-dark, #2980b9) !important;
}
.pdk_updates-page__component__actions {
display: flex;
flex-wrap: wrap;
align-items: flex-start;
align-content: flex-start;
flex: 0 0 auto;
flex-direction: column;
gap: 10px;
margin-top: auto;
}
.pdk_updates-page__component__actions--with-details {
border-top: 1px var(--background-color-low, lightgray) solid;
padding-top: 10px;
}
.pdk_updates-page__component__actions-main {
display: flex;
justify-content: flex-start;
align-items: center;
flex-wrap: nowrap;
gap: 6px;
min-height: 0;
}
.pdk_updates-page__component__actions > .pdk-partial-button {
margin-left: 0;
align-self: flex-start;
flex: 0 0 auto;
height: auto;
min-height: 0;
width: auto;
.pdk_updates-page__component__variants {
display: flex;
flex-direction: column;
gap: 6px;
margin-top: 4px;
}
.pdk_updates-page__component__release-link {
display: inline-block;
flex: 1 1 auto;
min-width: 0;
overflow-wrap: anywhere;
.pdk_updates-page__component__variants-title {
font-size: 11px;
line-height: 1.2;
text-align: right;
font-weight: bold;
color: var(--text-color-medium, gray);
}
.pdk_updates-page__component__variants-buttons {
display: flex;
flex-wrap: nowrap;
gap: 6px;
}
`;
@@ -489,6 +489,50 @@ function createSettingsContent(section, capabilities) {
return _("Use sing-box duration format like 1d, 12h or 30m");
};
o = section.option(
form.Flag,
"component_update_check_enabled",
_("Automatic component update checks"),
_("Automatically check installed components for new versions"),
);
o.default = "0";
o.rmempty = false;
o = section.option(
form.Value,
"component_update_check_interval",
_("Component update check interval"),
_("Use sing-box duration format like 1d, 12h or 30m"),
);
o.depends("component_update_check_enabled", "1");
o.placeholder = "1d";
o.default = "1d";
o.rmempty = false;
o.cfgvalue = function (section_id) {
return (
uci.get(UCI_PACKAGE, section_id, "component_update_check_interval") ||
"1d"
);
};
o.write = function (section_id, value) {
const normalized = value ? `${value}`.trim() : "";
uci.set(
UCI_PACKAGE,
section_id,
"component_update_check_interval",
normalized.length ? normalized : "1d",
);
};
o.validate = function (_section_id, value) {
const normalized = value ? `${value}`.trim() : "";
if (normalized.length && isSingBoxDuration(normalized)) {
return true;
}
return _("Use sing-box duration format like 1d, 12h or 30m");
};
o = section.option(
form.Value,
"latency_test_url",
+30 -30
View File
@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: PODKOP PLUS\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-10 23:17+0300\n"
"PO-Revision-Date: 2026-07-10 23:17+0300\n"
"POT-Creation-Date: 2026-07-10 02:05+0300\n"
"PO-Revision-Date: 2026-07-10 02:05+0300\n"
"Last-Translator: ushan0v\n"
"Language-Team: none\n"
"Language: ru\n"
@@ -143,12 +143,18 @@ msgstr "Автообновление"
msgid "Auto-generate HWID"
msgstr "Авто-генерация HWID"
msgid "Automatic component update checks"
msgstr "Автоматическая проверка обновлений компонентов"
msgid "Automatic User-Agent selection"
msgstr "Авто-подбор User-Agent"
msgid "Automatically add text to the name of each server from this subscription for convenient filtering."
msgstr "Автоматически добавлять текст к имени каждого сервера из этой подписки для удобной фильтрации."
msgid "Automatically check installed components for new versions"
msgstr "Автоматически проверять наличие новых версий установленных компонентов"
msgid "Automatically select the fastest node in the current level"
msgstr "Автовыбор самого быстрого узла уровня"
@@ -290,6 +296,12 @@ msgstr "Закрыть подключение"
msgid "Closed"
msgstr "Закрытые"
msgid "Component update available"
msgstr "Доступно обновление компонента"
msgid "Component update check interval"
msgstr "Период проверки обновлений"
msgid "Components"
msgstr "Компоненты"
@@ -371,9 +383,6 @@ msgstr "Определять страну сервера"
msgid "detour must be a non-empty string"
msgstr "detour должен быть непустой строкой"
msgid "Dev"
msgstr "Dev"
msgid "Device filter"
msgstr "Фильтр по устройствам"
@@ -764,17 +773,8 @@ msgstr "Информация"
msgid "Install"
msgstr "Установить"
msgid "Install extended"
msgstr "Установить extended"
msgid "Install extended compressed"
msgstr "Установить extended compressed"
msgid "Install stable"
msgstr "Установить stable"
msgid "Install tiny"
msgstr "Установить tiny"
msgid "Install another build:"
msgstr "Установить другую сборку:"
msgid "Installed version is newer than release"
msgstr "Установлена версия новее релиза"
@@ -1031,17 +1031,11 @@ msgstr "Проверка задержки не удалась"
msgid "Latency test URL"
msgstr "URL для проверки задержки"
msgid "Latest"
msgstr "Последняя"
msgid "Latest release"
msgstr "Последний релиз"
msgid "Latest version is installed"
msgstr "Установлена последняя версия"
msgid "Latest: %s"
msgstr "Последняя: %s"
msgid "Latest version:"
msgstr "Последняя версия:"
msgid "Level"
msgstr "Уровень"
@@ -1070,6 +1064,9 @@ msgstr "Загрузка клиентской ссылки..."
msgid "Loading connections"
msgstr "Загрузка подключений"
msgid "Loading..."
msgstr "Загрузка..."
msgid "Log Level"
msgstr "Уровень логов"
@@ -1121,6 +1118,9 @@ msgstr "Имя, которое отображается в списке уров
msgid "Network Interface"
msgstr "Сетевой интерфейс"
msgid "New version %s is available for %s"
msgstr "Доступна новая версия %s для %s"
msgid "NFQUEUE range is available"
msgstr "Диапазон NFQUEUE свободен"
@@ -1223,6 +1223,9 @@ msgstr "Только серверы, имена которых соответс
msgid "Only the specified servers."
msgstr "Только указанные серверы."
msgid "Open"
msgstr ""
msgid "Open priority settings and enter a display name"
msgstr "Откройте настройки приоритета и укажите отображаемое имя"
@@ -1244,9 +1247,6 @@ msgstr "Необязательные значения здесь должны з
msgid "outbounds must be a non-empty array of strings"
msgstr "outbounds должен быть непустым массивом строк"
msgid "Outdated"
msgstr "Устаревшая"
msgid "Output Network Interface"
msgstr "Выходной сетевой интерфейс"
@@ -1967,6 +1967,9 @@ msgstr "Обновить"
msgid "Update is available"
msgstr "Доступно обновление"
msgid "Update is available:"
msgstr "Доступна новая версия:"
msgid "Update subscriptions"
msgstr "Обновить подписки"
@@ -2069,9 +2072,6 @@ msgstr "Значение не может быть пустым"
msgid "Value must not contain control characters"
msgstr "Значение не должно содержать управляющие символы"
msgid "Version"
msgstr "Версия"
msgid "Via country.is"
msgstr "Через сервис country.is"
+135 -133
View File
@@ -7,8 +7,8 @@ msgid ""
msgstr ""
"Project-Id-Version: PODKOP PLUS\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-10 20:17+0300\n"
"PO-Revision-Date: 2026-07-10 20:17+0300\n"
"POT-Creation-Date: 2026-07-10 23:05+0300\n"
"PO-Revision-Date: 2026-07-10 23:05+0300\n"
"Last-Translator: ushan0v <ushanov2204@gmail.com>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
@@ -63,7 +63,7 @@ msgstr ""
msgid "Action"
msgstr ""
#: src/podkop/tabs/monitoring/initController.ts:663
#: src/podkop/tabs/monitoring/initController.ts:667
#: src/podkop/tabs/monitoring/render.ts:20
msgid "Active"
msgstr ""
@@ -120,7 +120,7 @@ msgstr ""
msgid "Advertise routes"
msgstr ""
#: src/podkop/tabs/monitoring/initController.ts:622
#: src/podkop/tabs/monitoring/initController.ts:626
#: src/podkop/tabs/monitoring/render.ts:39
msgid "All"
msgstr ""
@@ -193,6 +193,10 @@ msgstr ""
msgid "Auto-generate HWID"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:495
msgid "Automatic component update checks"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/section.js:1969
msgid "Automatic User-Agent selection"
msgstr ""
@@ -201,6 +205,10 @@ msgstr ""
msgid "Automatically add text to the name of each server from this subscription for convenient filtering."
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:496
msgid "Automatically check installed components for new versions"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/section.js:2536
#: src/podkop/tabs/dashboard/initController.ts:1155
msgid "Automatically select the fastest node in the current level"
@@ -296,11 +304,11 @@ msgstr ""
msgid "ByeDPI strategy cannot be empty"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:573
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:617
msgid "Cache File Path"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:587
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:631
msgid "Cache file path cannot be empty"
msgstr ""
@@ -341,7 +349,7 @@ msgstr ""
msgid "Check timeout after which the server is considered dead"
msgstr ""
#: src/podkop/tabs/updates/initController.ts:709
#: src/podkop/tabs/updates/initController.ts:696
msgid "Check update"
msgstr ""
@@ -407,8 +415,8 @@ msgstr ""
#: src/partials/modal/renderModal.ts:263
#: src/podkop/tabs/dashboard/initController.ts:1027
#: src/podkop/tabs/dashboard/initController.ts:1283
#: src/podkop/tabs/monitoring/initController.ts:828
#: src/podkop/tabs/monitoring/initController.ts:878
#: src/podkop/tabs/monitoring/initController.ts:832
#: src/podkop/tabs/monitoring/initController.ts:882
msgid "Close"
msgstr ""
@@ -417,21 +425,29 @@ msgstr ""
msgid "Close all connections"
msgstr ""
#: src/podkop/tabs/monitoring/initController.ts:795
#: src/podkop/tabs/monitoring/initController.ts:796
#: src/podkop/tabs/monitoring/initController.ts:799
#: src/podkop/tabs/monitoring/initController.ts:800
msgid "Close connection"
msgstr ""
#: src/podkop/tabs/monitoring/initController.ts:670
#: src/podkop/tabs/monitoring/initController.ts:674
#: src/podkop/tabs/monitoring/render.ts:29
msgid "Closed"
msgstr ""
#: src/podkop/services/core.service.ts:43
msgid "Component update available"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:504
msgid "Component update check interval"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/podkop.js:476
msgid "Components"
msgstr ""
#: src/podkop/helpers/singBoxVariant.ts:52
#: src/podkop/helpers/singBoxVariant.ts:51
msgid "compressed"
msgstr ""
@@ -443,7 +459,7 @@ msgstr ""
msgid "Conditions"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:560
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:604
msgid "Config File Path"
msgstr ""
@@ -463,11 +479,11 @@ msgstr ""
msgid "Connection URL"
msgstr ""
#: src/podkop/tabs/monitoring/initController.ts:943
#: src/podkop/tabs/monitoring/initController.ts:946
msgid "Connections are unavailable"
msgstr ""
#: src/podkop/tabs/diagnostic/checks/runSectionsCheck.ts:144
#: src/podkop/tabs/diagnostic/checks/runSectionsCheck.ts:145
msgid "Connectivity probe failed"
msgstr ""
@@ -516,7 +532,7 @@ msgstr ""
msgid "Dashboard currently unavailable"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:496
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:540
msgid "Default address for checking server availability and latency. URLTest uses its own address."
msgstr ""
@@ -541,10 +557,6 @@ msgstr ""
msgid "detour must be a non-empty string"
msgstr ""
#: src/podkop/tabs/updates/initController.ts:109
msgid "Dev"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/section.js:7056
msgid "Device filter"
msgstr ""
@@ -674,7 +686,7 @@ msgstr ""
msgid "Domains"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:551
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:595
msgid "Dont Touch My DHCP!"
msgstr ""
@@ -687,27 +699,27 @@ msgstr ""
msgid "Download"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:532
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:576
msgid "Download component packages via the selected section"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:539
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:583
msgid "Download components through"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:531
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:575
msgid "Download components through a section"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:519
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:563
msgid "Download lists through"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:511
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:555
msgid "Download lists through a section"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:512
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:556
msgid "Download remote lists and rule sets via the selected section"
msgstr ""
@@ -723,8 +735,8 @@ msgstr ""
msgid "Download through"
msgstr ""
#: src/podkop/tabs/monitoring/initController.ts:821
#: src/podkop/tabs/monitoring/initController.ts:875
#: src/podkop/tabs/monitoring/initController.ts:825
#: src/podkop/tabs/monitoring/initController.ts:879
msgid "Downloaded"
msgstr ""
@@ -826,11 +838,11 @@ msgstr ""
msgid "Exclude countries"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:625
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:669
msgid "Exclude NTP"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:626
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:670
msgid "Exclude NTP protocol traffic from the tunnel to prevent it from being routed through the proxy or VPN"
msgstr ""
@@ -859,13 +871,13 @@ msgstr ""
msgid "External nfqws2 config files bypass Podkop Plus queue management and explicit validation."
msgstr ""
#: src/podkop/tabs/monitoring/initController.ts:1210
#: src/podkop/tabs/monitoring/initController.ts:1225
#: src/podkop/tabs/monitoring/initController.ts:1213
#: src/podkop/tabs/monitoring/initController.ts:1228
msgid "Failed to close connection"
msgstr ""
#: src/podkop/tabs/monitoring/initController.ts:1244
#: src/podkop/tabs/monitoring/initController.ts:1257
#: src/podkop/tabs/monitoring/initController.ts:1247
#: src/podkop/tabs/monitoring/initController.ts:1260
msgid "Failed to close connections"
msgstr ""
@@ -876,10 +888,10 @@ msgstr ""
#: src/podkop/methods/shell/index.ts:170
#: src/podkop/methods/shell/index.ts:177
#: src/podkop/tabs/updates/initController.ts:466
#: src/podkop/tabs/updates/initController.ts:467
#: src/podkop/tabs/updates/initController.ts:527
#: src/podkop/tabs/updates/initController.ts:677
#: src/podkop/tabs/updates/initController.ts:464
#: src/podkop/tabs/updates/initController.ts:465
#: src/podkop/tabs/updates/initController.ts:525
#: src/podkop/tabs/updates/initController.ts:675
msgid "Failed to execute"
msgstr ""
@@ -1016,8 +1028,8 @@ msgstr ""
msgid "Higher-priority DNS Check"
msgstr ""
#: src/podkop/tabs/monitoring/initController.ts:811
#: src/podkop/tabs/monitoring/initController.ts:871
#: src/podkop/tabs/monitoring/initController.ts:815
#: src/podkop/tabs/monitoring/initController.ts:875
msgid "Host"
msgstr ""
@@ -1098,30 +1110,16 @@ msgstr ""
msgid "Information"
msgstr ""
#: src/podkop/tabs/updates/initController.ts:123
#: src/podkop/tabs/updates/initController.ts:822
#: src/podkop/tabs/updates/initController.ts:853
#: src/podkop/tabs/updates/initController.ts:884
#: src/podkop/tabs/updates/initController.ts:710
msgid "Install"
msgstr ""
#: src/podkop/tabs/updates/initController.ts:762
msgid "Install extended"
#: src/podkop/tabs/updates/initController.ts:1111
msgid "Install another build:"
msgstr ""
#: src/podkop/tabs/updates/initController.ts:772
msgid "Install extended compressed"
msgstr ""
#: src/podkop/tabs/updates/initController.ts:742
msgid "Install stable"
msgstr ""
#: src/podkop/tabs/updates/initController.ts:752
msgid "Install tiny"
msgstr ""
#: src/podkop/tabs/updates/initController.ts:249
#: src/podkop/tabs/updates/initController.ts:247
#: src/podkop/tabs/updates/initController.ts:969
msgid "Installed version is newer than release"
msgstr ""
@@ -1169,8 +1167,8 @@ msgid "Invalid DNS server format. Examples: 8.8.8.8 or dns.example.com or dns.ex
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/section.js:4405
#: src/validators/validateDomain.ts:71
#: src/validators/validateDomain.ts:77
#: src/validators/validateDomain.ts:67
#: src/validators/validateDomain.ts:73
msgid "Invalid domain address"
msgstr ""
@@ -1469,29 +1467,22 @@ msgstr ""
msgid "Keyword must not contain spaces or commas"
msgstr ""
#: src/podkop/methods/shell/index.ts:463
#: src/podkop/methods/shell/index.ts:484
#: src/podkop/methods/shell/index.ts:462
#: src/podkop/methods/shell/index.ts:483
msgid "Latency test failed"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:495
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:539
msgid "Latency test URL"
msgstr ""
#: src/podkop/tabs/updates/initController.ts:102
msgid "Latest"
msgstr ""
#: src/podkop/tabs/updates/initController.ts:941
msgid "Latest release"
msgstr ""
#: src/podkop/tabs/updates/initController.ts:252
#: src/podkop/tabs/updates/initController.ts:250
#: src/podkop/tabs/updates/initController.ts:967
msgid "Latest version is installed"
msgstr ""
#: src/podkop/tabs/updates/initController.ts:940
msgid "Latest: %s"
#: src/podkop/tabs/updates/initController.ts:969
msgid "Latest version:"
msgstr ""
#: src/podkop/tabs/dashboard/initController.ts:1100
@@ -1526,12 +1517,18 @@ msgstr ""
msgid "Loading client link..."
msgstr ""
#: src/podkop/tabs/monitoring/initController.ts:933
#: src/podkop/tabs/monitoring/initController.ts:936
#: src/podkop/tabs/monitoring/render.ts:88
msgid "Loading connections"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:609
#: src/podkop/tabs/updates/initController.ts:879
#: src/podkop/tabs/updates/initController.ts:892
#: src/podkop/tabs/updates/initController.ts:905
msgid "Loading..."
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:653
msgid "Log Level"
msgstr ""
@@ -1602,6 +1599,10 @@ msgstr ""
msgid "Network Interface"
msgstr ""
#: src/podkop/services/core.service.ts:38
msgid "New version %s is available for %s"
msgstr ""
#: src/podkop/tabs/diagnostic/checks/runZapret2Check.ts:100
#: src/podkop/tabs/diagnostic/checks/runZapretCheck.ts:97
msgid "NFQUEUE range is available"
@@ -1635,11 +1636,11 @@ msgstr ""
msgid "No"
msgstr ""
#: src/podkop/tabs/monitoring/initController.ts:957
#: src/podkop/tabs/monitoring/initController.ts:960
msgid "No active connections"
msgstr ""
#: src/podkop/tabs/monitoring/initController.ts:958
#: src/podkop/tabs/monitoring/initController.ts:961
msgid "No closed connections"
msgstr ""
@@ -1701,11 +1702,11 @@ msgstr ""
msgid "Not implement yet"
msgstr ""
#: src/podkop/helpers/singBoxVariant.ts:30
#: src/podkop/helpers/singBoxVariant.ts:38
#: src/podkop/tabs/updates/initController.ts:804
#: src/podkop/tabs/updates/initController.ts:835
#: src/podkop/tabs/updates/initController.ts:866
#: src/podkop/helpers/singBoxVariant.ts:29
#: src/podkop/helpers/singBoxVariant.ts:37
#: src/podkop/tabs/updates/initController.ts:882
#: src/podkop/tabs/updates/initController.ts:895
#: src/podkop/tabs/updates/initController.ts:908
msgid "Not installed"
msgstr ""
@@ -1718,9 +1719,9 @@ msgid "Not required for this listen address"
msgstr ""
#: src/podkop/tabs/diagnostic/checks/runSectionsCheck.ts:90
#: src/podkop/tabs/diagnostic/checks/runSectionsCheck.ts:116
#: src/podkop/tabs/diagnostic/checks/runSectionsCheck.ts:122
#: src/podkop/tabs/diagnostic/checks/runSectionsCheck.ts:150
#: src/podkop/tabs/diagnostic/checks/runSectionsCheck.ts:117
#: src/podkop/tabs/diagnostic/checks/runSectionsCheck.ts:123
#: src/podkop/tabs/diagnostic/checks/runSectionsCheck.ts:151
msgid "Not responding"
msgstr ""
@@ -1762,6 +1763,11 @@ msgstr ""
msgid "Only the specified servers."
msgstr ""
#: src/podkop/tabs/updates/initController.ts:960
#: src/podkop/tabs/updates/initController.ts:982
msgid "Open"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/section.js:3079
msgid "Open priority settings and enter a display name"
msgstr ""
@@ -1771,8 +1777,8 @@ msgid "Open Project Page"
msgstr ""
#: src/helpers/withTimeout.ts:7
#: src/podkop/methods/shell/index.ts:433
#: src/podkop/methods/shell/index.ts:521
#: src/podkop/methods/shell/index.ts:432
#: src/podkop/methods/shell/index.ts:520
msgid "Operation timed out"
msgstr ""
@@ -1794,10 +1800,6 @@ msgstr ""
msgid "outbounds must be a non-empty array of strings"
msgstr ""
#: src/podkop/tabs/updates/initController.ts:106
msgid "Outdated"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:307
msgid "Output Network Interface"
msgstr ""
@@ -1818,19 +1820,19 @@ msgstr ""
msgid "Path cannot be empty"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:591
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:635
msgid "Path must be absolute (start with /)"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:600
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:644
msgid "Path must contain at least one directory (like /tmp/cache.db)"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:595
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:639
msgid "Path must end with cache.db"
msgstr ""
#: src/podkop/tabs/monitoring/initController.ts:687
#: src/podkop/tabs/monitoring/initController.ts:691
#: src/podkop/tabs/monitoring/render.ts:70
#: src/podkop/tabs/monitoring/render.ts:71
msgid "Pause updates"
@@ -1849,7 +1851,7 @@ msgstr ""
msgid "Pin on dashboard"
msgstr ""
#: src/podkop/services/core.service.ts:22
#: src/podkop/services/core.service.ts:52
msgid "Podkop Plus Error"
msgstr ""
@@ -1869,7 +1871,7 @@ msgstr ""
msgid "Podkop Plus manages the nfqws2 process lifecycle itself, so daemon mode is not allowed here."
msgstr ""
#: src/podkop/tabs/monitoring/initController.ts:921
#: src/podkop/tabs/monitoring/initController.ts:925
msgid "Podkop Plus service is stopped. Start the service to display connections."
msgstr ""
@@ -1881,7 +1883,7 @@ msgstr ""
msgid "Podkop Plus Settings"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:552
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:596
msgid "Podkop Plus will not modify your DHCP configuration"
msgstr ""
@@ -2045,9 +2047,7 @@ msgstr ""
msgid "Regular expression must not contain spaces or commas"
msgstr ""
#: src/podkop/tabs/updates/initController.ts:813
#: src/podkop/tabs/updates/initController.ts:844
#: src/podkop/tabs/updates/initController.ts:875
#: src/podkop/tabs/updates/initController.ts:755
msgid "Remove"
msgstr ""
@@ -2095,12 +2095,12 @@ msgstr ""
msgid "Restart Podkop Plus"
msgstr ""
#: src/podkop/tabs/monitoring/initController.ts:687
#: src/podkop/tabs/monitoring/initController.ts:691
msgid "Resume updates"
msgstr ""
#: src/podkop/tabs/monitoring/initController.ts:815
#: src/podkop/tabs/monitoring/initController.ts:873
#: src/podkop/tabs/monitoring/initController.ts:819
#: src/podkop/tabs/monitoring/initController.ts:877
msgid "Route"
msgstr ""
@@ -2246,11 +2246,11 @@ msgstr ""
msgid "Select or enter DNS server addresses. The first available server has priority."
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:574
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:618
msgid "Select or enter path for sing-box cache file. Change this ONLY if you know what you are doing"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:561
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:605
msgid "Select path for sing-box config file. Change this ONLY if you know what you are doing"
msgstr ""
@@ -2259,7 +2259,7 @@ msgstr ""
msgid "Select the fastest node"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:610
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:654
msgid "Select the log level for sing-box"
msgstr ""
@@ -2338,8 +2338,8 @@ msgstr ""
msgid "Servers"
msgstr ""
#: src/podkop/methods/shell/index.ts:384
#: src/podkop/methods/shell/index.ts:405
#: src/podkop/methods/shell/index.ts:383
#: src/podkop/methods/shell/index.ts:404
#: src/podkop/tabs/diagnostic/initController.ts:594
msgid "Service action failed"
msgstr ""
@@ -2411,8 +2411,8 @@ msgstr ""
msgid "Sorts servers in this section by lowest latency in the dashboard."
msgstr ""
#: src/podkop/tabs/monitoring/initController.ts:827
#: src/podkop/tabs/monitoring/initController.ts:877
#: src/podkop/tabs/monitoring/initController.ts:831
#: src/podkop/tabs/monitoring/initController.ts:881
msgid "Source"
msgstr ""
@@ -2497,8 +2497,8 @@ msgstr ""
msgid "Subscription info:"
msgstr ""
#: src/podkop/methods/shell/index.ts:717
#: src/podkop/methods/shell/index.ts:737
#: src/podkop/methods/shell/index.ts:720
#: src/podkop/methods/shell/index.ts:740
msgid "Subscription update failed"
msgstr ""
@@ -2636,8 +2636,8 @@ msgstr ""
msgid "This option requires a value."
msgstr ""
#: src/podkop/tabs/monitoring/initController.ts:818
#: src/podkop/tabs/monitoring/initController.ts:874
#: src/podkop/tabs/monitoring/initController.ts:822
#: src/podkop/tabs/monitoring/initController.ts:878
msgid "Time"
msgstr ""
@@ -2649,7 +2649,7 @@ msgstr ""
msgid "Time skew tolerance"
msgstr ""
#: src/podkop/helpers/singBoxVariant.ts:54
#: src/podkop/helpers/singBoxVariant.ts:53
msgid "tiny"
msgstr ""
@@ -2712,8 +2712,8 @@ msgid "TTL value cannot be empty"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/server.js:1627
#: src/podkop/tabs/monitoring/initController.ts:812
#: src/podkop/tabs/monitoring/initController.ts:872
#: src/podkop/tabs/monitoring/initController.ts:816
#: src/podkop/tabs/monitoring/initController.ts:876
msgid "Type"
msgstr ""
@@ -2777,7 +2777,7 @@ msgstr ""
#: src/podkop/services/systemInfo.service.ts:22
#: src/podkop/services/systemInfo.service.ts:25
#: src/podkop/services/systemInfo.service.ts:26
#: src/podkop/tabs/updates/initController.ts:287
#: src/podkop/tabs/updates/initController.ts:285
#: src/podkop/tabs/diagnostic/checks/runInboundsCheck.ts:25
msgid "unknown"
msgstr ""
@@ -2812,14 +2812,18 @@ msgstr ""
msgid "Unsupported security mode for this protocol"
msgstr ""
#: src/podkop/tabs/updates/initController.ts:120
#: src/podkop/tabs/updates/initController.ts:710
msgid "Update"
msgstr ""
#: src/podkop/tabs/updates/initController.ts:245
#: src/podkop/tabs/updates/initController.ts:243
msgid "Update is available"
msgstr ""
#: src/podkop/tabs/updates/initController.ts:946
msgid "Update is available:"
msgstr ""
#: src/podkop/tabs/dashboard/partials/renderSections.ts:219
#: src/podkop/tabs/dashboard/partials/renderSections.ts:232
#: src/podkop/tabs/dashboard/partials/renderSections.ts:233
@@ -2835,8 +2839,8 @@ msgstr ""
msgid "Uplink"
msgstr ""
#: src/podkop/tabs/monitoring/initController.ts:824
#: src/podkop/tabs/monitoring/initController.ts:876
#: src/podkop/tabs/monitoring/initController.ts:828
#: src/podkop/tabs/monitoring/initController.ts:880
msgid "Uploaded"
msgstr ""
@@ -2940,6 +2944,8 @@ msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:460
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:482
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:489
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:505
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/settings.js:533
msgid "Use sing-box duration format like 1d, 12h or 30m"
msgstr ""
@@ -2963,8 +2969,8 @@ msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/section.js:4453
#: src/validators/validateDns.ts:21
#: src/validators/validateDns.ts:25
#: src/validators/validateDomain.ts:61
#: src/validators/validateDomain.ts:80
#: src/validators/validateDomain.ts:57
#: src/validators/validateDomain.ts:76
#: src/validators/validateHttpProxyUrl.ts:97
#: src/validators/validateHysteriaUrl.ts:162
#: src/validators/validateIp.ts:25
@@ -2996,10 +3002,6 @@ msgstr ""
msgid "Value must not contain control characters"
msgstr ""
#: src/podkop/tabs/updates/initController.ts:966
msgid "Version"
msgstr ""
#: ../luci-app-podkop-plus/htdocs/luci-static/resources/view/podkop/section.js:1663
msgid "Via country.is"
msgstr ""
+2
View File
@@ -18,6 +18,8 @@ config settings 'settings'
option disable_quic '0'
option list_update_enabled '1'
option update_interval '1d'
option component_update_check_enabled '0'
option component_update_check_interval '1d'
option latency_test_url 'https://www.gstatic.com/generate_204'
option download_lists_via_proxy '0'
option download_components_via_proxy '0'
+4
View File
@@ -115,6 +115,8 @@ function show_help() {
print(" component_action Manage Podkop Plus, sing-box, zapret, zapret2, and ByeDPI components\n");
print(" component_action_async Start a component action in the background\n");
print(" component_action_status Get background component action status\n");
print(" component_updates_if_due Check installed components for updates when due\n");
print(" component_update_check_cache Get cached component update check results\n");
print(" check_dns_available Check DNS server availability\n");
print(" global_check Run global system check\n");
print(" validate_nfqws_strategy_json\n");
@@ -185,6 +187,8 @@ function command_spec(command) {
component_action: [ "components/action.uc", "component-action", 2 ],
component_action_async: [ "components/updates.uc", "component-action-async", 2 ],
component_action_status: [ "components/updates.uc", "component-action-status", 1 ],
component_updates_if_due: [ "components/updates.uc", "component-updates-if-due", 0 ],
component_update_check_cache: [ "components/updates.uc", "component-update-check-cache", 0 ],
check_dns_available: [ "diagnostics/runtime.uc", "check-dns-available", 0 ],
global_check: [ "diagnostics/runtime.uc", "global-check", 2 ],
validate_nfqws_strategy_json: [ "diagnostics/runtime.uc", "validate-nfqws-strategy-json", 1 ],
+273 -24
View File
@@ -32,6 +32,9 @@ const SERVICE_INIT = getenv("PODKOP_SERVICE_INIT") || "/etc/init.d/podkop-plus";
const PRIORITY_UC = getenv("PODKOP_PRIORITY_UC") || LIB_DIR + "/singbox/priority.uc";
const DNS_FAILOVER_UC = getenv("PODKOP_DNS_FAILOVER_UC") || LIB_DIR + "/singbox/dns_failover.uc";
const COMPONENT_JOB_DIR = getenv("UPDATES_JOB_DIR") || getenv("PODKOP_UI_COMPONENT_ACTION_DIR") || "/var/run/podkop-plus/component-actions";
const COMPONENT_UPDATE_CHECK_CACHE_DIR = getenv("PODKOP_COMPONENT_UPDATE_CHECK_CACHE_DIR") || RUNTIME_STATE_DIR + "/component-update-checks";
const COMPONENT_UPDATE_CHECK_STATE_FILE = getenv("PODKOP_COMPONENT_UPDATE_CHECK_STATE_FILE") || RUNTIME_STATE_DIR + "/component-update-check.timestamp";
const COMPONENT_UPDATE_CHECK_LOCK_DIR = getenv("PODKOP_COMPONENT_UPDATE_CHECK_LOCK_DIR") || RUNTIME_STATE_DIR + "/component-update-check.lock";
const COMPONENT_JOB_FINISHED_TTL_MINUTES = getenv("UPDATES_JOB_FINISHED_TTL_MINUTES") || "60";
const COMPONENT_JOB_ORPHAN_OUTPUT_TTL_MINUTES = getenv("UPDATES_JOB_ORPHAN_OUTPUT_TTL_MINUTES") || "60";
const COMPONENT_JOB_STALE_GRACE_SECONDS = getenv("UPDATES_JOB_STALE_GRACE_SECONDS") || getenv("PODKOP_UI_ACTION_STALE_GRACE_SECONDS") || "15";
@@ -590,6 +593,16 @@ function settings_update_interval(settings) {
return value != "" ? value : "1d";
}
function settings_component_update_check_interval(settings) {
settings = object_or_empty(settings);
if (!bool_option(settings, "component_update_check_enabled", false))
return "";
let value = option(settings, "component_update_check_interval", "1d");
return value != "" ? value : "1d";
}
function section_subscription_update_interval(section) {
let result = "";
let result_seconds = 0;
@@ -653,7 +666,7 @@ function filter_cron_markers(markers) {
print(filter_cron_markers_text(read_stdin(), markers));
}
function cron_refresh_plan_rows(settings, sections, bin, list_marker, subscription_marker) {
function cron_refresh_plan_rows(settings, sections, bin, list_marker, subscription_marker, component_marker) {
let status = 0;
let rows = [];
@@ -703,6 +716,18 @@ function cron_refresh_plan_rows(settings, sections, bin, list_marker, subscripti
push(rows, "subscription\t" + due_check_cron_schedule_text(min_interval) + " " + as_string(bin) + " subscription_update_if_due " + as_string(subscription_marker));
}
let component_interval = settings_component_update_check_interval(settings);
if (component_interval != "") {
let component_seconds = duration_to_seconds_value(component_interval);
if (component_seconds == null) {
push(rows, "component-error\t" + as_string(component_interval));
status = 1;
}
else {
push(rows, "component\t" + due_check_cron_schedule_text(component_seconds) + " " + as_string(bin) + " component_updates_if_due " + as_string(component_marker));
}
}
return {
status,
rows
@@ -716,13 +741,13 @@ function print_cron_refresh_plan(result) {
exit(int(result.status || 0));
}
function cron_refresh_plan(settings, sections, bin, list_marker, subscription_marker) {
print_cron_refresh_plan(cron_refresh_plan_rows(settings, sections, bin, list_marker, subscription_marker));
function cron_refresh_plan(settings, sections, bin, list_marker, subscription_marker, component_marker) {
print_cron_refresh_plan(cron_refresh_plan_rows(settings, sections, bin, list_marker, subscription_marker, component_marker));
}
function cron_refresh_apply_result(settings, sections, existing_crontab, bin, list_marker, subscription_marker) {
let plan = cron_refresh_plan_rows(settings, sections, bin, list_marker, subscription_marker);
let filtered_crontab = filter_cron_markers_text(existing_crontab, [ list_marker, subscription_marker ]);
function cron_refresh_apply_result(settings, sections, existing_crontab, bin, list_marker, subscription_marker, component_marker) {
let plan = cron_refresh_plan_rows(settings, sections, bin, list_marker, subscription_marker, component_marker);
let filtered_crontab = filter_cron_markers_text(existing_crontab, [ list_marker, subscription_marker, component_marker ]);
let cron_jobs = "";
let logs = [ { level: "info", message: "The cron job removed" } ];
let tab = "\t";
@@ -753,6 +778,13 @@ function cron_refresh_apply_result(settings, sections, existing_crontab, bin, li
let interval = section_separator >= 0 ? substr(rest, section_separator + 1) : "";
push(logs, { level: "error", message: "Invalid subscription_update_interval value for rule '" + section + "': " + interval });
}
else if (type == "component") {
cron_jobs += rest + "\n";
push(logs, { level: "info", message: "The component update check cron job has been created: " + rest });
}
else if (type == "component-error") {
push(logs, { level: "error", message: "Invalid component_update_check_interval value: " + rest });
}
}
return {
@@ -782,10 +814,10 @@ function log_cron_apply_result(result) {
log_message(item.message, item.level);
}
function remove_cron_jobs(list_marker, subscription_marker) {
function remove_cron_jobs(list_marker, subscription_marker, component_marker) {
let crontab = command_output_from_args([ "crontab", "-l" ]);
let result = {
crontab: filter_cron_markers_text(crontab, [ list_marker, subscription_marker ]),
crontab: filter_cron_markers_text(crontab, [ list_marker, subscription_marker, component_marker ]),
logs: [ { level: "info", message: "The cron job removed" } ]
};
@@ -795,14 +827,15 @@ function remove_cron_jobs(list_marker, subscription_marker) {
log_cron_apply_result(result);
}
function refresh_cron_from_sources(settings, sections, bin, list_marker, subscription_marker) {
function refresh_cron_from_sources(settings, sections, bin, list_marker, subscription_marker, component_marker) {
let result = cron_refresh_apply_result(
settings,
sections,
command_output_from_args([ "crontab", "-l" ]),
bin,
list_marker,
subscription_marker
subscription_marker,
component_marker
);
if (!write_crontab_text(result.crontab))
@@ -1318,6 +1351,125 @@ function normalize_component_name(component) {
return component;
}
function valid_component_name(component) {
component = normalize_component_name(component);
return component == "podkop" || component == "sing_box" || component == "zapret" ||
component == "zapret2" || component == "byedpi";
}
function component_update_check_cache_path(component) {
component = normalize_component_name(component);
if (!valid_component_name(component))
return "";
return COMPONENT_UPDATE_CHECK_CACHE_DIR + "/" + component + ".json";
}
function component_update_check_cache_enabled() {
return settings_component_update_check_interval(uci_settings()) != "";
}
function component_update_check_result_cacheable(value) {
value = object_or_empty(value);
let status = as_string(value.status || "");
return value.success === true && as_string(value.action) == "check_update" &&
valid_component_name(value.component) &&
(status == "latest" || status == "outdated" || status == "dev");
}
function cache_component_update_check_result(value, notify_update) {
value = object_or_empty(value);
if (!component_update_check_result_cacheable(value))
return false;
let component = normalize_component_name(value.component);
let path = component_update_check_cache_path(component);
if (path == "" || !ensure_dir(COMPONENT_UPDATE_CHECK_CACHE_DIR))
return false;
let previous = object_or_empty(read_json_file(path));
let cached = {
success: true,
component,
action: "check_update",
message: as_string(value.message),
current_version: as_string(value.current_version),
latest_version: as_string(value.latest_version),
release_url: as_string(value.release_url),
changed: 0,
status: as_string(value.status),
updated_at: now_seconds()
};
if (!write_state_file(path, cached))
return false;
if (arg_bool(notify_update) && cached.status == "outdated" &&
(as_string(previous.status) != "outdated" ||
as_string(previous.latest_version) != cached.latest_version)) {
log_message("[component-update] " + component + " " + cached.latest_version, "info");
}
return true;
}
function update_component_check_cache_from_action(value) {
value = object_or_empty(value);
let component = normalize_component_name(value.component);
let action = as_string(value.action);
let path = component_update_check_cache_path(component);
if (path == "")
return;
if (action == "remove" && value.success === true) {
remove_file(path);
return;
}
if (!component_update_check_cache_enabled())
return;
if (action == "check_update") {
cache_component_update_check_result(value, false);
return;
}
if (value.success === true && (action == "install" || match(action, /^install_/) != null)) {
let latest_version = as_string(value.latest_version);
if (latest_version == "")
latest_version = as_string(value.current_version);
cache_component_update_check_result({
success: true,
component,
action: "check_update",
message: "Latest version is installed",
current_version: as_string(value.current_version),
latest_version,
release_url: as_string(value.release_url),
status: "latest"
}, false);
}
}
function clear_component_update_check_cache() {
for (let path in fs.glob(COMPONENT_UPDATE_CHECK_CACHE_DIR + "/*.json"))
remove_file(path);
remove_file(COMPONENT_UPDATE_CHECK_STATE_FILE);
}
function component_update_check_cache() {
let enabled = component_update_check_cache_enabled();
let results = [];
if (enabled) {
for (let component in [ "podkop", "sing_box", "zapret", "zapret2", "byedpi" ]) {
let value = read_json_file(component_update_check_cache_path(component));
if (component_update_check_result_cacheable(value))
push(results, value);
}
}
write_json({ enabled, results });
}
function valid_component_job_id(job_id) {
job_id = as_string(job_id);
return job_id != "" && job_id != "." && job_id != ".." && match(job_id, /[^A-Za-z0-9._-]/) == null;
@@ -1536,6 +1688,7 @@ function finish_component_job(path, component, action, exit_code, output_file) {
value.kind = "component";
value.exit_code = arg_number(exit_code);
value.updated_at = updated_at;
update_component_check_cache_from_action(value);
ok = write_state_file(path, value);
}
else {
@@ -1644,6 +1797,91 @@ function component_action_status(job_id) {
print(as_string(fs.readfile(state_file)));
}
function automatic_component_check_names() {
let result = [ "podkop" ];
if (fs.stat("/usr/bin/sing-box") != null)
push(result, "sing_box");
if (module_success([ LIB_DIR + "/providers/zapret/runtime.uc", "installed" ]))
push(result, "zapret");
if (module_success([ LIB_DIR + "/providers/zapret2/runtime.uc", "installed" ]))
push(result, "zapret2");
if (module_success([ LIB_DIR + "/providers/byedpi/runtime.uc", "installed" ]))
push(result, "byedpi");
return result;
}
function run_automatic_component_update_check(component) {
let output_file = temp_path();
if (output_file == "")
return false;
let command = command_env(component_worker_env()) + " " +
command_from_args([
"ucode",
"-L", LIB_DIR,
LIB_DIR + "/components/action.uc",
"component-action",
as_string(component),
"check_update"
]) + " >" + shell_quote(output_file) + " 2>&1";
let status = command_status(command);
let value = output_json_object(output_file);
remove_file(output_file);
if (status != 0 || !component_update_check_result_cacheable(value))
return false;
return cache_component_update_check_result(value, true);
}
function component_updates_if_due() {
let interval = settings_component_update_check_interval(uci_settings());
if (interval == "")
exit(0);
let seconds = duration_to_seconds_value(interval);
if (seconds == null) {
log_message("Invalid component_update_check_interval value: " + interval, "error");
exit(1);
}
let due = update_due_status(
now_seconds(),
file_first_line_value(COMPONENT_UPDATE_CHECK_STATE_FILE),
seconds
);
if (due == 1)
exit(0);
if (due != 0)
exit(1);
if (!acquire_runtime_lock(COMPONENT_UPDATE_CHECK_LOCK_DIR, false))
exit(0);
due = update_due_status(
now_seconds(),
file_first_line_value(COMPONENT_UPDATE_CHECK_STATE_FILE),
seconds
);
if (due != 0) {
release_runtime_lock(COMPONENT_UPDATE_CHECK_LOCK_DIR);
exit(due == 1 ? 0 : 1);
}
ensure_dir(COMPONENT_UPDATE_CHECK_CACHE_DIR);
for (let component in automatic_component_check_names())
run_automatic_component_update_check(component);
if (!write_file(COMPONENT_UPDATE_CHECK_STATE_FILE, as_string(now_seconds()) + "\n")) {
release_runtime_lock(COMPONENT_UPDATE_CHECK_LOCK_DIR);
log_message("Failed to write component update check timestamp", "error");
exit(1);
}
release_runtime_lock(COMPONENT_UPDATE_CHECK_LOCK_DIR);
}
function download_via_proxy_option_for_purpose(purpose) {
purpose = as_string(purpose || "lists");
if (purpose == "lists")
@@ -2512,7 +2750,7 @@ function fixture_section_list(data, type_name) {
return type(plural) == "array" ? plural : [];
}
function fixture_cron_refresh_plan(path, bin, list_marker, subscription_marker) {
function fixture_cron_refresh_plan(path, bin, list_marker, subscription_marker, component_marker) {
let data = object_or_empty(read_json_file(path));
connections.set_item_sections_from_data(data);
cron_refresh_plan(
@@ -2520,11 +2758,12 @@ function fixture_cron_refresh_plan(path, bin, list_marker, subscription_marker)
fixture_section_list(data),
bin,
list_marker,
subscription_marker
subscription_marker,
component_marker
);
}
function fixture_cron_refresh_apply(path, existing_crontab_path, bin, list_marker, subscription_marker) {
function fixture_cron_refresh_apply(path, existing_crontab_path, bin, list_marker, subscription_marker, component_marker) {
let data = object_or_empty(read_json_file(path));
connections.set_item_sections_from_data(data);
let result = cron_refresh_apply_result(
@@ -2533,7 +2772,8 @@ function fixture_cron_refresh_apply(path, existing_crontab_path, bin, list_marke
fs.readfile(as_string(existing_crontab_path)) || "",
bin,
list_marker,
subscription_marker
subscription_marker,
component_marker
);
write_json({
@@ -2551,23 +2791,28 @@ function fixture_section_by_name(data, target_name) {
return {};
}
function uci_cron_refresh_plan(bin, list_marker, subscription_marker) {
function uci_cron_refresh_plan(bin, list_marker, subscription_marker, component_marker) {
cron_refresh_plan(
uci_settings(),
uci_sections("section"),
bin,
list_marker,
subscription_marker
subscription_marker,
component_marker
);
}
function uci_refresh_cron(bin, list_marker, subscription_marker) {
function uci_refresh_cron(bin, list_marker, subscription_marker, component_marker) {
let settings = uci_settings();
if (settings_component_update_check_interval(settings) == "")
clear_component_update_check_cache();
refresh_cron_from_sources(
uci_settings(),
settings,
uci_sections("section"),
bin,
list_marker,
subscription_marker
subscription_marker,
component_marker
);
}
@@ -2608,15 +2853,15 @@ else if (mode == "subscription-update-cron-job")
else if (mode == "subscription-update-interval-plan")
subscription_update_interval_plan();
else if (mode == "cron-refresh-plan")
uci_cron_refresh_plan(ARGV[1], ARGV[2], ARGV[3]);
uci_cron_refresh_plan(ARGV[1], ARGV[2], ARGV[3], ARGV[4]);
else if (mode == "cron-refresh-plan-fixture")
fixture_cron_refresh_plan(ARGV[1], ARGV[2], ARGV[3], ARGV[4]);
fixture_cron_refresh_plan(ARGV[1], ARGV[2], ARGV[3], ARGV[4], ARGV[5]);
else if (mode == "refresh-cron-from-uci")
uci_refresh_cron(ARGV[1], ARGV[2], ARGV[3]);
uci_refresh_cron(ARGV[1], ARGV[2], ARGV[3], ARGV[4]);
else if (mode == "refresh-cron-fixture")
fixture_cron_refresh_apply(ARGV[1], ARGV[2], ARGV[3], ARGV[4], ARGV[5]);
fixture_cron_refresh_apply(ARGV[1], ARGV[2], ARGV[3], ARGV[4], ARGV[5], ARGV[6]);
else if (mode == "remove-cron-jobs")
remove_cron_jobs(ARGV[1], ARGV[2]);
remove_cron_jobs(ARGV[1], ARGV[2], ARGV[3]);
else if (mode == "list-update")
list_update();
else if (mode == "list-update-if-due")
@@ -2671,6 +2916,10 @@ else if (mode == "component-action-async")
component_action_async(ARGV[1], ARGV[2]);
else if (mode == "component-action-status")
component_action_status(ARGV[1]);
else if (mode == "component-updates-if-due")
component_updates_if_due();
else if (mode == "component-update-check-cache")
component_update_check_cache();
else {
warn("Usage: components/updates.uc <operation> ...\n");
exit(1);
+5 -2
View File
@@ -47,6 +47,7 @@ const RELOAD_LOCK_DIR = getenv("PODKOP_RELOAD_LOCK_DIR") || "/var/run/podkop-plu
const LIST_UPDATE_CRON_MARKER = getenv("PODKOP_LIST_UPDATE_CRON_MARKER") || "# podkop-plus-list-update";
const SUBSCRIPTION_UPDATE_CRON_MARKER = getenv("PODKOP_SUBSCRIPTION_UPDATE_CRON_MARKER") || "# podkop-plus-subscription-update";
const COMPONENT_UPDATE_CHECK_CRON_MARKER = getenv("PODKOP_COMPONENT_UPDATE_CHECK_CRON_MARKER") || "# podkop-plus-component-update-check";
const RELOAD_STATE_FORMAT = int(getenv("PODKOP_RELOAD_STATE_FORMAT") || "1");
const RUNTIME_CACHE_FORMAT = int(getenv("PODKOP_RUNTIME_CACHE_FORMAT") || "7");
const RUNTIME_STABLE_MIN_AGE = int(getenv("PODKOP_RUNTIME_STABLE_MIN_AGE") || "2");
@@ -620,7 +621,8 @@ function refresh_cron() {
"refresh-cron-from-uci",
BIN_PATH,
LIST_UPDATE_CRON_MARKER,
SUBSCRIPTION_UPDATE_CRON_MARKER
SUBSCRIPTION_UPDATE_CRON_MARKER,
COMPONENT_UPDATE_CHECK_CRON_MARKER
]);
}
@@ -628,7 +630,8 @@ function remove_cron_jobs() {
return module_status(UPDATES_UC, [
"remove-cron-jobs",
LIST_UPDATE_CRON_MARKER,
SUBSCRIPTION_UPDATE_CRON_MARKER
SUBSCRIPTION_UPDATE_CRON_MARKER,
COMPONENT_UPDATE_CHECK_CRON_MARKER
]);
}
+11
View File
@@ -777,6 +777,16 @@ function settings_update_interval(settings) {
return value != "" ? value : "1d";
}
function settings_component_update_check_interval(settings) {
settings = object_or_empty(settings);
if (!bool_option(settings, "component_update_check_enabled", false))
return "";
let value = option(settings, "component_update_check_interval", "1d");
return value != "" ? value : "1d";
}
function section_rule_ports_csv(section) {
return rule_config.rule_ports_csv_value(option(section, "ports", ""), option(section, "ports_text", ""));
}
@@ -1001,6 +1011,7 @@ function list_update_signature_body(sections) {
function cron_signature_body(settings, sections) {
let body = signature_add_value("", "settings.update_interval", settings_update_interval(settings));
body = signature_add_value(body, "settings.component_update_check_interval", settings_component_update_check_interval(settings));
for (let section in sections)
body = append_list_update_signature_body(body, object_or_empty(section));
@@ -72,6 +72,14 @@ component_out="$(PODKOP_LIB="$fake_lib" ucode "$PODKOP_BIN" component_action sin
[ "$component_out" = $'action\tcomponent-action\tsing_box\tupdate' ] ||
fail "component_action must dispatch through components/action.uc with arguments"
component_check_cache_out="$(PODKOP_LIB="$fake_lib" ucode "$PODKOP_BIN" component_update_check_cache)"
[ "$component_check_cache_out" = $'updates\tcomponent-update-check-cache\t\t' ] ||
fail "component_update_check_cache must dispatch through components/updates.uc"
component_updates_out="$(PODKOP_LIB="$fake_lib" ucode "$PODKOP_BIN" component_updates_if_due)"
[ "$component_updates_out" = $'updates\tcomponent-updates-if-due\t\t' ] ||
fail "component_updates_if_due must dispatch through components/updates.uc"
rm -f "$fake_lib/service/lifecycle.uc"
set +e
PODKOP_TEST_DNS_RESTORE_MARKER="$WORK_DIR/dns-restore.marker" PODKOP_LIB="$fake_lib" \
@@ -0,0 +1,125 @@
#!/usr/bin/env bash
set -eo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
PODKOP_LIB="$ROOT_DIR/podkop/files/usr/lib"
UPDATES_UC="$PODKOP_LIB/components/updates.uc"
WORK_DIR="$(mktemp -d)"
cleanup() {
rm -rf "$WORK_DIR"
}
trap cleanup EXIT
fail() {
printf 'FAIL: %s\n' "$1" >&2
exit 1
}
fake_lib="$WORK_DIR/lib"
cache_dir="$WORK_DIR/cache"
job_dir="$WORK_DIR/jobs"
state_file="$job_dir/manual.json"
output_file="$job_dir/manual.out"
timestamp_file="$WORK_DIR/component-update-check.timestamp"
mkdir -p \
"$fake_lib/core" \
"$fake_lib/config" \
"$fake_lib/components" \
"$fake_lib/service" \
"$fake_lib/providers/zapret" \
"$fake_lib/providers/zapret2" \
"$fake_lib/providers/byedpi" \
"$job_dir"
cat >"$fake_lib/core/uci.uc" <<'UCODE'
function get_all(_config, section) {
if (section == "settings") {
return {
component_update_check_enabled: getenv("TEST_COMPONENT_UPDATE_CHECK_ENABLED") || "0",
component_update_check_interval: getenv("TEST_COMPONENT_UPDATE_CHECK_INTERVAL") || "1d"
};
}
return {};
}
function section_objects(_config, _type) {
return [];
}
return {
get_all,
section_objects
};
UCODE
cat >"$fake_lib/config/connections.uc" <<'UCODE'
return {};
UCODE
cat >"$fake_lib/components/action.uc" <<'UCODE'
#!/usr/bin/env ucode
let component = ARGV[1] || "podkop";
let action = ARGV[2] || "check_update";
print(sprintf("%J\n", {
success: true,
component,
action,
message: "Update is available",
current_version: "1.0.0",
latest_version: "1.1.0",
release_url: "https://example.com/release",
changed: 0,
status: "outdated"
}));
UCODE
cat >"$fake_lib/providers/zapret/runtime.uc" <<'UCODE'
#!/usr/bin/env ucode
exit(1);
UCODE
cp "$fake_lib/providers/zapret/runtime.uc" "$fake_lib/providers/zapret2/runtime.uc"
cp "$fake_lib/providers/zapret/runtime.uc" "$fake_lib/providers/byedpi/runtime.uc"
cat >"$fake_lib/service/state.uc" <<'UCODE'
#!/usr/bin/env ucode
exit(0);
UCODE
updates_ucode() {
TEST_COMPONENT_UPDATE_CHECK_ENABLED="${TEST_COMPONENT_UPDATE_CHECK_ENABLED:-1}" \
TEST_COMPONENT_UPDATE_CHECK_INTERVAL="${TEST_COMPONENT_UPDATE_CHECK_INTERVAL:-1d}" \
PODKOP_LIB="$fake_lib" \
PODKOP_COMPONENT_UPDATE_CHECK_CACHE_DIR="$cache_dir" \
PODKOP_COMPONENT_UPDATE_CHECK_STATE_FILE="$timestamp_file" \
UPDATES_JOB_DIR="$job_dir" \
ucode -L "$fake_lib" -L "$PODKOP_LIB" "$UPDATES_UC" "$@"
}
updates_ucode component-action-worker "$state_file" "$output_file" zapret check_update
[ -s "$cache_dir/zapret.json" ] ||
fail "manual checks must be cached while automatic checks are enabled"
manual_cache="$(updates_ucode component-update-check-cache)"
node -e '
const value = JSON.parse(process.argv[1]);
if (!value.enabled || value.results.length !== 1 ||
value.results[0].component !== "zapret" ||
value.results[0].status !== "outdated" ||
value.results[0].latest_version !== "1.1.0") process.exit(1);
' "$manual_cache" || fail "cached manual check response is invalid"
rm -rf "$cache_dir"
TEST_COMPONENT_UPDATE_CHECK_ENABLED=0 updates_ucode \
component-action-worker "$state_file" "$output_file" zapret check_update
[ ! -e "$cache_dir/zapret.json" ] ||
fail "manual checks must not be cached while automatic checks are disabled"
rm -f "$state_file" "$timestamp_file"
updates_ucode component-updates-if-due
[ -s "$cache_dir/podkop.json" ] ||
fail "automatic checks must cache the Podkop Plus result"
[ -s "$timestamp_file" ] ||
fail "automatic checks must record their last run"
printf 'component update check cache regression checks passed\n'
@@ -983,7 +983,9 @@ cat >"$WORK_DIR/reload-state-signatures.json" <<'JSON'
{
"settings": {
"list_update_enabled": "1",
"update_interval": "6h"
"update_interval": "6h",
"component_update_check_enabled": "1",
"component_update_check_interval": "2h"
},
"section": [
{
@@ -1096,6 +1098,8 @@ EOF_LIST_SIG
cat >"$WORK_DIR/cron-signature.expected" <<'EOF_CRON_SIG'
[settings.update_interval]
6h
[settings.component_update_check_interval]
2h
[lists.list_proxy.ports]
443,80,443-444
[lists.list_proxy.community_subnet_lists]
+13 -8
View File
@@ -112,7 +112,9 @@ cat >"$WORK_DIR/cron-plan.json" <<'JSON'
{
"settings": {
"list_update_enabled": "1",
"update_interval": "30m"
"update_interval": "30m",
"component_update_check_enabled": "1",
"component_update_check_interval": "2h"
},
"section": [
{
@@ -148,8 +150,8 @@ cat >"$WORK_DIR/cron-plan.json" <<'JSON'
}
JSON
assert_eq $'list\t*/30 * * * * /usr/bin/podkop list_update_if_due # list\nsubscription-error\tbad_sub\tbad\nsubscription\t*/45 * * * * /usr/bin/podkop subscription_update_if_due # subscription' \
"$(updates_ucode cron-refresh-plan-fixture "$WORK_DIR/cron-plan.json" /usr/bin/podkop '# list' '# subscription')" \
assert_eq $'list\t*/30 * * * * /usr/bin/podkop list_update_if_due # list\nsubscription-error\tbad_sub\tbad\nsubscription\t*/45 * * * * /usr/bin/podkop subscription_update_if_due # subscription\ncomponent\t0 */2 * * * /usr/bin/podkop component_updates_if_due # component' \
"$(updates_ucode cron-refresh-plan-fixture "$WORK_DIR/cron-plan.json" /usr/bin/podkop '# list' '# subscription' '# component')" \
"cron refresh plan"
grep -Fq 'fs.writefile(tmp, as_string(text)) == null' "$UPDATES_UC" ||
@@ -173,7 +175,7 @@ cat >"$WORK_DIR/cron-plan-list-disabled.json" <<'JSON'
JSON
assert_eq "list-disabled" \
"$(updates_ucode cron-refresh-plan-fixture "$WORK_DIR/cron-plan-list-disabled.json" /usr/bin/podkop '# list' '# subscription')" \
"$(updates_ucode cron-refresh-plan-fixture "$WORK_DIR/cron-plan-list-disabled.json" /usr/bin/podkop '# list' '# subscription' '# component')" \
"disabled list cron plan"
cat >"$WORK_DIR/cron-plan-invalid-list.json" <<'JSON'
@@ -194,7 +196,7 @@ cat >"$WORK_DIR/cron-plan-invalid-list.json" <<'JSON'
JSON
status=0
invalid_plan="$(updates_ucode cron-refresh-plan-fixture "$WORK_DIR/cron-plan-invalid-list.json" /usr/bin/podkop '# list' '# subscription')" || status="$?"
invalid_plan="$(updates_ucode cron-refresh-plan-fixture "$WORK_DIR/cron-plan-invalid-list.json" /usr/bin/podkop '# list' '# subscription' '# component')" || status="$?"
assert_eq 1 "$status" "invalid list cron plan status"
assert_eq $'list-error\tbad' "$invalid_plan" "invalid list cron plan output"
@@ -202,9 +204,10 @@ cat >"$WORK_DIR/existing.cron" <<'CRON'
0 1 * * * /bin/true # keep
* * * * * /usr/bin/podkop list_update_if_due # list
* * * * * /usr/bin/podkop subscription_update_if_due # subscription
* * * * * /usr/bin/podkop component_updates_if_due # component
CRON
updates_ucode refresh-cron-fixture "$WORK_DIR/cron-plan.json" "$WORK_DIR/existing.cron" /usr/bin/podkop '# list' '# subscription' >"$WORK_DIR/cron-apply.json"
updates_ucode refresh-cron-fixture "$WORK_DIR/cron-plan.json" "$WORK_DIR/existing.cron" /usr/bin/podkop '# list' '# subscription' '# component' >"$WORK_DIR/cron-apply.json"
node - "$WORK_DIR/cron-apply.json" <<'JS'
const fs = require("fs");
const value = JSON.parse(fs.readFileSync(process.argv[2], "utf8"));
@@ -212,6 +215,7 @@ const expected = [
"0 1 * * * /bin/true # keep",
"*/30 * * * * /usr/bin/podkop list_update_if_due # list",
"*/45 * * * * /usr/bin/podkop subscription_update_if_due # subscription",
"0 */2 * * * /usr/bin/podkop component_updates_if_due # component",
""
].join("\n");
if (value.crontab !== expected) {
@@ -221,14 +225,15 @@ if (value.crontab !== expected) {
const messages = value.logs.map(item => item.message);
if (!messages.includes("The cron job removed") ||
!messages.includes("The cron job has been created: */30 * * * * /usr/bin/podkop list_update_if_due # list") ||
!messages.includes("The subscription cron job has been created: */45 * * * * /usr/bin/podkop subscription_update_if_due # subscription")) {
!messages.includes("The subscription cron job has been created: */45 * * * * /usr/bin/podkop subscription_update_if_due # subscription") ||
!messages.includes("The component update check cron job has been created: 0 */2 * * * /usr/bin/podkop component_updates_if_due # component")) {
console.error("unexpected cron apply logs", JSON.stringify(value.logs));
process.exit(1);
}
JS
status=0
updates_ucode refresh-cron-fixture "$WORK_DIR/cron-plan-invalid-list.json" "$WORK_DIR/existing.cron" /usr/bin/podkop '# list' '# subscription' >"$WORK_DIR/cron-apply-invalid.json" || status="$?"
updates_ucode refresh-cron-fixture "$WORK_DIR/cron-plan-invalid-list.json" "$WORK_DIR/existing.cron" /usr/bin/podkop '# list' '# subscription' '# component' >"$WORK_DIR/cron-apply-invalid.json" || status="$?"
assert_eq 1 "$status" "invalid cron apply status"
node - "$WORK_DIR/cron-apply-invalid.json" <<'JS'
const fs = require("fs");