style(luci): complete visual redesign of zapret2 strategy tuner with 1-click automated section domain optimization
This commit is contained in:
@@ -3274,3 +3274,21 @@ msgstr "Подбор завершен! Оптимальная стратегия
|
||||
|
||||
msgid "Error running tuner: "
|
||||
msgstr "Ошибка выполнения автоподбора: "
|
||||
|
||||
msgid "Section domains:"
|
||||
msgstr "Домены секции:"
|
||||
|
||||
msgid "Testing desync strategy:"
|
||||
msgstr "Тестирование стратегии:"
|
||||
|
||||
msgid "No working desync strategy found for section domains."
|
||||
msgstr "Рабочая стратегия обхода для доменов секции не найдена."
|
||||
|
||||
msgid "Optimal strategy found and applied: "
|
||||
msgstr "Оптимальная стратегия найдена и применена: "
|
||||
|
||||
msgid "DPI Blocked"
|
||||
msgstr "Заблокировано ТСПУ"
|
||||
|
||||
msgid "Applied"
|
||||
msgstr "Применено"
|
||||
|
||||
@@ -7398,7 +7398,7 @@ function createSectionContent(section) {
|
||||
attachNfqws2RemoteValidation,
|
||||
);
|
||||
|
||||
/* zapret2 Strategy Auto-Tuner Widget (AUTOMATED) */
|
||||
/* zapret2 Strategy Auto-Tuner Widget (AUTOMATED & STUNNING UI) */
|
||||
o = section.taboption("settings", form.DummyValue, "_zapret2_tune_widget", "");
|
||||
o.modalonly = true;
|
||||
o.depends("action", "zapret2");
|
||||
@@ -7409,62 +7409,87 @@ function createSectionContent(section) {
|
||||
document.querySelector(`.cbi-option-nfqws2_opt textarea`);
|
||||
if (el) {
|
||||
el.value = val;
|
||||
el.style.transition = "border-color 0.3s ease, box-shadow 0.3s ease";
|
||||
el.style.transition = "all 0.4s ease";
|
||||
el.style.borderColor = "#5cb85c";
|
||||
el.style.boxShadow = "0 0 8px rgba(92, 184, 92, 0.5)";
|
||||
el.style.boxShadow = "0 0 12px rgba(92, 184, 92, 0.6)";
|
||||
setTimeout(() => {
|
||||
el.style.borderColor = "";
|
||||
el.style.boxShadow = "";
|
||||
}, 2000);
|
||||
}, 2500);
|
||||
el.dispatchEvent(new Event("change", { bubbles: true }));
|
||||
}
|
||||
uci.set(UCI_PACKAGE, section_id, "nfqws2_opt", val);
|
||||
};
|
||||
|
||||
const sectionDomains = uci.get(UCI_PACKAGE, section_id, "domain") || [];
|
||||
const primaryDomain = sectionDomains[0] || "googlevideo.com";
|
||||
|
||||
const modeSelect = E("select", { class: "cbi-input-select", style: "width: 150px; font-size: 0.85em;" }, [
|
||||
const modeSelect = E("select", { class: "cbi-input-select", style: "width: 140px; font-size: 0.85em; border-radius: 4px; padding: 2px 6px;" }, [
|
||||
E("option", { value: "express" }, _("Express scan (~5-10s)")),
|
||||
E("option", { value: "deep" }, _("Deep scan (~20s)"))
|
||||
]);
|
||||
const resultsContainer = E("div", { style: "margin-top: 8px; max-height: 200px; overflow-y: auto;" });
|
||||
const statusText = E("div", { style: "margin-top: 6px; font-size: 0.88em; font-weight: bold; color: var(--cbi-link-color, #3c96d4);" }, "");
|
||||
|
||||
const startBtn = E("button", {
|
||||
class: "btn cbi-button cbi-button-action",
|
||||
type: "button",
|
||||
style: "font-weight: bold; padding: 4px 12px; font-size: 0.9em; background-color: var(--cbi-link-color, #2b78e4); color: #fff;"
|
||||
style: "font-weight: bold; padding: 5px 14px; font-size: 0.88em; background: linear-gradient(135deg, #2b78e4 0%, #1a5cb8 100%); color: #fff; border: none; border-radius: 4px; box-shadow: 0 2px 6px rgba(43,120,228,0.3); cursor: pointer;"
|
||||
}, _("Auto-Tune Section Strategy"));
|
||||
|
||||
const progressFill = E("div", {
|
||||
style: "width: 0%; height: 100%; background: linear-gradient(90deg, #3c96d4 0%, #5cb85c 100%); transition: width 0.3s ease; border-radius: 3px;"
|
||||
});
|
||||
const progressTrack = E("div", {
|
||||
style: "display: none; width: 100%; height: 6px; background: rgba(255,255,255,0.08); border-radius: 3px; overflow: hidden; margin-top: 8px;"
|
||||
}, progressFill);
|
||||
|
||||
const statusText = E("div", { style: "margin-top: 8px; font-size: 0.88em; font-weight: 500; color: var(--cbi-link-color, #3c96d4);" }, "");
|
||||
const resultsContainer = E("div", { style: "margin-top: 10px; max-height: 220px; overflow-y: auto;" });
|
||||
|
||||
const domainTagsContainer = E("div", { style: "display: flex; flex-wrap: wrap; gap: 4px; align-items: center;" }, [
|
||||
E("span", { style: "font-size: 0.82em; color: #999; font-weight: 500;" }, _("Section domains:"))
|
||||
]);
|
||||
|
||||
const inlineTuneCard = E("div", {
|
||||
class: "zapret2-inline-tuner-card",
|
||||
style: "margin-top: 8px; padding: 10px; border: 1px solid var(--cbi-border-color, #404040); background: rgba(0,0,0,0.2); border-radius: 4px;"
|
||||
style: "margin-top: 10px; padding: 12px 14px; border: 1px solid rgba(60, 150, 212, 0.25); background: linear-gradient(135deg, rgba(20,25,35,0.6) 0%, rgba(15,20,30,0.7) 100%); border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.25);"
|
||||
}, [
|
||||
E("div", { style: "display: flex; flex-wrap: wrap; gap: 10px; align-items: center;" }, [
|
||||
E("span", { style: "font-size: 0.85em; color: #aaa;" }, _("Section target domain: ") + ` ${primaryDomain}`),
|
||||
E("span", { style: "font-size: 0.85em; color: #888; margin-left: auto;" }, _("Scan mode:")), modeSelect,
|
||||
startBtn
|
||||
E("div", { style: "display: flex; flex-wrap: wrap; gap: 10px; align-items: center; justify-content: space-between;" }, [
|
||||
domainTagsContainer,
|
||||
E("div", { style: "display: flex; gap: 8px; align-items: center; margin-left: auto;" }, [
|
||||
modeSelect,
|
||||
startBtn
|
||||
])
|
||||
]),
|
||||
progressTrack,
|
||||
statusText,
|
||||
resultsContainer
|
||||
]);
|
||||
|
||||
// Populate domain tags dynamically from section
|
||||
const rawDomains = uci.get(UCI_PACKAGE, section_id, "domain") || [];
|
||||
const domainsList = (type(rawDomains) == "array" && rawDomains.length > 0) ? rawDomains : ["googlevideo.com"];
|
||||
domainsList.slice(0, 3).forEach((d) => {
|
||||
domainTagsContainer.appendChild(E("span", {
|
||||
style: "background: rgba(60,150,212,0.15); color: #5bc0de; border: 1px solid rgba(60,150,212,0.3); padding: 1px 7px; border-radius: 12px; font-size: 0.8em; font-family: monospace;"
|
||||
}, d));
|
||||
});
|
||||
|
||||
startBtn.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
const targetDomain = primaryDomain;
|
||||
const selectedMode = modeSelect.value;
|
||||
startBtn.disabled = true;
|
||||
startBtn.style.opacity = "0.6";
|
||||
statusText.style.color = "var(--cbi-link-color, #3c96d4)";
|
||||
statusText.textContent = _("Initializing Auto-Tune...");
|
||||
progressTrack.style.display = "block";
|
||||
progressFill.style.width = "5%";
|
||||
resultsContainer.innerHTML = "";
|
||||
|
||||
fs.exec("/usr/bin/tachyon", ["zapret2_tune_async", section_id, targetDomain, selectedMode])
|
||||
fs.exec("/usr/bin/tachyon", ["zapret2_tune_async", section_id, "", selectedMode])
|
||||
.then((res) => {
|
||||
let data = null;
|
||||
try { data = JSON.parse(res.stdout || "{}"); } catch (err) {}
|
||||
if (!data || !data.success || !data.job_id) {
|
||||
startBtn.disabled = false;
|
||||
startBtn.style.opacity = "1";
|
||||
progressTrack.style.display = "none";
|
||||
statusText.style.color = "var(--cbi-color-warning, #d9534f)";
|
||||
statusText.textContent = _("Failed to start Auto-Tune job.");
|
||||
return;
|
||||
@@ -7481,25 +7506,43 @@ function createSectionContent(section) {
|
||||
if (stData.running) {
|
||||
const prog = stData.progress || {};
|
||||
const currentItem = prog.current_item ? ` (${prog.current_item})` : "";
|
||||
const countStr = (prog.total > 0) ? ` [${prog.completed || 0}/${prog.total}]` : "";
|
||||
statusText.textContent = _("Testing desync strategies...") + countStr + currentItem;
|
||||
const completed = prog.completed || 0;
|
||||
const total = prog.total || 7;
|
||||
const pct = Math.min(100, Math.max(5, Math.round((completed / total) * 100)));
|
||||
progressFill.style.width = `${pct}%`;
|
||||
statusText.textContent = _("Testing desync strategy:") + ` ${prog.current_item || ""}... [${completed}/${total}]`;
|
||||
return;
|
||||
}
|
||||
|
||||
// Tuning finished!
|
||||
clearInterval(pollTimer);
|
||||
startBtn.disabled = false;
|
||||
startBtn.style.opacity = "1";
|
||||
progressFill.style.width = "100%";
|
||||
setTimeout(() => { progressTrack.style.display = "none"; }, 800);
|
||||
|
||||
if (!stData.success || !stData.winning_strategy) {
|
||||
statusText.style.color = "var(--cbi-color-warning, #d9534f)";
|
||||
statusText.textContent = (stData && stData.message) ? stData.message : _("No working desync strategy found for this domain.");
|
||||
statusText.textContent = (stData && stData.message) ? stData.message : _("No working desync strategy found for section domains.");
|
||||
} else {
|
||||
statusText.style.color = "var(--cbi-color-success, #5cb85c)";
|
||||
statusText.textContent = _("Auto-Tune complete! Winning strategy: ") + (stData.winning_preset_name || "");
|
||||
statusText.textContent = _("Optimal strategy found and applied: ") + `${stData.winning_preset_name || ""} (${stData.best_rtt_ms || 0} ms)`;
|
||||
}
|
||||
|
||||
const rows = (stData.results || []).map((r) => {
|
||||
const applyBtn = E("button", { class: "btn cbi-button cbi-button-save", type: "button", style: "padding: 1px 6px; font-size: 0.8em;" }, _("Apply"));
|
||||
const isWinner = (r.opt === stData.winning_strategy);
|
||||
const applyBtn = E("button", {
|
||||
class: "btn cbi-button cbi-button-save",
|
||||
type: "button",
|
||||
style: "padding: 1px 7px; font-size: 0.8em;"
|
||||
}, isWinner ? _("Applied") : _("Apply"));
|
||||
|
||||
if (isWinner) {
|
||||
applyBtn.style.background = "#5cb85c";
|
||||
applyBtn.style.borderColor = "#4cae4c";
|
||||
applyBtn.style.color = "#fff";
|
||||
}
|
||||
|
||||
applyBtn.addEventListener("click", (evt) => {
|
||||
evt.preventDefault();
|
||||
setNfqws2OptTextarea(r.opt);
|
||||
@@ -7508,19 +7551,23 @@ function createSectionContent(section) {
|
||||
}
|
||||
});
|
||||
|
||||
return E("tr", { style: r.success ? "background: rgba(0,255,0,0.05);" : "opacity: 0.6;" }, [
|
||||
E("td", { style: "padding: 4px; font-size: 0.85em; font-weight: bold;" }, r.name),
|
||||
E("td", { style: "padding: 4px; font-size: 0.85em;" }, r.success ? `${r.rtt_ms} ms (${r.message})` : _("Failed")),
|
||||
E("td", { style: "padding: 4px;" }, applyBtn)
|
||||
const statusBadge = r.success
|
||||
? E("span", { style: "color: #5cb85c; font-weight: bold; font-size: 0.85em;" }, `${r.rtt_ms} ms (${r.message})`)
|
||||
: E("span", { style: "color: #d9534f; opacity: 0.8; font-size: 0.85em;" }, _("DPI Blocked"));
|
||||
|
||||
return E("tr", { style: r.success ? (isWinner ? "background: rgba(92,184,92,0.12); font-weight: bold;" : "background: rgba(255,255,255,0.03);") : "opacity: 0.5;" }, [
|
||||
E("td", { style: "padding: 6px; font-size: 0.85em;" }, r.name),
|
||||
E("td", { style: "padding: 6px; font-size: 0.85em;" }, statusBadge),
|
||||
E("td", { style: "padding: 6px; text-align: right;" }, applyBtn)
|
||||
]);
|
||||
});
|
||||
|
||||
const table = E("table", { class: "table", style: "width: 100%; border-collapse: collapse; margin-top: 6px;" }, [
|
||||
E("thead", {}, [
|
||||
E("tr", {}, [
|
||||
E("th", { style: "text-align: left; font-size: 0.85em;" }, _("Preset")),
|
||||
E("th", { style: "text-align: left; font-size: 0.85em;" }, _("Status / RTT")),
|
||||
E("th", { style: "text-align: left; font-size: 0.85em;" }, _("Action"))
|
||||
E("tr", { style: "border-bottom: 1px solid rgba(255,255,255,0.1);" }, [
|
||||
E("th", { style: "text-align: left; font-size: 0.82em; color: #888;" }, _("Preset")),
|
||||
E("th", { style: "text-align: left; font-size: 0.82em; color: #888;" }, _("Status / RTT")),
|
||||
E("th", { style: "text-align: right; font-size: 0.82em; color: #888;" }, _("Action"))
|
||||
])
|
||||
]),
|
||||
E("tbody", {}, rows)
|
||||
@@ -7538,6 +7585,8 @@ function createSectionContent(section) {
|
||||
})
|
||||
.catch((err) => {
|
||||
startBtn.disabled = false;
|
||||
startBtn.style.opacity = "1";
|
||||
progressTrack.style.display = "none";
|
||||
statusText.style.color = "var(--cbi-color-warning, #d9534f)";
|
||||
statusText.textContent = _("Error running tuner: ") + err;
|
||||
});
|
||||
|
||||
@@ -3274,3 +3274,21 @@ msgstr "Подбор завершен! Оптимальная стратегия
|
||||
|
||||
msgid "Error running tuner: "
|
||||
msgstr "Ошибка выполнения автоподбора: "
|
||||
|
||||
msgid "Section domains:"
|
||||
msgstr "Домены секции:"
|
||||
|
||||
msgid "Testing desync strategy:"
|
||||
msgstr "Тестирование стратегии:"
|
||||
|
||||
msgid "No working desync strategy found for section domains."
|
||||
msgstr "Рабочая стратегия обхода для доменов секции не найдена."
|
||||
|
||||
msgid "Optimal strategy found and applied: "
|
||||
msgstr "Оптимальная стратегия найдена и применена: "
|
||||
|
||||
msgid "DPI Blocked"
|
||||
msgstr "Заблокировано ТСПУ"
|
||||
|
||||
msgid "Applied"
|
||||
msgstr "Применено"
|
||||
|
||||
@@ -79,20 +79,62 @@ function command_output(cmd) {
|
||||
return as_string(data);
|
||||
}
|
||||
|
||||
function get_section_domain(section_id) {
|
||||
function get_section_domains(section_id) {
|
||||
section_id = as_string(section_id);
|
||||
if (section_id == "")
|
||||
return "";
|
||||
let list = [];
|
||||
|
||||
let domain = uci_core.get(CONFIG_NAME, section_id, "domain");
|
||||
if (type(domain) == "array" && length(domain) > 0)
|
||||
return as_string(domain[0]);
|
||||
if (type(domain) == "string" && trim(domain) != "") {
|
||||
let parts = split(trim(domain), /[ \t\r\n]+/);
|
||||
if (length(parts) > 0)
|
||||
return parts[0];
|
||||
if (section_id != "") {
|
||||
let domain_setting = uci_core.get(CONFIG_NAME, section_id, "domain");
|
||||
if (type(domain_setting) == "array") {
|
||||
for (let d in domain_setting) {
|
||||
d = trim(as_string(d));
|
||||
if (d != "") push(list, d);
|
||||
}
|
||||
} else if (type(domain_setting) == "string" && trim(domain_setting) != "") {
|
||||
for (let d in split(trim(domain_setting), /[ \t\r\n,]+/)) {
|
||||
d = trim(as_string(d));
|
||||
if (d != "") push(list, d);
|
||||
}
|
||||
}
|
||||
|
||||
if (length(list) == 0) {
|
||||
let hostlists = uci_core.get(CONFIG_NAME, section_id, "hostlist");
|
||||
if (type(hostlists) == "string") hostlists = [ hostlists ];
|
||||
if (type(hostlists) == "array") {
|
||||
for (let hl in hostlists) {
|
||||
hl = trim(as_string(hl));
|
||||
if (hl == "") continue;
|
||||
let hl_path = "/etc/tachyon/hostlists/" + hl;
|
||||
if (fs.stat(hl_path) != null) {
|
||||
let content = fs.readfile(hl_path);
|
||||
if (content != null) {
|
||||
for (let line in split(content, "\n")) {
|
||||
line = trim(as_string(line));
|
||||
if (line != "" && substr(line, 0, 1) != "#" && match(line, /\.[a-z]{2,}$/i)) {
|
||||
push(list, line);
|
||||
if (length(list) >= 3) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (length(list) > 0) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return "";
|
||||
|
||||
if (length(list) == 0) {
|
||||
let sec_lower = lc(section_id);
|
||||
if (index(sec_lower, "youtube") >= 0 || index(sec_lower, "video") >= 0)
|
||||
return [ "googlevideo.com", "youtube.com" ];
|
||||
if (index(sec_lower, "discord") >= 0)
|
||||
return [ "discord.com", "gateway.discord.gg" ];
|
||||
if (index(sec_lower, "telegram") >= 0)
|
||||
return [ "t.me", "telegram.org" ];
|
||||
return [ "googlevideo.com" ];
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
function get_candidate_strategies(mode) {
|
||||
@@ -282,10 +324,13 @@ function run_tune(section_id, target_domain, mode, job_path) {
|
||||
mode = as_string(mode || "express");
|
||||
job_path = as_string(job_path);
|
||||
|
||||
if (target_domain == "")
|
||||
target_domain = get_section_domain(section_id);
|
||||
if (target_domain == "")
|
||||
target_domain = "googlevideo.com";
|
||||
let section_domains = get_section_domains(section_id);
|
||||
if (target_domain != "" && index(target_domain, ".") >= 0) {
|
||||
if (index(join(",", section_domains), target_domain) < 0) {
|
||||
unshift(section_domains, target_domain);
|
||||
}
|
||||
}
|
||||
target_domain = section_domains[0] || "googlevideo.com";
|
||||
|
||||
let candidates = get_candidate_strategies(mode);
|
||||
let total = length(candidates);
|
||||
@@ -374,6 +419,7 @@ function run_tune(section_id, target_domain, mode, job_path) {
|
||||
success: has_winner,
|
||||
section_id,
|
||||
target_domain,
|
||||
section_domains,
|
||||
mode,
|
||||
winning_strategy: has_winner ? best_candidate.opt : "",
|
||||
winning_preset_id: has_winner ? best_candidate.id : "",
|
||||
|
||||
Reference in New Issue
Block a user