Add automatic component update checks
This commit is contained in:
+160
-158
@@ -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",
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
@@ -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']);
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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 ],
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user