release: 1.3.26 — Watchdog proxy restart protection, WAN monitor debounce, and subscription bootstrap resilience

- Fix #52: Watchdog proxy restart on single URL failure:
  * Validate failure against secondary independent probe URL before declaring proxy down
  * Skip restart when direct WAN is also degraded or down (!direct_ok)
  * Implement 120s cooldown between automated proxy restarts
  * Fix Telegram notification to accurately state 'Перезапуск sing-box' instead of 'Перезапуск роутера'
- Fix #51: Telegram bot spamming WAN up/down notifications:
  * Filter hotplug events strictly by badwan_monitored_interfaces (defaults to wan)
  * Add atomic directory lock with debounce delay and real ubus state query
  * Prevent repeated notifications when interface state has not transitioned
- Feat: 2-VPN deadlock elimination & self-download subscription proxy:
  * Isolate unbootstrapped dependent subscription sections on initial boot (deferred_sections)
  * Allow self-download proxy target for subscriptions with explicit UI hint '(текущее правило при активности)'
  * Enable automatic local proxy fallback for subscription refresh when direct WAN download fails while sing-box is active
  * Add regression test in tests/subscription_bootstrap_deadlock.sh
- Fix: Parental control quota enforcement:
  * Correct route rule filtering when daily quota minutes are exhausted without a fixed time interval
- i18n & Version bump:
  * Bump fe-app-tachyon to 1.3.26 and update Russian PO/POT translations
This commit is contained in:
Dushnilin
2026-09-12 16:50:04 +03:00
parent afd7e270ce
commit 2019ddd12a
25 changed files with 391 additions and 74 deletions
@@ -780,7 +780,7 @@ function createParentalContent(section) {
o.rawhtml = true;
o.modalonly = false;
o.cfgvalue = function (sectionId) {
const target = uci.get(UCI_PACKAGE, sectionId, "target") || "all";
const target = uci.get(UCI_PACKAGE, sectionId, "target") || "domains";
if (target === "all") {
return (
'<span style="color:var(--error-color-medium, #e53e3e);font-weight:600;">🚫 ' +
@@ -788,11 +788,20 @@ function createParentalContent(section) {
"</span>"
);
}
if (target === "domains") {
const rawDomains = normalizeListValues(
uci.get(UCI_PACKAGE, sectionId, "blocked_domains"),
);
const count = rawDomains.length;
return (
'<span class="badge" style="background:var(--background-color-low, rgba(0,0,0,0.06));padding:2px 6px;border-radius:4px;font-size:11px;margin-right:4px;border:1px solid var(--border-color-low, rgba(0,0,0,0.12));display:inline-flex;align-items:center;margin-bottom:2px;">🌐 ' +
_("Blocked Sites") +
(count > 0 ? " (" + count + ")" : "") +
"</span>"
);
}
const rawSecs = uci.get(UCI_PACKAGE, sectionId, "sections");
const secNames = normalizeListValues(rawSecs);
if (secNames.length === 0 && target !== "sections") {
secNames.push(target);
}
if (secNames.length === 0) {
return (
'<span style="opacity:0.6;">' + _("No sections selected") + "</span>"
@@ -814,8 +823,17 @@ function createParentalContent(section) {
);
};
o.textvalue = function (sectionId) {
const target = uci.get(UCI_PACKAGE, sectionId, "target") || "all";
const target = uci.get(UCI_PACKAGE, sectionId, "target") || "domains";
if (target === "all") return _("All Internet");
if (target === "domains") {
const rawDomains = normalizeListValues(
uci.get(UCI_PACKAGE, sectionId, "blocked_domains"),
);
return (
_("Blocked Sites") +
(rawDomains.length > 0 ? " (" + rawDomains.length + ")" : "")
);
}
const secNames = normalizeListValues(
uci.get(UCI_PACKAGE, sectionId, "sections"),
);
@@ -1081,6 +1099,7 @@ function createParentalContent(section) {
),
);
o.modalonly = true;
o.rmempty = false;
o.default = "domains";
o.value("domains", _("Blocked Sites (Only block domains specified below)"));
o.value("all", _("All Internet (Complete internet cutoff)"));
@@ -1222,6 +1241,7 @@ function createParentalContent(section) {
),
);
o.modalonly = true;
o.depends("target", "domains");
o.rmempty = true;
o.rows = 6;
o.wrap = "soft";
@@ -2083,7 +2083,6 @@ function isDownloadThroughTargetSection(section, currentSectionId) {
if (
!sectionName ||
sectionName === currentSectionId ||
section.enabled === "0"
) {
return false;
@@ -2134,10 +2133,17 @@ function isDownloadThroughTargetSection(section, currentSectionId) {
function subscriptionDownloadTargetChoices(section_id) {
return (uci.sections(UCI_PACKAGE, "section") || [])
.filter((section) => isDownloadThroughTargetSection(section, section_id))
.map((section) => ({
value: getUciSectionName(section),
label: getUciSectionLabel(section),
}));
.map((section) => {
const name = getUciSectionName(section);
let label = getUciSectionLabel(section);
if (name === section_id) {
label += " " + _("(this rule, when active)");
}
return {
value: name,
label: label,
};
});
}
function dnsTypeChoices() {
@@ -2745,9 +2751,6 @@ function addSubscriptionUrlItemOptions(itemSection, options = {}) {
if (!value) {
return _("Select a section for downloading this subscription");
}
if (value === sectionId) {
return _("Current section cannot download its own subscription");
}
return true;
};
+3
View File
@@ -1586,6 +1586,9 @@ msgstr "Скачивать эту подписку без проверки се
msgid "Download through"
msgstr "Загружать через"
msgid "(this rule, when active)"
msgstr "(эта же секция, при активном VPN)"
msgid "Downloaded"
msgstr "Загружено"